diff --git a/Examples/PBDCutting/CMakeLists.txt b/Examples/PBDCutting/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a2aff794533c4c81eed61e23e58e45c4b97ec27f
--- /dev/null
+++ b/Examples/PBDCutting/CMakeLists.txt
@@ -0,0 +1,37 @@
+###########################################################################
+#
+# 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.
+#
+###########################################################################
+if(iMSTK_USE_OpenHaptics)
+
+  project(Example-PBDCutting)
+
+  #-----------------------------------------------------------------------------
+  # Create executable
+  #-----------------------------------------------------------------------------
+  imstk_add_executable(${PROJECT_NAME} PBDCuttingExample.cpp)
+
+  #-----------------------------------------------------------------------------
+  # Add the target to Examples folder
+  #-----------------------------------------------------------------------------
+  SET_TARGET_PROPERTIES (${PROJECT_NAME} PROPERTIES FOLDER Examples/Haptics)
+  
+  #-----------------------------------------------------------------------------
+  # Link libraries to executable
+  #-----------------------------------------------------------------------------
+  target_link_libraries(${PROJECT_NAME} SimulationManager apiUtilities Filtering)
+
+endif()
diff --git a/Examples/PBDCutting/PBDCuttingExample.cpp b/Examples/PBDCutting/PBDCuttingExample.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e20e4feddbcc7ee77b5124d3dd394a0eb3c6556c
--- /dev/null
+++ b/Examples/PBDCutting/PBDCuttingExample.cpp
@@ -0,0 +1,278 @@
+/*=========================================================================
+
+   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 "imstkLogger.h"
+#include "imstkNew.h"
+#include "imstkScene.h"
+#include "imstkSceneManager.h"
+#include "imstkVTKViewer.h"
+
+// Objects
+#include "imstkCamera.h"
+#include "imstkCollidingObject.h"
+#include "imstkLight.h"
+#include "imstkPbdObject.h"
+
+// Geometry
+#include "imstkMeshIO.h"
+#include "imstkCapsule.h"
+#include "imstkPlane.h"
+#include "imstkSurfaceMesh.h"
+
+// Devices and controllers
+#include "imstkHapticDeviceClient.h"
+#include "imstkHapticDeviceManager.h"
+#include "imstkKeyboardDeviceClient.h"
+#include "imstkKeyboardSceneControl.h"
+//#include "imstkLaparoscopicToolController.h"
+#include "imstkMouseSceneControl.h"
+#include "imstkSceneObjectController.h"
+
+// Collisions and Models
+#include "imstkCollisionGraph.h"
+#include "imstkCollisionPair.h"
+#include "imstkCollisionDetection.h"
+#include "imstkPbdModel.h"
+#include "imstkPbdObjectCuttingPair.h"
+#include "imstkRenderMaterial.h"
+#include "imstkVisualModel.h"
+
+#include "imstkSurfaceMeshCut.h"
+
+using namespace imstk;
+
+// Parameters to play with
+const double width  = 50.0;
+const double height = 50.0;
+const int    nRows  = 6;
+const int    nCols  = 6;
+
+///
+/// \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("Cloth_SurfaceMesh");
+
+    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, 1.0e3);
+    pbdParams->enableConstraint(PbdConstraint::Type::Dihedral, 1.0e2);
+    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, -9.8, 0.0);
+    pbdParams->m_defaultDt  = 0.005;
+    pbdParams->m_iterations = 5;
+
+    // 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("PBDCutting");
+    //scene->getConfig()->writeTaskGraph = true;
+
+    // Create a cutting plane object in the scene
+    imstkNew<Plane> planeGeom;
+    planeGeom->setWidth(40.0);
+    planeGeom->setTranslation(Vec3d(0.0, 0.0, 20.0));
+    planeGeom->setOrientationAxis(Vec3d(-1.0, 0.0, 0.0));
+    imstkNew<CollidingObject> planeObj("Plane");
+    planeObj->setVisualGeometry(planeGeom);
+    planeObj->setCollidingGeometry(planeGeom);
+    scene->addSceneObject(planeObj);
+
+    // Create a pbd cloth object in the scene
+    std::shared_ptr<PbdObject> clothObj = makeClothObj("Cloth", width, height, nRows, nCols);
+    scene->addSceneObject(clothObj);
+
+    // Add interaction pair for pbd cutting
+
+    // Device Server
+    imstkNew<HapticDeviceManager>       server;
+    std::shared_ptr<HapticDeviceClient> client = server->makeDeviceClient();
+
+    // Create the virtual coupling object controller
+    imstkNew<SceneObjectController> controller(planeObj, client);
+    scene->addController(controller);
+
+    // Camera
+    scene->getActiveCamera()->setPosition(Vec3d(1.0, 1.0, 1.0) * 100.0);
+    scene->getActiveCamera()->setFocalPoint(Vec3d(0, -50, 0));
+
+    // Light
+    imstkNew<DirectionalLight> light("light");
+    light->setFocalPoint(Vec3d(5.0, -8.0, -5.0));
+    light->setIntensity(1.0);
+    scene->addLight(light);
+
+    //Run the simulation
+    {
+        // Setup a viewer to render in its own thread
+        imstkNew<VTKViewer> viewer("Viewer");
+        viewer->setActiveScene(scene);
+
+        // Setup a scene manager to advance the scene in its own thread
+        imstkNew<SceneManager> sceneManager("Scene Manager");
+        sceneManager->setActiveScene(scene);
+        viewer->addChildThread(sceneManager); // SceneManager will start/stop with viewer
+
+        // Add server of haptic device to viewer
+        viewer->addChildThread(server);
+
+        // 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->setViewer(viewer);
+            viewer->addControl(keyControl);
+        }
+
+        // Queue keypress to be called after scene thread
+        queueConnect<KeyPressEvent>(viewer->getKeyboardDevice(), EventType::KeyEvent, sceneManager, [&](KeyPressEvent* e)
+        {
+            // When i is pressed replace the PBD cloth with a cut one
+            if (e->m_key == 'i' && e->m_keyPressType == KEY_PRESS)
+            {
+                // This has a number of issues that make it not physically realistic
+                // - Mass is not conservative when interpolated from subdivide
+                // - Constraint resting lengths are not correctly reinited
+                std::shared_ptr<SurfaceMesh> clothMesh = std::dynamic_pointer_cast<SurfaceMesh>(clothObj->getPhysicsGeometry());
+                imstkNew<SurfaceMeshCut> surfCut;
+                surfCut->setInputMesh(clothMesh);
+                surfCut->setPlane(planeGeom);
+                surfCut->update();
+                std::shared_ptr<SurfaceMesh> newClothMesh = surfCut->getOutputMesh();
+
+                // RenderDelegates cannot visually have entire geometries swapped yet, so even though we could just set the geometry
+                // on the model, you would not visually see it. Instead we replace the vertex and index buffers of the existing one
+                // Another issue here is that initial geometry is not remapped so reset will not reset to undeformed config
+                clothMesh->setInitialVertexPositions(std::make_shared<VecDataArray<double, 3>>(*newClothMesh->getVertexPositions()));
+                clothMesh->setVertexPositions(newClothMesh->getVertexPositions());
+                clothMesh->setTriangleIndices(newClothMesh->getTriangleIndices());
+                clothMesh->setVertexAttribute("Velocities", newClothMesh->getVertexAttribute("Velocities"));
+                clothMesh->modified();
+
+                // Re-setup the constraints on the object
+                clothObj->initialize();
+            }
+            });
+
+        // Start viewer running, scene as paused
+        sceneManager->requestStatus(ThreadStatus::Paused);
+        viewer->start();
+    }
+    return 0;
+}
diff --git a/Source/Filtering/imstkSurfaceMeshCut.cpp b/Source/Filtering/imstkSurfaceMeshCut.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3c7f0f11ff7b2a6b37dec2067ecc980ee4f26981
--- /dev/null
+++ b/Source/Filtering/imstkSurfaceMeshCut.cpp
@@ -0,0 +1,469 @@
+/*=========================================================================
+
+   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 "imstkSurfaceMeshCut.h"
+
+#include "imstkGeometryUtilities.h"
+#include "imstkLogger.h"
+#include "imstkPlane.h"
+
+#include <map>
+#include <set>
+
+namespace imstk
+{
+SurfaceMeshCut::SurfaceMeshCut()
+{
+    setNumberOfInputPorts(1);
+    setNumberOfOutputPorts(1);
+    setOutput(std::make_shared<SurfaceMesh>());
+}
+
+std::shared_ptr<SurfaceMesh>
+SurfaceMeshCut::getOutputMesh()
+{
+    return std::dynamic_pointer_cast<SurfaceMesh>(getOutput(0));
+}
+
+void
+SurfaceMeshCut::setInputMesh(std::shared_ptr<SurfaceMesh> inputMesh)
+{
+    setInput(inputMesh, 0);
+}
+
+void
+SurfaceMeshCut::requestUpdate()
+{
+    // input and output SurfaceMesh
+    std::shared_ptr<SurfaceMesh> inputSurf = std::dynamic_pointer_cast<SurfaceMesh>(getInput(0));
+    if (inputSurf == nullptr)
+    {
+        LOG(WARNING) << "Missing required SurfaceMesh input";
+        return;
+    }
+    std::shared_ptr<SurfaceMesh> outputSurf = std::dynamic_pointer_cast<SurfaceMesh>(getOutput(0));
+    outputSurf->deepCopy(inputSurf);
+
+    // vertices on the cutting path and whether they will be split
+    std::map<int, bool> cutVerts;
+
+    generateCutData(m_Plane, outputSurf);
+
+    // refinement
+    refinement(outputSurf, cutVerts);
+
+    // split cutting vertices
+    splitVerts(outputSurf, cutVerts);
+
+    // vtkPolyData to output SurfaceMesh
+    setOutput(outputSurf);
+}
+
+void
+SurfaceMeshCut::refinement(std::shared_ptr<SurfaceMesh> outputSurf, std::map<int, bool>& cutVerts)
+{
+    // map between one exsiting edge to the new vert generated from the cutting
+    std::map<std::pair<int, int>, int> edgeVertMap;
+
+    auto triangles = outputSurf->getTriangleIndices();
+    auto vertices  = outputSurf->getVertexPositions();
+
+    for (size_t i = 0; i < m_CutData->size(); i++)
+    {
+        CutData curCutData = m_CutData->at(i);
+        auto    curCutType = curCutData.cutType;
+        int     triId      = curCutData.triId;
+        int     ptId0      = curCutData.ptIds[0];
+        int     ptId1      = curCutData.ptIds[1];
+        Vec3d   cood0      = curCutData.cutCoords[0];
+        Vec3d   cood1      = curCutData.cutCoords[1];
+
+        if (curCutType == CutType::EDGE || curCutType == CutType::EDGE_VERT)
+        {
+            int  newPtId = -1;
+            auto edge0   = std::make_pair(ptId0, ptId1);
+            auto edge1   = std::make_pair(ptId1, ptId0);
+
+            // add new point
+            if (edgeVertMap.find(edge1) != edgeVertMap.end())
+            {
+                newPtId = edgeVertMap[edge1];
+            }
+            else
+            {
+                newPtId = vertices->size();
+                vertices->push_back(cood0);
+                edgeVertMap[edge0] = newPtId;
+            }
+
+            // update triangle indices
+            Vec3i ptIds = (*triangles)[triId];
+            int   ptId2 = -1;
+            if (ptIds[0] != ptId0 && ptIds[0] != ptId1)
+            {
+                ptId2 = ptIds[0];
+            }
+            else if (ptIds[1] != ptId0 && ptIds[1] != ptId1)
+            {
+                ptId2 = ptIds[1];
+            }
+            else
+            {
+                ptId2 = ptIds[2];
+            }
+            (*triangles)[triId] = Vec3i(ptId2, ptId0, newPtId);
+            triangles->push_back(Vec3i(ptId2, newPtId, ptId1));
+
+            // add vertices to cutting path
+            if (curCutType == CutType::EDGE_VERT)
+            {
+                cutVerts[ptId2]   = (cutVerts.find(ptId2) != cutVerts.end()) ? true : false;
+                cutVerts[newPtId] = (cutVerts.find(newPtId) != cutVerts.end()) ? true : false;
+            }
+        }
+        else if (curCutType == CutType::EDGE_EDGE)
+        {
+            int newPtId0 = -1;
+            int newPtId1 = -1;
+
+            Vec3i ptIds = (*triangles)[triId];
+            int   ptId2 = -1;
+            if (ptIds[0] != ptId0 && ptIds[0] != ptId1)
+            {
+                ptId2 = ptIds[0];
+            }
+            else if (ptIds[1] != ptId0 && ptIds[1] != ptId1)
+            {
+                ptId2 = ptIds[1];
+            }
+            else
+            {
+                ptId2 = ptIds[2];
+            }
+
+            auto edge00 = std::make_pair(ptId2, ptId0);
+            auto edge01 = std::make_pair(ptId0, ptId2);
+            auto edge10 = std::make_pair(ptId1, ptId2);
+            auto edge11 = std::make_pair(ptId2, ptId1);
+
+            // add new points
+            if (edgeVertMap.find(edge01) != edgeVertMap.end())
+            {
+                newPtId0 = edgeVertMap[edge01];
+            }
+            else
+            {
+                newPtId0 = vertices->size();
+                vertices->push_back(cood0);
+                edgeVertMap[edge00] = newPtId0;
+            }
+            if (edgeVertMap.find(edge11) != edgeVertMap.end())
+            {
+                newPtId1 = edgeVertMap[edge11];
+            }
+            else
+            {
+                newPtId1 = vertices->size();
+                vertices->push_back(cood1);
+                edgeVertMap[edge10] = newPtId1;
+            }
+
+            // update triangle indices
+            (*triangles)[triId] = Vec3i(ptId2, newPtId0, newPtId1);
+            triangles->push_back(Vec3i(newPtId0, ptId0, ptId1));
+            triangles->push_back(Vec3i(newPtId0, ptId1, newPtId1));
+
+            // add vertices to cutting path
+            cutVerts[newPtId0] = (cutVerts.find(newPtId0) != cutVerts.end()) ? true : false;
+            cutVerts[newPtId1] = (cutVerts.find(newPtId1) != cutVerts.end()) ? true : false;
+        }
+        else if (curCutType == CutType::VERT_VERT)
+        {
+            // add vertices to cutting path
+            cutVerts[ptId0] = (cutVerts.find(ptId0) != cutVerts.end()) ? true : false;
+            cutVerts[ptId1] = (cutVerts.find(ptId1) != cutVerts.end()) ? true : false;
+        }
+        else
+        {
+            //throw error
+        }
+    }
+    outputSurf->setInitialVertexPositions(std::make_shared<VecDataArray<double, 3>>(*vertices));
+    outputSurf->setVertexPositions(std::make_shared<VecDataArray<double, 3>>(*vertices));
+    outputSurf->setTriangleIndices(std::make_shared<VecDataArray<int, 3>>(*triangles));
+    outputSurf->modified();
+}
+
+void
+SurfaceMeshCut::splitVerts(std::shared_ptr<SurfaceMesh> outputSurf, std::map<int, bool>& cutVerts)
+{
+    auto triangles = outputSurf->getTriangleIndices();
+    auto vertices  = outputSurf->getVertexPositions();
+
+    // build vertexToTriangleListMap
+    std::vector<std::set<int>> vertexNeighborTriangles;
+    vertexNeighborTriangles.clear();
+    vertexNeighborTriangles.resize(vertices->size());
+
+    int triangleId = 0;
+
+    for (const auto& t : *triangles)
+    {
+        vertexNeighborTriangles.at(t[0]).insert(triangleId);
+        vertexNeighborTriangles.at(t[1]).insert(triangleId);
+        vertexNeighborTriangles.at(t[2]).insert(triangleId);
+        triangleId++;
+    }
+
+    // split cutting vertices
+    for (const auto& cutVert : cutVerts)
+    {
+        if (cutVert.second == false && !vertexOnBoundary(triangles, vertexNeighborTriangles[cutVert.first]))
+        {
+            // do not split vertex since it's the cutting end in surface
+        }
+        else
+        {
+            // split vertex
+            int newPtId = vertices->size();
+            vertices->push_back((*vertices)[cutVert.first]);
+
+            for (const auto& t : vertexNeighborTriangles[cutVert.first])
+            {
+                //if triangle on the negative side
+                Vec3d pt0 = (*vertices)[(*triangles)[t][0]];
+                Vec3d pt1 = (*vertices)[(*triangles)[t][1]];
+                Vec3d pt2 = (*vertices)[(*triangles)[t][2]];
+
+                if (pointOnPlaneSide(pt0) < 0 || pointOnPlaneSide(pt1) < 0 || pointOnPlaneSide(pt2) < 0)
+                {
+                    for (int i = 0; i < 3; i++)
+                    {
+                        if ((*triangles)[t][i] == cutVert.first)
+                        {
+                            (*triangles)[t][i] = newPtId;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    outputSurf->setInitialVertexPositions(std::make_shared<VecDataArray<double, 3>>(*vertices));
+    outputSurf->setVertexPositions(std::make_shared<VecDataArray<double, 3>>(*vertices));
+    outputSurf->setTriangleIndices(std::make_shared<VecDataArray<int, 3>>(*triangles));
+    outputSurf->modified();
+}
+
+int
+SurfaceMeshCut::pointOnPlaneSide(Vec3d pt)
+{
+    double normalProjection = m_Plane->getNormal().dot(pt - m_Plane->getPosition());
+    if (normalProjection > m_Epsilon)
+    {
+        return 1;
+    }
+    else if (normalProjection < -m_Epsilon)
+    {
+        return -1;
+    }
+    else
+    {
+        return 0;
+    }
+    return 0;
+}
+
+bool
+SurfaceMeshCut::vertexOnBoundary(std::shared_ptr<VecDataArray<int, 3>> triangleIndices, std::set<int>& triSet)
+{
+    std::set<int> nonRepeatNeighborVerts;
+    for (const auto& t : triSet)
+    {
+        for (int i = 0; i < 3; i++)
+        {
+            int ptId = (*triangleIndices)[t][i];
+            if (nonRepeatNeighborVerts.find(ptId) != nonRepeatNeighborVerts.end())
+            {
+                nonRepeatNeighborVerts.erase(ptId);
+            }
+            else
+            {
+                nonRepeatNeighborVerts.insert(ptId);
+            }
+        }
+    }
+    if (nonRepeatNeighborVerts.size() >= 2)
+    {
+        return true;
+    }
+    else
+    {
+        return false;
+    }
+}
+
+void
+SurfaceMeshCut::generateCutData(std::shared_ptr<Plane> plane, std::shared_ptr<SurfaceMesh> inputSurf)
+{
+    auto triangles = inputSurf->getTriangleIndices();
+    auto vertices  = inputSurf->getVertexPositions();
+
+    m_CutData->clear();
+    std::set<std::pair<int, int>> repeatEdges;  // make sure not boundary edge in vert-vert case
+
+    for (int i = 0; i < triangles->size(); i++)
+    {
+        auto&   tri = (*triangles)[i];
+        CutData newCutData;
+
+        auto ptSide = Vec3i(pointOnPlaneSide((*vertices)[tri[0]]),
+                            pointOnPlaneSide((*vertices)[tri[1]]),
+                            pointOnPlaneSide((*vertices)[tri[2]]));
+
+        switch (ptSide.squaredNorm())
+        {
+        case 1:
+            for (int j = 0; j < 3; j++)
+            {
+                if (ptSide[j] != 0)
+                {
+                    int idx0  = (j + 1) % 3;
+                    int idx1  = (j + 2) % 3;
+                    int ptId0 = tri[idx0];
+                    int ptId1 = tri[idx1];
+
+                    std::pair<int, int> cutEdge = std::make_pair(ptId1, ptId0);
+                    // if find cut triangle from the other side of the edge, add newCutData
+                    if (repeatEdges.find(cutEdge) != repeatEdges.end())
+                    {
+                        newCutData.cutType      = CutType::VERT_VERT;
+                        newCutData.triId        = i;
+                        newCutData.ptIds[0]     = ptId0;
+                        newCutData.ptIds[1]     = ptId1;
+                        newCutData.cutCoords[0] = (*vertices)[ptId0];
+                        newCutData.cutCoords[1] = (*vertices)[ptId1];
+                        m_CutData->push_back(newCutData);
+                    }
+                    else
+                    {
+                        repeatEdges.insert(std::make_pair(ptId0, ptId1));
+                    }
+                }
+            }
+            break;
+        case 2:
+            if (ptSide.sum() == 0)
+            {
+                for (int j = 0; j < 3; j++)
+                {
+                    if (ptSide[j] == 0)
+                    {
+                        int   idx0  = (j + 1) % 3;
+                        int   idx1  = (j + 2) % 3;
+                        int   ptId0 = tri[idx0];
+                        int   ptId1 = tri[idx1];
+                        Vec3d pos0  = (*vertices)[ptId0];
+                        Vec3d pos1  = (*vertices)[ptId1];
+                        Vec3d p     = m_Plane->getPosition();
+                        Vec3d n     = m_Plane->getNormal();
+
+                        newCutData.cutType      = CutType::EDGE_VERT;
+                        newCutData.triId        = i;
+                        newCutData.ptIds[0]     = ptId0;
+                        newCutData.ptIds[1]     = ptId1;
+                        newCutData.cutCoords[0] = (p - pos0).dot(n) / (pos1 - pos0).dot(n) * (pos1 - pos0) + pos0;
+                        newCutData.cutCoords[1] = (*vertices)[tri[j]];
+                        m_CutData->push_back(newCutData);
+                    }
+                }
+            }
+            else
+            {
+                // newCutData.cutType = CutType::VERT;
+            }
+            break;
+        case 3:
+            if (ptSide.sum() == 1)
+            {
+                for (int j = 0; j < 3; j++)
+                {
+                    if (ptSide[j] < 0)
+                    {
+                        int   idx0  = (j + 1) % 3;
+                        int   idx1  = (j + 2) % 3;
+                        int   ptId0 = tri[idx0];
+                        int   ptId1 = tri[idx1];
+                        int   ptId2 = tri[j];
+                        Vec3d pos0  = (*vertices)[ptId0];
+                        Vec3d pos1  = (*vertices)[ptId1];
+                        Vec3d pos2  = (*vertices)[ptId2];
+                        Vec3d p     = m_Plane->getPosition();
+                        Vec3d n     = m_Plane->getNormal();
+
+                        newCutData.cutType      = CutType::EDGE_EDGE;
+                        newCutData.triId        = i;
+                        newCutData.ptIds[0]     = ptId0;
+                        newCutData.ptIds[1]     = ptId1;
+                        newCutData.cutCoords[0] = (p - pos0).dot(n) / (pos2 - pos0).dot(n) * (pos2 - pos0) + pos0;
+                        newCutData.cutCoords[1] = (p - pos1).dot(n) / (pos2 - pos1).dot(n) * (pos2 - pos1) + pos1;
+                        m_CutData->push_back(newCutData);
+                    }
+                }
+            }
+            else if (ptSide.sum() == -1)
+            {
+                for (int j = 0; j < 3; j++)
+                {
+                    if (ptSide[j] > 0)
+                    {
+                        int   idx0  = (j + 1) % 3;
+                        int   idx1  = (j + 2) % 3;
+                        int   ptId0 = tri[idx0];
+                        int   ptId1 = tri[idx1];
+                        int   ptId2 = tri[j];
+                        Vec3d pos0  = (*vertices)[ptId0];
+                        Vec3d pos1  = (*vertices)[ptId1];
+                        Vec3d pos2  = (*vertices)[ptId2];
+                        Vec3d p     = m_Plane->getPosition();
+                        Vec3d n     = m_Plane->getNormal();
+
+                        newCutData.cutType      = CutType::EDGE_EDGE;
+                        newCutData.triId        = i;
+                        newCutData.ptIds[0]     = ptId0;
+                        newCutData.ptIds[1]     = ptId1;
+                        newCutData.cutCoords[0] = (p - pos0).dot(n) / (pos2 - pos0).dot(n) * (pos2 - pos0) + pos0;
+                        newCutData.cutCoords[1] = (p - pos1).dot(n) / (pos2 - pos1).dot(n) * (pos2 - pos1) + pos1;
+                        m_CutData->push_back(newCutData);
+                    }
+                }
+            }
+            else
+            {
+                // no intersection
+            }
+            break;
+        default:
+            break;
+        }
+    }
+}
+}
\ No newline at end of file
diff --git a/Source/Filtering/imstkSurfaceMeshCut.h b/Source/Filtering/imstkSurfaceMeshCut.h
new file mode 100644
index 0000000000000000000000000000000000000000..ceb4240798302a90da037143f586e1f8b7503aae
--- /dev/null
+++ b/Source/Filtering/imstkSurfaceMeshCut.h
@@ -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 "imstkGeometryAlgorithm.h"
+#include "imstkSurfaceMesh.h"
+
+namespace imstk
+{
+class Plane;
+
+// vertex on the plane (0), positive side (+1), negative side (-1)
+// pt0 and pt1 follows the triangle's indexing order when tri is presented
+enum class CutType
+{
+    NONE = 0,
+    /* triangle is not cut through
+    *     pt0 (-+1)
+    *         /  \
+    *        /    \
+    *       / tri  \
+    * pt1 (+-1)------(?)
+    */
+    EDGE,
+    /*
+    *      (-+1)
+    *       /  \
+    *      /    \
+    *     / tri  \
+    *  (-+1)------(0) pt0
+    */
+    VERT,
+    /*
+    *        (+-1) pt1
+    *        /  \
+    *       /    \
+    *      / tri  \
+    *  (-+1)------(+-1) pt0
+    */
+    EDGE_EDGE,
+    /*
+    *        pt0 (+-1)
+    *            /  \
+    *           /    \
+    *          / tri  \
+    *  pt1 (-+1)------(0)
+    */
+    EDGE_VERT,
+    /*
+    *    pt0 (0)-----(+-1)
+    *       /  \      /
+    *      /    \    /
+    *     /      \  /
+    *  (-+1)------(0) pt1
+    */
+    VERT_VERT
+};
+
+struct CutData
+{
+    public:
+        Vec3d cutCoords[2];
+        int triId       = -1;
+        int ptIds[2]    = { -1, -1 };
+        CutType cutType = CutType::NONE;
+};
+
+///
+/// \class SurfaceMeshCut
+///
+/// \brief This filter cuts the triangles of a SurfaceMesh into smaller
+/// triangles using input cutData
+/// \todo: test
+///
+class SurfaceMeshCut : public GeometryAlgorithm
+{
+public:
+    SurfaceMeshCut();
+    virtual ~SurfaceMeshCut() override = default;
+
+public:
+    std::shared_ptr<SurfaceMesh> getOutputMesh();
+    void setInputMesh(std::shared_ptr<SurfaceMesh> inputSurf);
+
+    imstkGetMacro(CutData, std::shared_ptr<std::vector<CutData>>);
+    imstkSetMacro(CutData, std::shared_ptr<std::vector<CutData>>);
+    imstkGetMacro(Plane, std::shared_ptr<Plane>);
+    imstkSetMacro(Plane, std::shared_ptr<Plane>);
+    imstkGetMacro(Epsilon, double);
+    imstkSetMacro(Epsilon, double);
+
+protected:
+    void requestUpdate() override;
+    void refinement(std::shared_ptr<SurfaceMesh> outputSurf,
+                    std::map<int, bool>& cutVerts);
+
+    void splitVerts(std::shared_ptr<SurfaceMesh> outputSurf,
+                    std::map<int, bool>& cutVerts);
+    int pointOnPlaneSide(Vec3d pt);
+    bool vertexOnBoundary(std::shared_ptr<VecDataArray<int, 3>> triangleIndices,
+                          std::set<int>& triSet);
+
+    void generateCutData(std::shared_ptr<Plane> plane, std::shared_ptr<SurfaceMesh> inputSurf);
+// void generateCutData(triangle);
+
+private:
+    std::shared_ptr<std::vector<CutData>> m_CutData = std::make_shared<std::vector<CutData>>();
+    std::shared_ptr<Plane> m_Plane = std::make_shared<Plane>();
+    double m_Epsilon = 1;
+};
+}
\ No newline at end of file