diff --git a/Examples/PBD/PBDInjection/CMakeLists.txt b/Examples/PBD/PBDInjection/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..6c514bbce02afa50b945347831de47c94b4045c0 --- /dev/null +++ b/Examples/PBD/PBDInjection/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. +# +########################################################################### + +project(Example-PBDInjection) + +#----------------------------------------------------------------------------- +# Create executable +#----------------------------------------------------------------------------- +imstk_add_executable(${PROJECT_NAME} + PBDInjectExample.cpp InflatableObject.h InflatableObject.cpp + imstkPbdInflatableVolumeConstraint.h imstkPbdInflatableVolumeConstraint.cpp + imstkPbdInflatableDistanceConstraint.h imstkPbdInflatableDistanceConstraint.cpp) + +#----------------------------------------------------------------------------- +# 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) diff --git a/Examples/PBD/PBDInjection/InflatableObject.cpp b/Examples/PBD/PBDInjection/InflatableObject.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9f0aa716cac778fd8757d7a85eb924fda4e8c491 --- /dev/null +++ b/Examples/PBD/PBDInjection/InflatableObject.cpp @@ -0,0 +1,395 @@ +/*========================================================================= + + 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 "InflatableObject.h" +#include "imstkNew.h" +#include "imstkVecDataArray.h" +#include "imstkTetrahedralMesh.h" +#include "imstkSurfaceMesh.h" +#include "imstkCollisionUtils.h" +#include "imstkImageData.h" +#include "imstkLogger.h" +#include "imstkMeshIO.h" +#include "imstkOneToOneMap.h" +#include "imstkParallelFor.h" +#include "imstkPbdConstraint.h" +#include "imstkPbdModel.h" +#include "imstkPbdObject.h" +#include "imstkRenderMaterial.h" +#include "imstkTexture.h" +#include "imstkVisualModel.h" +#include "imstkCollisionUtils.h" + +#include "imstkPbdInflatableDistanceConstraint.h" +#include "imstkPbdInflatableVolumeConstraint.h" + +InflatableObject::InflatableObject(const std::string& name, const Vec3d& tissueSize, const Vec3i& tissueDim, const Vec3d& tissueCenter) : PbdObject(name) +{ + // Setup the Geometry + makeTetGrid(tissueSize, tissueDim, tissueCenter); + m_objectSurfMesh = m_objectTetMesh->extractSurfaceMesh(); + m_objectSurfMesh->flipNormals(); + //setXZPlaneTexCoords(4.0); + setSphereTexCoords(4.0); + + // Setup the Parameters + imstkNew<PBDModelConfig> pbdParams; + pbdParams->m_doPartitioning = false; + pbdParams->m_uniformMassValue = 0.1; + pbdParams->m_gravity = Vec3d(0.0, 0.0, 0.0); + pbdParams->m_dt = 0.05; + pbdParams->m_iterations = 2; + pbdParams->m_viscousDampingCoeff = 0.05; + + // Fix the borders + for (int z = 0; z < tissueDim[2]; z++) + { + for (int y = 0; y < tissueDim[1]; y++) + { + for (int x = 0; x < tissueDim[0]; x++) + { + if (x == 0 || z == 0 || x == tissueDim[0] - 1 || z == tissueDim[2] - 1 || y == 0) + { + pbdParams->m_fixedNodeIds.push_back(x + tissueDim[0] * (y + tissueDim[1] * z)); + } + } + } + } + + // Setup the Model + imstkNew<PbdModel> pbdModel; + pbdModel->setModelGeometry(m_objectTetMesh); + pbdModel->configure(pbdParams); + + auto distanceFunctor = std::make_shared<PbdInflatableDistanceConstraintFunctor>(); + distanceFunctor->setStiffness(0.95); + auto volumeFunctor = std::make_shared<PbdInflatableVolumeConstraintFunctor>(); + volumeFunctor->setStiffness(0.9); + pbdModel->addPbdConstraintFunctor(distanceFunctor); + pbdModel->addPbdConstraintFunctor(volumeFunctor); + + // Setup the material + imstkNew<RenderMaterial> material; + material->setBackFaceCulling(false); + 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)); + + // Add a visual model to render the surface of the tet mesh + imstkNew<VisualModel> visualModel(m_objectSurfMesh); + visualModel->setRenderMaterial(material); + addVisualModel(visualModel); + + // Add a visual model to render the normals of the surface + imstkNew<VisualModel> normalsVisualModel(m_objectSurfMesh); + normalsVisualModel->getRenderMaterial()->setDisplayMode(RenderMaterial::DisplayMode::Surface); + normalsVisualModel->getRenderMaterial()->setPointSize(0.5); + addVisualModel(normalsVisualModel); + + // Setup the Object + setPhysicsGeometry(m_objectTetMesh); + setCollidingGeometry(m_objectSurfMesh); + setPhysicsToCollidingMap(std::make_shared<OneToOneMap>(m_objectTetMesh, m_objectSurfMesh)); + setDynamicalModel(pbdModel); +} + +bool +InflatableObject::initialize() +{ + PbdObject::initialize(); + + m_constraintContainer = m_pbdModel->getConstraints(); + + return true; +} + +void +InflatableObject::makeTetGrid(const Vec3d& size, const Vec3i& dim, const Vec3d& center) +{ + imstkNew<TetrahedralMesh> tissueMesh; + + imstkNew<VecDataArray<double, 3>> verticesPtr(dim[0] * dim[1] * dim[2]); + VecDataArray<double, 3>& vertices = *verticesPtr.get(); + + const Vec3d dx = size.cwiseQuotient((dim - Vec3i(1, 1, 1)).cast<double>()); + for (int z = 0; z < dim[2]; z++) + { + for (int y = 0; y < dim[1]; y++) + { + for (int x = 0; x < dim[0]; x++) + { + vertices[x + dim[0] * (y + dim[1] * z)] = Vec3i(x, y, z).cast<double>().cwiseProduct(dx) - size * 0.5 + center; + } + } + } + + // Add connectivity data + imstkNew<VecDataArray<int, 4>> indicesPtr; + VecDataArray<int, 4>& indices = *indicesPtr.get(); + for (int z = 0; z < dim[2] - 1; z++) + { + for (int y = 0; y < dim[1] - 1; y++) + { + for (int x = 0; x < dim[0] - 1; x++) + { + int cubeIndices[8] = + { + x + dim[0] * (y + dim[1] * z), + (x + 1) + dim[0] * (y + dim[1] * z), + (x + 1) + dim[0] * (y + dim[1] * (z + 1)), + x + dim[0] * (y + dim[1] * (z + 1)), + x + dim[0] * ((y + 1) + dim[1] * z), + (x + 1) + dim[0] * ((y + 1) + dim[1] * z), + (x + 1) + dim[0] * ((y + 1) + dim[1] * (z + 1)), + x + dim[0] * ((y + 1) + dim[1] * (z + 1)) + }; + + // Alternate the pattern so the edges line up on the sides of each voxel + if ((z % 2 ^ x % 2) ^ y % 2) + { + indices.push_back(Vec4i(cubeIndices[0], cubeIndices[7], cubeIndices[5], cubeIndices[4])); + indices.push_back(Vec4i(cubeIndices[3], cubeIndices[7], cubeIndices[2], cubeIndices[0])); + indices.push_back(Vec4i(cubeIndices[2], cubeIndices[7], cubeIndices[5], cubeIndices[0])); + indices.push_back(Vec4i(cubeIndices[1], cubeIndices[2], cubeIndices[0], cubeIndices[5])); + indices.push_back(Vec4i(cubeIndices[2], cubeIndices[6], cubeIndices[7], cubeIndices[5])); + } + else + { + indices.push_back(Vec4i(cubeIndices[3], cubeIndices[7], cubeIndices[6], cubeIndices[4])); + indices.push_back(Vec4i(cubeIndices[1], cubeIndices[3], cubeIndices[6], cubeIndices[4])); + indices.push_back(Vec4i(cubeIndices[3], cubeIndices[6], cubeIndices[2], cubeIndices[1])); + indices.push_back(Vec4i(cubeIndices[1], cubeIndices[6], cubeIndices[5], cubeIndices[4])); + indices.push_back(Vec4i(cubeIndices[0], cubeIndices[3], cubeIndices[1], cubeIndices[4])); + } + } + } + } + + imstkNew<VecDataArray<float, 2>> uvCoordsPtr(dim[0] * dim[1] * dim[2]); + VecDataArray<float, 2>& uvCoords = *uvCoordsPtr.get(); + for (int z = 0; z < dim[2]; z++) + { + for (int y = 0; y < dim[1]; y++) + { + for (int x = 0; x < dim[0]; x++) + { + uvCoords[x + dim[0] * (y + dim[1] * z)] = Vec2f(static_cast<float>(x) / dim[0], static_cast<float>(z) / dim[2]) * 3.0; + } + } + } + + // Ensure correct windings + for (int i = 0; i < indices.size(); i++) + { + if (tetVolume(vertices[indices[i][0]], vertices[indices[i][1]], vertices[indices[i][2]], vertices[indices[i][3]]) < 0.0) + { + std::swap(indices[i][0], indices[i][2]); + } + } + + tissueMesh->initialize(verticesPtr, indicesPtr); + tissueMesh->setVertexTCoords("uvs", uvCoordsPtr); + + m_objectTetMesh = tissueMesh; +} + +void +InflatableObject::setXZPlaneTexCoords(const double uvScale) +{ + Vec3d min, max; + m_objectSurfMesh->computeBoundingBox(min, max); + const Vec3d size = max - min; + + imstkNew<VecDataArray<float, 2>> uvCoordsPtr(m_objectSurfMesh->getNumVertices()); + VecDataArray<float, 2>& uvCoords = *uvCoordsPtr.get(); + for (int i = 0; i < m_objectSurfMesh->getNumVertices(); i++) + { + const Vec3d& vertex = m_objectSurfMesh->getVertexPosition(i); + uvCoords[i] = Vec2f((vertex[0] - min[0]) / size[0], (vertex[2] - min[2]) / size[2]) * uvScale; + } + m_objectSurfMesh->setVertexTCoords("tcoords", uvCoordsPtr); +} + +void +InflatableObject::setSphereTexCoords(const double uvScale) +{ + Vec3d min, max; + m_objectSurfMesh->computeBoundingBox(min, max); + const Vec3d size = max - min; + const Vec3d center = (max + min) * 0.5; + + const double radius = (size * 0.5).norm(); + + imstkNew<VecDataArray<float, 2>> uvCoordsPtr(m_objectSurfMesh->getNumVertices()); + VecDataArray<float, 2>& uvCoords = *uvCoordsPtr.get(); + for (int i = 0; i < m_objectSurfMesh->getNumVertices(); i++) + { + Vec3d vertex = m_objectSurfMesh->getVertexPosition(i) - center; + + // Compute phi and theta on the sphere + const double theta = asin(vertex[0] / radius); + const double phi = atan2(vertex[1], vertex[2]); + uvCoords[i] = Vec2f(phi / (PI * 2.0) + 0.5, theta / (PI * 2.0) + 0.5) * uvScale; + } + m_objectSurfMesh->setVertexTCoords("tcoords", uvCoordsPtr); +} + +void +InflatableObject::findAffectedConstraint(const Vec3d& toolTip, const double radius) +{ + m_constraintIDandWeight.clear(); + + Vec3d min, max; + m_objectTetMesh->computeBoundingBox(min, max); + if (!CollisionUtils::testAABBToAABB(toolTip[0], toolTip[0], toolTip[1], toolTip[1], toolTip[2], toolTip[2], + min[0], max[0], min[1], max[1], min[2], max[2])) + { + return; + } + + const auto& vertices = m_objectTetMesh->getVertexPositions(); + + std::vector<int> constraintIDs; + std::vector<double> weights; + int counter = 0; + double minDistance = LONG_MAX; + for (auto& c : m_constraintContainer->getConstraints()) + { + auto& ids = c->getVertexIds(); + + Vec3d center(0.0, 0.0, 0.0); + for (auto i : ids) + { + center += (*vertices)[i]; + } + + double distance = (center / ids.size() - toolTip).norm(); + + if (distance < radius) + { + constraintIDs.push_back(counter); + weights.push_back(computeGaussianWeight(distance)); + } + + if (distance < minDistance) + { + minDistance = distance; + } + + counter++; + } + + if (minDistance > 0.5) + { + return; + } + else + { + for (int i = 0; i < constraintIDs.size(); i++) + { + m_constraintIDandWeight.push_back(std::make_pair(constraintIDs[i], weights[i])); + } + + m_affectedAreaUpdated = true; + } +} + +void +InflatableObject::inject(const Vec3d& toolTip, const double radius, double rate) +{ + if (!m_affectedAreaUpdated) + { + findAffectedConstraint(toolTip, radius); + } + + if (m_inlationTpye == InflationType::Exponential) + { + m_inflationRatio *= (1 + rate); + } + else if (m_inlationTpye == InflationType::Linear) + { + m_inflationRatio += rate; + } + + for (auto& id : m_constraintIDandWeight) + { + auto& c = (m_constraintContainer->getConstraints())[id.first]; + if (c->getType() == PbdConstraint::Type::Volume) + { + const auto& constraint = std::dynamic_pointer_cast<PbdInflatableVolumeConstraint>(c); + constraint->multiplyInitRestVolumeBy(m_inflationRatio * id.second); + } + else if (c->getType() == PbdConstraint::Type::Distance) + { + //const auto& constraint = std::dynamic_pointer_cast<PbdInflatableDistanceConstraint>(c); + //constraint->multiplyInitRestLengthBy(0.1 * m_inflationRatio * id.second); + } + } +} + +void +InflatableObject::switchInflationType() +{ + if (m_inlationTpye == InflationType::Linear) + { + m_inlationTpye = InflationType::Exponential; + std::cout << "Inflation Type: Exponential." << std::endl; + } + else if (m_inlationTpye == InflationType::Exponential) + { + m_inlationTpye = InflationType::Linear; + std::cout << "Inflation Type: Linear." << std::endl; + } +} + +void +InflatableObject::setUpdateAffectedConstraint() +{ + m_affectedAreaUpdated = false; + m_inflationRatio = 1.0; +} + +void +InflatableObject::reset() +{ + PbdObject::reset(); + + m_inflationRatio = 1.0; + + for (auto& c : m_constraintContainer->getConstraints()) + { + if (c->getType() == PbdConstraint::Type::Volume) + { + const auto& constraint = std::dynamic_pointer_cast<PbdInflatableVolumeConstraint>(c); + constraint->resetRestVolume(); + } + else if (c->getType() == PbdConstraint::Type::Distance) + { + const auto& constraint = std::dynamic_pointer_cast<PbdInflatableDistanceConstraint>(c); + constraint->resetRestLength(); + } + } +} diff --git a/Examples/PBD/PBDInjection/InflatableObject.h b/Examples/PBD/PBDInjection/InflatableObject.h new file mode 100644 index 0000000000000000000000000000000000000000..a9c26fdf1bd1c91f4ac7ecf8ac9415250c08a1ab --- /dev/null +++ b/Examples/PBD/PBDInjection/InflatableObject.h @@ -0,0 +1,118 @@ +/*========================================================================= + + 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 "imstkPbdObject.h" +#include "imstkPbdConstraintContainer.h" + +namespace imstk +{ +class ImageData; +class SurfaceMesh; +class TetrahedralMesh; +class Texture; +}; + +using namespace imstk; + +/// +/// \class InflatableObject +/// +/// \brief Inflatable object based on PBD, with inflatable volume and distance constraints +/// +class InflatableObject : public PbdObject +{ +public: + enum class InflationType + { + Linear, + Exponential, + None + }; + + InflatableObject(const std::string& name, const Vec3d& tissueSize, const Vec3i& tissueDim, const Vec3d& tissueCenter); + +public: + const std::string getTypeName() const override { return "InflatableObject"; } + + bool initialize() override; + + /// + /// \brief Perform injection on the tissue given tool tip position + /// + void inject(const Vec3d& toolTip, const double radius, double rate); + + /// + /// \brief Switch between linear and exponential inflation type + /// + void switchInflationType(); + + void setInflationRatio(double ratio) { m_inflationRatio = ratio; } + void setInflationSize(double sigma) { m_sigma = sigma; } + + /// + /// \brief set update affected constraints flag + /// + void setUpdateAffectedConstraint(); + + void reset() override; + +protected: + /// + /// \brief Creates a tetraheral grid + /// + void makeTetGrid(const Vec3d& size, const Vec3i& dim, const Vec3d& center); + + /// + /// \brief Compute the texture coordinates of the tissue on a plane + /// + void setXZPlaneTexCoords(const double uvScale); + + /// + /// \brief Spherically project the texture coordinates + /// + void setSphereTexCoords(const double uvScale); + + /// + /// \brief find affected constraints id and distance in the injection area + /// + void findAffectedConstraint(const Vec3d& toolTip, const double radius); + + /// + /// \brief Compute weight Gaussian distribution + /// + inline double computeGaussianWeight(double x) { return 10 * exp(-0.5 * x * x / m_sigma / m_sigma) / m_sigma; } + +protected: + std::shared_ptr<TetrahedralMesh> m_objectTetMesh; + std::shared_ptr<SurfaceMesh> m_objectSurfMesh; + + std::shared_ptr<PbdConstraintContainer> m_constraintContainer; + std::vector<std::pair<int, double>> m_constraintIDandWeight; + + bool m_affectedAreaUpdated = false; + + double m_inflationRatio = 1.0; + double m_sigma = 1.0; + + InflationType m_inlationTpye = InflationType::Linear; +}; \ No newline at end of file diff --git a/Examples/PBD/PBDInjection/PBDInjectExample.cpp b/Examples/PBD/PBDInjection/PBDInjectExample.cpp new file mode 100644 index 0000000000000000000000000000000000000000..37de20a2fe9e76c7c02a4310be583ac9047cac69 --- /dev/null +++ b/Examples/PBD/PBDInjection/PBDInjectExample.cpp @@ -0,0 +1,249 @@ +/*========================================================================= + + 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 "imstkImageData.h" +#include "imstkKeyboardDeviceClient.h" +#include "imstkKeyboardSceneControl.h" +#include "imstkLight.h" +#include "imstkMouseSceneControl.h" +#include "imstkOneToOneMap.h" +#include "imstkPbdModel.h" +#include "imstkPbdObject.h" +#include "imstkRbdConstraint.h" +#include "imstkRigidBodyModel2.h" +#include "imstkRigidObject2.h" +#include "imstkScene.h" +#include "imstkSceneManager.h" +#include "imstkSimulationManager.h" +#include "imstkTetrahedralMesh.h" +#include "imstkVTKViewer.h" +#include "imstkLineMesh.h" +#include "imstkVisualModel.h" +#include "imstkRenderMaterial.h" +#include "imstkLogger.h" + +#include "imstkMeshToMeshBruteForceCD.h" +#include "imstkNew.h" +#include "InflatableObject.h" + +#ifdef EXAMPLE_USE_HAPTICS +#include "imstkHapticDeviceManager.h" +#include "imstkHapticDeviceClient.h" +#include "imstkRigidObjectController.h" +#endif + +using namespace imstk; + +static std::shared_ptr<RigidObject2> +makeToolObj(const std::string& name) +{ + // Setup a Sphere tool + //imstkNew<Sphere> toolGeometry(Vec3d(0.0, 0.0, 0.0), 1.0); + + imstkNew<LineMesh> toolGeometry; + imstkNew<VecDataArray<double, 3>> verticesPtr(2); + (*verticesPtr)[0] = Vec3d(0.0, 0.0, 0.0); + (*verticesPtr)[1] = Vec3d(0.0, 2.0, 0.0); + imstkNew<VecDataArray<int, 2>> indicesPtr(1); + (*indicesPtr)[0] = Vec2i(0, 1); + toolGeometry->initialize(verticesPtr, indicesPtr); + + imstkNew<RigidObject2> toolObj(name); + toolObj->setVisualGeometry(toolGeometry); + toolObj->setCollidingGeometry(toolGeometry); + toolObj->setPhysicsGeometry(toolGeometry); + toolObj->getVisualModel(0)->getRenderMaterial()->setColor(Color::Blue); + toolObj->getVisualModel(0)->getRenderMaterial()->setDisplayMode(RenderMaterial::DisplayMode::Wireframe); + toolObj->getVisualModel(0)->getRenderMaterial()->setBackFaceCulling(false); + toolObj->getVisualModel(0)->getRenderMaterial()->setLineWidth(10.0); + + std::shared_ptr<RigidBodyModel2> rbdModel = std::make_shared<RigidBodyModel2>(); + rbdModel->getConfig()->m_gravity = Vec3d::Zero(); + rbdModel->getConfig()->m_maxNumIterations = 2; + toolObj->setDynamicalModel(rbdModel); + + toolObj->getRigidBody()->m_mass = 1000.0; + toolObj->getRigidBody()->m_intertiaTensor = Mat3d::Identity() * 10000.0; + toolObj->getRigidBody()->m_initPos = Vec3d(0.0, 0.8, 0.0); + toolObj->getRigidBody()->m_isStatic = true; + + return toolObj; +} + +/// +/// \brief This example demonstrates the inflatable distance and volume Constraint +/// using Position based dynamics +/// +int +main() +{ + // Setup logger (write to file and stdout) + Logger::startLogger(); + + // Setup the scene + imstkNew<Scene> scene("PBD inflatable object example"); + scene->getActiveCamera()->setPosition(0.12, 4.51, 16.51); + scene->getActiveCamera()->setFocalPoint(0.0, 0.0, 0.0); + scene->getActiveCamera()->setViewUp(0.0, 0.96, -0.28); + + // Setup a tissue + Vec3d tissueSize = Vec3d(10.0, 3.0, 10.0); + Vec3i tissueDim = Vec3i(20, 5, 20); + Vec3d tissueCenter = Vec3d(0.1, -1.0, 0.0); + double radius = tissueSize[0] / 5.0; + imstkNew<InflatableObject> tissueObj("PBDTissue", tissueSize, tissueDim, tissueCenter); + scene->addSceneObject(tissueObj); + + // Setup a tool + Vec3d toolTip = tissueCenter + Vec3d(0.0, tissueSize[1] / 2.0, 0.0); + std::shared_ptr<RigidObject2> toolObj = makeToolObj("RBDTool"); + scene->addSceneObject(toolObj); + + //auto interaction = std::make_shared<PbdRigidObjectCollision>(tissueObj, toolObj, "MeshToMeshBruteForceCD"); + //scene->getCollisionGraph()->addInteraction(interaction); + + // Light + imstkNew<DirectionalLight> light("Light"); + light->setFocalPoint(Vec3d(5, -8, -5)); + light->setIntensity(1); + scene->addLight(light); + + // Run the simulation + { + // Setup a viewer to render + imstkNew<VTKViewer> viewer("Viewer"); + viewer->setActiveScene(scene); + viewer->setVtkLoggerMode(VTKViewer::VTKLoggerMode::MUTE); + viewer->setDebugAxesLength(0.1, 0.1, 0.1); + + // 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.01); + + // 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); + +#ifdef EXAMPLE_USE_HAPTICS + imstkNew<HapticDeviceManager> hapticManager; + hapticManager->setSleepDelay(1.0); // Delay for 1ms (haptics thread is limited to max 1000hz) + 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->setAngularKs(10000000.0); + controller->setAngularKd(500000.0); + controller->setForceScaling(0.005); + controller->setSmoothingKernelSize(25); + controller->setUseForceSmoothening(true); + scene->addController(controller); +#else + // Use keyboard controls + connect<Event>(sceneManager, SceneManager::preUpdate, [&](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) + { + (*toolObj->getRigidBody()->m_pos) += Vec3d(0.0, 0.0, 0.01); + } + else if (viewer->getKeyboardDevice()->getButton('s') == KEY_PRESS) + { + // The LineMesh used for collision with the PBD tissue + std::shared_ptr<LineMesh> lineMesh = std::dynamic_pointer_cast<LineMesh>(toolObj->getCollidingGeometry()); + Vec3d vertice = lineMesh->getVertexPosition(0); + + if ((toolTip - vertice).norm() > 0.01) + { + toolTip = vertice; + tissueObj->setUpdateAffectedConstraint(); + } + + tissueObj->inject(toolTip, radius, 0.01); + } + else if (viewer->getKeyboardDevice()->getButton('a') == KEY_PRESS) + { + // The LineMesh used for collision with the PBD tissue + std::shared_ptr<LineMesh> lineMesh = std::dynamic_pointer_cast<LineMesh>(toolObj->getCollidingGeometry()); + Vec3d vertice = lineMesh->getVertexPosition(0); + + if ((toolTip - vertice).norm() > 0.01) + { + toolTip = vertice; + tissueObj->setUpdateAffectedConstraint(); + } + + tissueObj->inject(toolTip, radius, -0.01); + } + }); +#endif + + connect<Event>(sceneManager, &SceneManager::postUpdate, [&](Event*) + { + // Keep the tool moving in real time + toolObj->getRigidBodyModel2()->getConfig()->m_dt = sceneManager->getDt(); + //tissueObj->getPbdModel()->getParameters()->m_dt = sceneManager->getDt(); + }); + + std::cout << "================================================" << std::endl; + std::cout << "Key s : injection \n" << "Key a : deflation \n"; + std::cout << "================================================" << std::endl << std::endl; + + driver->start(); + } + + return 0; +} \ No newline at end of file diff --git a/Examples/PBD/PBDInjection/imstkPbdInflatableDistanceConstraint.cpp b/Examples/PBD/PBDInjection/imstkPbdInflatableDistanceConstraint.cpp new file mode 100644 index 0000000000000000000000000000000000000000..db72f94c2e24e4999505e3ec808152d00683d011 --- /dev/null +++ b/Examples/PBD/PBDInjection/imstkPbdInflatableDistanceConstraint.cpp @@ -0,0 +1,53 @@ +/*========================================================================= + + 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 "imstkPbdInflatableDistanceConstraint.h" + +namespace imstk +{ +void +PbdInflatableDistanceConstraint::initConstraint(const VecDataArray<double, 3>& initVertexPositions, + const size_t& pIdx0, + const size_t& pIdx1, + const double k) +{ + PbdDistanceConstraint::initConstraint(initVertexPositions, pIdx0, pIdx1, k); + m_initialRestLength = m_restLength; +} + +void +PbdInflatableDistanceConstraint::multiplyRestLengthBy(const double ratio) +{ + // Multiplied ratio must be positive + if (ratio < DBL_EPSILON) + { + return; + } + + m_restLength *= ratio; + + // Modified RestLenght can't be less than initial RestLenght + if (m_restLength < m_initialRestLength) + { + m_restLength = m_initialRestLength; + } +} +} diff --git a/Examples/PBD/PBDInjection/imstkPbdInflatableDistanceConstraint.h b/Examples/PBD/PBDInjection/imstkPbdInflatableDistanceConstraint.h new file mode 100644 index 0000000000000000000000000000000000000000..9efad07af4875db891d7494f5406af82d8e0c877 --- /dev/null +++ b/Examples/PBD/PBDInjection/imstkPbdInflatableDistanceConstraint.h @@ -0,0 +1,139 @@ +/*========================================================================= + + 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 "imstkPbdDistanceConstraint.h" +#include "imstkPbdConstraintFunctor.h" + +namespace imstk +{ +/// +/// \class PbdInflatableDistanceConstraint +/// +class PbdInflatableDistanceConstraint : public PbdDistanceConstraint +{ +public: + /// + /// \brief Constructor + /// + PbdInflatableDistanceConstraint() : PbdDistanceConstraint() {} + + /// + /// \brief Initializes the inflatable distance constraint + /// + void initConstraint(const VecDataArray<double, 3>& initVertexPositions, + const size_t& pIdx0, + const size_t& pIdx1, + const double k = 1e5); + + /// + /// \brief Soften or strengthen constraint by multiplying current restLength by a ratio + /// + void multiplyRestLengthBy(const double ratio); + + /// + /// \brief Soften or strengthen constraint by multiplying initial restLength by a ratio + /// + void multiplyInitRestLengthBy(const double ratio) { m_restLength = ratio * m_initialRestLength; } + + /// + /// \brief Reset constraint rest measurement + /// + void resetRestLength() { m_restLength = m_initialRestLength; } + +public: + double m_initialRestLength = 0.0; ///> Rest measurement(length, area, volume, etc.) +}; + +struct PbdInflatableDistanceConstraintFunctor : public PbdDistanceConstraintFunctor +{ + PbdInflatableDistanceConstraintFunctor() : PbdDistanceConstraintFunctor() {} + ~PbdInflatableDistanceConstraintFunctor() override = default; + + virtual void operator()(PbdConstraintContainer& constraints) override + { + const VecDataArray<double, 3>& vertices = *m_geom->getVertexPositions(); + auto addDistConstraint = + [&](std::vector<std::vector<bool>>& E, size_t i1, size_t i2) + { + if (i1 > i2) // Make sure i1 is always smaller than i2 + { + std::swap(i1, i2); + } + if (E[i1][i2]) + { + E[i1][i2] = 0; + auto c = std::make_shared<PbdInflatableDistanceConstraint>(); + c->initConstraint(vertices, i1, i2, m_stiffness); + constraints.addConstraint(c); + } + }; + + if (m_geom->getTypeName() == "TetrahedralMesh") + { + const auto& tetMesh = std::dynamic_pointer_cast<TetrahedralMesh>(m_geom); + const VecDataArray<int, 4>& elements = *tetMesh->getTetrahedraIndices(); + const auto nV = tetMesh->getNumVertices(); + std::vector<std::vector<bool>> E(nV, std::vector<bool>(nV, 1)); + + for (int k = 0; k < elements.size(); ++k) + { + auto& tet = elements[k]; + addDistConstraint(E, tet[0], tet[1]); + addDistConstraint(E, tet[0], tet[2]); + addDistConstraint(E, tet[0], tet[3]); + addDistConstraint(E, tet[1], tet[2]); + addDistConstraint(E, tet[1], tet[3]); + addDistConstraint(E, tet[2], tet[3]); + } + } + else if (m_geom->getTypeName() == "SurfaceMesh") + { + const auto& triMesh = std::dynamic_pointer_cast<SurfaceMesh>(m_geom); + const VecDataArray<int, 3>& elements = *triMesh->getTriangleIndices(); + const auto nV = triMesh->getNumVertices(); + std::vector<std::vector<bool>> E(nV, std::vector<bool>(nV, 1)); + + for (int k = 0; k < elements.size(); ++k) + { + auto& tri = elements[k]; + addDistConstraint(E, tri[0], tri[1]); + addDistConstraint(E, tri[0], tri[2]); + addDistConstraint(E, tri[1], tri[2]); + } + } + else if (m_geom->getTypeName() == "LineMesh") + { + const auto& lineMesh = std::dynamic_pointer_cast<LineMesh>(m_geom); + const VecDataArray<int, 2>& elements = *lineMesh->getLinesIndices(); + const auto& nV = lineMesh->getNumVertices(); + std::vector<std::vector<bool>> E(nV, std::vector<bool>(nV, 1)); + + for (int k = 0; k < elements.size(); k++) + { + auto& seg = elements[k]; + addDistConstraint(E, seg[0], seg[1]); + } + } + } +}; +} // imstk diff --git a/Examples/PBD/PBDInjection/imstkPbdInflatableVolumeConstraint.cpp b/Examples/PBD/PBDInjection/imstkPbdInflatableVolumeConstraint.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bb4a6f4453eec774423cabbe502f4eb143801745 --- /dev/null +++ b/Examples/PBD/PBDInjection/imstkPbdInflatableVolumeConstraint.cpp @@ -0,0 +1,53 @@ +/*========================================================================= + + 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 "imstkPbdInflatableVolumeConstraint.h" + +namespace imstk +{ +void +PbdInflatableVolumeConstraint::initConstraint(const VecDataArray<double, 3>& initVertexPositions, + const size_t& pIdx0, const size_t& pIdx1, + const size_t& pIdx2, const size_t& pIdx3, + const double k) +{ + PbdVolumeConstraint::initConstraint(initVertexPositions, pIdx0, pIdx1, pIdx2, pIdx3, k); + m_initialRestVolume = m_restVolume; +} + +void +PbdInflatableVolumeConstraint::multiplyRestVolumeBy(const double ratio) +{ + // Multiplied ratio must be positive + if (ratio < DBL_EPSILON) + { + return; + } + + m_restVolume *= ratio; + + // Modified RestVolume can't be less than initial RestVolume + if (abs(m_restVolume) < abs(m_initialRestVolume)) + { + m_restVolume = m_initialRestVolume; + } +} +} // imstk diff --git a/Examples/PBD/PBDInjection/imstkPbdInflatableVolumeConstraint.h b/Examples/PBD/PBDInjection/imstkPbdInflatableVolumeConstraint.h new file mode 100644 index 0000000000000000000000000000000000000000..c5db102a893a6f06e6eaef863ea33b2d00f866a7 --- /dev/null +++ b/Examples/PBD/PBDInjection/imstkPbdInflatableVolumeConstraint.h @@ -0,0 +1,94 @@ +/*========================================================================= + + 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 "imstkPbdVolumeConstraint.h" +#include "imstkPbdConstraintFunctor.h" + +namespace imstk +{ +/// +/// \class PbdInflatableVolumeConstraint +/// +class PbdInflatableVolumeConstraint : public PbdVolumeConstraint +{ +public: + /// + /// \brief constructor + /// + PbdInflatableVolumeConstraint() : PbdVolumeConstraint() {} + + /// + /// \brief Initializes the inflatable volume constraint + /// + void initConstraint(const VecDataArray<double, 3>& initVertexPositions, + const size_t& pIdx1, const size_t& pIdx2, + const size_t& pIdx3, const size_t& pIdx4, + const double k = 2.0); + + /// + /// \brief Soften or strengthen constraint by multiplying current restVolume by a ratio + /// + void multiplyRestVolumeBy(const double ratio); + + /// + /// \brief Soften or strengthen constraint by multiplying initial restVolume by a ratio + /// + void multiplyInitRestVolumeBy(const double ratio) { m_restVolume = ratio * m_initialRestVolume; } + + /// + /// \brief Reset constraint rest volume + /// + void resetRestVolume() { m_restVolume = m_initialRestVolume; } + +protected: + double m_initialRestVolume = 0.0; ///> Rest measurement(length, area, volume, etc.) +}; + +struct PbdInflatableVolumeConstraintFunctor : public PbdVolumeConstraintFunctor +{ + PbdInflatableVolumeConstraintFunctor() : PbdVolumeConstraintFunctor() {} + ~PbdInflatableVolumeConstraintFunctor() override = default; + + virtual void operator()(PbdConstraintContainer& constraints) override + { + // Check if constraint type matches the mesh type + CHECK(m_geom->getTypeName() == "TetrahedralMesh") + << "Volume constraint should come with volumetric mesh"; + + // Create constraints + auto tetMesh = std::dynamic_pointer_cast<TetrahedralMesh>(m_geom); + const VecDataArray<double, 3>& vertices = *m_geom->getVertexPositions(); + const VecDataArray<int, 4>& elements = *tetMesh->getTetrahedraIndices(); + + ParallelUtils::parallelFor(elements.size(), + [&](const size_t k) + { + auto& tet = elements[k]; + auto c = std::make_shared<PbdInflatableVolumeConstraint>(); + c->initConstraint(vertices, + tet[0], tet[1], tet[2], tet[3], m_stiffness); + constraints.addConstraint(c); + }); + } +}; +} // imstk