diff --git a/CMake/External/External_iMSTKData.cmake b/CMake/External/External_iMSTKData.cmake
index df40d3d47823bcd013044bddb9b781e43c2f4014..445a926f42082a86be6e102cbb8242c45988766e 100644
--- a/CMake/External/External_iMSTKData.cmake
+++ b/CMake/External/External_iMSTKData.cmake
@@ -16,7 +16,7 @@ set(copy_data_command
   )
 
 include(imstkAddExternalProject)
-set(GIT_SHA "14824e3d53328ed6be481981959780f32881030b")
+set(GIT_SHA "2166a3715358e9794e53baf5dfa7deeaf36321cd")
 set(DATA_URL "https://gitlab.kitware.com/iMSTK/imstk-data/-/archive/${GIT_SHA}/imstk-data-${GIT_SHA}.zip")
 
 imstk_add_external_project( iMSTKData
diff --git a/Examples/FemurCut/FemurCutExample.cpp b/Examples/FemurCut/FemurCutExample.cpp
index 8d5eb2225fc6393616473ffff85e962a9d5b6229..7cef4280863b824cbe8199924b0b1d7ab6bab814 100644
--- a/Examples/FemurCut/FemurCutExample.cpp
+++ b/Examples/FemurCut/FemurCutExample.cpp
@@ -97,7 +97,6 @@ main()
     Logger::startLogger();
 
     imstkNew<Scene> scene("FemurCut");
-    scene->getConfig()->taskParallelizationEnabled = false;
 
     imstkNew<FemurObject> femurObj;
     scene->addSceneObject(femurObj);
diff --git a/Examples/PBDCutting/CMakeLists.txt b/Examples/PBD/PBDCutting/CMakeLists.txt
similarity index 100%
rename from Examples/PBDCutting/CMakeLists.txt
rename to Examples/PBD/PBDCutting/CMakeLists.txt
diff --git a/Examples/PBDCutting/PBDCuttingExample.cpp b/Examples/PBD/PBDCutting/PBDCuttingExample.cpp
similarity index 100%
rename from Examples/PBDCutting/PBDCuttingExample.cpp
rename to Examples/PBD/PBDCutting/PBDCuttingExample.cpp
diff --git a/Examples/PBDPicking/CMakeLists.txt b/Examples/PBD/PBDPicking/CMakeLists.txt
similarity index 100%
rename from Examples/PBDPicking/CMakeLists.txt
rename to Examples/PBD/PBDPicking/CMakeLists.txt
diff --git a/Examples/PBDPicking/PBDPickingExample.cpp b/Examples/PBD/PBDPicking/PBDPickingExample.cpp
similarity index 97%
rename from Examples/PBDPicking/PBDPickingExample.cpp
rename to Examples/PBD/PBDPicking/PBDPickingExample.cpp
index e61d8000153efc0ca5008f276d77fb77bb9efbbf..11d46fde1dcd7d5ccbb702f2440575e0b8d29412 100644
--- a/Examples/PBDPicking/PBDPickingExample.cpp
+++ b/Examples/PBD/PBDPicking/PBDPickingExample.cpp
@@ -1,297 +1,297 @@
-/*=========================================================================
-
-   Library: iMSTK
-
-   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
-   & Imaging in Medicine, Rensselaer Polytechnic Institute.
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0.txt
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
-=========================================================================*/
-
-#include "imstkCamera.h"
-#include "imstkCapsule.h"
-#include "imstkCollisionGraph.h"
-#include "imstkDirectionalLight.h"
-#include "imstkHapticDeviceClient.h"
-#include "imstkHapticDeviceManager.h"
-#include "imstkKeyboardSceneControl.h"
-#include "imstkLaparoscopicToolController.h"
-#include "imstkLogger.h"
-#include "imstkMeshIO.h"
-#include "imstkMouseSceneControl.h"
-#include "imstkNew.h"
-#include "imstkPbdModel.h"
-#include "imstkPbdObject.h"
-#include "imstkPbdObjectCollision.h"
-#include "imstkPbdObjectPicking.h"
-#include "imstkPBDPickingCH.h"
-#include "imstkRenderMaterial.h"
-#include "imstkScene.h"
-#include "imstkSceneManager.h"
-#include "imstkSimulationManager.h"
-#include "imstkSurfaceMesh.h"
-#include "imstkVisualModel.h"
-#include "imstkVTKViewer.h"
-
-using namespace imstk;
-
-///
-/// \brief Creates cloth geometry
-///
-static std::shared_ptr<SurfaceMesh>
-makeClothGeometry(const double width,
-                  const double height,
-                  const int    nRows,
-                  const int    nCols)
-{
-    imstkNew<SurfaceMesh> clothMesh;
-
-    imstkNew<VecDataArray<double, 3>> verticesPtr(nRows * nCols);
-    VecDataArray<double, 3>&          vertices = *verticesPtr.get();
-    const double                      dy       = width / static_cast<double>(nCols - 1);
-    const double                      dx       = height / static_cast<double>(nRows - 1);
-    for (int i = 0; i < nRows; i++)
-    {
-        for (int j = 0; j < nCols; j++)
-        {
-            vertices[i * nCols + j] = Vec3d(dx * static_cast<double>(i), 1.0, dy * static_cast<double>(j));
-        }
-    }
-
-    // Add connectivity data
-    imstkNew<VecDataArray<int, 3>> indicesPtr;
-    VecDataArray<int, 3>&          indices = *indicesPtr.get();
-    for (int i = 0; i < nRows - 1; i++)
-    {
-        for (int j = 0; j < nCols - 1; j++)
-        {
-            const int index1 = i * nCols + j;
-            const int index2 = index1 + nCols;
-            const int index3 = index1 + 1;
-            const int index4 = index2 + 1;
-
-            // Interleave [/][\] pattern
-            if (i % 2 ^ j % 2)
-            {
-                indices.push_back(Vec3i(index1, index2, index3));
-                indices.push_back(Vec3i(index4, index3, index2));
-            }
-            else
-            {
-                indices.push_back(Vec3i(index2, index4, index1));
-                indices.push_back(Vec3i(index4, index3, index1));
-            }
-        }
-    }
-
-    clothMesh->initialize(verticesPtr, indicesPtr);
-
-    return clothMesh;
-}
-
-///
-/// \brief Creates cloth object
-///
-static std::shared_ptr<PbdObject>
-makeClothObj(const std::string& name,
-             const double       width,
-             const double       height,
-             const int          nRows,
-             const int          nCols)
-{
-    imstkNew<PbdObject> clothObj(name);
-
-    // Setup the Geometry
-    std::shared_ptr<SurfaceMesh> clothMesh(makeClothGeometry(width, height, nRows, nCols));
-
-    // Setup the Parameters
-    imstkNew<PBDModelConfig> pbdParams;
-    pbdParams->enableConstraint(PbdConstraint::Type::Distance, 4000.0);
-    pbdParams->enableConstraint(PbdConstraint::Type::Dihedral, 100.0);
-    pbdParams->m_fixedNodeIds     = { 0, static_cast<size_t>(nCols) - 1 };
-    pbdParams->m_uniformMassValue = width * height / ((double)nRows * (double)nCols);
-    pbdParams->m_gravity    = Vec3d(0.0, -140.0, 0.0);
-    pbdParams->m_dt         = 0.01;
-    pbdParams->m_iterations = 5;
-    pbdParams->m_viscousDampingCoeff = 0.01;
-
-    // Setup the Model
-    imstkNew<PbdModel> pbdModel;
-    pbdModel->setModelGeometry(clothMesh);
-    pbdModel->configure(pbdParams);
-
-    // Setup the VisualModel
-    imstkNew<RenderMaterial> material;
-    material->setBackFaceCulling(false);
-    material->setDisplayMode(RenderMaterial::DisplayMode::WireframeSurface);
-
-    imstkNew<VisualModel> visualModel(clothMesh);
-    visualModel->setRenderMaterial(material);
-
-    // Setup the Object
-    clothObj->addVisualModel(visualModel);
-    clothObj->setPhysicsGeometry(clothMesh);
-    clothObj->setCollidingGeometry(clothMesh);
-    clothObj->setDynamicalModel(pbdModel);
-
-    return clothObj;
-}
-
-///
-/// \brief This example demonstrates the concept of PBD picking
-/// for haptic interaction. NOTE: Requires GeoMagic Touch device
-///
-int
-main()
-{
-    // Setup logger (write to file and stdout)
-    Logger::startLogger();
-
-    // Scene
-    imstkNew<Scene> scene("PBDPicking");
-
-    // Create the virtual coupling object controller
-
-    // Device Server
-    imstkNew<HapticDeviceManager> server;
-    server->setSleepDelay(1.0);
-    std::shared_ptr<HapticDeviceClient> client = server->makeDeviceClient();
-
-    // Load the meshes
-    auto upperSurfMesh = MeshIO::read<SurfaceMesh>(iMSTK_DATA_ROOT "/laptool/upper.obj");
-    auto lowerSurfMesh = MeshIO::read<SurfaceMesh>(iMSTK_DATA_ROOT "/laptool/lower.obj");
-    auto pivotSurfMesh = MeshIO::read<SurfaceMesh>(iMSTK_DATA_ROOT "/laptool/pivot.obj");
-
-    imstkNew<Capsule> geomShaft;
-    geomShaft->setLength(20.0);
-    geomShaft->setRadius(1.0);
-    geomShaft->setOrientation(Quatd(Rotd(PI_2, Vec3d(1.0, 0.0, 0.0))));
-    geomShaft->setTranslation(Vec3d(0.0, 0.0, 10.0));
-    imstkNew<CollidingObject> objShaft("ShaftObject");
-    objShaft->setVisualGeometry(pivotSurfMesh);
-    objShaft->setCollidingGeometry(geomShaft);
-    scene->addSceneObject(objShaft);
-
-    imstkNew<Capsule> geomUpperJaw;
-    geomUpperJaw->setLength(25.0);
-    geomUpperJaw->setTranslation(Vec3d(0.0, 1.0, -12.5));
-    geomUpperJaw->setRadius(2.0);
-    geomUpperJaw->setOrientation(Quatd(Rotd(PI_2, Vec3d(1.0, 0.0, 0.0))));
-    imstkNew<CollidingObject> objUpperJaw("UpperJawObject");
-    objUpperJaw->setVisualGeometry(upperSurfMesh);
-    objUpperJaw->setCollidingGeometry(geomUpperJaw);
-    scene->addSceneObject(objUpperJaw);
-
-    imstkNew<Capsule> geomLowerJaw;
-    geomLowerJaw->setLength(25.0);
-    geomLowerJaw->setTranslation(Vec3d(0.0, -1.0, -12.5));
-    geomLowerJaw->setRadius(2.0);
-    geomLowerJaw->setOrientation(Quatd(Rotd(PI_2, Vec3d(1.0, 0.0, 0.0))));
-    imstkNew<CollidingObject> objLowerJaw("LowerJawObject");
-    objLowerJaw->setVisualGeometry(lowerSurfMesh);
-    objLowerJaw->setCollidingGeometry(geomLowerJaw);
-    scene->addSceneObject(objLowerJaw);
-
-    std::shared_ptr<PbdObject> clothObj = makeClothObj("Cloth", 50.0, 50.0, 15, 15);
-    scene->addSceneObject(clothObj);
-
-    // Create and add virtual coupling object controller in the scene
-    imstkNew<LaparoscopicToolController> controller(objShaft, objUpperJaw, objLowerJaw, client);
-    controller->setJawAngleChange(6.0e-3);
-    scene->addController(controller);
-
-    // Add collision for both jaws of the tool
-    auto upperJawCollision = std::make_shared<PbdObjectCollision>(clothObj, objUpperJaw, "SurfaceMeshToCapsuleCD");
-    auto lowerJawCollision = std::make_shared<PbdObjectCollision>(clothObj, objLowerJaw, "SurfaceMeshToCapsuleCD");
-    scene->getCollisionGraph()->addInteraction(upperJawCollision);
-    scene->getCollisionGraph()->addInteraction(lowerJawCollision);
-
-    // Add picking interaction for both jaws of the tool
-    auto upperJawPicking = std::make_shared<PbdObjectPicking>(clothObj, objUpperJaw, "PointSetToCapsuleCD");
-    auto lowerJawPicking = std::make_shared<PbdObjectPicking>(clothObj, objLowerJaw, "PointSetToCapsuleCD");
-    scene->getCollisionGraph()->addInteraction(upperJawPicking);
-    scene->getCollisionGraph()->addInteraction(lowerJawPicking);
-
-    // Camera
-    scene->getActiveCamera()->setPosition(Vec3d(1.0, 1.0, 1.0) * 100.0);
-    scene->getActiveCamera()->setFocalPoint(Vec3d(0.0, -50.0, 0.0));
-
-    // Light
-    imstkNew<DirectionalLight> light;
-    light->setFocalPoint(Vec3d(5.0, -8.0, -5.0));
-    light->setIntensity(1.0);
-    scene->addLight("light", light);
-
-    // Run the simulation
-    {
-        // Setup a viewer to render
-        imstkNew<VTKViewer> viewer("Viewer");
-        viewer->setActiveScene(scene);
-
-        // Setup a scene manager to advance the scene
-        imstkNew<SceneManager> sceneManager("Scene Manager");
-        sceneManager->setActiveScene(scene);
-        sceneManager->setExecutionType(Module::ExecutionType::ADAPTIVE);
-        sceneManager->pause(); // Start simulation paused
-
-        imstkNew<SimulationManager> driver;
-        driver->addModule(server);
-        driver->addModule(viewer);
-        driver->addModule(sceneManager);
-        driver->setDesiredDt(0.005);
-
-        // Add mouse and keyboard controls to the viewer
-        {
-            imstkNew<MouseSceneControl> mouseControl(viewer->getMouseDevice());
-            mouseControl->setSceneManager(sceneManager);
-            viewer->addControl(mouseControl);
-
-            imstkNew<KeyboardSceneControl> keyControl(viewer->getKeyboardDevice());
-            keyControl->setSceneManager(sceneManager);
-            keyControl->setModuleDriver(driver);
-            viewer->addControl(keyControl);
-        }
-
-        connect<Event>(sceneManager, &SceneManager::postUpdate, [&](Event*)
-        {
-            // Simulate the cloth in real time
-            clothObj->getPbdModel()->getParameters()->m_dt = sceneManager->getDt();
-        });
-
-        connect<Event>(controller, &LaparoscopicToolController::JawClosed,
-            [&](Event*)
-        {
-            LOG(INFO) << "Jaw Closed!";
-            auto chUpper = std::dynamic_pointer_cast<PBDPickingCH>(upperJawPicking->getCollisionHandlingA());
-            auto chLower = std::dynamic_pointer_cast<PBDPickingCH>(lowerJawPicking->getCollisionHandlingA());
-
-            chUpper->beginPick();
-            chLower->beginPick();
-            });
-        connect<Event>(controller, &LaparoscopicToolController::JawOpened,
-            [&](Event*)
-        {
-            LOG(INFO) << "Jaw Opened!";
-            auto chUpper = std::dynamic_pointer_cast<PBDPickingCH>(upperJawPicking->getCollisionHandlingA());
-            auto chLower = std::dynamic_pointer_cast<PBDPickingCH>(lowerJawPicking->getCollisionHandlingA());
-
-            chUpper->endPick();
-            chLower->endPick();
-            });
-
-        driver->start();
-    }
-
-    return 0;
-}
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkCamera.h"
+#include "imstkCapsule.h"
+#include "imstkCollisionGraph.h"
+#include "imstkDirectionalLight.h"
+#include "imstkHapticDeviceClient.h"
+#include "imstkHapticDeviceManager.h"
+#include "imstkKeyboardSceneControl.h"
+#include "imstkLaparoscopicToolController.h"
+#include "imstkLogger.h"
+#include "imstkMeshIO.h"
+#include "imstkMouseSceneControl.h"
+#include "imstkNew.h"
+#include "imstkPbdModel.h"
+#include "imstkPbdObject.h"
+#include "imstkPbdObjectCollision.h"
+#include "imstkPbdObjectPicking.h"
+#include "imstkPBDPickingCH.h"
+#include "imstkRenderMaterial.h"
+#include "imstkScene.h"
+#include "imstkSceneManager.h"
+#include "imstkSimulationManager.h"
+#include "imstkSurfaceMesh.h"
+#include "imstkVisualModel.h"
+#include "imstkVTKViewer.h"
+
+using namespace imstk;
+
+///
+/// \brief Creates cloth geometry
+///
+static std::shared_ptr<SurfaceMesh>
+makeClothGeometry(const double width,
+                  const double height,
+                  const int    nRows,
+                  const int    nCols)
+{
+    imstkNew<SurfaceMesh> clothMesh;
+
+    imstkNew<VecDataArray<double, 3>> verticesPtr(nRows * nCols);
+    VecDataArray<double, 3>&          vertices = *verticesPtr.get();
+    const double                      dy       = width / static_cast<double>(nCols - 1);
+    const double                      dx       = height / static_cast<double>(nRows - 1);
+    for (int i = 0; i < nRows; i++)
+    {
+        for (int j = 0; j < nCols; j++)
+        {
+            vertices[i * nCols + j] = Vec3d(dx * static_cast<double>(i), 1.0, dy * static_cast<double>(j));
+        }
+    }
+
+    // Add connectivity data
+    imstkNew<VecDataArray<int, 3>> indicesPtr;
+    VecDataArray<int, 3>&          indices = *indicesPtr.get();
+    for (int i = 0; i < nRows - 1; i++)
+    {
+        for (int j = 0; j < nCols - 1; j++)
+        {
+            const int index1 = i * nCols + j;
+            const int index2 = index1 + nCols;
+            const int index3 = index1 + 1;
+            const int index4 = index2 + 1;
+
+            // Interleave [/][\] pattern
+            if (i % 2 ^ j % 2)
+            {
+                indices.push_back(Vec3i(index1, index2, index3));
+                indices.push_back(Vec3i(index4, index3, index2));
+            }
+            else
+            {
+                indices.push_back(Vec3i(index2, index4, index1));
+                indices.push_back(Vec3i(index4, index3, index1));
+            }
+        }
+    }
+
+    clothMesh->initialize(verticesPtr, indicesPtr);
+
+    return clothMesh;
+}
+
+///
+/// \brief Creates cloth object
+///
+static std::shared_ptr<PbdObject>
+makeClothObj(const std::string& name,
+             const double       width,
+             const double       height,
+             const int          nRows,
+             const int          nCols)
+{
+    imstkNew<PbdObject> clothObj(name);
+
+    // Setup the Geometry
+    std::shared_ptr<SurfaceMesh> clothMesh(makeClothGeometry(width, height, nRows, nCols));
+
+    // Setup the Parameters
+    imstkNew<PBDModelConfig> pbdParams;
+    pbdParams->enableConstraint(PbdConstraint::Type::Distance, 4000.0);
+    pbdParams->enableConstraint(PbdConstraint::Type::Dihedral, 100.0);
+    pbdParams->m_fixedNodeIds     = { 0, static_cast<size_t>(nCols) - 1 };
+    pbdParams->m_uniformMassValue = width * height / ((double)nRows * (double)nCols);
+    pbdParams->m_gravity    = Vec3d(0.0, -140.0, 0.0);
+    pbdParams->m_dt         = 0.01;
+    pbdParams->m_iterations = 5;
+    pbdParams->m_viscousDampingCoeff = 0.01;
+
+    // Setup the Model
+    imstkNew<PbdModel> pbdModel;
+    pbdModel->setModelGeometry(clothMesh);
+    pbdModel->configure(pbdParams);
+
+    // Setup the VisualModel
+    imstkNew<RenderMaterial> material;
+    material->setBackFaceCulling(false);
+    material->setDisplayMode(RenderMaterial::DisplayMode::WireframeSurface);
+
+    imstkNew<VisualModel> visualModel(clothMesh);
+    visualModel->setRenderMaterial(material);
+
+    // Setup the Object
+    clothObj->addVisualModel(visualModel);
+    clothObj->setPhysicsGeometry(clothMesh);
+    clothObj->setCollidingGeometry(clothMesh);
+    clothObj->setDynamicalModel(pbdModel);
+
+    return clothObj;
+}
+
+///
+/// \brief This example demonstrates the concept of PBD picking
+/// for haptic interaction. NOTE: Requires GeoMagic Touch device
+///
+int
+main()
+{
+    // Setup logger (write to file and stdout)
+    Logger::startLogger();
+
+    // Scene
+    imstkNew<Scene> scene("PBDPicking");
+
+    // Create the virtual coupling object controller
+
+    // Device Server
+    imstkNew<HapticDeviceManager> server;
+    server->setSleepDelay(1.0);
+    std::shared_ptr<HapticDeviceClient> client = server->makeDeviceClient();
+
+    // Load the meshes
+    auto upperSurfMesh = MeshIO::read<SurfaceMesh>(iMSTK_DATA_ROOT "/laptool/upper.obj");
+    auto lowerSurfMesh = MeshIO::read<SurfaceMesh>(iMSTK_DATA_ROOT "/laptool/lower.obj");
+    auto pivotSurfMesh = MeshIO::read<SurfaceMesh>(iMSTK_DATA_ROOT "/laptool/pivot.obj");
+
+    imstkNew<Capsule> geomShaft;
+    geomShaft->setLength(20.0);
+    geomShaft->setRadius(1.0);
+    geomShaft->setOrientation(Quatd(Rotd(PI_2, Vec3d(1.0, 0.0, 0.0))));
+    geomShaft->setTranslation(Vec3d(0.0, 0.0, 10.0));
+    imstkNew<CollidingObject> objShaft("ShaftObject");
+    objShaft->setVisualGeometry(pivotSurfMesh);
+    objShaft->setCollidingGeometry(geomShaft);
+    scene->addSceneObject(objShaft);
+
+    imstkNew<Capsule> geomUpperJaw;
+    geomUpperJaw->setLength(25.0);
+    geomUpperJaw->setTranslation(Vec3d(0.0, 1.0, -12.5));
+    geomUpperJaw->setRadius(2.0);
+    geomUpperJaw->setOrientation(Quatd(Rotd(PI_2, Vec3d(1.0, 0.0, 0.0))));
+    imstkNew<CollidingObject> objUpperJaw("UpperJawObject");
+    objUpperJaw->setVisualGeometry(upperSurfMesh);
+    objUpperJaw->setCollidingGeometry(geomUpperJaw);
+    scene->addSceneObject(objUpperJaw);
+
+    imstkNew<Capsule> geomLowerJaw;
+    geomLowerJaw->setLength(25.0);
+    geomLowerJaw->setTranslation(Vec3d(0.0, -1.0, -12.5));
+    geomLowerJaw->setRadius(2.0);
+    geomLowerJaw->setOrientation(Quatd(Rotd(PI_2, Vec3d(1.0, 0.0, 0.0))));
+    imstkNew<CollidingObject> objLowerJaw("LowerJawObject");
+    objLowerJaw->setVisualGeometry(lowerSurfMesh);
+    objLowerJaw->setCollidingGeometry(geomLowerJaw);
+    scene->addSceneObject(objLowerJaw);
+
+    std::shared_ptr<PbdObject> clothObj = makeClothObj("Cloth", 50.0, 50.0, 15, 15);
+    scene->addSceneObject(clothObj);
+
+    // Create and add virtual coupling object controller in the scene
+    imstkNew<LaparoscopicToolController> controller(objShaft, objUpperJaw, objLowerJaw, client);
+    controller->setJawAngleChange(6.0e-3);
+    scene->addController(controller);
+
+    // Add collision for both jaws of the tool
+    auto upperJawCollision = std::make_shared<PbdObjectCollision>(clothObj, objUpperJaw, "SurfaceMeshToCapsuleCD");
+    auto lowerJawCollision = std::make_shared<PbdObjectCollision>(clothObj, objLowerJaw, "SurfaceMeshToCapsuleCD");
+    scene->getCollisionGraph()->addInteraction(upperJawCollision);
+    scene->getCollisionGraph()->addInteraction(lowerJawCollision);
+
+    // Add picking interaction for both jaws of the tool
+    auto upperJawPicking = std::make_shared<PbdObjectPicking>(clothObj, objUpperJaw, "PointSetToCapsuleCD");
+    auto lowerJawPicking = std::make_shared<PbdObjectPicking>(clothObj, objLowerJaw, "PointSetToCapsuleCD");
+    scene->getCollisionGraph()->addInteraction(upperJawPicking);
+    scene->getCollisionGraph()->addInteraction(lowerJawPicking);
+
+    // Camera
+    scene->getActiveCamera()->setPosition(Vec3d(1.0, 1.0, 1.0) * 100.0);
+    scene->getActiveCamera()->setFocalPoint(Vec3d(0.0, -50.0, 0.0));
+
+    // Light
+    imstkNew<DirectionalLight> light;
+    light->setFocalPoint(Vec3d(5.0, -8.0, -5.0));
+    light->setIntensity(1.0);
+    scene->addLight("light", light);
+
+    // Run the simulation
+    {
+        // Setup a viewer to render
+        imstkNew<VTKViewer> viewer("Viewer");
+        viewer->setActiveScene(scene);
+
+        // Setup a scene manager to advance the scene
+        imstkNew<SceneManager> sceneManager("Scene Manager");
+        sceneManager->setActiveScene(scene);
+        sceneManager->setExecutionType(Module::ExecutionType::ADAPTIVE);
+        sceneManager->pause(); // Start simulation paused
+
+        imstkNew<SimulationManager> driver;
+        driver->addModule(server);
+        driver->addModule(viewer);
+        driver->addModule(sceneManager);
+        driver->setDesiredDt(0.005);
+
+        // Add mouse and keyboard controls to the viewer
+        {
+            imstkNew<MouseSceneControl> mouseControl(viewer->getMouseDevice());
+            mouseControl->setSceneManager(sceneManager);
+            viewer->addControl(mouseControl);
+
+            imstkNew<KeyboardSceneControl> keyControl(viewer->getKeyboardDevice());
+            keyControl->setSceneManager(sceneManager);
+            keyControl->setModuleDriver(driver);
+            viewer->addControl(keyControl);
+        }
+
+        connect<Event>(sceneManager, &SceneManager::postUpdate, [&](Event*)
+        {
+            // Simulate the cloth in real time
+            clothObj->getPbdModel()->getParameters()->m_dt = sceneManager->getDt();
+        });
+
+        connect<Event>(controller, &LaparoscopicToolController::JawClosed,
+            [&](Event*)
+        {
+            LOG(INFO) << "Jaw Closed!";
+            auto chUpper = std::dynamic_pointer_cast<PBDPickingCH>(upperJawPicking->getCollisionHandlingA());
+            auto chLower = std::dynamic_pointer_cast<PBDPickingCH>(lowerJawPicking->getCollisionHandlingA());
+
+            chUpper->beginPick();
+            chLower->beginPick();
+            });
+        connect<Event>(controller, &LaparoscopicToolController::JawOpened,
+            [&](Event*)
+        {
+            LOG(INFO) << "Jaw Opened!";
+            auto chUpper = std::dynamic_pointer_cast<PBDPickingCH>(upperJawPicking->getCollisionHandlingA());
+            auto chLower = std::dynamic_pointer_cast<PBDPickingCH>(lowerJawPicking->getCollisionHandlingA());
+
+            chUpper->endPick();
+            chLower->endPick();
+            });
+
+        driver->start();
+    }
+
+    return 0;
+}
diff --git a/Examples/PBD/PBDStaticSuture/CMakeLists.txt b/Examples/PBD/PBDStaticSuture/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c8ba865d36da86763bcbcd6b981b97b2d15447d0
--- /dev/null
+++ b/Examples/PBD/PBDStaticSuture/CMakeLists.txt
@@ -0,0 +1,44 @@
+###########################################################################
+#
+# Copyright (c) Kitware, Inc.
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0.txt
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+###########################################################################
+
+project(Example-PBDStaticSuture)
+
+if (iMSTK_USE_OpenHaptics)
+    #-----------------------------------------------------------------------------
+    # Create executable
+    #-----------------------------------------------------------------------------
+    imstk_add_executable(${PROJECT_NAME}
+        pbdStaticSutureExample.cpp
+        NeedleInteraction.h
+        NeedleInteraction.cpp
+        NeedleObject.h
+        NeedleObject.cpp
+        NeedleRigidBodyCH.h
+        NeedleRigidBodyCH.cpp
+        RbdPointToArcConstraint.h)
+
+    #-----------------------------------------------------------------------------
+    # Add the target to Examples folder
+    #-----------------------------------------------------------------------------
+    SET_TARGET_PROPERTIES (${PROJECT_NAME} PROPERTIES FOLDER Examples/PBD)
+
+    #-----------------------------------------------------------------------------
+    # Link libraries to executable
+    #-----------------------------------------------------------------------------
+    target_link_libraries(${PROJECT_NAME} SimulationManager)
+endif()
diff --git a/Examples/PBD/PBDStaticSuture/NeedleInteraction.cpp b/Examples/PBD/PBDStaticSuture/NeedleInteraction.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..004832f9dc6c8f749ee484435e4d06378f97206d
--- /dev/null
+++ b/Examples/PBD/PBDStaticSuture/NeedleInteraction.cpp
@@ -0,0 +1,55 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "NeedleInteraction.h"
+#include "NeedleObject.h"
+#include "NeedleRigidBodyCH.h"
+#include "imstkCollisionDetectionAlgorithm.h"
+#include "imstkLineMesh.h"
+#include "imstkImplicitGeometry.h"
+#include "imstkTaskGraph.h"
+
+using namespace imstk;
+
+NeedleInteraction::NeedleInteraction(std::shared_ptr<CollidingObject> tissueObj,
+                                     std::shared_ptr<NeedleObject>    needleObj) : RigidObjectCollision(needleObj, tissueObj, "ImplicitGeometryToPointSetCD")
+{
+    if (std::dynamic_pointer_cast<LineMesh>(needleObj->getCollidingGeometry()) == nullptr)
+    {
+        LOG(WARNING) << "NeedleInteraction only works with LineMesh collision geometry on rigid DrillObject";
+    }
+    if (std::dynamic_pointer_cast<ImplicitGeometry>(tissueObj->getCollidingGeometry()) == nullptr)
+    {
+        LOG(WARNING) << "NeedleInteraction only works with SDF colliding geometry on colliding tissueObj";
+    }
+
+    // First replace the handlers with our needle subclasses
+
+    // This handler consumes collision data to resolve the tool from the tissue
+    // except when the needle is inserted
+    auto needleRbdCH = std::make_shared<NeedleRigidBodyCH>();
+    needleRbdCH->setInputRigidObjectA(needleObj);
+    needleRbdCH->setInputCollidingObjectB(tissueObj);
+    needleRbdCH->setInputCollisionData(getCollisionDetection()->getCollisionData());
+    needleRbdCH->setBeta(0.01);
+    needleRbdCH->getTaskNode()->m_isCritical = true;
+    setCollisionHandlingA(needleRbdCH);
+}
\ No newline at end of file
diff --git a/Examples/PBD/PBDStaticSuture/NeedleInteraction.h b/Examples/PBD/PBDStaticSuture/NeedleInteraction.h
new file mode 100644
index 0000000000000000000000000000000000000000..8d101daffa719a8fc2916746addbbfa4cbe87540
--- /dev/null
+++ b/Examples/PBD/PBDStaticSuture/NeedleInteraction.h
@@ -0,0 +1,41 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#pragma once
+
+#include "imstkRigidObjectCollision.h"
+
+using namespace imstk;
+
+class NeedleObject;
+
+///
+/// \class NeedleInteraction
+///
+/// \brief Defines interaction between NeedleObject and PbdObject
+///
+class NeedleInteraction : public RigidObjectCollision
+{
+public:
+    NeedleInteraction(std::shared_ptr<CollidingObject> tissueObj,
+                      std::shared_ptr<NeedleObject>    needleObj);
+    ~NeedleInteraction() override = default;
+};
\ No newline at end of file
diff --git a/Examples/PBD/PBDStaticSuture/NeedleObject.cpp b/Examples/PBD/PBDStaticSuture/NeedleObject.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d3ad917aae59aa30f13d0c21d107f03a3aef8fe9
--- /dev/null
+++ b/Examples/PBD/PBDStaticSuture/NeedleObject.cpp
@@ -0,0 +1,85 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "NeedleObject.h"
+#include "imstkIsometricMap.h"
+#include "imstkLineMesh.h"
+#include "imstkMeshIO.h"
+#include "imstkRbdConstraint.h"
+#include "imstkRenderMaterial.h"
+#include "imstkRigidBodyModel2.h"
+#include "imstkSurfaceMesh.h"
+#include "imstkVecDataArray.h"
+#include "imstkVisualModel.h"
+
+using namespace imstk;
+
+NeedleObject::NeedleObject() : RigidObject2("Needle")
+{
+    auto sutureMesh     = MeshIO::read<SurfaceMesh>(iMSTK_DATA_ROOT "/Surgical Instruments/Needles/c6_suture.stl");
+    auto sutureLineMesh = MeshIO::read<LineMesh>(iMSTK_DATA_ROOT "/Surgical Instruments/Needles/c6_suture_hull.vtk");
+
+    const Mat4d rot = mat4dRotation(Rotd(-PI_2, Vec3d(0.0, 1.0, 0.0))) *
+                      mat4dRotation(Rotd(-0.6, Vec3d(1.0, 0.0, 0.0)));
+    sutureMesh->transform(rot, Geometry::TransformType::ApplyToData);
+    sutureLineMesh->transform(rot, Geometry::TransformType::ApplyToData);
+
+    setVisualGeometry(sutureMesh);
+    setCollidingGeometry(sutureLineMesh);
+    setPhysicsGeometry(sutureLineMesh);
+    setPhysicsToVisualMap(std::make_shared<IsometricMap>(sutureLineMesh, sutureMesh));
+    getVisualModel(0)->getRenderMaterial()->setColor(Color(0.9, 0.9, 0.9));
+    getVisualModel(0)->getRenderMaterial()->setShadingModel(RenderMaterial::ShadingModel::PBR);
+    getVisualModel(0)->getRenderMaterial()->setRoughness(0.5);
+    getVisualModel(0)->getRenderMaterial()->setMetalness(1.0);
+
+    std::shared_ptr<RigidBodyModel2> rbdModel = std::make_shared<RigidBodyModel2>();
+    rbdModel->getConfig()->m_gravity = Vec3d::Zero();
+    rbdModel->getConfig()->m_maxNumIterations = 5;
+    setDynamicalModel(rbdModel);
+
+    getRigidBody()->m_mass = 1.0;
+    getRigidBody()->m_intertiaTensor = Mat3d::Identity() * 10000.0;
+    getRigidBody()->m_initPos = Vec3d(0.0, 0.0, 0.0);
+
+    // Manually setup an arc aligned with the geometry, some sort of needle+arc generator
+    // could be a nice addition to imstk
+    Mat3d arcBasis = Mat3d::Identity();
+    arcBasis.col(0) = Vec3d(0.0, 0.0, -1.0);
+    arcBasis.col(1) = Vec3d(1.0, 0.0, 0.0);
+    arcBasis.col(2) = Vec3d(0.0, 1.0, 0.0);
+    arcBasis = rot.block<3, 3>(0, 0) * arcBasis;
+    const Vec3d  arcCenter = (rot * Vec4d(0.0, -0.005455, 0.008839, 1.0)).head<3>();
+    const double arcRadius = 0.010705;
+    setArc(arcCenter, arcBasis, arcRadius, 0.558, 2.583);
+}
+
+const Mat3d
+NeedleObject::getArcBasis()
+{
+    return getRigidBody()->getOrientation().toRotationMatrix() * m_arcBasis;
+}
+
+const Vec3d
+NeedleObject::getArcCenter()
+{
+    return getRigidBody()->getPosition() + getRigidBody()->getOrientation().toRotationMatrix() * m_arcCenter;
+}
\ No newline at end of file
diff --git a/Examples/PBD/PBDStaticSuture/NeedleObject.h b/Examples/PBD/PBDStaticSuture/NeedleObject.h
new file mode 100644
index 0000000000000000000000000000000000000000..eb2698b85f909819b93554db6cbdf52d4019c704
--- /dev/null
+++ b/Examples/PBD/PBDStaticSuture/NeedleObject.h
@@ -0,0 +1,105 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#pragma once
+
+#include "imstkRigidObject2.h"
+
+using namespace imstk;
+
+class NeedleObject : public RigidObject2
+{
+public:
+    enum class CollisionState
+    {
+        REMOVED,
+        TOUCHING,
+        INSERTED
+    };
+
+public:
+    NeedleObject();
+    virtual ~NeedleObject() = default;
+
+    virtual const std::string getTypeName() const override { return "NeedleObject"; }
+
+public:
+    // *INDENT-OFF*
+    SIGNAL(NeedleObject, inserted);
+    SIGNAL(NeedleObject, removed);
+    // *INDENT-ON*
+
+public:
+    void setCollisionState(const CollisionState state)
+    {
+        // If current state is inserted and previous was not inserted
+        if (m_collisionState == CollisionState::INSERTED && state != CollisionState::INSERTED)
+        {
+            this->postEvent(Event(removed()));
+        }
+        // If current state not inserted and previous inserted
+        else if (m_collisionState != CollisionState::INSERTED && state == CollisionState::INSERTED)
+        {
+            this->postEvent(Event(inserted()));
+        }
+        m_collisionState = state;
+    }
+
+    CollisionState getCollisionState() const { return m_collisionState; }
+
+    ///
+    /// \brief Set the force threshold for the needle
+    ///
+    void setForceThreshold(const double forceThreshold) { m_forceThreshold = forceThreshold; }
+    double getForceThreshold() const { return m_forceThreshold; }
+
+    void setArc(const Vec3d& arcCenter, const Mat3d& arcBasis,
+                double arcRadius, double beginRad, double endRad)
+    {
+        m_arcCenter = arcCenter;
+        m_arcBasis  = arcBasis;
+        m_beginRad  = beginRad;
+        m_endRad    = endRad;
+        m_arcRadius = arcRadius;
+    }
+
+    ///
+    /// \brief Get the basis post transformation of the rigid body
+    ///
+    const Mat3d getArcBasis();
+    ///
+    /// \brief Get the arc center post transformation of the rigid body
+    ///
+    const Vec3d getArcCenter();
+    const double getBeginRad() const { return m_beginRad; }
+    const double getEndRad() const { return m_endRad; }
+    const double getArcRadius() const { return m_arcRadius; }
+
+protected:
+    CollisionState m_collisionState = CollisionState::REMOVED;
+    double m_forceThreshold = 5.0;
+
+    Mat3d  m_arcBasis  = Mat3d::Identity();
+    Vec3d  m_arcCenter = Vec3d::Zero();
+    double m_arcRadius = 1.0;
+    double m_beginRad  = 0.0;
+    double m_endRad    = PI * 2.0;
+};
\ No newline at end of file
diff --git a/Examples/PBD/PBDStaticSuture/NeedleRigidBodyCH.cpp b/Examples/PBD/PBDStaticSuture/NeedleRigidBodyCH.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4d08b626d270775a8c08f71b38ee8285c5810968
--- /dev/null
+++ b/Examples/PBD/PBDStaticSuture/NeedleRigidBodyCH.cpp
@@ -0,0 +1,121 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "NeedleRigidBodyCH.h"
+#include "NeedleObject.h"
+#include "imstkRbdContactConstraint.h"
+#include "imstkRigidBodyModel2.h"
+#include "RbdPointToArcConstraint.h"
+
+void
+NeedleRigidBodyCH::handle(
+    const std::vector<CollisionElement>& elementsA,
+    const std::vector<CollisionElement>& elementsB)
+{
+    // Do it the normal way
+    RigidBodyCH::handle(elementsA, elementsB);
+
+    // If no collision, needle must be removed
+    auto                         needleObj = std::dynamic_pointer_cast<NeedleObject>(getInputObjectA());
+    NeedleObject::CollisionState state     = needleObj->getCollisionState();
+
+    if (elementsA.size() != 0)
+    {
+        if (state == NeedleObject::CollisionState::INSERTED)
+        {
+            const Mat3d& arcBasis    = needleObj->getArcBasis();
+            const Vec3d& arcCenter   = needleObj->getArcCenter();
+            const double arcRadius   = needleObj->getArcRadius();
+            const double arcBeginRad = needleObj->getBeginRad();
+            const double arcEndRad   = needleObj->getEndRad();
+
+            // Constrain along the axes, whilst allowing "pushing" of the contact point
+            auto pointToArcConstraint = std::make_shared<RbdPointToArcConstraint>(
+                needleObj->getRigidBody(),
+                arcCenter,
+                arcBeginRad, arcEndRad,
+                arcRadius,
+                arcBasis,
+                m_initContactPt,
+                m_beta);
+            pointToArcConstraint->compute(needleObj->getRigidBodyModel2()->getTimeStep());
+            needleObj->getRigidBodyModel2()->addConstraint(pointToArcConstraint);
+        }
+    }
+    else
+    {
+        if (state == NeedleObject::CollisionState::INSERTED || state == NeedleObject::CollisionState::TOUCHING)
+        {
+            if (state == NeedleObject::CollisionState::INSERTED)
+            {
+                LOG(INFO) << "Unpuncture!";
+            }
+            needleObj->setCollisionState(NeedleObject::CollisionState::REMOVED);
+        }
+    }
+}
+
+void
+NeedleRigidBodyCH::addConstraint(
+    std::shared_ptr<RigidObject2> rbdObj,
+    const Vec3d& contactPt, const Vec3d& contactNormal,
+    const double contactDepth)
+{
+    auto obj = std::dynamic_pointer_cast<NeedleObject>(rbdObj);
+
+    // If removed and we are here, we must now be touching
+    if (obj->getCollisionState() == NeedleObject::CollisionState::REMOVED)
+    {
+        obj->setCollisionState(NeedleObject::CollisionState::TOUCHING);
+    }
+
+    // If touching we may test for insertion
+    const Vec3d n = contactNormal.normalized();
+    if (obj->getCollisionState() == NeedleObject::CollisionState::TOUCHING)
+    {
+        // Get all inwards force
+        const double fN = std::max(-contactNormal.dot(obj->getRigidBody()->getForce()), 0.0);
+
+        // If the normal force exceeds threshold the needle may insert
+        if (fN > obj->getForceThreshold())
+        {
+            LOG(INFO) << "Puncture!";
+            obj->setCollisionState(NeedleObject::CollisionState::INSERTED);
+
+            // Record the initial contact point
+            m_initOrientation = Quatd(rbdObj->getCollidingGeometry()->getRotation());
+            m_initContactPt   = contactPt;
+        }
+    }
+
+    // Only add contact normal constraint if not inserted
+    NeedleObject::CollisionState state = obj->getCollisionState();
+    if (state == NeedleObject::CollisionState::TOUCHING)
+    {
+        auto contactConstraint = std::make_shared<RbdContactConstraint>(
+            rbdObj->getRigidBody(), nullptr,
+            n, contactPt, contactDepth,
+            m_beta,
+            RbdConstraint::Side::A);
+        contactConstraint->compute(rbdObj->getRigidBodyModel2()->getTimeStep());
+        rbdObj->getRigidBodyModel2()->addConstraint(contactConstraint);
+    }
+}
\ No newline at end of file
diff --git a/Examples/PBD/PBDStaticSuture/NeedleRigidBodyCH.h b/Examples/PBD/PBDStaticSuture/NeedleRigidBodyCH.h
new file mode 100644
index 0000000000000000000000000000000000000000..0e86780089a30d1fb2a078a43bce19c5a6b8c4de
--- /dev/null
+++ b/Examples/PBD/PBDStaticSuture/NeedleRigidBodyCH.h
@@ -0,0 +1,56 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#pragma once
+
+#include "imstkRigidBodyCH.h"
+
+using namespace imstk;
+
+class NeedleRigidBodyCH : public RigidBodyCH
+{
+public:
+    NeedleRigidBodyCH() = default;
+    ~NeedleRigidBodyCH() override = default;
+
+    virtual const std::string getTypeName() const override { return "NeedleRigidBodyCH"; }
+
+protected:
+    ///
+    /// \brief Handle the collision/contact data
+    ///
+    virtual void handle(
+        const std::vector<CollisionElement>& elementsA,
+        const std::vector<CollisionElement>& elementsB) override;
+
+    ///
+    /// \brief Add constraint for the rigid body given contact
+    ///
+    void addConstraint(
+        std::shared_ptr<RigidObject2> rbdObj,
+        const Vec3d& contactPt, const Vec3d& contactNormal,
+        const double contactDepth) override;
+
+protected:
+    Vec3d m_initContactPt   = Vec3d::Zero();
+    Vec3d m_initAxes        = Vec3d::Zero();
+    Quatd m_initOrientation = Quatd::Identity();
+};
\ No newline at end of file
diff --git a/Examples/PBD/PBDStaticSuture/RbdPointToArcConstraint.h b/Examples/PBD/PBDStaticSuture/RbdPointToArcConstraint.h
new file mode 100644
index 0000000000000000000000000000000000000000..78bbfe962180a183aa40695d59afffdd6d67311f
--- /dev/null
+++ b/Examples/PBD/PBDStaticSuture/RbdPointToArcConstraint.h
@@ -0,0 +1,108 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#pragma once
+
+#include "imstkRbdConstraint.h"
+
+using namespace imstk;
+
+///
+/// \class RbdPointToArcConstraint
+///
+/// \brief Constrains an rigid body arc geometry to a point by computing the
+/// linear force and angular torque to get the arc to the point
+///
+class RbdPointToArcConstraint : public RbdConstraint
+{
+public:
+    ///
+    /// \param the Rigid body
+    /// \param the center of the circle arc is defined with
+    /// \param the radians/range of the arc, with relation to the arcBasis
+    /// \param the radians/range of the arc, with relation to the arcBasis
+    /// \param the radius of the circle the arc is defined with
+    /// \param the basis of the arc. Where any point on the plane has a radian with relation to x,y columns.
+    /// and the z column gives the normal of the plane the circle+arc lie on
+    /// \param the fixed point
+    /// \param baumgarte stabilization, varies step length
+    ///
+    RbdPointToArcConstraint(
+        std::shared_ptr<RigidBody> obj,
+        const Vec3d arcCenter, const double beginRadian, const double endRadian,
+        const double arcCircleRadius, const Mat3d arcBasis,
+        const Vec3d fixedPoint,
+        const double beta = 0.05) : RbdConstraint(obj, nullptr, Side::A),
+        m_arcCenter(arcCenter), m_beginRadian(beginRadian), m_endRadian(endRadian),
+        m_arcCircleRadius(arcCircleRadius), m_arcBasis(arcBasis),
+        m_fixedPoint(fixedPoint),
+        m_beta(beta)
+    {
+        // Check orthonormal basis
+    }
+
+    ~RbdPointToArcConstraint() override = default;
+
+public:
+    void compute(double dt) override
+    {
+        // Jacobian of contact (defines linear and angular constraint axes)
+        J = Eigen::Matrix<double, 3, 4>::Zero();
+        if ((m_side == Side::AB || m_side == Side::A) && !m_obj1->m_isStatic)
+        {
+            // Compute the direction and closest point to the arc from the fixedPoint
+            const Vec3d circleDiff = m_fixedPoint - m_arcCenter;
+            const Vec3d dir = circleDiff.normalized();
+
+            // m_arcBasis Should be orthonormal, this should project onto the axes
+            const Mat3d  invArcBasis = m_arcBasis.transpose();
+            const Vec3d  p   = invArcBasis * circleDiff;
+            const double rad = atan2(-p[2], -p[0]) + PI;
+            // Clamp to range (if closest point on circle is outside on range we want the end)
+            const double clampedRad = std::min(std::max(rad, m_beginRadian), m_endRadian);
+            // Finally compute the closest point to the arc using the new radian
+            const Vec3d closestPt = (cos(clampedRad) * m_arcBasis.col(0) +
+                                     sin(clampedRad) * m_arcBasis.col(2)) * m_arcCircleRadius +
+                                    m_arcCenter;
+
+            Vec3d       diff = m_fixedPoint - closestPt;
+            const Vec3d r    = closestPt - m_obj1->getPosition();
+            const Vec3d c    = r.cross(diff);
+
+            vu      = diff.norm() * m_beta / dt;
+            diff    = diff.normalized();
+            J(0, 0) = diff[0]; J(0, 1) = c[0];
+            J(1, 0) = diff[1]; J(1, 1) = c[1];
+            J(2, 0) = diff[2]; J(2, 1) = c[2];
+        }
+    }
+
+private:
+    Vec3d  m_arcCenter       = Vec3d::Zero();
+    Mat3d  m_arcBasis        = Mat3d::Zero(); // Should be orthonormal
+    double m_arcCircleRadius = 0.0;
+    double m_beginRadian     = 0.0;
+    double m_endRadian       = 0.0;
+
+    Vec3d m_fixedPoint = Vec3d::Zero();
+
+    double m_beta = 0.05;
+};
\ No newline at end of file
diff --git a/Examples/PBD/PBDStaticSuture/pbdStaticSutureExample.cpp b/Examples/PBD/PBDStaticSuture/pbdStaticSutureExample.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4205458d6ea541f7b9807607dfc5c7910092703f
--- /dev/null
+++ b/Examples/PBD/PBDStaticSuture/pbdStaticSutureExample.cpp
@@ -0,0 +1,301 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+// This example is haptic only
+#include "imstkCamera.h"
+#include "imstkCollisionGraph.h"
+#include "imstkKeyboardSceneControl.h"
+#include "imstkLineMesh.h"
+#include "imstkLogger.h"
+#include "imstkMeshIO.h"
+#include "imstkMouseSceneControl.h"
+#include "imstkNew.h"
+#include "imstkOrientedBox.h"
+#include "imstkPbdModel.h"
+#include "imstkPbdObject.h"
+#include "imstkPbdObjectCollision.h"
+#include "imstkRbdConstraint.h"
+#include "imstkRenderMaterial.h"
+#include "imstkScene.h"
+#include "imstkSceneManager.h"
+#include "imstkSimulationManager.h"
+#include "imstkSurfaceMesh.h"
+#include "imstkVisualModel.h"
+#include "imstkVTKViewer.h"
+#include "NeedleInteraction.h"
+#include "NeedleObject.h"
+#include "imstkHapticDeviceManager.h"
+#include "imstkHapticDeviceClient.h"
+#include "imstkRigidObjectController.h"
+
+using namespace imstk;
+
+///
+/// \brief Create pbd string geometry
+///
+static std::shared_ptr<LineMesh>
+makeStringGeometry(const Vec3d& pos, const Vec3d& dx, const int numVerts)
+{
+    // Create the geometry
+    imstkNew<LineMesh> stringGeometry;
+
+    imstkNew<VecDataArray<double, 3>> verticesPtr(numVerts);
+    VecDataArray<double, 3>&          vertices = *verticesPtr.get();
+    for (int i = 0; i < numVerts; i++)
+    {
+        vertices[i] = pos + dx * i;
+    }
+
+    // Add connectivity data
+    imstkNew<VecDataArray<int, 2>> segmentsPtr;
+    VecDataArray<int, 2>&          segments = *segmentsPtr.get();
+    for (int i = 0; i < numVerts - 1; i++)
+    {
+        segments.push_back(Vec2i(i, i + 1));
+    }
+
+    stringGeometry->initialize(verticesPtr, segmentsPtr);
+    return stringGeometry;
+}
+
+///
+/// \brief Create pbd string object
+///
+static std::shared_ptr<PbdObject>
+makePbdString(
+    const std::string& name,
+    const Vec3d& pos, const Vec3d& dir, const int numVerts,
+    const double stringLength)
+{
+    imstkNew<PbdObject> stringObj(name);
+
+    // Setup the Geometry
+    const double              dx = stringLength / numVerts;
+    std::shared_ptr<LineMesh> stringMesh = makeStringGeometry(pos, dir * dx, numVerts);
+
+    // Setup the Parameters
+    imstkNew<PBDModelConfig> pbdParams;
+    pbdParams->enableConstraint(PbdConstraint::Type::Distance, 100.0);
+    pbdParams->enableBendConstraint(100000.0, 1);
+    pbdParams->enableBendConstraint(100000.0, 2);
+    pbdParams->m_fixedNodeIds     = { 0, 1 };
+    pbdParams->m_uniformMassValue = 0.002 / numVerts; // grams
+    pbdParams->m_gravity = Vec3d(0.0, -9.8, 0.0);
+    pbdParams->m_dt      = 0.0005;                    // Overwritten for real time
+
+    // Requires large amounts of iterations the longer, a different
+    // solver would help
+    pbdParams->m_iterations = 100;
+    pbdParams->m_viscousDampingCoeff = 0.01;
+
+    // Setup the Model
+    imstkNew<PbdModel> pbdModel;
+    pbdModel->setModelGeometry(stringMesh);
+    pbdModel->configure(pbdParams);
+
+    // Setup the VisualModel
+    imstkNew<RenderMaterial> material;
+    material->setBackFaceCulling(false);
+    material->setColor(Color::Red);
+    material->setLineWidth(2.0);
+    material->setPointSize(6.0);
+    material->setDisplayMode(RenderMaterial::DisplayMode::Wireframe);
+
+    imstkNew<VisualModel> visualModel(stringMesh);
+    visualModel->setRenderMaterial(material);
+
+    // Setup the Object
+    stringObj->addVisualModel(visualModel);
+    stringObj->setPhysicsGeometry(stringMesh);
+    stringObj->setCollidingGeometry(stringMesh);
+    stringObj->setDynamicalModel(pbdModel);
+
+    return stringObj;
+}
+
+///
+/// \brief Generate a static/immovable tissue for static suturing
+///
+static std::shared_ptr<CollidingObject>
+makeTissueObj()
+{
+    imstkNew<CollidingObject> tissueObj("tissue");
+
+    imstkNew<OrientedBox> box1(Vec3d(0.0, -0.1, -0.1), Vec3d(0.1, 0.025, 0.1));
+    imstkNew<VisualModel> box1Model;
+    box1Model->setGeometry(box1);
+    box1Model->getRenderMaterial()->setShadingModel(RenderMaterial::ShadingModel::Gouraud);
+    box1Model->getRenderMaterial()->setColor(Color::LightSkin);
+    tissueObj->addVisualModel(box1Model);
+
+    tissueObj->setCollidingGeometry(box1);
+
+    imstkNew<OrientedBox> box2(Vec3d(0.0, -0.105, -0.1), Vec3d(0.1001, 0.025, 0.1001));
+    imstkNew<VisualModel> box2Model;
+    box2Model->setGeometry(box2);
+    box2Model->getRenderMaterial()->setShadingModel(RenderMaterial::ShadingModel::Gouraud);
+    box2Model->getRenderMaterial()->setColor(Color::darken(Color::Yellow, 0.2));
+    tissueObj->addVisualModel(box2Model);
+
+    return tissueObj;
+}
+
+static std::shared_ptr<SceneObject>
+makeToolObj()
+{
+    auto surfMesh =
+        MeshIO::read<SurfaceMesh>(iMSTK_DATA_ROOT "/Surgical Instruments/Clamps/Gregory Suture Clamp/gregory_suture_clamp.obj");
+
+    auto toolObj = std::make_shared<SceneObject>("Clamps");
+    toolObj->setVisualGeometry(surfMesh);
+    auto renderMaterial = std::make_shared<RenderMaterial>();
+    renderMaterial->setColor(Color::LightGray);
+    renderMaterial->setShadingModel(RenderMaterial::ShadingModel::PBR);
+    renderMaterial->setRoughness(0.5);
+    renderMaterial->setMetalness(1.0);
+    toolObj->getVisualModel(0)->setRenderMaterial(renderMaterial);
+
+    return toolObj;
+}
+
+///
+/// \brief This example is an initial suturing example testbed. It provides the constraint
+/// required for an arc shaped needle puncturing vs a static/immovable tissue. What it
+/// does not do:
+///  - The tissue is not deformable yet, so insertion is a bit stiff
+///  - It only constrains the arc to the surface point it punctures not the volume
+///  - The suture thread isn't constrained yet
+///  - Ability to graps/release the needle, combining into one body
+///
+int
+main()
+{
+    // Setup logger (write to file and stdout)
+    Logger::startLogger();
+
+    imstkNew<Scene> scene("PBDStaticSuture");
+
+    imstkNew<NeedleObject> needleObj;
+    needleObj->setForceThreshold(2.0);
+    scene->addSceneObject(needleObj);
+
+    const double               stringLength      = 0.2;
+    const int                  stringVertexCount = 30;
+    std::shared_ptr<PbdObject> sutureThreadObj   =
+        makePbdString("SutureThread", Vec3d(0.0, 0.0, 0.018), Vec3d(0.0, 0.0, 1.0),
+            stringVertexCount, stringLength);
+    scene->addSceneObject(sutureThreadObj);
+
+    std::shared_ptr<CollidingObject> tissueObj = makeTissueObj();
+    scene->addSceneObject(tissueObj);
+
+    std::shared_ptr<SceneObject> clampsObj = makeToolObj();
+    scene->addSceneObject(clampsObj);
+
+    auto interaction = std::make_shared<PbdObjectCollision>(sutureThreadObj, tissueObj, "ImplicitGeometryToPointSetCD");
+    interaction->setFriction(0.0);
+    scene->getCollisionGraph()->addInteraction(interaction);
+
+    auto needleInteraction = std::make_shared<NeedleInteraction>(tissueObj, needleObj);
+    scene->getCollisionGraph()->addInteraction(needleInteraction);
+
+    // Adjust the camera
+    scene->getActiveCamera()->setFocalPoint(0.00138345, -0.0601133, -0.0261938);
+    scene->getActiveCamera()->setPosition(0.00137719, 0.0492882, 0.201508);
+    scene->getActiveCamera()->setViewUp(-0.000780726, 0.901361, -0.433067);
+
+    // Run the simulation
+    {
+        // Setup a viewer to render
+        imstkNew<VTKViewer> viewer("Viewer");
+        viewer->setActiveScene(scene);
+        viewer->setDebugAxesLength(0.01, 0.01, 0.01);
+
+        // Setup a scene manager to advance the scene
+        imstkNew<SceneManager> sceneManager("Scene Manager");
+        sceneManager->setActiveScene(scene);
+        sceneManager->setExecutionType(Module::ExecutionType::ADAPTIVE);
+        sceneManager->pause(); // Start simulation paused
+
+        imstkNew<SimulationManager> driver;
+        driver->addModule(viewer);
+        driver->addModule(sceneManager);
+        driver->setDesiredDt(0.001); // 1ms, 1000hz
+
+#ifdef iMSTK_USE_OPENHAPTICS
+        imstkNew<HapticDeviceManager>       hapticManager;
+        std::shared_ptr<HapticDeviceClient> hapticDeviceClient = hapticManager->makeDeviceClient();
+        driver->addModule(hapticManager);
+
+        imstkNew<RigidObjectController> controller(needleObj, hapticDeviceClient);
+        controller->setTranslationOffset(Vec3d(0.05, -0.05, 0.0));
+        controller->setTranslationScaling(0.001);
+        controller->setLinearKs(1000.0);
+        controller->setLinearKd(50.0);
+        controller->setAngularKs(10000000.0);
+        controller->setAngularKd(1000000.0);
+        controller->setForceScaling(0.2);
+        controller->setSmoothingKernelSize(5);
+        controller->setUseForceSmoothening(true);
+        scene->addController(controller);
+#endif
+
+        connect<Event>(sceneManager, &SceneManager::preUpdate,
+            [&](Event*)
+        {
+            //needleObj->getRigidBodyModel2()->getConfig()->m_dt = sceneManager->getDt();
+            sutureThreadObj->getPbdModel()->getParameters()->m_dt = sceneManager->getDt();
+            });
+        Vec3d trans = Vec3d(-0.009, 0.01, 0.001);
+        connect<Event>(sceneManager, &SceneManager::postUpdate,
+            [&](Event*)
+        {
+            // Constrain the first two vertices of the string to the needle
+            auto needleLineMesh = std::dynamic_pointer_cast<LineMesh>(needleObj->getPhysicsGeometry());
+            auto sutureLineMesh = std::dynamic_pointer_cast<LineMesh>(sutureThreadObj->getPhysicsGeometry());
+            (*sutureLineMesh->getVertexPositions())[1] = (*needleLineMesh->getVertexPositions())[0];
+            (*sutureLineMesh->getVertexPositions())[0] = (*needleLineMesh->getVertexPositions())[1];
+
+            // Transform the clamps relative to the needle
+            clampsObj->getVisualGeometry()->setTransform(
+                    needleObj->getVisualGeometry()->getTransform() *
+                    mat4dTranslate(trans) *
+                    mat4dRotation(Rotd(PI, Vec3d(0.0, 1.0, 0.0))));
+            clampsObj->getVisualGeometry()->postModified();
+            });
+
+        // Add mouse and keyboard controls to the viewer
+        {
+            imstkNew<MouseSceneControl> mouseControl(viewer->getMouseDevice());
+            mouseControl->setSceneManager(sceneManager);
+            viewer->addControl(mouseControl);
+
+            imstkNew<KeyboardSceneControl> keyControl(viewer->getKeyboardDevice());
+            keyControl->setSceneManager(sceneManager);
+            keyControl->setModuleDriver(driver);
+            viewer->addControl(keyControl);
+        }
+
+        driver->start();
+    }
+
+    return 0;
+}
\ No newline at end of file
diff --git a/Examples/PBD/PBDString/pbdStringExample.cpp b/Examples/PBD/PBDString/pbdStringExample.cpp
deleted file mode 100644
index c7a10279be12fd8775827be10dd82389fdf3437a..0000000000000000000000000000000000000000
--- a/Examples/PBD/PBDString/pbdStringExample.cpp
+++ /dev/null
@@ -1,231 +0,0 @@
-/*=========================================================================
-
-   Library: iMSTK
-
-   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
-   & Imaging in Medicine, Rensselaer Polytechnic Institute.
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0.txt
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
-=========================================================================*/
-
-#include "imstkCamera.h"
-#include "imstkKeyboardSceneControl.h"
-#include "imstkLineMesh.h"
-#include "imstkLogger.h"
-#include "imstkMouseSceneControl.h"
-#include "imstkNew.h"
-#include "imstkPbdModel.h"
-#include "imstkPbdObject.h"
-#include "imstkRenderMaterial.h"
-#include "imstkScene.h"
-#include "imstkSceneManager.h"
-#include "imstkSimulationManager.h"
-#include "imstkVisualModel.h"
-#include "imstkVTKViewer.h"
-
-using namespace imstk;
-
-///
-/// \brief Create pbd string geometry
-///
-static std::shared_ptr<LineMesh>
-makeStringGeometry(const Vec3d& pos, const int numVerts, const double stringLength)
-{
-    // Create the geometry
-    imstkNew<LineMesh> stringGeometry;
-
-    imstkNew<VecDataArray<double, 3>> verticesPtr(numVerts);
-    VecDataArray<double, 3>&          vertices      = *verticesPtr.get();
-    const double                      vertexSpacing = stringLength / numVerts;
-    for (int i = 0; i < numVerts; i++)
-    {
-        vertices[i] = pos - Vec3d(0.0, static_cast<double>(i) * vertexSpacing, 0.0);
-    }
-
-    // Add connectivity data
-    imstkNew<VecDataArray<int, 2>> segmentsPtr;
-    VecDataArray<int, 2>&          segments = *segmentsPtr.get();
-    for (int i = 0; i < numVerts - 1; i++)
-    {
-        segments.push_back(Vec2i(i, i + 1));
-    }
-
-    stringGeometry->initialize(verticesPtr, segmentsPtr);
-    return stringGeometry;
-}
-
-///
-/// \brief Create pbd string object
-///
-static std::shared_ptr<PbdObject>
-makePbdString(
-    const std::string& name,
-    const Vec3d&       pos,
-    const int          numVerts,
-    const double       stringLength,
-    const double       bendStiffness,
-    const Color&       color)
-{
-    imstkNew<PbdObject> stringObj(name);
-
-    // Setup the Geometry
-    std::shared_ptr<LineMesh> stringMesh = makeStringGeometry(pos, numVerts, stringLength);
-
-    // Setup the Parameters
-    imstkNew<PBDModelConfig> pbdParams;
-    pbdParams->enableConstraint(PbdConstraint::Type::Distance, 1.0e7);
-    pbdParams->enableConstraint(PbdConstraint::Type::Bend, bendStiffness);
-    pbdParams->m_fixedNodeIds     = { 0 };
-    pbdParams->m_uniformMassValue = 5.0;
-    pbdParams->m_gravity    = Vec3d(0.0, -9.8, 0.0);
-    pbdParams->m_dt         = 0.0005;
-    pbdParams->m_iterations = 5;
-
-    // Setup the Model
-    imstkNew<PbdModel> pbdModel;
-    pbdModel->setModelGeometry(stringMesh);
-    pbdModel->configure(pbdParams);
-
-    // Setup the VisualModel
-    imstkNew<RenderMaterial> material;
-    material->setBackFaceCulling(false);
-    material->setColor(color);
-    material->setLineWidth(2.0f);
-    material->setPointSize(6.0f);
-    material->setDisplayMode(RenderMaterial::DisplayMode::Wireframe);
-
-    imstkNew<VisualModel> visualModel(stringMesh);
-    visualModel->setRenderMaterial(material);
-
-    // Setup the Object
-    stringObj->addVisualModel(visualModel);
-    stringObj->setPhysicsGeometry(stringMesh);
-    stringObj->setDynamicalModel(pbdModel);
-
-    return stringObj;
-}
-
-static std::vector<std::shared_ptr<PbdObject>>
-makePbdStrings(const size_t numStrings,
-               const int    numVerts,
-               const double stringSpacing,
-               const double stringLength,
-               const Color& startColor,
-               const Color& endColor)
-{
-    std::vector<std::shared_ptr<PbdObject>> pbdStringObjs(numStrings);
-
-    const double size = stringSpacing * (numStrings - 1);
-
-    for (unsigned int i = 0; i < numStrings; i++)
-    {
-        const Vec3d  tipPos = Vec3d(static_cast<double>(i) * stringSpacing - size * 0.5, stringLength * 0.5, 0.0);
-        const double t      = static_cast<double>(i) / (numStrings - 1);
-
-        pbdStringObjs[i] = makePbdString(
-            "String " + std::to_string(i),
-            tipPos,
-            numVerts,
-            stringLength,
-            (static_cast<double>(i) * 0.1 / numStrings + 0.001) * 1e6,
-            Color::lerpRgb(startColor, endColor, t));
-    }
-
-    return pbdStringObjs;
-}
-
-const double dt            = 0.0005;
-const double radius        = 1.5;
-const size_t numStrings    = 8;                    // Number of strings
-const int    numVerts      = 30;                   // Number of vertices on each string
-const double stringSpacing = 2.0;                  // How far each string is apart
-const double stringLength  = 10.0;                 // Total length of string
-const Color  startColor    = Color(1.0, 0.0, 0.0); // Color of first string
-const Color  endColor      = Color(0.0, 1.0, 0.0); // Color of last string
-
-///
-/// \brief This example demonstrates string simulation
-/// using Position based dynamics with varying bend stiffnesses
-///
-int
-main()
-{
-    // Setup logger (write to file and stdout)
-    Logger::startLogger();
-
-    imstkNew<Scene> scene("PBDString");
-
-    // Setup N separate strings with varying bend stiffnesses
-    std::vector<std::shared_ptr<PbdObject>> pbdStringObjs =
-        makePbdStrings(numStrings, numVerts, stringSpacing, stringLength, startColor, endColor);
-    for (auto obj : pbdStringObjs)
-    {
-        scene->addSceneObject(obj);
-    }
-
-    // Adjust the camera
-    scene->getActiveCamera()->setFocalPoint(0.0, 0.0, 0.0);
-    scene->getActiveCamera()->setPosition(0.0, 0.0, 15.0);
-
-    // Move the points every frame
-    double t = 0.0;
-    auto   movePoints =
-        [&pbdStringObjs, &t](Event*)
-        {
-            for (size_t i = 0; i < pbdStringObjs.size(); i++)
-            {
-                std::shared_ptr<PbdModel>                model     = pbdStringObjs[i]->getPbdModel();
-                std::shared_ptr<VecDataArray<double, 3>> positions = model->getCurrentState()->getPositions();
-                (*positions)[0] += Vec3d(
-                -std::sin(t) * radius * dt,
-                0.0,
-                std::cos(t) * radius * dt);
-            }
-            t += dt;
-        };
-
-    // Run the simulation
-    {
-        // Setup a viewer to render
-        imstkNew<VTKViewer> viewer("Viewer");
-        viewer->setActiveScene(scene);
-
-        // Setup a scene manager to advance the scene
-        imstkNew<SceneManager> sceneManager("Scene Manager");
-        sceneManager->setActiveScene(scene);
-        sceneManager->pause(); // Start simulation paused
-
-        connect<Event>(sceneManager, &SceneManager::postUpdate, movePoints);
-
-        imstkNew<SimulationManager> driver;
-        driver->addModule(viewer);
-        driver->addModule(sceneManager);
-
-        // Add mouse and keyboard controls to the viewer
-        {
-            imstkNew<MouseSceneControl> mouseControl(viewer->getMouseDevice());
-            mouseControl->setSceneManager(sceneManager);
-            viewer->addControl(mouseControl);
-
-            imstkNew<KeyboardSceneControl> keyControl(viewer->getKeyboardDevice());
-            keyControl->setSceneManager(sceneManager);
-            keyControl->setModuleDriver(driver);
-            viewer->addControl(keyControl);
-        }
-
-        driver->start();
-    }
-
-    return 0;
-}
\ No newline at end of file
diff --git a/Examples/PBD/PBDTissueSurfaceNeedleContact/CMakeLists.txt b/Examples/PBD/PBDTissueSurfaceNeedleContact/CMakeLists.txt
index 66296b0626e8c3c37eb842b6846eba61a119374a..c1ce25b8723e34020e640eaff9407489094e4e11 100644
--- a/Examples/PBD/PBDTissueSurfaceNeedleContact/CMakeLists.txt
+++ b/Examples/PBD/PBDTissueSurfaceNeedleContact/CMakeLists.txt
@@ -26,8 +26,8 @@ imstk_add_executable(${PROJECT_NAME}
     NeedleRigidBodyCH.h
     NeedlePbdCH.h
     NeedleObject.h
-    RbdAngularNeedleLockingConstraint.h
-    RbdLinearNeedleLockingConstraint.h
+    RbdAngularLockingConstraint.h
+    RbdAxesLockingConstraint.h
     NeedleInteraction.h)
 
 #-----------------------------------------------------------------------------
diff --git a/Examples/PBD/PBDTissueSurfaceNeedleContact/NeedleInteraction.h b/Examples/PBD/PBDTissueSurfaceNeedleContact/NeedleInteraction.h
index 839ba7a055cc698bac3fe49507e1be5d2c1a6822..d6c52a661e5af083db18a98131218eb025c317f8 100644
--- a/Examples/PBD/PBDTissueSurfaceNeedleContact/NeedleInteraction.h
+++ b/Examples/PBD/PBDTissueSurfaceNeedleContact/NeedleInteraction.h
@@ -61,16 +61,4 @@ public:
     }
 
     ~NeedleInteraction() override = default;
-
-public:
-    ///
-    /// \brief Set the force threshold for the needle
-    ///
-    void setForceThreshold(const double forceThreshold)
-    {
-        if (auto needleRbdCh = std::dynamic_pointer_cast<NeedleRigidBodyCH>(getCollisionHandlingB()))
-        {
-            needleRbdCh->setNeedleForceThreshold(forceThreshold);
-        }
-    }
 };
\ No newline at end of file
diff --git a/Examples/PBD/PBDTissueSurfaceNeedleContact/NeedleObject.h b/Examples/PBD/PBDTissueSurfaceNeedleContact/NeedleObject.h
index 22781bcf4567c0d4d679a786b3c83dd9b28ef2ad..e87b2e743623574d4419b55f1f9b784e10625d72 100644
--- a/Examples/PBD/PBDTissueSurfaceNeedleContact/NeedleObject.h
+++ b/Examples/PBD/PBDTissueSurfaceNeedleContact/NeedleObject.h
@@ -27,6 +27,14 @@ using namespace imstk;
 
 class NeedleObject : public RigidObject2
 {
+public:
+    enum class CollisionState
+    {
+        REMOVED,
+        TOUCHING,
+        INSERTED
+    };
+
 public:
     NeedleObject(const std::string& name) : RigidObject2(name) { }
     virtual ~NeedleObject() = default;
@@ -34,17 +42,24 @@ public:
     virtual const std::string getTypeName() const override { return "NeedleObject"; }
 
 public:
-    void setInserted(const bool inserted) { m_inserted = inserted; }
-    bool getInserted() const { return m_inserted; }
+    void setCollisionState(const CollisionState state) { m_collisionState = state; }
+    CollisionState getCollisionState() const { return m_collisionState; }
+
+    ///
+    /// \brief Set the force threshold for the needle
+    ///
+    void setForceThreshold(const double forceThreshold) { m_forceThreshold = forceThreshold; }
+    double getForceThreshold() const { return m_forceThreshold; }
 
     ///
-    /// \brief Returns the axes of the needle (tip-tail)
+    /// \brief Returns the current axes of the needle (tip-tail)
     ///
-    const Vec3d getNeedleAxes() const
+    const Vec3d getAxes() const
     {
         return (-getCollidingGeometry()->getRotation().col(1)).normalized();
     }
 
 protected:
-    bool m_inserted = false;
+    CollisionState m_collisionState = CollisionState::REMOVED;
+    double m_forceThreshold = 10.0;
 };
\ No newline at end of file
diff --git a/Examples/PBD/PBDTissueSurfaceNeedleContact/NeedlePbdCH.h b/Examples/PBD/PBDTissueSurfaceNeedleContact/NeedlePbdCH.h
index 0c1a50430b8cefc8d157ccda5244680b006799fe..f0d53e5263b6bf0b9cd9bdee78fc34089ab4d7e2 100644
--- a/Examples/PBD/PBDTissueSurfaceNeedleContact/NeedlePbdCH.h
+++ b/Examples/PBD/PBDTissueSurfaceNeedleContact/NeedlePbdCH.h
@@ -26,6 +26,9 @@
 
 using namespace imstk;
 
+///
+/// \brief Surface collision disabled upon puncture
+///
 class NeedlePbdCH : public PBDCollisionHandling
 {
 public:
@@ -43,8 +46,8 @@ protected:
         VertexMassPair ptB1, VertexMassPair ptB2, VertexMassPair ptB3,
         double stiffnessA, double stiffnessB)
     {
-        auto needleObject = std::dynamic_pointer_cast<NeedleObject>(getInputObjectB());
-        if (!needleObject->getInserted())
+        auto needleObj = std::dynamic_pointer_cast<NeedleObject>(getInputObjectB());
+        if (needleObj->getCollisionState() == NeedleObject::CollisionState::TOUCHING)
         {
             PBDCollisionHandling::addVTConstraint(ptA, ptB1, ptB2, ptB3, stiffnessA, stiffnessB);
         }
@@ -58,8 +61,8 @@ protected:
         VertexMassPair ptB1, VertexMassPair ptB2,
         double stiffnessA, double stiffnessB)
     {
-        auto needleObject = std::dynamic_pointer_cast<NeedleObject>(getInputObjectB());
-        if (!needleObject->getInserted())
+        auto needleObj = std::dynamic_pointer_cast<NeedleObject>(getInputObjectB());
+        if (needleObj->getCollisionState() == NeedleObject::CollisionState::TOUCHING)
         {
             PBDCollisionHandling::addEEConstraint(ptA1, ptA2, ptB1, ptB2, stiffnessA, stiffnessB);
         }
@@ -73,8 +76,8 @@ protected:
         VertexMassPair ptB1, VertexMassPair ptB2,
         double stiffnessA, double stiffnessB)
     {
-        auto needleObject = std::dynamic_pointer_cast<NeedleObject>(getInputObjectB());
-        if (!needleObject->getInserted())
+        auto needleObj = std::dynamic_pointer_cast<NeedleObject>(getInputObjectB());
+        if (needleObj->getCollisionState() == NeedleObject::CollisionState::TOUCHING)
         {
             PBDCollisionHandling::addPEConstraint(ptA1, ptB1, ptB2, stiffnessA, stiffnessB);
         }
@@ -87,8 +90,8 @@ protected:
         VertexMassPair ptA, VertexMassPair ptB,
         double stiffnessA, double stiffnessB)
     {
-        auto needleObject = std::dynamic_pointer_cast<NeedleObject>(getInputObjectB());
-        if (!needleObject->getInserted())
+        auto needleObj = std::dynamic_pointer_cast<NeedleObject>(getInputObjectB());
+        if (needleObj->getCollisionState() == NeedleObject::CollisionState::TOUCHING)
         {
             PBDCollisionHandling::addPPConstraint(ptA, ptB, stiffnessA, stiffnessB);
         }
diff --git a/Examples/PBD/PBDTissueSurfaceNeedleContact/NeedleRigidBodyCH.h b/Examples/PBD/PBDTissueSurfaceNeedleContact/NeedleRigidBodyCH.h
index ac541541cc5d3eeb27da56ca8cc0af4277a7b558..0ed362206c66acf992d9a822a5199148707941b4 100644
--- a/Examples/PBD/PBDTissueSurfaceNeedleContact/NeedleRigidBodyCH.h
+++ b/Examples/PBD/PBDTissueSurfaceNeedleContact/NeedleRigidBodyCH.h
@@ -25,8 +25,8 @@
 #include "imstkRbdContactConstraint.h"
 #include "imstkRigidBodyModel2.h"
 #include "NeedleObject.h"
-#include "RbdLinearNeedleLockingConstraint.h"
-#include "RbdAngularNeedleLockingConstraint.h"
+#include "RbdAxesLockingConstraint.h"
+#include "RbdAngularLockingConstraint.h"
 
 using namespace imstk;
 
@@ -56,8 +56,8 @@ protected:
         // If no collision, needle must be removed
         if (elementsA.size() == 0)
         {
-            auto needleObject = std::dynamic_pointer_cast<NeedleObject>(getInputObjectA());
-            needleObject->setInserted(false);
+            auto needleObj = std::dynamic_pointer_cast<NeedleObject>(getInputObjectA());
+            needleObj->setCollisionState(NeedleObject::CollisionState::REMOVED);
         }
     }
 
@@ -69,27 +69,35 @@ protected:
         const Vec3d& contactPt, const Vec3d& contactNormal,
         const double contactDepth) override
     {
-        auto        needleObject = std::dynamic_pointer_cast<NeedleObject>(rbdObj);
-        const Vec3d n = contactNormal.normalized();
-
-        // Get all inwards force
-        const Vec3d  needleAxes = needleObject->getNeedleAxes();
-        const double fN = std::max(needleAxes.dot(needleObject->getRigidBody()->getForce()), 0.0);
+        auto needleObj = std::dynamic_pointer_cast<NeedleObject>(rbdObj);
 
-        // If the normal force exceeds 150 the needle may insert
-        if (fN > m_needleForceThreshold && !needleObject->getInserted())
+        // If the normal force exceeds threshold the needle may insert
+        if (needleObj->getCollisionState() == NeedleObject::CollisionState::REMOVED)
         {
-            LOG(INFO) << "Puncture!\n";
-            needleObject->setInserted(true);
+            needleObj->setCollisionState(NeedleObject::CollisionState::TOUCHING);
+        }
 
-            // Record the axes to constrain too
-            m_initNeedleAxes = needleAxes;
-            m_initNeedleOrientation = Quatd(needleObject->getCollidingGeometry()->getRotation());
-            m_initContactPt = contactPt;
+        const Vec3d n = contactNormal.normalized();
+        if (needleObj->getCollisionState() == NeedleObject::CollisionState::TOUCHING)
+        {
+            // Get all inwards force
+            const Vec3d  needleAxes = needleObj->getAxes();
+            const double fN = std::max(needleAxes.dot(needleObj->getRigidBody()->getForce()), 0.0);
+
+            if (fN > m_needleForceThreshold)
+            {
+                LOG(INFO) << "Puncture!\n";
+                needleObj->setCollisionState(NeedleObject::CollisionState::INSERTED);
+
+                // Record the axes to constrain too
+                m_initNeedleAxes = needleAxes;
+                m_initNeedleOrientation = Quatd(needleObj->getCollidingGeometry()->getRotation());
+                m_initContactPt = contactPt;
+            }
         }
 
         // Only add contact normal constraint if not inserted
-        if (!needleObject->getInserted())
+        if (needleObj->getCollisionState() == NeedleObject::CollisionState::TOUCHING)
         {
             auto contactConstraint = std::make_shared<RbdContactConstraint>(
                 rbdObj->getRigidBody(), nullptr,
@@ -100,32 +108,20 @@ protected:
             rbdObj->getRigidBodyModel2()->addConstraint(contactConstraint);
         }
         // Lock to the initial axes when the needle is inserted
-        else
+        else if (needleObj->getCollisionState() == NeedleObject::CollisionState::INSERTED)
         {
-            auto needleLockConstraint = std::make_shared<RbdLinearNeedleLockingConstraint>(
+            auto needleLockConstraint = std::make_shared<RbdAxesLockingConstraint>(
                 rbdObj->getRigidBody(),
                 m_initContactPt, m_initNeedleAxes,
                 0.05);
             needleLockConstraint->compute(rbdObj->getRigidBodyModel2()->getTimeStep());
             rbdObj->getRigidBodyModel2()->addConstraint(needleLockConstraint);
 
-            auto needleLockConstraint2 = std::make_shared<RbdAngularNeedleLockingConstraint>(
+            auto needleLockConstraint2 = std::make_shared<RbdAngularLockingConstraint>(
                 rbdObj->getRigidBody(), m_initNeedleOrientation, 0.05);
             needleLockConstraint2->compute(rbdObj->getRigidBodyModel2()->getTimeStep());
             rbdObj->getRigidBodyModel2()->addConstraint(needleLockConstraint2);
         }
-
-        /*if (m_useFriction)
-        {
-            std::shared_ptr<RbdFrictionConstraint> frictionConstraint =
-                std::make_shared<RbdFrictionConstraint>(
-                    rbdObj->getRigidBody(), nullptr,
-                    contactPt, contactNormal.normalized(), contactDepth,
-                    m_frictionalCoefficient,
-                    RbdConstraint::Side::A);
-            frictionConstraint->compute(rbdObj->getRigidBodyModel2()->getTimeStep());
-            rbdObj->getRigidBodyModel2()->addConstraint(frictionConstraint);
-        }*/
     }
 
 protected:
diff --git a/Examples/PBD/PBDTissueSurfaceNeedleContact/PBDTissueSurfaceNeedleContactExample.cpp b/Examples/PBD/PBDTissueSurfaceNeedleContact/PBDTissueSurfaceNeedleContactExample.cpp
index f55533f8280de58232613db7526a03cf10e8076a..f3623c3bc300f62ddb8325f22c91a23f0b188c07 100644
--- a/Examples/PBD/PBDTissueSurfaceNeedleContact/PBDTissueSurfaceNeedleContactExample.cpp
+++ b/Examples/PBD/PBDTissueSurfaceNeedleContact/PBDTissueSurfaceNeedleContactExample.cpp
@@ -50,7 +50,7 @@
 #include "imstkHapticDeviceClient.h"
 #include "imstkRigidObjectController.h"
 #else
-#include "imstkKeyboardDeviceClient.h"
+#include "imstkMouseDeviceClient.h"
 #endif
 
 using namespace imstk;
@@ -322,6 +322,7 @@ main()
     scene->addSceneObject(tissueObj);
 
     std::shared_ptr<NeedleObject> toolObj = makeToolObj();
+    toolObj->setForceThreshold(250.0);
     scene->addSceneObject(toolObj);
 
     // Setup a ghost tool object to show off virtual coupling
@@ -338,7 +339,6 @@ main()
     scene->addSceneObject(ghostToolObj);
 
     auto interaction = std::make_shared<NeedleInteraction>(tissueObj, toolObj);
-    interaction->setForceThreshold(250.0);
     scene->getCollisionGraph()->addInteraction(interaction);
 
     // Light
@@ -382,34 +382,47 @@ main()
         controller->setSmoothingKernelSize(20);
         controller->setUseForceSmoothening(true);
         scene->addController(controller);
+
+        connect<Event>(sceneManager, &SceneManager::postUpdate, [&](Event*)
+        {
+            ghostToolObj->getVisualModel(0)->getRenderMaterial()->setOpacity(std::min(1.0, controller->getForce().norm() / 15.0));
+
+            // Also apply controller transform to ghost geometry
+            toolGhostMesh->setTranslation(controller->getPosition());
+            toolGhostMesh->setRotation(controller->getRotation());
+            toolGhostMesh->updatePostTransformData();
+            toolGhostMesh->postModified();
+            });
 #else
-        // Use keyboard controls
-        connect<Event>(sceneManager, SceneManager::preUpdate, [&](Event*)
+        connect<Event>(sceneManager, &SceneManager::postUpdate, [&](Event*)
         {
-            if (viewer->getKeyboardDevice()->getButton('k') == KEY_PRESS)
-            {
-                (*toolObj->getRigidBody()->m_pos) += Vec3d(0.0, -0.01, 0.0);
-            }
-            else if (viewer->getKeyboardDevice()->getButton('i') == KEY_PRESS)
-            {
-                (*toolObj->getRigidBody()->m_pos) += Vec3d(0.0, 0.01, 0.0);
-            }
-            else if (viewer->getKeyboardDevice()->getButton('j') == KEY_PRESS)
-            {
-                (*toolObj->getRigidBody()->m_pos) += Vec3d(-0.01, 0.0, 0.0);
-            }
-            else if (viewer->getKeyboardDevice()->getButton('l') == KEY_PRESS)
-            {
-                (*toolObj->getRigidBody()->m_pos) += Vec3d(0.01, 0.0, 0.0);
-            }
-            else if (viewer->getKeyboardDevice()->getButton('u') == KEY_PRESS)
-            {
-                (*toolObj->getRigidBody()->m_pos) += Vec3d(0.0, 0.0, -0.01);
-            }
-            else if (viewer->getKeyboardDevice()->getButton('o') == KEY_PRESS)
+            const Vec2d mousePos   = viewer->getMouseDevice()->getPos();
+            const Vec3d desiredPos = Vec3d(mousePos[0] - 0.5, mousePos[1] - 0.5, 0.0) * 2.0 + Vec3d(0.0, 1.0, 0.0);
+            const Quatd desiredOrientation = Quatd(Rotd(0.0, Vec3d(1.0, 0.0, 0.0)));
+
+            Vec3d virtualForce;
             {
-                (*toolObj->getRigidBody()->m_pos) += Vec3d(0.0, 0.0, 0.01);
+                const Vec3d fS = (desiredPos - toolObj->getRigidBody()->getPosition()) * 1000.0;   // Spring force
+                const Vec3d fD = -toolObj->getRigidBody()->getVelocity() * 100.0;                  // Spring damping
+
+                const Quatd dq       = desiredOrientation * toolObj->getRigidBody()->getOrientation().inverse();
+                const Rotd angleAxes = Rotd(dq);
+                const Vec3d tS       = angleAxes.axis() * angleAxes.angle() * 10000000.0;
+                const Vec3d tD       = -toolObj->getRigidBody()->getAngularVelocity() * 1000.0;
+
+                virtualForce = fS + fD;
+                (*toolObj->getRigidBody()->m_force)  += virtualForce;
+                (*toolObj->getRigidBody()->m_torque) += tS + tD;
             }
+
+            // Update the ghost debug geometry
+            std::shared_ptr<Geometry> toolGhostMesh = ghostToolObj->getVisualGeometry();
+            toolGhostMesh->setRotation(desiredOrientation);
+            toolGhostMesh->setTranslation(desiredPos);
+            toolGhostMesh->updatePostTransformData();
+            toolGhostMesh->postModified();
+
+            ghostToolObj->getVisualModel(0)->getRenderMaterial()->setOpacity(std::min(1.0, virtualForce.norm() / 15.0));
             });
 #endif
 
@@ -418,22 +431,6 @@ main()
             // Keep the tool moving in real time
             toolObj->getRigidBodyModel2()->getConfig()->m_dt = sceneManager->getDt();
             //tissueObj->getPbdModel()->getParameters()->m_dt = sceneManager->getDt();
-
-#ifdef iMSTK_USE_OPENHAPTICS
-            if (controller->getForce().norm() > 1.0)
-            {
-                ghostToolObj->getVisualModel(0)->setIsVisible(true);
-            }
-            else
-            {
-                ghostToolObj->getVisualModel(0)->setIsVisible(false);
-            }
-            // Also apply controller transform to ghost geometry
-            toolGhostMesh->setTranslation(controller->getPosition());
-            toolGhostMesh->setRotation(controller->getRotation());
-            toolGhostMesh->updatePostTransformData();
-            toolGhostMesh->postModified();
-#endif
         });
 
         // Add mouse and keyboard controls to the viewer
diff --git a/Examples/PBD/PBDTissueVolumeNeedleContact/RbdAngularNeedleLockingConstraint.h b/Examples/PBD/PBDTissueSurfaceNeedleContact/RbdAngularLockingConstraint.h
similarity index 76%
rename from Examples/PBD/PBDTissueVolumeNeedleContact/RbdAngularNeedleLockingConstraint.h
rename to Examples/PBD/PBDTissueSurfaceNeedleContact/RbdAngularLockingConstraint.h
index 1b8eb1b0625a9841f0692b1ed5bab0408036f737..1cb1a10bc12d99dfe7195520b0b9e6291658dcc3 100644
--- a/Examples/PBD/PBDTissueVolumeNeedleContact/RbdAngularNeedleLockingConstraint.h
+++ b/Examples/PBD/PBDTissueSurfaceNeedleContact/RbdAngularLockingConstraint.h
@@ -26,23 +26,23 @@
 using namespace imstk;
 
 ///
-/// \class RbdAngularNeedleLockingConstraint
+/// \class RbdAngularLockingConstraint
 ///
-/// \brief Constrains the orientation to some initial orientation
+/// \brief Constrains the orientation to some fixed orientation
 ///
-class RbdAngularNeedleLockingConstraint : public RbdConstraint
+class RbdAngularLockingConstraint : public RbdConstraint
 {
 public:
-    RbdAngularNeedleLockingConstraint(
+    RbdAngularLockingConstraint(
         std::shared_ptr<RigidBody> obj,
-        const Quatd&               initNeedleOrientation,
+        const Quatd&               fixedOrientation,
         const double               beta = 0.05) : RbdConstraint(obj, nullptr, Side::A),
-        m_initNeedleOrientation(initNeedleOrientation),
+        m_fixedOrientation(fixedOrientation),
         m_beta(beta)
     {
     }
 
-    ~RbdAngularNeedleLockingConstraint() override = default;
+    ~RbdAngularLockingConstraint() override = default;
 
 public:
     void compute(double dt) override
@@ -51,7 +51,7 @@ public:
         J = Eigen::Matrix<double, 3, 4>::Zero();
         if ((m_side == Side::AB || m_side == Side::A) && !m_obj1->m_isStatic)
         {
-            const Quatd dq = m_initNeedleOrientation * m_obj1->getOrientation().inverse();
+            const Quatd dq = m_fixedOrientation * m_obj1->getOrientation().inverse();
             const Rotd  angleAxes = Rotd(dq);
             const Vec3d rotAxes   = angleAxes.axis();
             vu      = angleAxes.angle() * m_beta / dt;
@@ -62,6 +62,6 @@ public:
     }
 
 private:
-    Quatd  m_initNeedleOrientation; ///> Orientation to fix too
+    Quatd  m_fixedOrientation; ///> Orientation to fix too
     double m_beta = 0.05;
 };
\ No newline at end of file
diff --git a/Examples/PBD/PBDTissueSurfaceNeedleContact/RbdLinearNeedleLockingConstraint.h b/Examples/PBD/PBDTissueSurfaceNeedleContact/RbdAxesLockingConstraint.h
similarity index 67%
rename from Examples/PBD/PBDTissueSurfaceNeedleContact/RbdLinearNeedleLockingConstraint.h
rename to Examples/PBD/PBDTissueSurfaceNeedleContact/RbdAxesLockingConstraint.h
index 46ef84a5cb1f0601419e7c31476aa0980d2460f0..fd1485780d5ae5683682bb6ac6e2905f742fc0bc 100644
--- a/Examples/PBD/PBDTissueSurfaceNeedleContact/RbdLinearNeedleLockingConstraint.h
+++ b/Examples/PBD/PBDTissueSurfaceNeedleContact/RbdAxesLockingConstraint.h
@@ -23,33 +23,28 @@
 
 #include "imstkRbdConstraint.h"
 
-#include <functional>
-
 using namespace imstk;
 
 ///
-/// \class RbdLinearNeedleLockingConstraint
+/// \class RbdAxesLockingConstraint
 ///
-/// \brief Constrains the body to specified orientation
-/// and only allows linear movement along the inital axes
+/// \brief Constrains the body center of mass to a fixed axes
 ///
-class RbdLinearNeedleLockingConstraint : public RbdConstraint
+class RbdAxesLockingConstraint : public RbdConstraint
 {
 public:
-    RbdLinearNeedleLockingConstraint(
+    RbdAxesLockingConstraint(
         std::shared_ptr<RigidBody> obj,
-        const Vec3d&               initNeedleAxesPt,
-        const Vec3d&               initNeedleAxes,
-        //const Quatd& initNeedleOrientation,
+        const Vec3d&               axesPt,
+        const Vec3d&               axesDir,
         const double               beta = 0.05) : RbdConstraint(obj, nullptr, Side::A),
-        m_initNeedleAxesPt(initNeedleAxesPt),
-        m_initNeedleAxes(initNeedleAxes),
-        //m_initNeedleOrientation(initNeedleOrientation),
+        m_axesPt(axesPt),
+        m_axesDir(axesDir),
         m_beta(beta)
     {
     }
 
-    ~RbdLinearNeedleLockingConstraint() override = default;
+    ~RbdAxesLockingConstraint() override = default;
 
 public:
     void compute(double dt) override
@@ -58,9 +53,9 @@ public:
         J = Eigen::Matrix<double, 3, 4>::Zero();
         if ((m_side == Side::AB || m_side == Side::A) && !m_obj1->m_isStatic)
         {
-            // Displacement to needle Axes
-            const Vec3d  diff = m_obj1->getPosition() - m_initNeedleAxesPt;
-            const Vec3d  perpDisplacement = diff - m_initNeedleAxes.dot(diff) * m_initNeedleAxes;
+            // Displacement to needle Axes, constrain it to the axes
+            const Vec3d  diff = m_obj1->getPosition() - m_axesPt;
+            const Vec3d  perpDisplacement = diff - m_axesDir.dot(diff) * m_axesDir;
             const double displacement     = perpDisplacement.norm();
             if (displacement != 0)
             {
@@ -80,7 +75,7 @@ public:
     }
 
 private:
-    Vec3d  m_initNeedleAxesPt; ///> Point on the axes to constrain too
-    Vec3d  m_initNeedleAxes;   //> Axes to constrain too
+    Vec3d  m_axesPt;  ///> Point on the axes to constrain too
+    Vec3d  m_axesDir; //> Axes to constrain too
     double m_beta = 0.05;
 };
\ No newline at end of file
diff --git a/Examples/PBD/PBDTissueVolumeNeedleContact/CMakeLists.txt b/Examples/PBD/PBDTissueVolumeNeedleContact/CMakeLists.txt
index 9473e955ce447536bb346ac55916f4ae2d8e10ef..c5e4368fda65d9cda91b92d4313accc567049cfd 100644
--- a/Examples/PBD/PBDTissueVolumeNeedleContact/CMakeLists.txt
+++ b/Examples/PBD/PBDTissueVolumeNeedleContact/CMakeLists.txt
@@ -23,13 +23,15 @@ project(Example-PBDTissueVolumeNeedleContact)
 #-----------------------------------------------------------------------------
 imstk_add_executable(${PROJECT_NAME}
     PBDTissueVolumeNeedleContactExample.cpp
-    NeedleRigidBodyCH.h
-    NeedlePbdCH.h
+    EmbeddingConstraint.h
+    EmbeddingConstraint.cpp
+    NeedleEmbeddedCH.h
+    NeedleEmbeddedCH.cpp
+    NeedleInteraction.h
+    NeedleInteraction.cpp
     NeedleObject.h
-    RbdAngularNeedleLockingConstraint.h
-    RbdLinearNeedleLockingConstraint.h
-    PbdTriangleEmbeddingConstraint.h
-    NeedleInteraction.h)
+    NeedlePbdCH.h
+    NeedleRigidBodyCH.h)
 
 #-----------------------------------------------------------------------------
 # Add the target to Examples folder
@@ -39,4 +41,4 @@ SET_TARGET_PROPERTIES (${PROJECT_NAME} PROPERTIES FOLDER Examples/PBD)
 #-----------------------------------------------------------------------------
 # Link libraries to executable
 #-----------------------------------------------------------------------------
-target_link_libraries(${PROJECT_NAME} SimulationManager)
+target_link_libraries(${PROJECT_NAME} SimulationManager Filtering)
diff --git a/Examples/PBD/PBDTissueVolumeNeedleContact/EmbeddingConstraint.cpp b/Examples/PBD/PBDTissueVolumeNeedleContact/EmbeddingConstraint.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9683244294e2b84b52afb50d8431896291c8dbe1
--- /dev/null
+++ b/Examples/PBD/PBDTissueVolumeNeedleContact/EmbeddingConstraint.cpp
@@ -0,0 +1,147 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#pragma once
+
+#include "EmbeddingConstraint.h"
+#include "imstkCollisionUtils.h"
+
+namespace imstk
+{
+// \todo: Try adding torque/rotation to the tool, two interpolant points a and b with line center of mass c
+// then rotation a to b around c is given with torques (a-c)x(b-c)
+
+void
+EmbeddingConstraint::initConstraint(
+    VertexMassPair ptB1, VertexMassPair ptB2, VertexMassPair ptB3,
+    Vec3d* p, Vec3d* q)
+{
+    // Set the triangle
+    m_bodiesSecond[0] = ptB1;
+    m_bodiesSecond[1] = ptB2;
+    m_bodiesSecond[2] = ptB3;
+    const Vec3d& x1 = *m_bodiesSecond[0].vertex;
+    const Vec3d& x2 = *m_bodiesSecond[1].vertex;
+    const Vec3d& x3 = *m_bodiesSecond[2].vertex;
+
+    // Compute intersection point & interpolant on triangle
+    CollisionUtils::testSegmentTriangle(*p, *q, x1, x2, x3, m_uvw);
+    m_iPt    = x1 * m_uvw[0] + x2 * m_uvw[1] + x3 * m_uvw[2];
+    m_iPtVel = Vec3d::Zero();
+
+    // Set the point on the line for PBD
+    m_bodiesFirst[0] = { &m_iPt, 0.0, &m_iPtVel };
+    const Vec3d& x0 = *m_bodiesFirst[0].vertex;
+
+    // Completely rigid for PBD
+    m_stiffnessA = m_stiffnessB = 1.0;
+
+    // Compute the interpolant on the line
+    {
+        m_p = p;
+        m_q = q;
+        const Vec3d pq = (*p - *q).normalized();
+        const Vec3d d  = x0 - *q;
+        m_t = pq.dot(d);
+    }
+}
+
+Vec3d
+EmbeddingConstraint::computeInterpolantDifference() const
+{
+    //const Vec3d& x0 = *m_bodiesFirst[0].vertex;
+    const Vec3d& x1 = *m_bodiesSecond[0].vertex;
+    const Vec3d& x2 = *m_bodiesSecond[1].vertex;
+    const Vec3d& x3 = *m_bodiesSecond[2].vertex;
+
+    Vec3d*      p    = m_p;
+    Vec3d*      q    = m_q;
+    const Vec3d pq   = (*p - *q);
+    const Vec3d pq_n = pq.normalized();
+
+    // Compute the location of the intersection point on both elements
+    const Vec3d triPos  = x1 * m_uvw[0] + x2 * m_uvw[1] + x3 * m_uvw[2];
+    const Vec3d linePos = (*q) + pq_n * m_t;
+
+    // Compute the transform to align the triangle to the line
+    return triPos - linePos;
+}
+
+bool
+EmbeddingConstraint::computeValueAndGradient(double&             c,
+                                             std::vector<Vec3d>& dcdxA,
+                                             std::vector<Vec3d>& dcdxB) const
+{
+    //const Vec3d& x0 = *m_bodiesFirst[0].vertex;
+    //const Vec3d& x1 = *m_bodiesSecond[0].vertex;
+    //const Vec3d& x2 = *m_bodiesSecond[1].vertex;
+    //const Vec3d& x3 = *m_bodiesSecond[2].vertex;
+
+    // Compute the normal/axes of the line
+    const Vec3d pq   = *m_p - *m_q;
+    const Vec3d pq_n = pq.normalized();
+
+    // Compute the difference between the two interpolated points on the elements
+    Vec3d diff = computeInterpolantDifference();
+
+    // Remove any normal movement (remove only fraction for sort of friction)
+    // Frees normal movement
+    diff = diff - diff.dot(pq_n) * pq_n * (1.0 - m_normalFriction);
+    const Vec3d n = diff.normalized();
+
+    dcdxA[0] = Vec3d::Zero();
+    dcdxB[0] = n;
+    dcdxB[1] = n;
+    dcdxB[2] = n;
+
+    c = -diff.norm() * (1.0 - m_compliance);
+
+    return true;
+}
+
+void
+EmbeddingConstraint::compute(double dt)
+{
+    // Jacobian of contact (defines linear and angular constraint axes)
+    J = Eigen::Matrix<double, 3, 4>::Zero();
+    if (!m_obj1->m_isStatic)
+    {
+        // Compute the normal/axes of the line
+        const Vec3d pq   = *m_p - *m_q;
+        const Vec3d pq_n = pq.normalized();
+
+        // Compute the difference between the two interpolated points on the elements
+        Vec3d diff = computeInterpolantDifference();
+
+        // Remove any normal movement (remove only fraction for sort of friction)
+        // Frees normal movement
+        diff = diff - diff.dot(pq_n) * pq_n * (1.0 - m_normalFriction);
+        const Vec3d n = -diff.normalized();
+
+        vu = diff.norm() * m_beta / dt * m_compliance;
+
+        // Displacement from center of mass
+        J(0, 0) = -n[0]; J(0, 1) = 0.0;
+        J(1, 0) = -n[1]; J(1, 1) = 0.0;
+        J(2, 0) = -n[2]; J(2, 1) = 0.0;
+    }
+}
+}
\ No newline at end of file
diff --git a/Examples/PBD/PBDTissueVolumeNeedleContact/EmbeddingConstraint.h b/Examples/PBD/PBDTissueVolumeNeedleContact/EmbeddingConstraint.h
new file mode 100644
index 0000000000000000000000000000000000000000..95c16e56be5200ed8362d16d412b96f395cbbab3
--- /dev/null
+++ b/Examples/PBD/PBDTissueVolumeNeedleContact/EmbeddingConstraint.h
@@ -0,0 +1,119 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#pragma once
+
+#include "imstkPbdCollisionConstraint.h"
+#include "imstkRbdConstraint.h"
+
+namespace imstk
+{
+///
+/// \brief Given a line and a triangle it computes the intersection between
+/// them upon initialation for which it saves the interpolation weights.
+///
+/// This allows us to deform both shapes whilst maintaining a relative
+/// position on that element.
+///
+/// To then constrain we compute the difference between the two interpolated
+/// positions on each element and pull the line back towards the triangle
+/// via rbd constraint and pull the triangle back towards the line via pbd
+///
+/// A compliance term gives the weighting for which to do this. To make more
+/// physically accurate we would need to take an approach like XPBD in solving.
+///
+class EmbeddingConstraint : public PbdCollisionConstraint, public RbdConstraint
+{
+public:
+    EmbeddingConstraint(std::shared_ptr<RigidBody> obj1) :
+        PbdCollisionConstraint(1, 3), RbdConstraint(obj1, nullptr, RbdConstraint::Side::A)
+    {
+    }
+
+    ~EmbeddingConstraint() override = default;
+
+public:
+    Type getType() const { return Type::PointTriangle; }
+
+    ///
+    /// \brief Initializes both PBD and RBD constraint
+    ///
+    void initConstraint(
+        VertexMassPair ptB1, VertexMassPair ptB2, VertexMassPair ptB3,
+        Vec3d* p, Vec3d* q);
+
+public:
+    ///
+    /// \brief Given two interpolants on the two elements, compute the difference
+    /// between them and use for resolution
+    ///
+    Vec3d computeInterpolantDifference() const;
+
+public:
+    ///
+    /// \brief Update the pbd constraint
+    ///
+    bool computeValueAndGradient(double&             c,
+                                 std::vector<Vec3d>& dcdxA,
+                                 std::vector<Vec3d>& dcdxB) const override;
+
+public:
+    ///
+    /// \brief Update the rbd constraint
+    ///
+    void compute(double dt) override;
+
+protected:
+    // Intersection point via interpolants on triangle
+    Vec3d m_uvw = Vec3d::Zero();
+    // Intersection point via interpolants on line
+    double m_t = 0.0;
+
+    // The actual line
+    Vec3d* m_p = nullptr;
+    Vec3d* m_q = nullptr;
+
+// The triangle is stored in PBD base class
+
+protected:
+    // Step for rbd constraint
+    double m_beta = 0.05;
+
+    Vec3d m_iPt;
+    Vec3d m_iPtVel;
+
+protected:
+    // Ratio between the two models (ie: how much rbd tool is moved vs how much pbd tissue is)
+    //
+    // If 0.0, rbd tool is completely resolved and PBD tissue does not move
+    // If 1.0, pbd tissue completely moves and rbd tool feels no resistance
+    double m_compliance = 0.5;
+
+    //// Ratio between the two models. This gives how much the rbd tool is moved vs how much pbd tissue is
+    //// in the normal direction
+    //double m_normalCompliance = 0.5;
+
+    // If 0.0, completely removes pbd reaction in line axes direction, the pbd triangle will completely let
+    // the tool slide in that direction
+    // If 1.0, completely resist normal movement
+    double m_normalFriction = 0.0;
+};
+}
\ No newline at end of file
diff --git a/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleEmbeddedCH.cpp b/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleEmbeddedCH.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..60897c6dfa067db31d7fb013fc022835f739fd36
--- /dev/null
+++ b/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleEmbeddedCH.cpp
@@ -0,0 +1,252 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "NeedleEmbeddedCH.h"
+#include "EmbeddingConstraint.h"
+#include "imstkCollisionUtils.h"
+#include "imstkLineMesh.h"
+#include "imstkPbdObject.h"
+#include "imstkPbdSolver.h"
+#include "imstkRigidBodyModel2.h"
+#include "imstkTetrahedralMesh.h"
+#include "NeedleObject.h"
+
+#include "imstkMeshIO.h"
+
+#include <unordered_set>
+
+using namespace imstk;
+
+NeedleEmbeddedCH::NeedleEmbeddedCH() :
+    m_solver(std::make_shared<PbdCollisionSolver>())
+{
+}
+
+std::shared_ptr<Geometry>
+NeedleEmbeddedCH::getHandlingGeometryA()
+{
+    auto tissueObj = std::dynamic_pointer_cast<PbdObject>(getInputObjectA());
+    return (tissueObj == nullptr) ? nullptr : tissueObj->getPhysicsGeometry();
+}
+
+void
+NeedleEmbeddedCH::correctVelocities()
+{
+    for (int i = 0; i < m_solverConstraints.size(); i++)
+    {
+        m_solverConstraints[i]->correctVelocity(m_friction, 1.0);
+    }
+}
+
+void
+NeedleEmbeddedCH::solve()
+{
+    m_solver->solve();
+}
+
+void
+NeedleEmbeddedCH::handle(
+    const std::vector<CollisionElement>& elementsA,
+    const std::vector<CollisionElement>& elementsB)
+{
+    auto tissueObj = std::dynamic_pointer_cast<PbdObject>(getInputObjectA());
+    auto needleObj = std::dynamic_pointer_cast<NeedleObject>(getInputObjectB());
+
+    // If needle needle is touching the surface
+    if (needleObj->getCollisionState() == NeedleObject::CollisionState::TOUCHING)
+    {
+        // Get force along the needle axes
+        const Vec3d  needleAxes = needleObj->getNeedleAxes();
+        const double fN = std::max(needleAxes.dot(needleObj->getRigidBody()->getForce()), 0.0);
+
+        // If the normal force exceeds the threshold, mark needle as inserted
+        if (fN > needleObj->getForceThreshold())
+        {
+            LOG(INFO) << "Puncture!";
+            needleObj->setCollisionState(NeedleObject::CollisionState::INSERTED);
+        }
+    }
+
+    // If the needle is inserted
+    if (needleObj->getCollisionState() == NeedleObject::CollisionState::INSERTED)
+    {
+        // Check if there are no tet intersections. If none, mark removed
+        if (elementsA.size() == 0)
+        {
+            LOG(INFO) << "Unpunctured!";
+            needleObj->setCollisionState(NeedleObject::CollisionState::REMOVED);
+            m_faceConstraints.clear();
+        }
+    }
+    // If the needle is not inserted
+    else
+    {
+        // Don't run the embedded CH
+        return;
+    }
+
+    auto tissueGeom = std::dynamic_pointer_cast<TetrahedralMesh>(tissueObj->getPhysicsGeometry());
+    auto needleGeom = std::dynamic_pointer_cast<LineMesh>(needleObj->getCollidingGeometry());
+    needleGeom->updatePostTransformData();
+
+    std::shared_ptr<VecDataArray<double, 3>> tissueVerticesPtr = tissueGeom->getVertexPositions();
+    std::shared_ptr<VecDataArray<int, 4>>    tissueIndicesPtr  = tissueGeom->getTetrahedraIndices();
+
+    auto tissueVelocitiesPtr = std::dynamic_pointer_cast<VecDataArray<double, 3>>(tissueGeom->getVertexAttribute("Velocities"));
+    auto tissueInvMassesPtr  = std::dynamic_pointer_cast<DataArray<double>>(tissueGeom->getVertexAttribute("InvMass"));
+
+    VecDataArray<double, 3>&    tissueVertices   = *tissueVerticesPtr;
+    const VecDataArray<int, 4>& tissueIndices    = *tissueIndicesPtr;
+    VecDataArray<double, 3>&    tissueVelocities = *tissueVelocitiesPtr;
+    const DataArray<double>&    tissueInvMasses  = *tissueInvMassesPtr;
+
+    std::shared_ptr<VecDataArray<double, 3>> needleVerticesPtr = needleGeom->getVertexPositions();
+    std::shared_ptr<VecDataArray<int, 2>>    needleIndicesPtr  = needleGeom->getLinesIndices();
+    VecDataArray<double, 3>&                 needleVertices    = *needleVerticesPtr;
+    const VecDataArray<int, 2>&              needleIndices     = *needleIndicesPtr;
+
+    // Keep track of the constraints that are added *this iteration* vs those already present
+    // in m_faceConstraints so we can find the set that are no longer present
+    std::unordered_set<std::shared_ptr<EmbeddingConstraint>> m_constraintEnabled;
+
+    // Constrain the triangle to the intersection point
+    // If constraint for triangle already exists, update existing intersection point
+    m_debugEmbeddingPoints.clear();
+    m_debugEmbeddedTriangles.clear();
+    auto addConstraint =
+        [&](int v1, int v2, int v3, const Vec3d& iPt)
+        {
+            // Hashable triangle (to resolve shared triangles, any order of v1,v2,v3 maps to same constraint)
+            TriCell triCell(v1, v2, v3);
+
+            m_debugEmbeddingPoints.push_back(iPt);
+            m_debugEmbeddedTriangles.push_back(Vec3i(v1, v2, v3));
+
+            // If constraint doesn't already exist for this triangle
+            if (m_faceConstraints.count(triCell) == 0)
+            {
+                auto constraint = std::make_shared<EmbeddingConstraint>(needleObj->getRigidBody());
+
+                constraint->initConstraint(
+                    { &tissueVertices[v1], tissueInvMasses[v1], &tissueVelocities[v1] },
+                    { &tissueVertices[v2], tissueInvMasses[v2], &tissueVelocities[v2] },
+                    { &tissueVertices[v3], tissueInvMasses[v3], &tissueVelocities[v3] },
+                &needleVertices[0], &needleVertices[1]);
+
+                // Add the constraint to a map of face->constraint
+                m_faceConstraints[triCell] = constraint;
+                //printf("Adding embedding constraint at %d, %d, %d\n", v1, v2, v3);
+            }
+
+            // Mark as present
+            m_constraintEnabled.insert(m_faceConstraints[triCell]);
+        };
+
+    // For every intersected element
+    for (int i = 0; i < elementsA.size(); i++)
+    {
+        const CollisionElement& colElemA = elementsA[i];
+        const CollisionElement& colElemB = elementsB[i];
+
+        if (colElemA.m_type != CollisionElementType::CellIndex || colElemB.m_type != CollisionElementType::CellIndex)
+        {
+            continue;
+        }
+
+        const CellIndexElement& elemA = colElemA.m_element.m_CellIndexElement;
+        const CellIndexElement& elemB = colElemB.m_element.m_CellIndexElement;
+
+        if (elemA.cellType == IMSTK_TETRAHEDRON && elemB.cellType == IMSTK_EDGE)
+        {
+            Vec4i tet;
+            if (elemA.idCount == 1)
+            {
+                tet = tissueIndices[elemA.ids[0]];
+            }
+            else if (elemA.idCount == 4)
+            {
+                tet = Vec4i(elemA.ids[0], elemA.ids[1], elemA.ids[2], elemA.ids[3]);
+            }
+
+            std::array<Vec3d, 2> lineVerts;
+            if (elemB.idCount == 1)
+            {
+                const Vec2i& line = needleIndices[elemB.ids[0]];
+                lineVerts[0] = needleVertices[line[0]];
+                lineVerts[1] = needleVertices[line[1]];
+            }
+            else if (elemB.idCount == 2)
+            {
+                lineVerts[0] = needleVertices[elemB.ids[0]];
+                lineVerts[1] = needleVertices[elemB.ids[1]];
+            }
+            const double lineLength = (lineVerts[1] - lineVerts[0]).norm();
+
+            // For every face of the tet
+            static int faces[4][3] = { { 0, 1, 2 }, { 1, 2, 3 }, { 0, 2, 3 }, { 0, 1, 3 } };
+            for (int j = 0; j < 4; j++)
+            {
+                // Find intersection point and add constraints
+                const Vec3d& a = tissueVertices[tet[faces[j][0]]];
+                const Vec3d& b = tissueVertices[tet[faces[j][1]]];
+                const Vec3d& c = tissueVertices[tet[faces[j][2]]];
+
+                Vec3d iPt;
+                if (CollisionUtils::testPlaneLine(lineVerts[0], lineVerts[1], a, (b - a).cross(c - a).normalized(), iPt))
+                {
+                    Vec3d uvw = baryCentric(iPt, a, b, c);
+                    if (uvw[0] >= 0.0 && uvw[1] >= 0.0 && uvw[2] >= 0.0)
+                    {
+                        // If within line bounds
+                        const double t = (lineVerts[1] - lineVerts[0]).normalized().dot(iPt - lineVerts[0]);
+                        if (t > 0.0 && t < lineLength)
+                        {
+                            addConstraint(
+                                tet[faces[j][0]],
+                                tet[faces[j][1]],
+                                tet[faces[j][2]],
+                                iPt);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    // Add constraint to the PBD solver and RBD system
+    m_solverConstraints.resize(0);
+    m_solverConstraints.reserve(m_faceConstraints.size());
+    for (auto i = m_faceConstraints.begin(); i != m_faceConstraints.end(); i++)
+    {
+        if (m_constraintEnabled.count(i->second) != 0)
+        {
+            // Add pbd and rbd constraint
+            m_solverConstraints.push_back(i->second.get());
+            i->second->compute(needleObj->getRigidBodyModel2()->getTimeStep());
+            needleObj->getRigidBodyModel2()->addConstraint(i->second);
+        }
+        else
+        {
+            i = m_faceConstraints.erase(i);
+        }
+    }
+    m_solver->addCollisionConstraints(&m_solverConstraints);
+}
\ No newline at end of file
diff --git a/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleEmbeddedCH.h b/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleEmbeddedCH.h
index ecb1fd43d0e013ed5f1cf76c3ba2f7d365d329c3..d5c703bf348d2f0185fb682bcd01f3ba353c00a7 100644
--- a/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleEmbeddedCH.h
+++ b/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleEmbeddedCH.h
@@ -22,20 +22,15 @@
 #pragma once
 
 #include "imstkCollisionHandling.h"
-#include "imstkPbdCollisionConstraint.h"
-#include "imstkCollisionUtils.h"
-#include "PbdTriangleEmbeddingConstraint.h"
 
 #include <unordered_map>
 
 namespace imstk
 {
+class EmbeddingConstraint;
+class Geometry;
 class PbdCollisionSolver;
-class PbdEdgeEdgeConstraint;
-class PbdObject;
-class PbdPointEdgeConstraint;
-class PbdPointPointConstraint;
-class PbdPointTriangleConstraint;
+class PbdCollisionConstraint;
 }
 
 using namespace imstk;
@@ -96,231 +91,54 @@ struct hash<TriCell>
 ///
 /// \class NeedleEmbeddedCH
 ///
-/// \brief Implements PBD-RBD embedded tissue handling
+/// \brief Implements PBD-RBD embedded tissue handling for when the
+/// needle is embedded in the tissue
 ///
 class NeedleEmbeddedCH : public CollisionHandling
 {
 public:
-    NeedleEmbeddedCH() :
-        m_solver(std::make_shared<PbdCollisionSolver>())
-    {
-    }
-
+    NeedleEmbeddedCH();
     virtual ~NeedleEmbeddedCH() override = default;
 
     virtual const std::string getTypeName() const override { return "NeedleEmbeddedCH"; }
 
 public:
-    std::shared_ptr<Geometry> getHandlingGeometryA() override
-    {
-        auto tissueObj = std::dynamic_pointer_cast<PbdObject>(getInputObjectA());
-        return (tissueObj == nullptr) ? nullptr : tissueObj->getPhysicsGeometry();
-    }
+    std::shared_ptr<Geometry> getHandlingGeometryA() override;
 
 public:
     ///
     /// \brief Corrects for velocity (restitution and friction) after PBD is complete
     ///
-    void correctVelocities()
-    {
-        for (int i = 0; i < m_solverConstraints.size(); i++)
-        {
-            m_solverConstraints[i]->correctVelocity(m_friction, 1.0);
-        }
-    }
+    void correctVelocities();
 
-    void solve() { m_solver->solve(); }
+    void solve();
 
     void setFriction(const double friction) { m_friction = friction; }
     const double getFriction() const { return m_friction; }
 
+    void setCollisionSolver(std::shared_ptr<PbdCollisionSolver> solver) { m_solver = solver; }
+    std::shared_ptr<PbdCollisionSolver> getCollisionSolver() const { return m_solver; }
+
 protected:
     ///
     /// \brief Add embedding constraints based off contact data
+    /// We need to add the constraint once and then update it later
     ///
     void handle(
         const std::vector<CollisionElement>& elementsA,
-        const std::vector<CollisionElement>& elementsB) override
-    {
-        auto tissueObj = std::dynamic_pointer_cast<PbdObject>(getInputObjectA());
-        auto needleObj = std::dynamic_pointer_cast<NeedleObject>(getInputObjectB());
-
-        auto tissueGeom = std::dynamic_pointer_cast<TetrahedralMesh>(tissueObj->getPhysicsGeometry());
-        auto needleGeom = std::dynamic_pointer_cast<LineMesh>(needleObj->getCollidingGeometry());
-
-        std::shared_ptr<VecDataArray<double, 3>> tissueVerticesPtr   = tissueGeom->getVertexPositions();
-        std::shared_ptr<VecDataArray<int, 4>>    tissueIndicesPtr    = tissueGeom->getTetrahedraIndices();
-        auto                                     tissueVelocitiesPtr = std::dynamic_pointer_cast<VecDataArray<double, 3>>(tissueGeom->getVertexAttribute("Velocities"));
-        auto                                     tissueInvMassesPtr  = std::dynamic_pointer_cast<DataArray<double>>(tissueGeom->getVertexAttribute("InvMass"));
-
-        VecDataArray<double, 3>&    tissueVertices   = *tissueVerticesPtr;
-        const VecDataArray<int, 4>& tissueIndices    = *tissueIndicesPtr;
-        VecDataArray<double, 3>&    tissueVelocities = *tissueVelocitiesPtr;
-        const DataArray<double>&    tissueInvMasses  = *tissueInvMassesPtr;
-
-        std::shared_ptr<VecDataArray<double, 3>> needleVerticesPtr = needleGeom->getVertexPositions();
-        std::shared_ptr<VecDataArray<int, 2>>    needleIndicesPtr  = needleGeom->getLinesIndices();
-        VecDataArray<double, 3>&                 needleVertices    = *needleVerticesPtr;
-        const VecDataArray<int, 2>&              needleIndices     = *needleIndicesPtr;
-
-        // Keep track of the constraints that are added *this iteration* vs those already present
-        // so we can find the set that are no longer present
-        std::unordered_set<std::shared_ptr<PbdTriangleEmbeddingConstraint>> m_constraintEnabled;
-        auto                                                                isConstraintEnabled = [&](std::shared_ptr<PbdTriangleEmbeddingConstraint> constraint)
-                                                                                                  {
-                                                                                                      return m_constraintEnabled.count(constraint) != 0;
-                                                                                                  };
-
-        // Constrain the triangle to the intersection point
-        // If constraint for triangle already exists, update existing intersection point
-        auto addConstraint = [&](int v1, int v2, int v3, const Vec3d& iPt)
-                             {
-                                 // Hashable triangle
-                                 TriCell triCell(v1, v2, v3);
-
-                                 // If constraint doesn't exist OR if constraint exists but has been disabled
-                                 if (m_faceConstraints.count(triCell) == 0 || m_faceConstraints[triCell] == nullptr)
-                                 {
-                                     auto constraint = std::make_shared<PbdTriangleEmbeddingConstraint>();
-
-                                     // Push back to a list so mem locations don't change
-                                     m_fixedPoints.push_back(iPt);
-                                     m_fixedPointVelocities.push_back(Vec3d::Zero());
-
-                                     constraint->initConstraint(
-                                         { &m_fixedPoints.back(), 0.0, &m_fixedPointVelocities.back() },
-                                         { &tissueVertices[v1], tissueInvMasses[v1], &tissueVelocities[v1] },
-                                         { &tissueVertices[v2], tissueInvMasses[v2], &tissueVelocities[v2] },
-                                         { &tissueVertices[v3], tissueInvMasses[v3], &tissueVelocities[v3] },
-                    1.0, 1.0, &needleVertices[0], &needleVertices[1], m_friction);
-
-                                     // Add the constraint to a map of face->constraint
-                                     m_faceConstraints[triCell] = constraint;
-                                     m_constraintEnabled.insert(constraint);
-                                 }
-                                 // If already contains, then update the intersection point
-                                 else
-                                 {
-                                     // Get the existing constraint
-                                     std::shared_ptr<PbdTriangleEmbeddingConstraint> constraint = m_faceConstraints[triCell];
-                                     (*constraint->getVertexMassA().vertex) = iPt;
-                                     m_constraintEnabled.insert(constraint);
-                                 }
-                             };
-
-        // If needle has been inserted
-        if (needleObj->getInserted())
-        {
-            if (elementsA.size() != elementsB.size())
-            {
-                return;
-            }
-
-            // Tet faces
-            static int faces[4][3] = { { 0, 1, 2 }, { 1, 2, 3 }, { 0, 2, 3 }, { 0, 1, 3 } };
-
-            // For every intersected element
-            for (int i = 0; i < elementsA.size(); i++)
-            {
-                const CollisionElement& colElemA = elementsA[i];
-                const CollisionElement& colElemB = elementsB[i];
-
-                if (colElemA.m_type != CollisionElementType::CellIndex || colElemB.m_type != CollisionElementType::CellIndex)
-                {
-                    continue;
-                }
-
-                const CellIndexElement& elemA = colElemA.m_element.m_CellIndexElement;
-                const CellIndexElement& elemB = colElemB.m_element.m_CellIndexElement;
-
-                if (elemA.cellType == IMSTK_TETRAHEDRON && elemB.cellType == IMSTK_EDGE)
-                {
-                    Vec4i tet;
-                    if (elemA.idCount == 1)
-                    {
-                        tet = tissueIndices[elemA.ids[0]];
-                    }
-                    else if (elemA.idCount == 4)
-                    {
-                        tet = Vec4i(elemA.ids[0], elemA.ids[1], elemA.ids[2], elemA.ids[3]);
-                    }
-                    std::array<Vec3d, 4> tetVerts;
-                    tetVerts[0] = tissueVertices[tet[0]];
-                    tetVerts[1] = tissueVertices[tet[1]];
-                    tetVerts[2] = tissueVertices[tet[2]];
-                    tetVerts[3] = tissueVertices[tet[3]];
-
-                    std::array<Vec3d, 2> lineVerts;
-                    if (elemB.idCount == 1)
-                    {
-                        const Vec2i& line = needleIndices[elemB.ids[0]];
-                        lineVerts[0] = needleVertices[line[0]];
-                        lineVerts[1] = needleVertices[line[1]];
-                    }
-                    else if (elemB.idCount == 2)
-                    {
-                        lineVerts[0] = needleVertices[elemB.ids[0]];
-                        lineVerts[1] = needleVertices[elemB.ids[1]];
-                    }
-
-                    // Determine which faces intersect
-                    int   face0 = -1;
-                    int   face1 = -1;
-                    Vec3d iPt0  = Vec3d::Zero();
-                    Vec3d iPt1  = Vec3d::Zero();
-                    if (CollisionUtils::testTetToSegment(tetVerts,
-                        lineVerts[0], lineVerts[1],
-                        face0, face1, iPt0, iPt1))
-                    {
-                        if (face0 != -1)
-                        {
-                            addConstraint(
-                                tet[faces[face0][0]],
-                                tet[faces[face0][1]],
-                                tet[faces[face0][2]],
-                                iPt0);
-                        }
-                        if (face1 != -1)
-                        {
-                            addConstraint(
-                                tet[faces[face1][0]],
-                                tet[faces[face1][1]],
-                                tet[faces[face1][2]],
-                                iPt1);
-                        }
-                    }
-                }
-            }
-        }
-
-        m_solverConstraints.resize(0);
-        m_solverConstraints.reserve(m_faceConstraints.size());
-        for (auto& i : m_faceConstraints)
-        {
-            if (i.second != nullptr && isConstraintEnabled(i.second))
-            {
-                m_solverConstraints.push_back(i.second.get());
-            }
-            else
-            {
-                // Cleanup (without deleting to avoid slight cost)
-                i.second = nullptr;
-            }
-        }
-        m_solver->addCollisionConstraints(&m_solverConstraints);
-    }
+        const std::vector<CollisionElement>& elementsB) override;
 
 private:
     // TriCell takes care of duplicate faces
-    std::unordered_map<TriCell, std::shared_ptr<PbdTriangleEmbeddingConstraint>> m_faceConstraints;
+    std::unordered_map<TriCell, std::shared_ptr<EmbeddingConstraint>> m_faceConstraints;
 
     std::shared_ptr<PbdCollisionSolver>  m_solver = nullptr;
     std::vector<PbdCollisionConstraint*> m_solverConstraints; ///> List of PBD constraints
 
-    // Lists to hold valid references to them in memory
-    std::list<Vec3d> m_fixedPoints;
-    std::list<Vec3d> m_fixedPointVelocities;
-
     //double m_restitution = 0.0; ///> Coefficient of restitution (1.0 = perfect elastic, 0.0 = inelastic)
     double m_friction = 0.001; ///> Coefficient of friction (1.0 = full frictional force, 0.0 = none)
+
+public:
+    std::vector<Vec3d> m_debugEmbeddingPoints; ///> Used for debug visualization
+    std::vector<Vec3i> m_debugEmbeddedTriangles;
 };
\ No newline at end of file
diff --git a/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleInteraction.cpp b/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleInteraction.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..24cc125c0ec98e01ab7978f42dbdbaadd73f8388
--- /dev/null
+++ b/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleInteraction.cpp
@@ -0,0 +1,123 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "NeedleInteraction.h"
+#include "imstkLineMesh.h"
+#include "imstkPbdObject.h"
+#include "imstkTaskGraph.h"
+#include "imstkTetraToLineMeshCD.h"
+#include "NeedleEmbeddedCH.h"
+#include "NeedlePbdCH.h"
+#include "NeedleRigidBodyCH.h"
+#include "imstkTetrahedralMesh.h"
+
+using namespace imstk;
+
+NeedleInteraction::NeedleInteraction(std::shared_ptr<PbdObject>    tissueObj,
+                                     std::shared_ptr<NeedleObject> needleObj) : PbdRigidObjectCollision(tissueObj, needleObj)
+{
+    if (std::dynamic_pointer_cast<LineMesh>(needleObj->getCollidingGeometry()) == nullptr)
+    {
+        LOG(WARNING) << "NeedleInteraction only works with LineMesh collision geometry on rigid NeedleObject";
+    }
+    if (std::dynamic_pointer_cast<TetrahedralMesh>(tissueObj->getPhysicsGeometry()) == nullptr)
+    {
+        LOG(WARNING) << "NeedleInteraction only works with TetrahedralMesh physics geometry on pbd tissueObj";
+    }
+
+    // First replace the handlers with our needle subclasses
+
+    // This handler consumes collision data to resolve the tool from the tissue
+    // except when the needle is inserted
+    auto needleRbdCH = std::make_shared<NeedleRigidBodyCH>();
+    needleRbdCH->setInputRigidObjectA(needleObj);
+    needleRbdCH->setInputCollidingObjectB(tissueObj);
+    needleRbdCH->setInputCollisionData(getCollisionDetection()->getCollisionData());
+    needleRbdCH->setBeta(0.001);
+    needleRbdCH->getTaskNode()->m_isCritical = true;
+    setCollisionHandlingB(needleRbdCH);
+
+    // This handler consumes the collision data to resolve the tissue from the tool
+    // except when the needle is inserted
+    auto needlePbdCH = std::make_shared<NeedlePbdCH>();
+    needlePbdCH->setInputObjectA(tissueObj);
+    needlePbdCH->setInputObjectB(needleObj);
+    needlePbdCH->setInputCollisionData(getCollisionDetection()->getCollisionData());
+    //needlePbdCH->getCollisionSolver()->setCollisionIterations(1);
+    needlePbdCH->getTaskNode()->m_isCritical = true;
+    setCollisionHandlingA(needlePbdCH);
+
+    // Then add a separate scheme for when the needle is embedded
+
+    // Assumes usage of physics geometry for this
+    tetMeshCD = std::make_shared<TetraToLineMeshCD>();
+    tetMeshCD->setInputGeometryA(tissueObj->getPhysicsGeometry());
+    tetMeshCD->setInputGeometryB(needleObj->getCollidingGeometry());
+
+    embeddedCH = std::make_shared<NeedleEmbeddedCH>();
+    embeddedCH->setInputCollisionData(tetMeshCD->getCollisionData());
+    embeddedCH->setCollisionSolver(needlePbdCH->getCollisionSolver());
+    embeddedCH->setInputObjectA(tissueObj);
+    embeddedCH->setInputObjectB(needleObj);
+    embeddedCH->getTaskNode()->m_isCritical = true;
+}
+
+void
+NeedleInteraction::apply()
+{
+    PbdRigidObjectCollision::apply();
+
+    std::shared_ptr<TaskGraph> taskGraphA = m_objects.first->getTaskGraph();  // Pbd
+    std::shared_ptr<TaskGraph> taskGraphB = m_objects.second->getTaskGraph(); // Rbd
+
+    std::shared_ptr<CollisionDetectionAlgorithm> cd = m_colDetect;
+
+    auto                               pbdObj = std::dynamic_pointer_cast<PbdObject>(m_objects.first);
+    std::shared_ptr<CollisionHandling> pbdCH  = m_colHandlingA;
+
+    auto                               rbdObj = std::dynamic_pointer_cast<RigidObject2>(m_objects.second);
+    std::shared_ptr<CollisionHandling> rbdCH  = m_colHandlingB;
+
+    // Needle interaction introduces its own collision detection step, handling, solve, and velocity correction
+    auto needleEmbeddedCD =
+        std::make_shared<TaskNode>([&]() { tetMeshCD->update(); }, "NeedleEmbeddingCD", true);
+    auto embeddingCHNode =
+        std::make_shared<TaskNode>([&]() { embeddedCH->update(); }, "NeedleEmbeddingCH", true);
+
+    {
+        taskGraphA->addNode(needleEmbeddedCD);
+        taskGraphA->addNode(embeddingCHNode);
+
+        // PBD CH -> EmbeddedCD -> EmbeddedCH -> Collision Solve
+        taskGraphA->addEdge(m_colHandlingA->getTaskNode(), needleEmbeddedCD);
+        taskGraphA->addEdge(needleEmbeddedCD, embeddingCHNode);
+        taskGraphA->addEdge(embeddingCHNode, m_pbdCollisionSolveNode);
+    }
+
+    {
+        taskGraphB->addNode(needleEmbeddedCD);
+        taskGraphB->addNode(embeddingCHNode);
+
+        taskGraphB->addEdge(m_colHandlingB->getTaskNode(), needleEmbeddedCD);
+        taskGraphB->addEdge(needleEmbeddedCD, embeddingCHNode);
+        taskGraphB->addEdge(embeddingCHNode, rbdObj->getRigidBodyModel2()->getSolveNode());
+    }
+}
\ No newline at end of file
diff --git a/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleInteraction.h b/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleInteraction.h
index cb726402ec4e4f95257569bea1fc8f491ea7245b..4c12dba748d12adca2244e2ea9a79da8424c2ba7 100644
--- a/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleInteraction.h
+++ b/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleInteraction.h
@@ -22,16 +22,17 @@
 #pragma once
 
 #include "imstkPbdRigidObjectCollision.h"
-#include "imstkTetraToLineMeshCD.h"
 
-#include "NeedlePbdCH.h"
-#include "NeedleRigidBodyCH.h"
-#include "NeedleEmbeddedCH.h"
+using namespace imstk;
 
-#include "imstkTaskGraph.h"
-#include "imstkTaskNode.h"
+class NeedleEmbeddedCH;
+class NeedleObject;
 
-using namespace imstk;
+namespace imstk
+{
+class PbdObject;
+class TetraToLineMeshCD;
+}
 
 ///
 /// \class NeedleInteraction
@@ -41,121 +42,16 @@ using namespace imstk;
 class NeedleInteraction : public PbdRigidObjectCollision
 {
 public:
-    NeedleInteraction(std::shared_ptr<PbdObject> tissueObj, std::shared_ptr<NeedleObject> needleObj) : PbdRigidObjectCollision(tissueObj, needleObj)
-    {
-        if (std::dynamic_pointer_cast<LineMesh>(needleObj->getCollidingGeometry()) == nullptr)
-        {
-            LOG(WARNING) << "NeedleInteraction only works with LineMesh collision geometry on NeedleObject";
-        }
-
-        // This handler consumes collision data to resolve the tool from the tissue
-        imstkNew<NeedleRigidBodyCH> needleRbdCH;
-        needleRbdCH->setInputRigidObjectA(needleObj);
-        needleRbdCH->setInputCollidingObjectB(tissueObj);
-        needleRbdCH->setInputCollisionData(getCollisionDetection()->getCollisionData());
-        needleRbdCH->setBeta(0.001);
-        needleRbdCH->getTaskNode()->m_isCritical = true;
-        setCollisionHandlingB(needleRbdCH);
-
-        // This handler consumes the collision data to resolve the tissue from the tool
-        imstkNew<NeedlePbdCH> needlePbdCH;
-        needlePbdCH->setInputObjectA(tissueObj);
-        needlePbdCH->setInputObjectB(needleObj);
-        needlePbdCH->setInputCollisionData(getCollisionDetection()->getCollisionData());
-        needlePbdCH->getCollisionSolver()->setCollisionIterations(1);
-        needlePbdCH->getTaskNode()->m_isCritical = true;
-        setCollisionHandlingA(needlePbdCH);
-
-        // This handler consumes collision data to produce PBD and RBD constraints when the tool
-        // is embedded in the tissue (ie: when NeedleObject::inserted is on)
-
-        // Assumes usage of physics geometry for this
-        tetMeshCD = std::make_shared<TetraToLineMeshCD>();
-        tetMeshCD->setInputGeometryA(tissueObj->getPhysicsGeometry());
-        tetMeshCD->setInputGeometryB(needleObj->getCollidingGeometry());
-
-        embeddedCH = std::make_shared<NeedleEmbeddedCH>();
-        embeddedCH->setInputCollisionData(tetMeshCD->getCollisionData());
-        embeddedCH->setInputObjectA(tissueObj);
-        embeddedCH->setInputObjectB(needleObj);
-        embeddedCH->getTaskNode()->m_isCritical = true;
-    }
-
+    NeedleInteraction(std::shared_ptr<PbdObject>    tissueObj,
+                      std::shared_ptr<NeedleObject> needleObj);
     ~NeedleInteraction() override = default;
 
 public:
-    ///
-    /// \brief Set the force threshold for the needle
-    ///
-    void setForceThreshold(const double forceThreshold)
-    {
-        if (auto needleRbdCh = std::dynamic_pointer_cast<NeedleRigidBodyCH>(getCollisionHandlingB()))
-        {
-            needleRbdCh->setNeedleForceThreshold(forceThreshold);
-        }
-    }
+    std::shared_ptr<TetraToLineMeshCD> getEmbeddingCD() const { return tetMeshCD; }
+    std::shared_ptr<NeedleEmbeddedCH> getEmbeddingCH() const { return embeddedCH; }
 
 protected:
-    void apply() override
-    {
-        PbdRigidObjectCollision::apply();
-
-        std::shared_ptr<TaskGraph> taskGraphA = m_objects.first->getTaskGraph();  // Pbd
-        std::shared_ptr<TaskGraph> taskGraphB = m_objects.second->getTaskGraph(); // Rbd
-
-        std::shared_ptr<CollisionDetectionAlgorithm> cd = m_colDetect;
-
-        auto                               pbdObj = std::dynamic_pointer_cast<PbdObject>(m_objects.first);
-        std::shared_ptr<CollisionHandling> pbdCH  = m_colHandlingA;
-
-        auto                               rbdObj = std::dynamic_pointer_cast<RigidObject2>(m_objects.second);
-        std::shared_ptr<CollisionHandling> rbdCH  = m_colHandlingB;
-
-        // Detection collision with the tet mesh
-        auto needleEmbeddedCD =
-            std::make_shared<TaskNode>([&]() { tetMeshCD->update(); }, "NeedleEmbeddingCD", true);
-        taskGraphA->addNode(needleEmbeddedCD);
-        taskGraphB->addNode(needleEmbeddedCD);
-
-        taskGraphA->addEdge(m_colDetect->getTaskNode(), needleEmbeddedCD);
-        taskGraphA->addEdge(needleEmbeddedCD, pbdCH->getTaskNode());
-
-        taskGraphB->addEdge(m_colDetect->getTaskNode(), needleEmbeddedCD);
-        taskGraphB->addEdge(needleEmbeddedCD, pbdCH->getTaskNode());
-
-        // Consume the collision data after main handler executes but before collisions are solved for
-        auto embeddingCHNode =
-            std::make_shared<TaskNode>([&]() { embeddedCH->update(); }, "NeedleEmbeddingCH", true);
-        taskGraphA->addNode(embeddingCHNode);
-        taskGraphB->addNode(embeddingCHNode);
-
-        // pbdCH -> embeddingCH -> collision solve (moves vertices)
-        taskGraphA->addEdge(pbdCH->getTaskNode(), embeddingCHNode);
-        taskGraphA->addEdge(embeddingCHNode, m_pbdCollisionSolveNode);
-
-        taskGraphB->addEdge(pbdCH->getTaskNode(), embeddingCHNode);
-        taskGraphB->addEdge(embeddingCHNode, m_pbdCollisionSolveNode);
-
-        // Add a solver step for the embedding constraints solver
-        // Should happen after collision to ensure convergence on final constraints
-        auto embeddingSolveNode =
-            std::make_shared<TaskNode>([&]() { embeddedCH->solve(); }, "NeedleEmbeddingSolve", true);
-        auto correctNeedleVelocitiesNode =
-            std::make_shared<TaskNode>([&]() { embeddedCH->correctVelocities(); }, "NeedleEmbeddingCorrectVelocities");
-        taskGraphA->addNode(embeddingSolveNode);
-        taskGraphA->addNode(correctNeedleVelocitiesNode);
-        taskGraphB->addNode(embeddingSolveNode);
-        taskGraphB->addNode(correctNeedleVelocitiesNode);
-
-        taskGraphA->addEdge(m_pbdCollisionSolveNode, embeddingSolveNode);
-        taskGraphA->addEdge(embeddingSolveNode, pbdObj->getPbdModel()->getUpdateVelocityNode());
-
-        taskGraphA->addEdge(m_correctVelocitiesNode, correctNeedleVelocitiesNode);
-        taskGraphA->addEdge(correctNeedleVelocitiesNode, pbdObj->getPbdModel()->getTaskGraph()->getSink());
-
-        taskGraphB->addEdge(m_pbdCollisionSolveNode, embeddingSolveNode);
-        taskGraphB->addEdge(embeddingSolveNode, rbdObj->getRigidBodyModel2()->getIntegrateNode());
-    }
+    void apply() override;
 
 protected:
     std::shared_ptr<TetraToLineMeshCD> tetMeshCD;
diff --git a/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleObject.h b/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleObject.h
index 22781bcf4567c0d4d679a786b3c83dd9b28ef2ad..0e1998e8305b986bfc02f70d1bd39fed8cf95cc4 100644
--- a/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleObject.h
+++ b/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleObject.h
@@ -27,6 +27,14 @@ using namespace imstk;
 
 class NeedleObject : public RigidObject2
 {
+public:
+    enum class CollisionState
+    {
+        REMOVED,
+        TOUCHING,
+        INSERTED
+    };
+
 public:
     NeedleObject(const std::string& name) : RigidObject2(name) { }
     virtual ~NeedleObject() = default;
@@ -34,11 +42,17 @@ public:
     virtual const std::string getTypeName() const override { return "NeedleObject"; }
 
 public:
-    void setInserted(const bool inserted) { m_inserted = inserted; }
-    bool getInserted() const { return m_inserted; }
+    void setCollisionState(const CollisionState state) { m_collisionState = state; }
+    CollisionState getCollisionState() const { return m_collisionState; }
+
+    ///
+    /// \brief Set the force threshold for the needle
+    ///
+    void setForceThreshold(const double forceThreshold) { m_forceThreshold = forceThreshold; }
+    double getForceThreshold() const { return m_forceThreshold; }
 
     ///
-    /// \brief Returns the axes of the needle (tip-tail)
+    /// \brief Returns the current axes of the needle (tip-tail)
     ///
     const Vec3d getNeedleAxes() const
     {
@@ -46,5 +60,6 @@ public:
     }
 
 protected:
-    bool m_inserted = false;
+    CollisionState m_collisionState = CollisionState::REMOVED;
+    double m_forceThreshold = 10.0;
 };
\ No newline at end of file
diff --git a/Examples/PBD/PBDTissueVolumeNeedleContact/NeedlePbdCH.h b/Examples/PBD/PBDTissueVolumeNeedleContact/NeedlePbdCH.h
index 0c1a50430b8cefc8d157ccda5244680b006799fe..f0d53e5263b6bf0b9cd9bdee78fc34089ab4d7e2 100644
--- a/Examples/PBD/PBDTissueVolumeNeedleContact/NeedlePbdCH.h
+++ b/Examples/PBD/PBDTissueVolumeNeedleContact/NeedlePbdCH.h
@@ -26,6 +26,9 @@
 
 using namespace imstk;
 
+///
+/// \brief Surface collision disabled upon puncture
+///
 class NeedlePbdCH : public PBDCollisionHandling
 {
 public:
@@ -43,8 +46,8 @@ protected:
         VertexMassPair ptB1, VertexMassPair ptB2, VertexMassPair ptB3,
         double stiffnessA, double stiffnessB)
     {
-        auto needleObject = std::dynamic_pointer_cast<NeedleObject>(getInputObjectB());
-        if (!needleObject->getInserted())
+        auto needleObj = std::dynamic_pointer_cast<NeedleObject>(getInputObjectB());
+        if (needleObj->getCollisionState() == NeedleObject::CollisionState::TOUCHING)
         {
             PBDCollisionHandling::addVTConstraint(ptA, ptB1, ptB2, ptB3, stiffnessA, stiffnessB);
         }
@@ -58,8 +61,8 @@ protected:
         VertexMassPair ptB1, VertexMassPair ptB2,
         double stiffnessA, double stiffnessB)
     {
-        auto needleObject = std::dynamic_pointer_cast<NeedleObject>(getInputObjectB());
-        if (!needleObject->getInserted())
+        auto needleObj = std::dynamic_pointer_cast<NeedleObject>(getInputObjectB());
+        if (needleObj->getCollisionState() == NeedleObject::CollisionState::TOUCHING)
         {
             PBDCollisionHandling::addEEConstraint(ptA1, ptA2, ptB1, ptB2, stiffnessA, stiffnessB);
         }
@@ -73,8 +76,8 @@ protected:
         VertexMassPair ptB1, VertexMassPair ptB2,
         double stiffnessA, double stiffnessB)
     {
-        auto needleObject = std::dynamic_pointer_cast<NeedleObject>(getInputObjectB());
-        if (!needleObject->getInserted())
+        auto needleObj = std::dynamic_pointer_cast<NeedleObject>(getInputObjectB());
+        if (needleObj->getCollisionState() == NeedleObject::CollisionState::TOUCHING)
         {
             PBDCollisionHandling::addPEConstraint(ptA1, ptB1, ptB2, stiffnessA, stiffnessB);
         }
@@ -87,8 +90,8 @@ protected:
         VertexMassPair ptA, VertexMassPair ptB,
         double stiffnessA, double stiffnessB)
     {
-        auto needleObject = std::dynamic_pointer_cast<NeedleObject>(getInputObjectB());
-        if (!needleObject->getInserted())
+        auto needleObj = std::dynamic_pointer_cast<NeedleObject>(getInputObjectB());
+        if (needleObj->getCollisionState() == NeedleObject::CollisionState::TOUCHING)
         {
             PBDCollisionHandling::addPPConstraint(ptA, ptB, stiffnessA, stiffnessB);
         }
diff --git a/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleRigidBodyCH.h b/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleRigidBodyCH.h
index 03fb6a3ae3d85fded19d0d2a6e80564900fb6f15..2222ce38b75790b09a5c55a4e6e2e949bd1af14c 100644
--- a/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleRigidBodyCH.h
+++ b/Examples/PBD/PBDTissueVolumeNeedleContact/NeedleRigidBodyCH.h
@@ -25,11 +25,12 @@
 #include "imstkRbdContactConstraint.h"
 #include "imstkRigidBodyModel2.h"
 #include "NeedleObject.h"
-#include "RbdLinearNeedleLockingConstraint.h"
-#include "RbdAngularNeedleLockingConstraint.h"
 
 using namespace imstk;
 
+///
+/// \brief Surface collision disabled upon puncture
+///
 class NeedleRigidBodyCH : public RigidBodyCH
 {
 public:
@@ -38,29 +39,7 @@ public:
 
     virtual const std::string getTypeName() const override { return "NeedleRigidBodyCH"; }
 
-public:
-    void setNeedleForceThreshold(double needleForceThreshold) { m_needleForceThreshold = needleForceThreshold; }
-    double getNeedleForceThrehsold() const { return m_needleForceThreshold; }
-
 protected:
-    ///
-    /// \brief Handle the collision/contact data
-    ///
-    virtual void handle(
-        const std::vector<CollisionElement>& elementsA,
-        const std::vector<CollisionElement>& elementsB) override
-    {
-        // Do it the normal way
-        RigidBodyCH::handle(elementsA, elementsB);
-
-        // If no collision, needle must be removed
-        if (elementsA.size() == 0)
-        {
-            auto needleObject = std::dynamic_pointer_cast<NeedleObject>(getInputObjectA());
-            needleObject->setInserted(false);
-        }
-    }
-
     ///
     /// \brief Add constraint for the rigid body given contact
     ///
@@ -69,28 +48,18 @@ protected:
         const Vec3d& contactPt, const Vec3d& contactNormal,
         const double contactDepth) override
     {
-        auto        needleObject = std::dynamic_pointer_cast<NeedleObject>(rbdObj);
-        const Vec3d n = contactNormal.normalized();
-
-        // Get all inwards force
-        const Vec3d  needleAxes = needleObject->getNeedleAxes();
-        const double fN = std::max(needleAxes.dot(needleObject->getRigidBody()->getForce()), 0.0);
+        auto needleObj = std::dynamic_pointer_cast<NeedleObject>(rbdObj);
 
-        // If the normal force exceeds 150 the needle may insert
-        if (fN > m_needleForceThreshold && !needleObject->getInserted())
+        if (needleObj->getCollisionState() == NeedleObject::CollisionState::REMOVED)
         {
-            LOG(INFO) << "Puncture!\n";
-            needleObject->setInserted(true);
-
-            // Record the axes to constrain too
-            m_initNeedleAxes = needleAxes;
-            m_initNeedleOrientation = Quatd(needleObject->getCollidingGeometry()->getRotation());
-            m_initContactPt = contactPt;
+            needleObj->setCollisionState(NeedleObject::CollisionState::TOUCHING);
         }
 
         // Only add contact normal constraint if not inserted
-        if (!needleObject->getInserted())
+        if (needleObj->getCollisionState() == NeedleObject::CollisionState::TOUCHING)
         {
+            const Vec3d n = contactNormal.normalized();
+
             auto contactConstraint = std::make_shared<RbdContactConstraint>(
                 rbdObj->getRigidBody(), nullptr,
                 n, contactPt, contactDepth,
@@ -99,39 +68,5 @@ protected:
             contactConstraint->compute(rbdObj->getRigidBodyModel2()->getTimeStep());
             rbdObj->getRigidBodyModel2()->addConstraint(contactConstraint);
         }
-        // Lock to the initial axes when the needle is inserted
-        else
-        {
-            auto needleLockConstraint = std::make_shared<RbdLinearNeedleLockingConstraint>(
-                rbdObj->getRigidBody(),
-                &m_initContactPt, &m_initNeedleAxes,
-                0.05);
-            needleLockConstraint->compute(rbdObj->getRigidBodyModel2()->getTimeStep());
-            rbdObj->getRigidBodyModel2()->addConstraint(needleLockConstraint);
-
-            auto needleLockConstraint2 = std::make_shared<RbdAngularNeedleLockingConstraint>(
-                rbdObj->getRigidBody(), m_initNeedleOrientation, 0.05);
-            needleLockConstraint2->compute(rbdObj->getRigidBodyModel2()->getTimeStep());
-            rbdObj->getRigidBodyModel2()->addConstraint(needleLockConstraint2);
-        }
-
-        if (m_useFriction)
-        {
-            /*std::shared_ptr<RbdFrictionConstraint> frictionConstraint =
-                std::make_shared<RbdFrictionConstraint>(
-                    rbdObj->getRigidBody(), nullptr,
-                    contactPt, contactNormal.normalized(), contactDepth,
-                    m_frictionalCoefficient,
-                    RbdConstraint::Side::A);
-            frictionConstraint->compute(rbdObj->getRigidBodyModel2()->getTimeStep());
-            rbdObj->getRigidBodyModel2()->addConstraint(frictionConstraint);*/
-        }
     }
-
-protected:
-    double m_needleForceThreshold = 250.0; ///> When needle body exceeds this it inserts
-
-    Vec3d m_initContactPt  = Vec3d::Zero();
-    Vec3d m_initNeedleAxes = Vec3d::Zero();
-    Quatd m_initNeedleOrientation = Quatd::Identity();
 };
\ No newline at end of file
diff --git a/Examples/PBD/PBDTissueVolumeNeedleContact/PBDTissueVolumeNeedleContactExample.cpp b/Examples/PBD/PBDTissueVolumeNeedleContact/PBDTissueVolumeNeedleContactExample.cpp
index e8ee92615dfdfe595cba81c536512901199e5be8..2cadb54a99d1c10c7a2a7b303af011597e5580b3 100644
--- a/Examples/PBD/PBDTissueVolumeNeedleContact/PBDTissueVolumeNeedleContactExample.cpp
+++ b/Examples/PBD/PBDTissueVolumeNeedleContact/PBDTissueVolumeNeedleContactExample.cpp
@@ -21,8 +21,8 @@
 
 #include "imstkCamera.h"
 #include "imstkCollisionGraph.h"
+#include "imstkDebugGeometryObject.h"
 #include "imstkDirectionalLight.h"
-#include "imstkImageData.h"
 #include "imstkIsometricMap.h"
 #include "imstkKeyboardSceneControl.h"
 #include "imstkLineMesh.h"
@@ -32,8 +32,9 @@
 #include "imstkOneToOneMap.h"
 #include "imstkPbdModel.h"
 #include "imstkPbdObject.h"
-#include "imstkPbdSolver.h"
+#include "imstkRbdConstraint.h"
 #include "imstkRenderMaterial.h"
+#include "imstkRigidBodyModel2.h"
 #include "imstkScene.h"
 #include "imstkSceneManager.h"
 #include "imstkSimulationManager.h"
@@ -41,14 +42,16 @@
 #include "imstkTetrahedralMesh.h"
 #include "imstkVisualModel.h"
 #include "imstkVTKViewer.h"
+#include "NeedleEmbeddedCH.h"
 #include "NeedleInteraction.h"
+#include "NeedleObject.h"
 
 #ifdef iMSTK_USE_OPENHAPTICS
 #include "imstkHapticDeviceManager.h"
 #include "imstkHapticDeviceClient.h"
 #include "imstkRigidObjectController.h"
 #else
-#include "imstkKeyboardDeviceClient.h"
+#include "imstkMouseDeviceClient.h"
 #endif
 
 using namespace imstk;
@@ -195,17 +198,31 @@ makeTissueObj(const std::string& name,
 
     // Setup the Parameters
     imstkNew<PBDModelConfig> pbdParams;
-    // Use FEMTet constraints
-    pbdParams->m_femParams->m_YoungModulus = 5.0;
-    pbdParams->m_femParams->m_PoissonRatio = 0.4;
+    // Actual skin young's modulus, 0.42MPa to 0.85Mpa, as reported in papers
+    // Actual skin possion ratio, 0.48, as reported in papers
+    pbdParams->m_femParams->m_YoungModulus = 420000.0;
+    pbdParams->m_femParams->m_PoissonRatio = 0.48;
+    // FYI:
+    //  - Poisson ratio gives shear to bulk, with 0.5 being complete shear
+    //    where everything is like a fluid and can slide past each other. 0.0
+    //    gives complete bulk where its rigid
+    //  - Youngs modulus then gives the scaling of the above in pressure
+    //    (pascals).
     pbdParams->enableFEMConstraint(PbdConstraint::Type::FEMTet,
         PbdFEMConstraint::MaterialType::StVK);
-    pbdParams->m_doPartitioning   = true;
-    pbdParams->m_uniformMassValue = 0.1;
-    pbdParams->m_gravity    = Vec3d(0.0, 0.0, 0.0);
-    pbdParams->m_dt         = 0.05;
-    pbdParams->m_iterations = 9;
-    pbdParams->m_viscousDampingCoeff = 0.05;
+    pbdParams->m_doPartitioning   = false;
+    pbdParams->m_uniformMassValue = 100.0;
+    pbdParams->m_dt = 0.001; // realtime used in update calls later in main
+    pbdParams->m_iterations = 5;
+
+    // Due to poor boundary conditions turning off gravity is useful. But then makes
+    // your tissue look like it's in space (springy and no resistance). So viscous
+    // damping is introduced to approximate these conditions.
+    //
+    // Ultimately this is a result of not modelling everything around the tissue.
+    // and poor/hard to model boundary conditions.
+    pbdParams->m_gravity = Vec3d::Zero();
+    pbdParams->m_viscousDampingCoeff = 0.03; // Removed from velocity
 
     // Fix the borders
     for (int z = 0; z < dim[2]; z++)
@@ -230,15 +247,15 @@ makeTissueObj(const std::string& name,
     // Setup the material
     imstkNew<RenderMaterial> material;
     material->setDisplayMode(RenderMaterial::DisplayMode::WireframeSurface);
-    material->setShadingModel(RenderMaterial::ShadingModel::PBR);
-    auto diffuseTex = MeshIO::read<ImageData>(iMSTK_DATA_ROOT "/textures/fleshDiffuse.jpg");
-    material->addTexture(std::make_shared<Texture>(diffuseTex, Texture::Type::Diffuse));
-    auto normalTex = MeshIO::read<ImageData>(iMSTK_DATA_ROOT "/textures/fleshNormal.jpg");
-    material->addTexture(std::make_shared<Texture>(normalTex, Texture::Type::Normal));
-    auto ormTex = MeshIO::read<ImageData>(iMSTK_DATA_ROOT "/textures/fleshORM.jpg");
-    material->addTexture(std::make_shared<Texture>(ormTex, Texture::Type::ORM));
-    material->setNormalStrength(0.3);
-    //material->setOpacity(0.5);
+    /* material->setShadingModel(RenderMaterial::ShadingModel::PBR);
+     auto diffuseTex = MeshIO::read<ImageData>(iMSTK_DATA_ROOT "/textures/fleshDiffuse.jpg");
+     material->addTexture(std::make_shared<Texture>(diffuseTex, Texture::Type::Diffuse));
+     auto normalTex = MeshIO::read<ImageData>(iMSTK_DATA_ROOT "/textures/fleshNormal.jpg");
+     material->addTexture(std::make_shared<Texture>(normalTex, Texture::Type::Normal));
+     auto ormTex = MeshIO::read<ImageData>(iMSTK_DATA_ROOT "/textures/fleshORM.jpg");
+     material->addTexture(std::make_shared<Texture>(ormTex, Texture::Type::ORM));
+     material->setNormalStrength(0.3);*/
+    material->setOpacity(0.5);
 
     // Add a visual model to render the surface of the tet mesh
     imstkNew<VisualModel> visualModel(surfMesh);
@@ -265,16 +282,17 @@ makeToolObj()
 {
     imstkNew<LineMesh>                toolGeometry;
     imstkNew<VecDataArray<double, 3>> verticesPtr(2);
-    (*verticesPtr)[0] = Vec3d(0.0, -1.0, 0.0);
-    (*verticesPtr)[1] = Vec3d(0.0, 1.0, 0.0);
+    (*verticesPtr)[0] = Vec3d(0.0, -0.05, 0.0);
+    (*verticesPtr)[1] = Vec3d(0.0, 0.05, 0.0);
     imstkNew<VecDataArray<int, 2>> indicesPtr(1);
     (*indicesPtr)[0] = Vec2i(0, 1);
     toolGeometry->initialize(verticesPtr, indicesPtr);
 
     auto syringeMesh = MeshIO::read<SurfaceMesh>(iMSTK_DATA_ROOT "/Surgical Instruments/Syringes/Disposable_Syringe.stl");
-    syringeMesh->scale(0.4, Geometry::TransformType::ApplyToData);
     syringeMesh->rotate(Vec3d(1.0, 0.0, 0.0), -PI_2, Geometry::TransformType::ApplyToData);
     syringeMesh->translate(Vec3d(0.0, 4.4, 0.0), Geometry::TransformType::ApplyToData);
+    syringeMesh->scale(0.0055, Geometry::TransformType::ApplyToData);
+    syringeMesh->translate(Vec3d(0.0, 0.1, 0.0));
 
     imstkNew<NeedleObject> toolObj("NeedleRbdTool");
     toolObj->setVisualGeometry(syringeMesh);
@@ -285,16 +303,15 @@ makeToolObj()
     toolObj->getVisualModel(0)->getRenderMaterial()->setShadingModel(RenderMaterial::ShadingModel::PBR);
     toolObj->getVisualModel(0)->getRenderMaterial()->setRoughness(0.5);
     toolObj->getVisualModel(0)->getRenderMaterial()->setMetalness(1.0);
-    //toolObj->getVisualModel(0)->getRenderMaterial()->setLineWidth(5.0);
 
     std::shared_ptr<RigidBodyModel2> rbdModel = std::make_shared<RigidBodyModel2>();
     rbdModel->getConfig()->m_gravity = Vec3d::Zero();
-    rbdModel->getConfig()->m_maxNumIterations = 2;
+    rbdModel->getConfig()->m_maxNumIterations = 5;
     toolObj->setDynamicalModel(rbdModel);
 
-    toolObj->getRigidBody()->m_mass = 0.5;
+    toolObj->getRigidBody()->m_mass = 1.0;
     toolObj->getRigidBody()->m_intertiaTensor = Mat3d::Identity() * 10000.0;
-    toolObj->getRigidBody()->m_initPos = Vec3d(0.0, 2.0, 0.0);
+    toolObj->getRigidBody()->m_initPos = Vec3d(0.0, 0.1, 0.0);
 
     return toolObj;
 }
@@ -311,21 +328,43 @@ main()
 
     // Setup the scene
     imstkNew<Scene> scene("PBDTissueVolumeNeedleContact");
-    scene->getActiveCamera()->setPosition(-0.06, 7.29, 11.69);
-    scene->getActiveCamera()->setFocalPoint(0.0, 0.0, 0.0);
-    scene->getActiveCamera()->setViewUp(0.0, 1.0, 0.0);
+    scene->getActiveCamera()->setPosition(-0.00149496, 0.0562587, 0.168353);
+    scene->getActiveCamera()->setFocalPoint(0.00262407, -0.026582, -0.00463737);
+    scene->getActiveCamera()->setViewUp(-0.00218222, 0.901896, -0.431947);
 
     // Setup a tissue with surface collision geometry
+    // 0.1m tissue patch 6x3x6 tet grid
     std::shared_ptr<PbdObject> tissueObj = makeTissueObj("PBDTissue",
-        Vec3d(10.0, 3.0, 10.0), Vec3i(7, 3, 6), Vec3d(0.1, -1.0, 0.0));
+        Vec3d(0.1, 0.025, 0.1), Vec3i(6, 3, 6), Vec3d(0.0, -0.03, 0.0));
     scene->addSceneObject(tissueObj);
 
     // Setup a tool for the user to move
     std::shared_ptr<NeedleObject> toolObj = makeToolObj();
+    toolObj->setForceThreshold(15.0);
     scene->addSceneObject(toolObj);
 
+    // Setup a debug ghost tool for virtual coupling
+    auto ghostToolObj = std::make_shared<SceneObject>("ghostTool");
+    {
+        auto                  toolMesh = std::dynamic_pointer_cast<SurfaceMesh>(toolObj->getVisualGeometry());
+        imstkNew<SurfaceMesh> toolGhostMesh;
+        toolGhostMesh->initialize(
+            std::make_shared<VecDataArray<double, 3>>(*toolMesh->getVertexPositions(Geometry::DataType::PreTransform)),
+            std::make_shared<VecDataArray<int, 3>>(*toolMesh->getTriangleIndices()));
+        ghostToolObj->setVisualGeometry(toolGhostMesh);
+        ghostToolObj->getVisualModel(0)->getRenderMaterial()->setColor(Color::Orange);
+        ghostToolObj->getVisualModel(0)->getRenderMaterial()->setLineWidth(5.0);
+        ghostToolObj->getVisualModel(0)->getRenderMaterial()->setOpacity(0.3);
+    }
+    scene->addSceneObject(ghostToolObj);
+
+    // Setup a debug polygon soup for debug contact points
+    imstkNew<DebugGeometryObject> debugGeomObj;
+    debugGeomObj->setLineWidth(0.1);
+    scene->addSceneObject(debugGeomObj);
+
+    // This adds both contact and puncture functionality
     auto interaction = std::make_shared<NeedleInteraction>(tissueObj, toolObj);
-    interaction->setForceThreshold(250.0);
     scene->getCollisionGraph()->addInteraction(interaction);
 
     // Light
@@ -351,60 +390,95 @@ main()
         imstkNew<SimulationManager> driver;
         driver->addModule(viewer);
         driver->addModule(sceneManager);
-        driver->setDesiredDt(0.001);
+        driver->setDesiredDt(0.001); // 1ms, 1000hz
 
 #ifdef iMSTK_USE_OPENHAPTICS
         imstkNew<HapticDeviceManager> hapticManager;
-        hapticManager->setSleepDelay(0.1); // Delay for 1ms (haptics thread is limited to max 1000hz)
+        //hapticManager->setSleepDelay(0.01);
         std::shared_ptr<HapticDeviceClient> hapticDeviceClient = hapticManager->makeDeviceClient();
         driver->addModule(hapticManager);
 
         imstkNew<RigidObjectController> controller(toolObj, hapticDeviceClient);
-        controller->setTranslationScaling(0.05);
-        controller->setLinearKs(1000.0);
-        controller->setLinearKd(50.0);
+        controller->setTranslationScaling(0.001);
+        controller->setLinearKs(10000.0);
+        controller->setLinearKd(300.0);
         controller->setAngularKs(10000000.0);
-        controller->setAngularKd(500000.0);
-        controller->setForceScaling(0.005);
-        controller->setSmoothingKernelSize(20);
+        controller->setAngularKd(1000000.0);
+        /*controller->setAngularKs(0.0);
+        controller->setAngularKd(0.0);*/
+        controller->setForceScaling(0.08);
+        controller->setSmoothingKernelSize(5);
         controller->setUseForceSmoothening(true);
         scene->addController(controller);
+
+        connect<Event>(sceneManager, &SceneManager::postUpdate, [&](Event*)
+        {
+            // Update the ghost debug geometry
+            std::shared_ptr<Geometry> toolGhostMesh = ghostToolObj->getVisualGeometry();
+            toolGhostMesh->setRotation(controller->getRotation());
+            toolGhostMesh->setTranslation(controller->getPosition());
+            toolGhostMesh->updatePostTransformData();
+            toolGhostMesh->postModified();
+
+            ghostToolObj->getVisualModel(0)->getRenderMaterial()->setOpacity(std::min(1.0, controller->getForce().norm() / 15.0));
+            });
 #else
-        // Use keyboard controls
-        connect<Event>(sceneManager, SceneManager::preUpdate, [&](Event*)
+        connect<Event>(sceneManager, &SceneManager::postUpdate, [&](Event*)
         {
-            if (viewer->getKeyboardDevice()->getButton('k') == KEY_PRESS)
-            {
-                (*toolObj->getRigidBody()->m_pos) += Vec3d(0.0, -0.01, 0.0);
-            }
-            else if (viewer->getKeyboardDevice()->getButton('i') == KEY_PRESS)
-            {
-                (*toolObj->getRigidBody()->m_pos) += Vec3d(0.0, 0.01, 0.0);
-            }
-            else if (viewer->getKeyboardDevice()->getButton('j') == KEY_PRESS)
-            {
-                (*toolObj->getRigidBody()->m_pos) += Vec3d(-0.01, 0.0, 0.0);
-            }
-            else if (viewer->getKeyboardDevice()->getButton('l') == KEY_PRESS)
-            {
-                (*toolObj->getRigidBody()->m_pos) += Vec3d(0.01, 0.0, 0.0);
-            }
-            else if (viewer->getKeyboardDevice()->getButton('u') == KEY_PRESS)
-            {
-                (*toolObj->getRigidBody()->m_pos) += Vec3d(0.0, 0.0, -0.01);
-            }
-            else if (viewer->getKeyboardDevice()->getButton('o') == KEY_PRESS)
+            const Vec2d mousePos   = viewer->getMouseDevice()->getPos();
+            const Vec3d desiredPos = Vec3d(mousePos[0] - 0.5, mousePos[1] - 0.5, 0.0) * 0.1;
+            const Quatd desiredOrientation = Quatd(Rotd(0.0, Vec3d(1.0, 0.0, 0.0)));
+
+            Vec3d virtualForce;
             {
-                (*toolObj->getRigidBody()->m_pos) += Vec3d(0.0, 0.0, 0.01);
+                const Vec3d fS = (desiredPos - toolObj->getRigidBody()->getPosition()) * 1000.0; // Spring force
+                const Vec3d fD = -toolObj->getRigidBody()->getVelocity() * 100.0;                // Spring damping
+
+                const Quatd dq       = desiredOrientation * toolObj->getRigidBody()->getOrientation().inverse();
+                const Rotd angleAxes = Rotd(dq);
+                const Vec3d tS       = angleAxes.axis() * angleAxes.angle() * 10000000.0;
+                const Vec3d tD       = -toolObj->getRigidBody()->getAngularVelocity() * 1000.0;
+
+                virtualForce = fS + fD;
+                (*toolObj->getRigidBody()->m_force)  += virtualForce;
+                (*toolObj->getRigidBody()->m_torque) += tS + tD;
             }
-            });
+
+            // Update the ghost debug geometry
+            std::shared_ptr<Geometry> toolGhostMesh = ghostToolObj->getVisualGeometry();
+            toolGhostMesh->setRotation(desiredOrientation);
+            toolGhostMesh->setTranslation(desiredPos);
+            toolGhostMesh->updatePostTransformData();
+            toolGhostMesh->postModified();
+
+            ghostToolObj->getVisualModel(0)->getRenderMaterial()->setOpacity(std::min(1.0, virtualForce.norm() / 15.0));
+        });
 #endif
 
         connect<Event>(sceneManager, &SceneManager::postUpdate, [&](Event*)
         {
             // Keep the tool moving in real time
             toolObj->getRigidBodyModel2()->getConfig()->m_dt = sceneManager->getDt();
-        });
+
+            // Copy debug geometry
+            auto needleEmbeddedCH = std::dynamic_pointer_cast<NeedleEmbeddedCH>(interaction->getEmbeddingCH());
+            const std::vector<Vec3d>& debugEmbeddingPts  = needleEmbeddedCH->m_debugEmbeddingPoints;
+            const std::vector<Vec3i>& debugEmbeddingTris = needleEmbeddedCH->m_debugEmbeddedTriangles;
+            debugGeomObj->clear();
+            for (int i = 0; i < debugEmbeddingPts.size(); i++)
+            {
+                debugGeomObj->addPoint(debugEmbeddingPts[i]);
+            }
+            auto verticesPtr = std::dynamic_pointer_cast<TetrahedralMesh>(tissueObj->getPhysicsGeometry())->getVertexPositions();
+            VecDataArray<double, 3>& vertices = *verticesPtr;
+            for (int i = 0; i < debugEmbeddingTris.size(); i++)
+            {
+                debugGeomObj->addTriangle(
+                        vertices[debugEmbeddingTris[i][0]],
+                        vertices[debugEmbeddingTris[i][1]],
+                        vertices[debugEmbeddingTris[i][2]]);
+            }
+            });
 
         // Add mouse and keyboard controls to the viewer
         {
diff --git a/Examples/PBD/PBDTissueVolumeNeedleContact/PbdTriangleEmbeddingConstraint.h b/Examples/PBD/PBDTissueVolumeNeedleContact/PbdTriangleEmbeddingConstraint.h
deleted file mode 100644
index 87c6eb4e242a747e0ca978d7d9db83eb25b301b2..0000000000000000000000000000000000000000
--- a/Examples/PBD/PBDTissueVolumeNeedleContact/PbdTriangleEmbeddingConstraint.h
+++ /dev/null
@@ -1,168 +0,0 @@
-/*=========================================================================
-
-   Library: iMSTK
-
-   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
-   & Imaging in Medicine, Rensselaer Polytechnic Institute.
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0.txt
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
-=========================================================================*/
-
-#pragma once
-
-#include "imstkPbdCollisionConstraint.h"
-
-namespace imstk
-{
-///
-/// \class PbdTriangleEmbeddingConstraint
-///
-/// \brief Constraint to keep a specified vertex at a given uvw in a triangle
-///
-class PbdTriangleEmbeddingConstraint : public PbdCollisionConstraint
-{
-public:
-    PbdTriangleEmbeddingConstraint() : PbdCollisionConstraint(1, 3) { }
-    ~PbdTriangleEmbeddingConstraint() override = default;
-
-public:
-    ///
-    /// \brief Returns the type of the pbd collision constraint
-    ///
-    Type getType() const { return Type::PointTriangle; }
-
-    ///
-    /// \brief initialize constraint
-    /// \param pIdxA1 index of the point from object1
-    /// \param pIdxB1 first point of the triangle from object2
-    /// \param pIdxB2 second point of the triangle from object2
-    /// \param pIdxB3 third point of the triangle from object2
-    /// \return
-    ///
-    void initConstraint(VertexMassPair ptA,
-                        VertexMassPair ptB1, VertexMassPair ptB2, VertexMassPair ptB3,
-                        double stiffnessA, double stiffnessB,
-                        Vec3d* p, Vec3d* q,
-                        double friction)
-    {
-        m_bodiesFirst[0] = ptA;
-
-        m_bodiesSecond[0] = ptB1;
-        m_bodiesSecond[1] = ptB2;
-        m_bodiesSecond[2] = ptB3;
-
-        m_stiffnessA = stiffnessA;
-        m_stiffnessB = stiffnessB;
-        m_friction   = friction;
-
-        const Vec3d& x0 = *m_bodiesFirst[0].vertex; // Intersection point
-        const Vec3d& x1 = *m_bodiesSecond[0].vertex;
-        const Vec3d& x2 = *m_bodiesSecond[1].vertex;
-        const Vec3d& x3 = *m_bodiesSecond[2].vertex;
-
-        // Compute the interpolant on the triangle
-        {
-            // Compute barycentric coordinates of ptA in tri B
-            // This effectively puts the point in a local coordinate
-            // system of the triangle so when the triangle begins
-            // to move and deform the embedded point will as well
-            const Vec3d  v0    = x2 - x1;
-            const Vec3d  v1    = x3 - x1;
-            const Vec3d  v2    = x0 - x1;
-            const double d00   = v0.dot(v0);
-            const double d01   = v0.dot(v1);
-            const double d11   = v1.dot(v1);
-            const double d20   = v2.dot(v0);
-            const double d21   = v2.dot(v1);
-            const double denom = d00 * d11 - d01 * d01;
-            const double v     = (d11 * d20 - d01 * d21) / denom;
-            const double w     = (d00 * d21 - d01 * d20) / denom;
-            const double u     = 1.0 - v - w;
-            m_uvw[0] = u;
-            m_uvw[1] = v;
-            m_uvw[2] = w;
-        }
-        // Compute the interpolant on the line
-        {
-            m_p = p;
-            m_q = q;
-            const Vec3d pq = (*p - *q).normalized();
-            const Vec3d d  = x0 - *q;
-            m_t = pq.dot(d);
-        }
-    }
-
-    // Sets the point of vertex
-    VertexMassPair& getVertexMassA()
-    {
-        return m_bodiesFirst[0];
-    }
-
-    ///
-    /// \brief compute value and gradient of constraint function
-    ///
-    /// \param[in] currVertexPositionsA current positions from object A
-    /// \param[in] currVertexPositionsA current positions from object B
-    /// \param[inout] c constraint value
-    /// \param[inout] dcdx constraint gradient
-    ///
-    bool computeValueAndGradient(double&             c,
-                                 std::vector<Vec3d>& dcdxA,
-                                 std::vector<Vec3d>& dcdxB) const override
-    {
-        //const Vec3d& x0 = *m_bodiesFirst[0].vertex;
-        const Vec3d& x1 = *m_bodiesSecond[0].vertex;
-        const Vec3d& x2 = *m_bodiesSecond[1].vertex;
-        const Vec3d& x3 = *m_bodiesSecond[2].vertex;
-
-        Vec3d*      p    = m_p;
-        Vec3d*      q    = m_q;
-        const Vec3d pq   = (*p - *q);
-        const Vec3d pq_n = pq.normalized();
-
-        const Vec3d triPos  = x1 * m_uvw[0] + x2 * m_uvw[1] + x3 * m_uvw[2];
-        const Vec3d linePos = (*q) + pq_n * m_t;
-
-        // Directly transform to each other
-        Vec3d diff = triPos - linePos;
-        // Remove normal component to allow slide (don't completely remove to allow a sort of friction)
-        diff = diff - diff.dot(pq_n) * pq_n * (1.0 - m_friction);
-        const Vec3d n = diff.normalized();
-
-        dcdxA[0] = Vec3d::Zero();
-        dcdxB[0] = n;
-        dcdxB[1] = n;
-        dcdxB[2] = n;
-
-        c = -diff.norm();
-
-        return true;
-    }
-
-protected:
-    // Interpolant for triangle (uv coords)
-    Vec3d m_uvw = Vec3d::Zero();
-
-    // Interpolant for line
-    double m_t = 0.0;
-
-    // Line
-    Vec3d* m_p = nullptr;
-    Vec3d* m_q = nullptr;
-
-    bool m_enabled = true;
-
-    double m_friction = 0.99;
-};
-}
\ No newline at end of file
diff --git a/Examples/PBD/PBDTissueVolumeNeedleContact/RbdLinearNeedleLockingConstraint.h b/Examples/PBD/PBDTissueVolumeNeedleContact/RbdLinearNeedleLockingConstraint.h
deleted file mode 100644
index a50b5f91c2a6b5343d573a95092fd7f4595c49a9..0000000000000000000000000000000000000000
--- a/Examples/PBD/PBDTissueVolumeNeedleContact/RbdLinearNeedleLockingConstraint.h
+++ /dev/null
@@ -1,89 +0,0 @@
-/*=========================================================================
-
-   Library: iMSTK
-
-   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
-   & Imaging in Medicine, Rensselaer Polytechnic Institute.
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0.txt
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
-=========================================================================*/
-
-#pragma once
-
-#include "imstkRbdConstraint.h"
-
-#include <functional>
-
-using namespace imstk;
-
-///
-/// \class RbdLinearNeedleLockingConstraint
-///
-/// \brief Constrains the body to specified orientation
-/// and only allows linear movement along the inital axes
-///
-class RbdLinearNeedleLockingConstraint : public RbdConstraint
-{
-public:
-    RbdLinearNeedleLockingConstraint(
-        std::shared_ptr<RigidBody> obj,
-        Vec3d*                     initNeedleAxesPt,
-        Vec3d*                     initNeedleAxes,
-        //const Quatd& initNeedleOrientation,
-        const double               beta = 0.05) : RbdConstraint(obj, nullptr, Side::A),
-        m_initNeedleAxesPt(initNeedleAxesPt),
-        m_initNeedleAxes(initNeedleAxes),
-        //m_initNeedleOrientation(initNeedleOrientation),
-        m_beta(beta)
-    {
-    }
-
-    ~RbdLinearNeedleLockingConstraint() override = default;
-
-public:
-    void compute(double dt) override
-    {
-        // Jacobian of contact (defines linear and angular constraint axes)
-        J = Eigen::Matrix<double, 3, 4>::Zero();
-        if ((m_side == Side::AB || m_side == Side::A) && !m_obj1->m_isStatic)
-        {
-            // Displacement to needle Axes
-            const Vec3d  diff = m_obj1->getPosition() - (*m_initNeedleAxesPt);
-            const Vec3d  perpDisplacement = diff - (*m_initNeedleAxes).dot(diff) * (*m_initNeedleAxes);
-            const double displacement     = perpDisplacement.norm();
-            if (displacement != 0)
-            {
-                const Vec3d displacementDir = perpDisplacement / displacement;
-                vu = displacement * m_beta / dt;
-
-                // Displacement from center of mass
-                J(0, 0) = -displacementDir[0]; J(0, 1) = 0.0;
-                J(1, 0) = -displacementDir[1]; J(1, 1) = 0.0;
-                J(2, 0) = -displacementDir[2]; J(2, 1) = 0.0;
-
-                // Move the coupled needle location but not completely rigidly
-                *m_initNeedleAxesPt += displacementDir * 0.001;
-            }
-            else
-            {
-                vu = 0.0;
-            }
-        }
-    }
-
-private:
-    Vec3d* m_initNeedleAxesPt; ///> Point on the axes to constrain too
-    Vec3d* m_initNeedleAxes;   //> Axes to constrain too
-    double m_beta = 0.05;
-};
\ No newline at end of file
diff --git a/Examples/PBD/PBDString/CMakeLists.txt b/Examples/RBD/RbdSDFPivotNeedle/CMakeLists.txt
similarity index 78%
rename from Examples/PBD/PBDString/CMakeLists.txt
rename to Examples/RBD/RbdSDFPivotNeedle/CMakeLists.txt
index ebfc89726577b3c07b3037e56dffad62a5c853ac..38630c77be291e5d33a1df91604065c1d643e9c2 100644
--- a/Examples/PBD/PBDString/CMakeLists.txt
+++ b/Examples/RBD/RbdSDFPivotNeedle/CMakeLists.txt
@@ -16,19 +16,27 @@
 #
 ###########################################################################
 
-project(Example-PBDString)
+project(Example-RbdSDFPivotNeedle)
 
 #-----------------------------------------------------------------------------
 # Create executable
 #-----------------------------------------------------------------------------
-imstk_add_executable(${PROJECT_NAME} pbdStringExample.cpp)
+imstk_add_executable(${PROJECT_NAME} RbdSDFPivotNeedleExample.cpp
+  NeedleObject.h
+  NeedleInteraction.h
+  NeedleInteraction.cpp
+  NeedleRigidBodyCH.h
+  NeedleRigidBodyCH.cpp
+  RbdLineToPointTranslationConstraint.h
+  RbdLineToPointRotationConstraint.h
+  NeedleRigidBodyCH.h)
 
 #-----------------------------------------------------------------------------
 # Add the target to Examples folder
 #-----------------------------------------------------------------------------
-SET_TARGET_PROPERTIES (${PROJECT_NAME} PROPERTIES FOLDER Examples/PBD)
+SET_TARGET_PROPERTIES (${PROJECT_NAME} PROPERTIES FOLDER Examples/RBD)
 
 #-----------------------------------------------------------------------------
 # Link libraries to executable
 #-----------------------------------------------------------------------------
-target_link_libraries(${PROJECT_NAME} SimulationManager)
+target_link_libraries(${PROJECT_NAME} SimulationManager Filtering)
diff --git a/Examples/RBD/RbdSDFPivotNeedle/NeedleInteraction.cpp b/Examples/RBD/RbdSDFPivotNeedle/NeedleInteraction.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fba61a9482a3ee904a1de29148539605c5331c67
--- /dev/null
+++ b/Examples/RBD/RbdSDFPivotNeedle/NeedleInteraction.cpp
@@ -0,0 +1,56 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "NeedleInteraction.h"
+#include "imstkCollidingObject.h"
+#include "imstkCollisionDetectionAlgorithm.h"
+#include "imstkLineMesh.h"
+#include "imstkSignedDistanceField.h"
+#include "imstkTaskGraph.h"
+#include "NeedleObject.h"
+#include "NeedleRigidBodyCH.h"
+
+using namespace imstk;
+
+NeedleInteraction::NeedleInteraction(std::shared_ptr<CollidingObject> tissueObj,
+                                     std::shared_ptr<NeedleObject>    needleObj) : RigidObjectCollision(needleObj, tissueObj, "ImplicitGeometryToPointSetCD")
+{
+    if (std::dynamic_pointer_cast<LineMesh>(needleObj->getCollidingGeometry()) == nullptr)
+    {
+        LOG(WARNING) << "NeedleInteraction only works with LineMesh collision geometry on rigid NeedleObject";
+    }
+    if (std::dynamic_pointer_cast<ImplicitGeometry>(tissueObj->getCollidingGeometry()) == nullptr)
+    {
+        LOG(WARNING) << "NeedleInteraction only works with SDF colliding geometry on colliding tissueObj";
+    }
+
+    // First replace the handlers with our needle subclasses
+
+    // This handler consumes collision data to resolve the tool from the tissue
+    // except when the needle is inserted
+    auto needleRbdCH = std::make_shared<NeedleRigidBodyCH>();
+    needleRbdCH->setInputRigidObjectA(needleObj);
+    needleRbdCH->setInputCollidingObjectB(tissueObj);
+    needleRbdCH->setInputCollisionData(getCollisionDetection()->getCollisionData());
+    needleRbdCH->setBeta(0.001);
+    needleRbdCH->getTaskNode()->m_isCritical = true;
+    setCollisionHandlingA(needleRbdCH);
+}
\ No newline at end of file
diff --git a/Examples/RBD/RbdSDFPivotNeedle/NeedleInteraction.h b/Examples/RBD/RbdSDFPivotNeedle/NeedleInteraction.h
new file mode 100644
index 0000000000000000000000000000000000000000..8d101daffa719a8fc2916746addbbfa4cbe87540
--- /dev/null
+++ b/Examples/RBD/RbdSDFPivotNeedle/NeedleInteraction.h
@@ -0,0 +1,41 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#pragma once
+
+#include "imstkRigidObjectCollision.h"
+
+using namespace imstk;
+
+class NeedleObject;
+
+///
+/// \class NeedleInteraction
+///
+/// \brief Defines interaction between NeedleObject and PbdObject
+///
+class NeedleInteraction : public RigidObjectCollision
+{
+public:
+    NeedleInteraction(std::shared_ptr<CollidingObject> tissueObj,
+                      std::shared_ptr<NeedleObject>    needleObj);
+    ~NeedleInteraction() override = default;
+};
\ No newline at end of file
diff --git a/Examples/RBD/RbdSDFPivotNeedle/NeedleObject.h b/Examples/RBD/RbdSDFPivotNeedle/NeedleObject.h
new file mode 100644
index 0000000000000000000000000000000000000000..ab8fbbee7bf043c2199d69218d0c7200c9d8bb91
--- /dev/null
+++ b/Examples/RBD/RbdSDFPivotNeedle/NeedleObject.h
@@ -0,0 +1,65 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#pragma once
+
+#include "imstkRigidObject2.h"
+
+using namespace imstk;
+
+class NeedleObject : public RigidObject2
+{
+public:
+    enum class CollisionState
+    {
+        REMOVED,
+        TOUCHING,
+        INSERTED
+    };
+
+public:
+    NeedleObject(const std::string& name) : RigidObject2(name) { }
+    virtual ~NeedleObject() = default;
+
+    virtual const std::string getTypeName() const override { return "NeedleObject"; }
+
+public:
+    void setCollisionState(const CollisionState state) { m_collisionState = state; }
+    CollisionState getCollisionState() const { return m_collisionState; }
+
+    ///
+    /// \brief Set the force threshold for the needle
+    ///
+    void setForceThreshold(const double forceThreshold) { m_forceThreshold = forceThreshold; }
+    double getForceThreshold() const { return m_forceThreshold; }
+
+    ///
+    /// \brief Returns the current axes of the needle (tip-tail)
+    ///
+    const Vec3d getAxes() const
+    {
+        return (-getCollidingGeometry()->getRotation().col(2)).normalized();
+    }
+
+protected:
+    CollisionState m_collisionState = CollisionState::REMOVED;
+    double m_forceThreshold = 10.0;
+};
\ No newline at end of file
diff --git a/Examples/RBD/RbdSDFPivotNeedle/NeedleRigidBodyCH.cpp b/Examples/RBD/RbdSDFPivotNeedle/NeedleRigidBodyCH.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7f58cce14456ad0574796660b82de87d4f730d6d
--- /dev/null
+++ b/Examples/RBD/RbdSDFPivotNeedle/NeedleRigidBodyCH.cpp
@@ -0,0 +1,129 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#pragma once
+
+#include "NeedleRigidBodyCH.h"
+#include "imstkRbdContactConstraint.h"
+#include "imstkRigidBodyModel2.h"
+#include "NeedleObject.h"
+#include "RbdLineToPointTranslationConstraint.h"
+#include "RbdLineToPointRotationConstraint.h"
+#include "imstkLineMesh.h"
+
+using namespace imstk;
+
+void
+NeedleRigidBodyCH::handle(
+    const std::vector<CollisionElement>& elementsA,
+    const std::vector<CollisionElement>& elementsB)
+{
+    // Do it the normal way
+    RigidBodyCH::handle(elementsA, elementsB);
+
+    // If no collision, needle must be removed
+    // If using point based collision in an SDF you may want a differing unpuncturing constraint
+    auto                         needleObj = std::dynamic_pointer_cast<NeedleObject>(getInputObjectA());
+    NeedleObject::CollisionState state     = needleObj->getCollisionState();
+    if (elementsA.size() == 0)
+    {
+        if (state == NeedleObject::CollisionState::INSERTED)
+        {
+            needleObj->setCollisionState(NeedleObject::CollisionState::REMOVED);
+            LOG(INFO) << "Unpuncture!\n";
+        }
+        else if (state == NeedleObject::CollisionState::TOUCHING)
+        {
+            needleObj->setCollisionState(NeedleObject::CollisionState::REMOVED);
+        }
+    }
+}
+
+void
+NeedleRigidBodyCH::addConstraint(
+    std::shared_ptr<RigidObject2> rbdObj,
+    const Vec3d& contactPt, const Vec3d& contactNormal,
+    const double contactDepth)
+{
+    auto needleObj = std::dynamic_pointer_cast<NeedleObject>(rbdObj);
+
+    // If the normal force exceeds threshold the needle may insert
+    if (needleObj->getCollisionState() == NeedleObject::CollisionState::REMOVED)
+    {
+        needleObj->setCollisionState(NeedleObject::CollisionState::TOUCHING);
+    }
+
+    const Vec3d n = contactNormal.normalized();
+    if (needleObj->getCollisionState() == NeedleObject::CollisionState::TOUCHING)
+    {
+        // Get all inwards force
+        const Vec3d  needleAxes = needleObj->getAxes();
+        const double fN = std::max(needleAxes.dot(needleObj->getRigidBody()->getForce()), 0.0);
+
+        if (fN > needleObj->getForceThreshold())
+        {
+            LOG(INFO) << "Puncture!\n";
+            needleObj->setCollisionState(NeedleObject::CollisionState::INSERTED);
+
+            // Record the axes to constrain too
+            m_initNeedleAxes = needleAxes;
+            m_initNeedleOrientation = Quatd(needleObj->getCollidingGeometry()->getRotation());
+            m_initContactPt = contactPt;
+        }
+    }
+
+    // Only add contact normal constraint if not inserted
+    NeedleObject::CollisionState state = needleObj->getCollisionState();
+    if (state == NeedleObject::CollisionState::TOUCHING)
+    {
+        auto contactConstraint = std::make_shared<RbdContactConstraint>(
+            rbdObj->getRigidBody(), nullptr,
+            n, contactPt, contactDepth,
+            m_beta,
+            RbdConstraint::Side::A);
+        contactConstraint->compute(rbdObj->getRigidBodyModel2()->getTimeStep());
+        rbdObj->getRigidBodyModel2()->addConstraint(contactConstraint);
+    }
+    // Lock to the initial axes when the needle is inserted
+    else if (state == NeedleObject::CollisionState::INSERTED)
+    {
+        auto   lineMesh = std::dynamic_pointer_cast<LineMesh>(needleObj->getPhysicsGeometry());
+        Vec3d* p = &(*lineMesh->getVertexPositions())[0];
+        Vec3d* q = &(*lineMesh->getVertexPositions())[1];
+
+        // This constraint solves for the translation to bring line p,q to m_initContactPt
+        auto translationConstraint = std::make_shared<RbdLineToPointTranslationConstraint>(
+            rbdObj->getRigidBody(), m_initContactPt, p, q, 0.1);
+        translationConstraint->compute(rbdObj->getRigidBodyModel2()->getTimeStep());
+        rbdObj->getRigidBodyModel2()->addConstraint(translationConstraint);
+
+        // Bit of a cheat, but I parameterize the inertia by depth linearly. I use a large
+        // jump after x=1 to really lock in the orientation after the needle has go so far
+        double x = contactDepth / 0.02;
+        if (x > 1.0)
+        {
+            x = 100.0;
+        }
+        rbdObj->getRigidBody()->m_intertiaTensor = Mat3d::Identity() *
+                                                   (10000.0 + x * 10000.0);
+        rbdObj->getRigidBodyModel2()->updateMass();
+    }
+}
\ No newline at end of file
diff --git a/Examples/RBD/RbdSDFPivotNeedle/NeedleRigidBodyCH.h b/Examples/RBD/RbdSDFPivotNeedle/NeedleRigidBodyCH.h
new file mode 100644
index 0000000000000000000000000000000000000000..da37cda221b711534ff72153e4841187b23e3a07
--- /dev/null
+++ b/Examples/RBD/RbdSDFPivotNeedle/NeedleRigidBodyCH.h
@@ -0,0 +1,56 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#pragma once
+
+#include "imstkRigidBodyCH.h"
+
+using namespace imstk;
+
+class NeedleRigidBodyCH : public RigidBodyCH
+{
+public:
+    NeedleRigidBodyCH() = default;
+    ~NeedleRigidBodyCH() override = default;
+
+    virtual const std::string getTypeName() const override { return "NeedleRigidBodyCH"; }
+
+protected:
+    ///
+    /// \brief Handle the collision/contact data
+    ///
+    virtual void handle(
+        const std::vector<CollisionElement>& elementsA,
+        const std::vector<CollisionElement>& elementsB) override;
+
+    ///
+    /// \brief Add constraint for the rigid body given contact
+    ///
+    void addConstraint(
+        std::shared_ptr<RigidObject2> rbdObj,
+        const Vec3d& contactPt, const Vec3d& contactNormal,
+        const double contactDepth) override;
+
+protected:
+    Vec3d m_initContactPt  = Vec3d::Zero();
+    Vec3d m_initNeedleAxes = Vec3d::Zero();
+    Quatd m_initNeedleOrientation = Quatd::Identity();
+};
\ No newline at end of file
diff --git a/Examples/PBD/PBDTissueSurfaceNeedleContact/RbdAngularNeedleLockingConstraint.h b/Examples/RBD/RbdSDFPivotNeedle/RbdLineToPointRotationConstraint.h
similarity index 59%
rename from Examples/PBD/PBDTissueSurfaceNeedleContact/RbdAngularNeedleLockingConstraint.h
rename to Examples/RBD/RbdSDFPivotNeedle/RbdLineToPointRotationConstraint.h
index 1b8eb1b0625a9841f0692b1ed5bab0408036f737..1fdf9c1118e4bea381879ede0d237206be28435e 100644
--- a/Examples/PBD/PBDTissueSurfaceNeedleContact/RbdAngularNeedleLockingConstraint.h
+++ b/Examples/RBD/RbdSDFPivotNeedle/RbdLineToPointRotationConstraint.h
@@ -26,23 +26,24 @@
 using namespace imstk;
 
 ///
-/// \class RbdAngularNeedleLockingConstraint
+/// \class RbdAxesLockingConstraint
 ///
-/// \brief Constrains the orientation to some initial orientation
+/// \brief Constraints the line p, q to the fixedPt by rotating p and q
 ///
-class RbdAngularNeedleLockingConstraint : public RbdConstraint
+class RbdLineToPointRotationConstraint : public RbdConstraint
 {
 public:
-    RbdAngularNeedleLockingConstraint(
+    RbdLineToPointRotationConstraint(
         std::shared_ptr<RigidBody> obj,
-        const Quatd&               initNeedleOrientation,
-        const double               beta = 0.05) : RbdConstraint(obj, nullptr, Side::A),
-        m_initNeedleOrientation(initNeedleOrientation),
+        const Vec3d& fixedPt,
+        Vec3d* p, Vec3d* q,
+        const double beta = 0.05) : RbdConstraint(obj, nullptr, Side::A),
+        m_fixedPt(fixedPt), m_p(p), m_q(q),
         m_beta(beta)
     {
     }
 
-    ~RbdAngularNeedleLockingConstraint() override = default;
+    ~RbdLineToPointRotationConstraint() override = default;
 
 public:
     void compute(double dt) override
@@ -51,10 +52,14 @@ public:
         J = Eigen::Matrix<double, 3, 4>::Zero();
         if ((m_side == Side::AB || m_side == Side::A) && !m_obj1->m_isStatic)
         {
-            const Quatd dq = m_initNeedleOrientation * m_obj1->getOrientation().inverse();
-            const Rotd  angleAxes = Rotd(dq);
-            const Vec3d rotAxes   = angleAxes.axis();
-            vu      = angleAxes.angle() * m_beta / dt;
+            // Gives the rotation to bring the line p,q to pass through point fixedPt
+            const Vec3d axes    = (*m_q - m_obj1->getPosition()).normalized();
+            const Vec3d diff    = m_fixedPt - m_obj1->getPosition();
+            const Vec3d rot     = axes.cross(diff.normalized());
+            const Vec3d rotAxes = rot.normalized();
+
+            // rot.norm gives area of crossed vectors, should be 0 when rotated to each other
+            vu      = rot.norm() * m_beta / dt;
             J(0, 0) = 0.0; J(0, 1) = rotAxes[0];
             J(1, 0) = 0.0; J(1, 1) = rotAxes[1];
             J(2, 0) = 0.0; J(2, 1) = rotAxes[2];
@@ -62,6 +67,9 @@ public:
     }
 
 private:
-    Quatd  m_initNeedleOrientation; ///> Orientation to fix too
     double m_beta = 0.05;
+
+    Vec3d  m_fixedPt;
+    Vec3d* m_p = nullptr;
+    Vec3d* m_q = nullptr;
 };
\ No newline at end of file
diff --git a/Examples/RBD/RbdSDFPivotNeedle/RbdLineToPointTranslationConstraint.h b/Examples/RBD/RbdSDFPivotNeedle/RbdLineToPointTranslationConstraint.h
new file mode 100644
index 0000000000000000000000000000000000000000..39061ef0a40c0a741157c49bbebfc3b175e7d9c2
--- /dev/null
+++ b/Examples/RBD/RbdSDFPivotNeedle/RbdLineToPointTranslationConstraint.h
@@ -0,0 +1,74 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#pragma once
+
+#include "imstkRbdConstraint.h"
+
+using namespace imstk;
+
+///
+/// \class RbdLineToPointTranslationConstraint
+///
+/// \brief Constraints the line p, q to the fixedPt by rotating p and q
+///
+class RbdLineToPointTranslationConstraint : public RbdConstraint
+{
+public:
+    RbdLineToPointTranslationConstraint(
+        std::shared_ptr<RigidBody> obj,
+        const Vec3d& fixedPt,
+        Vec3d* p, Vec3d* q,
+        const double beta = 0.05) : RbdConstraint(obj, nullptr, Side::A),
+        m_fixedPt(fixedPt), m_p(p), m_q(q),
+        m_beta(beta)
+    {
+    }
+
+    ~RbdLineToPointTranslationConstraint() override = default;
+
+public:
+    void compute(double dt) override
+    {
+        // Jacobian of contact (defines linear and angular constraint axes)
+        J = Eigen::Matrix<double, 3, 4>::Zero();
+        if ((m_side == Side::AB || m_side == Side::A) && !m_obj1->m_isStatic)
+        {
+            // Gives the translation to bring the line p,q to pass through point fixedPt
+            const Vec3d axes      = (*m_q - *m_p).normalized();
+            const Vec3d diff      = m_fixedPt - *m_p;
+            const Vec3d vecToLine = (diff - diff.dot(axes) * axes);
+            const Vec3d dirToLine = vecToLine.normalized();
+
+            vu      = vecToLine.norm() * m_beta / dt;
+            J(0, 0) = dirToLine[0]; J(0, 1) = 0.0;
+            J(1, 0) = dirToLine[1]; J(1, 1) = 0.0;
+            J(2, 0) = dirToLine[2]; J(2, 1) = 0.0;
+        }
+    }
+
+private:
+    double m_beta = 0.05;
+
+    Vec3d  m_fixedPt;
+    Vec3d* m_p = nullptr;
+    Vec3d* m_q = nullptr;
+};
\ No newline at end of file
diff --git a/Examples/RBD/RbdSDFPivotNeedle/RbdSDFPivotNeedleExample.cpp b/Examples/RBD/RbdSDFPivotNeedle/RbdSDFPivotNeedleExample.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c7ccad428e618f9facd4fa2213ae8ff4310667a4
--- /dev/null
+++ b/Examples/RBD/RbdSDFPivotNeedle/RbdSDFPivotNeedleExample.cpp
@@ -0,0 +1,274 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkCamera.h"
+#include "imstkCollisionGraph.h"
+#include "imstkDirectionalLight.h"
+#include "imstkGeometryUtilities.h"
+#include "imstkIsometricMap.h"
+#include "imstkKeyboardSceneControl.h"
+#include "imstkLineMesh.h"
+#include "imstkMeshIO.h"
+#include "imstkMouseSceneControl.h"
+#include "imstkNew.h"
+#include "imstkPlane.h"
+#include "imstkRbdConstraint.h"
+#include "imstkRenderMaterial.h"
+#include "imstkRigidBodyModel2.h"
+#include "imstkScene.h"
+#include "imstkSceneManager.h"
+#include "imstkSimulationManager.h"
+#include "imstkSurfaceMesh.h"
+#include "imstkVisualModel.h"
+#include "imstkVTKViewer.h"
+#include "NeedleInteraction.h"
+#include "NeedleObject.h"
+
+#ifdef iMSTK_USE_OPENHAPTICS
+#include "imstkHapticDeviceClient.h"
+#include "imstkHapticDeviceManager.h"
+#include "imstkRigidObjectController.h"
+#else
+#include "imstkMouseDeviceClient.h"
+#endif
+
+using namespace imstk;
+
+///
+/// \brief Returns a colliding object tissue plane that uses an implicit geometry for collision
+///
+static std::shared_ptr<CollidingObject>
+createTissueObj()
+{
+    imstkNew<CollidingObject> tissueObj("Tissue");
+
+    imstkNew<Plane> tissuePlane(Vec3d(0.0, 0.0, 0.0), Vec3d(0.0, 1.0, 0.0));
+    tissuePlane->setWidth(0.1);
+
+    tissueObj->setVisualGeometry(tissuePlane);
+    tissueObj->setCollidingGeometry(tissuePlane);
+
+    auto material = std::make_shared<RenderMaterial>();
+    material->setShadingModel(RenderMaterial::ShadingModel::PBR);
+    material->setColor(Color::Bone);
+    material->setRoughness(0.5);
+    material->setMetalness(0.1);
+    tissueObj->getVisualModel(0)->setRenderMaterial(material);
+
+    return tissueObj;
+}
+
+static std::shared_ptr<NeedleObject>
+createNeedleObj()
+{
+    imstkNew<LineMesh>                toolGeometry;
+    imstkNew<VecDataArray<double, 3>> verticesPtr(2);
+    (*verticesPtr)[0] = Vec3d(0.0, 0.0, 0.0);
+    (*verticesPtr)[1] = Vec3d(0.0, 0.0, -0.1);
+    imstkNew<VecDataArray<int, 2>> indicesPtr(1);
+    (*indicesPtr)[0] = Vec2i(0, 1);
+    toolGeometry->initialize(verticesPtr, indicesPtr);
+
+    auto syringeMesh = MeshIO::read<SurfaceMesh>(iMSTK_DATA_ROOT "/Surgical Instruments/Syringes/Disposable_Syringe.stl");
+    syringeMesh->scale(0.0075, Geometry::TransformType::ApplyToData);
+    toolGeometry->rotate(Vec3d(0.0, 1.0, 0.0), PI, Geometry::TransformType::ApplyToData);
+    syringeMesh->translate(Vec3d(0.0, 0.0, 0.1), Geometry::TransformType::ApplyToData);
+
+    imstkNew<NeedleObject> toolObj("NeedleRbdTool");
+    toolObj->setVisualGeometry(syringeMesh);
+    toolObj->setCollidingGeometry(toolGeometry);
+    toolObj->setPhysicsGeometry(toolGeometry);
+    toolObj->setPhysicsToVisualMap(std::make_shared<IsometricMap>(toolGeometry, syringeMesh));
+    toolObj->getVisualModel(0)->getRenderMaterial()->setColor(Color(0.9, 0.9, 0.9));
+    toolObj->getVisualModel(0)->getRenderMaterial()->setShadingModel(RenderMaterial::ShadingModel::PBR);
+    toolObj->getVisualModel(0)->getRenderMaterial()->setRoughness(0.5);
+    toolObj->getVisualModel(0)->getRenderMaterial()->setMetalness(1.0);
+    toolObj->getVisualModel(0)->getRenderMaterial()->setOpacity(0.5);
+
+    auto lineModel = std::make_shared<VisualModel>();
+    lineModel->setGeometry(toolGeometry);
+    toolObj->addVisualModel(lineModel);
+    //toolObj->getVisualModel(0)->getRenderMaterial()->setLineWidth(5.0);
+
+    std::shared_ptr<RigidBodyModel2> rbdModel = std::make_shared<RigidBodyModel2>();
+    rbdModel->getConfig()->m_gravity = Vec3d::Zero();
+    rbdModel->getConfig()->m_maxNumIterations       = 20;
+    rbdModel->getConfig()->m_angularVelocityDamping = 0.8; // Helps with lack of 6dof
+    toolObj->setDynamicalModel(rbdModel);
+
+    toolObj->getRigidBody()->m_mass = 1.0;
+    toolObj->getRigidBody()->m_intertiaTensor = Mat3d::Identity() * 1000.0;
+    toolObj->getRigidBody()->m_initPos = Vec3d(0.0, 0.1, 0.0);
+
+    return toolObj;
+}
+
+///
+/// \brief This examples demonstrates rigid body with pivot constraint
+///
+int
+main()
+{
+    // Write log to stdout and file
+    Logger::startLogger();
+
+    imstkNew<Scene> scene("RbdSDFNeedle");
+
+    // Create the bone
+    std::shared_ptr<CollidingObject> tissueObj = createTissueObj();
+    scene->addSceneObject(tissueObj);
+
+    // Create the needle
+    std::shared_ptr<NeedleObject> needleObj = createNeedleObj();
+    needleObj->setForceThreshold(50.0);
+    scene->addSceneObject(needleObj);
+
+    // Setup a debug ghost tool for virtual coupling
+    auto ghostToolObj = std::make_shared<SceneObject>("ghostTool");
+    {
+        auto                  toolMesh = std::dynamic_pointer_cast<SurfaceMesh>(needleObj->getVisualGeometry());
+        imstkNew<SurfaceMesh> toolGhostMesh;
+        toolGhostMesh->initialize(
+            std::make_shared<VecDataArray<double, 3>>(*toolMesh->getVertexPositions(Geometry::DataType::PreTransform)),
+            std::make_shared<VecDataArray<int, 3>>(*toolMesh->getTriangleIndices()));
+        ghostToolObj->setVisualGeometry(toolGhostMesh);
+        ghostToolObj->getVisualModel(0)->getRenderMaterial()->setColor(Color::Orange);
+        ghostToolObj->getVisualModel(0)->getRenderMaterial()->setLineWidth(5.0);
+        ghostToolObj->getVisualModel(0)->getRenderMaterial()->setOpacity(0.3);
+    }
+    scene->addSceneObject(ghostToolObj);
+
+    // Setup interaction between tissue and needle
+    auto needleInteraction = std::make_shared<NeedleInteraction>(tissueObj, needleObj);
+    scene->getCollisionGraph()->addInteraction(needleInteraction);
+
+    // Camera
+    scene->getActiveCamera()->setPosition(0.0, 0.2, 0.2);
+    scene->getActiveCamera()->setFocalPoint(0.0, 0.0, 0.0);
+    scene->getActiveCamera()->setViewUp(0.0, 1.0, 0.0);
+
+    // Light
+    imstkNew<DirectionalLight> light;
+    light->setDirection(Vec3d(0.0, -1.0, -1.0));
+    light->setIntensity(1.0);
+    scene->addLight("light", light);
+
+    // Run the simulation
+    {
+        // Setup a viewer to render in its own thread
+        imstkNew<VTKViewer> viewer("Viewer");
+        viewer->setActiveScene(scene);
+        viewer->setDebugAxesLength(0.005, 0.005, 0.005);
+
+        // Setup a scene manager to advance the scene in its own thread
+        imstkNew<SceneManager> sceneManager("Scene Manager");
+        sceneManager->setActiveScene(scene);
+        sceneManager->setExecutionType(Module::ExecutionType::ADAPTIVE);
+        sceneManager->pause();
+
+        imstkNew<SimulationManager> driver;
+        driver->addModule(viewer);
+        driver->addModule(sceneManager);
+        driver->setDesiredDt(0.001);
+
+#ifdef iMSTK_USE_OPENHAPTICS
+        imstkNew<HapticDeviceManager>       hapticManager;
+        std::shared_ptr<HapticDeviceClient> deviceClient = hapticManager->makeDeviceClient();
+        driver->addModule(hapticManager);
+
+        imstkNew<RigidObjectController> controller(needleObj, deviceClient);
+        controller->setTranslationScaling(0.001);
+        controller->setLinearKs(8000.0);
+        controller->setLinearKd(200.0);
+        controller->setAngularKs(1000000.0);
+        controller->setAngularKd(100000.0);
+        controller->setForceScaling(0.02);
+        controller->setSmoothingKernelSize(5);
+        controller->setUseForceSmoothening(true);
+        scene->addController(controller);
+
+        connect<Event>(sceneManager, &SceneManager::postUpdate, [&](Event*)
+        {
+            // Keep the tool moving in real time
+            needleObj->getRigidBodyModel2()->getConfig()->m_dt = sceneManager->getDt();
+
+            // Update the ghost debug geometry
+            std::shared_ptr<Geometry> toolGhostMesh = ghostToolObj->getVisualGeometry();
+            toolGhostMesh->setRotation(controller->getRotation());
+            toolGhostMesh->setTranslation(controller->getPosition());
+            toolGhostMesh->updatePostTransformData();
+            toolGhostMesh->postModified();
+
+            ghostToolObj->getVisualModel(0)->getRenderMaterial()->setOpacity(std::min(1.0, controller->getForce().norm() / 15.0));
+            });
+#else
+        connect<Event>(sceneManager, &SceneManager::postUpdate, [&](Event*)
+        {
+            // Keep the tool moving in real time
+            needleObj->getRigidBodyModel2()->getConfig()->m_dt = sceneManager->getDt();
+
+            const Vec2d mousePos   = viewer->getMouseDevice()->getPos();
+            const Vec3d desiredPos = Vec3d(mousePos[0] - 0.5, mousePos[1] - 0.5, 0.0) * 0.25;
+            const Quatd desiredOrientation = Quatd(Rotd(-1.0, Vec3d(1.0, 0.0, 0.0)));
+
+            Vec3d virutalForce;
+            {
+                const Vec3d fS = (desiredPos - needleObj->getRigidBody()->getPosition()) * 1000.0;   // Spring force
+                const Vec3d fD = -needleObj->getRigidBody()->getVelocity() * 100.0;                  // Spring damping
+
+                const Quatd dq       = desiredOrientation * needleObj->getRigidBody()->getOrientation().inverse();
+                const Rotd angleAxes = Rotd(dq);
+                const Vec3d tS       = angleAxes.axis() * angleAxes.angle() * 10000000.0;
+                const Vec3d tD       = -needleObj->getRigidBody()->getAngularVelocity() * 1000.0;
+
+                virutalForce = fS + fD;
+                (*needleObj->getRigidBody()->m_force)  += virutalForce;
+                (*needleObj->getRigidBody()->m_torque) += tS + tD;
+            }
+
+            // Update the ghost debug geometry
+            std::shared_ptr<Geometry> toolGhostMesh = ghostToolObj->getVisualGeometry();
+            toolGhostMesh->setRotation(desiredOrientation);
+            toolGhostMesh->setTranslation(desiredPos);
+            toolGhostMesh->updatePostTransformData();
+            toolGhostMesh->postModified();
+
+            ghostToolObj->getVisualModel(0)->getRenderMaterial()->setOpacity(std::min(1.0, virutalForce.norm() / 15.0));
+            });
+#endif
+
+        // Add mouse and keyboard controls to the viewer
+        {
+            imstkNew<MouseSceneControl> mouseControl(viewer->getMouseDevice());
+            mouseControl->setSceneManager(sceneManager);
+            viewer->addControl(mouseControl);
+
+            imstkNew<KeyboardSceneControl> keyControl(viewer->getKeyboardDevice());
+            keyControl->setSceneManager(sceneManager);
+            keyControl->setModuleDriver(driver);
+            viewer->addControl(keyControl);
+        }
+
+        driver->start();
+    }
+
+    return 0;
+}
diff --git a/Examples/RBD/RbdSurfaceMeshToSphereCD/RbdSurfaceMeshToSphereCDExample.cpp b/Examples/RBD/RbdSurfaceMeshToSphereCD/RbdSurfaceMeshToSphereCDExample.cpp
index 5a41e4286467bdda3f9b112fdfa3d433b86d5fd9..ebe92231b884466890c7feb0bd674ab3120ceda2 100644
--- a/Examples/RBD/RbdSurfaceMeshToSphereCD/RbdSurfaceMeshToSphereCDExample.cpp
+++ b/Examples/RBD/RbdSurfaceMeshToSphereCD/RbdSurfaceMeshToSphereCDExample.cpp
@@ -104,7 +104,6 @@ main()
     Logger::startLogger();
 
     imstkNew<Scene> scene("RbdMeshMeshCollision");
-    scene->getConfig()->taskParallelizationEnabled = false;
 
     // This model is shared among interacting rigid bodies
     imstkNew<RigidBodyModel2> rbdModel;
diff --git a/Source/Common/imstkColor.cpp b/Source/Common/imstkColor.cpp
index 63c2b31dd83d23458477b45bb113391ce365cf52..d8f73adf6aa021987688d912f2f61ca58c3aef8a 100644
--- a/Source/Common/imstkColor.cpp
+++ b/Source/Common/imstkColor.cpp
@@ -47,6 +47,8 @@ Color Color::Marigold(0.9, 0.9, 0.4);
 Color Color::YellowBone(0.828, 0.785, 0.501);
 Color Color::Bone(0.89, 0.86, 0.79);
 Color Color::Blood(0.4, 0.0, 0.0);
+Color Color::LightSkin(1.0, 0.86, 0.7);
+Color Color::DarkSkin(0.38, 0.27, 0.18);
 
 Color::Color()
 {
diff --git a/Source/Common/imstkColor.h b/Source/Common/imstkColor.h
index 4f33c02e3dfbfbff774b59069f3b8d3452a7ebbc..c0526bbc66ce90a1e2f353afa5cf2649225c28c1 100644
--- a/Source/Common/imstkColor.h
+++ b/Source/Common/imstkColor.h
@@ -143,6 +143,8 @@ struct Color
     static Color Bone;
     static Color YellowBone;
     static Color Blood;
+    static Color LightSkin;
+    static Color DarkSkin;
 };
 #ifdef WIN32
 #pragma warning(default : 4201)