diff --git a/CMakeLists.txt b/CMakeLists.txt
index b9aecc38d0055c66f784bffdfdd730d2c7f03f93..d634a2ed8c749b184316cccc1b6e4e4f73c68d21 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -343,6 +343,7 @@ add_subdirectory(Source/SimulationManager)
 add_subdirectory(Source/Constraint)
 add_subdirectory(Source/Materials)
 add_subdirectory(Source/GUIOverlay)
+add_subdirectory(Source/Animation)
 
 #--------------------------------------------------------------------------
 # Export Targets
diff --git a/Examples/VulkanParticles/VulkanParticlesExample.cpp b/Examples/VulkanParticles/VulkanParticlesExample.cpp
index 6044f17b1c5b8e97665eee72b3c527beefd3a629..9737ad1fed0cb9b574890fa0b0b21da3e137a332 100644
--- a/Examples/VulkanParticles/VulkanParticlesExample.cpp
+++ b/Examples/VulkanParticles/VulkanParticlesExample.cpp
@@ -20,6 +20,8 @@
 =========================================================================*/
 
 #include "imstkSimulationManager.h"
+#include "imstkAnimationObject.h"
+#include "imstkRenderParticles.h"
 #include "imstkRenderParticleEmitter.h"
 #include "imstkAPIUtilities.h"
 
@@ -42,28 +44,36 @@ int main()
 
     // Smoke
     {
+        // Create sparks material
         auto particleMaterial = std::make_shared<RenderMaterial>();
         auto particleTexture = std::make_shared<Texture>
                                (iMSTK_DATA_ROOT "/particles/smoke_01.png", Texture::Type::DIFFUSE);
         particleMaterial->addTexture(particleTexture);
         particleMaterial->setBlendMode(RenderMaterial::BlendMode::ALPHA);
 
-        auto particleEmitter = std::make_shared<RenderParticleEmitter>(128, 2000.0f);
+        // Create particle geometry (for visual and animation)
+        auto particles = std::make_shared<RenderParticles>(128);
+        particles->setParticleSize(0.4f);
+
+        // Create particle animation model
+        auto particleEmitter = std::make_shared<RenderParticleEmitter>(particles, 2000.0f);
         particleEmitter->setInitialVelocityRange(Vec3f(-1, 5, -1), Vec3f(1, 5, 1),
             0.5, 1.0,
             -1.0, 1.0);
         particleEmitter->setEmitterSize(0.3f);
-        particleEmitter->setParticleSize(0.4f);
 
+        // Modify the first keyframe
         auto startKeyFrame = particleEmitter->getStartKeyFrame();
         startKeyFrame->m_color = Color(1.0, 0.7, 0.0, 1.0);
 
+        // Add another keyframe
         RenderParticleKeyFrame midFrame0;
         midFrame0.m_time = 700.0f;
         midFrame0.m_color = Color::Red;
         midFrame0.m_scale = 1.5f;
         particleEmitter->addKeyFrame(midFrame0);
 
+        // Add another keyframe
         RenderParticleKeyFrame midFrame1;
         midFrame1.m_time = 1300.0f;
         midFrame1.m_color = Color::DarkGray;
@@ -71,48 +81,58 @@ int main()
         midFrame1.m_scale = 2.0f;
         particleEmitter->addKeyFrame(midFrame1);
 
+        // Modify the last keyframe
         auto endKeyFrame = particleEmitter->getEndKeyFrame();
         endKeyFrame->m_color = Color::Black;
         endKeyFrame->m_color.a = 0.0;
         endKeyFrame->m_scale = 4.0;
 
-        auto particleObject = std::make_shared<VisualObject>("Smoke");
-        auto particleModel = std::make_shared<VisualModel>(particleEmitter);
+        // Create and add animation scene object
+        auto particleObject = std::make_shared<AnimationObject>("Smoke");
+        auto particleModel = std::make_shared<VisualModel>(particles);
         particleModel->setRenderMaterial(particleMaterial);
         particleObject->addVisualModel(particleModel);
-
+        particleObject->setAnimationModel(particleEmitter);
         scene->addSceneObject(particleObject);
     }
 
     // Sparks
     {
+        // Create sparks material
         auto particleMaterial = std::make_shared<RenderMaterial>();
         auto particleTexture = std::make_shared<Texture>
                                (iMSTK_DATA_ROOT "/particles/flare_01.png", Texture::Type::DIFFUSE);
         particleMaterial->addTexture(particleTexture);
         particleMaterial->setBlendMode(RenderMaterial::BlendMode::ALPHA);
 
-        auto particleEmitter = std::make_shared<RenderParticleEmitter>(128,
+        // Create particle geometry (for visual and animation)
+        auto particles = std::make_shared<RenderParticles>(128);
+        particles->setTranslation(2, 0.1, 0);
+        particles->setParticleSize(0.3f);
+
+        // Create animation model
+        auto particleEmitter = std::make_shared<RenderParticleEmitter>(particles,
             850.0f, RenderParticleEmitter::Mode::BURST);
-        particleEmitter->setTranslation(2, 0.1, 0);
         particleEmitter->setInitialVelocityRange(Vec3f(-1, 5, -1), Vec3f(1, 5, 1),
             4.0, 5.0,
             -1.0, 1.0);
         particleEmitter->setEmitterSize(0.1f);
-        particleEmitter->setParticleSize(0.3f);
 
+        // Modifying the first keyframe
         auto startKeyFrame = particleEmitter->getStartKeyFrame();
         startKeyFrame->m_acceleration = Vec3f(0, -9.8, 0);
         startKeyFrame->m_color = Color::Yellow;
 
+        // Modifying the last keyframe
         auto endKeyFrame = particleEmitter->getEndKeyFrame();
         endKeyFrame->m_color = Color::Orange;
 
-        auto particleObject = std::make_shared<VisualObject>("Sparks");
-        auto particleModel = std::make_shared<VisualModel>(particleEmitter);
+        // Create and add animation object
+        auto particleObject = std::make_shared<AnimationObject>("Sparks");
+        auto particleModel = std::make_shared<VisualModel>(particles);
         particleModel->setRenderMaterial(particleMaterial);
         particleObject->addVisualModel(particleModel);
-
+        particleObject->setAnimationModel(particleEmitter);
         scene->addSceneObject(particleObject);
     }
 
@@ -131,9 +151,9 @@ int main()
     // Create a call back on key press of 'b' to trigger the sparks emitter
     viewer->setOnCharFunction('b', [&](InteractorStyle* c) -> bool
     {
-        auto geometry = scene->getSceneObject("Sparks")->getVisualModel(0)->getGeometry();
-        auto sparks = std::static_pointer_cast<RenderParticleEmitter>(geometry);
-        sparks->reset();
+        auto sparks = std::static_pointer_cast<AnimationObject>(
+            scene->getSceneObject("Sparks"));
+        sparks->getAnimationModel()->reset();
         return false;
     });
 
diff --git a/Source/Animation/CMakeLists.txt b/Source/Animation/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..1b8f764a4a22e2ceeccfde0cae3a6a35b64b436f
--- /dev/null
+++ b/Source/Animation/CMakeLists.txt
@@ -0,0 +1,17 @@
+#-----------------------------------------------------------------------------
+# Create target
+#-----------------------------------------------------------------------------
+include(imstkAddLibrary)
+imstk_add_library( Animation
+  DEPENDS
+    Core
+    Geometry
+    SceneElements
+  )
+
+#-----------------------------------------------------------------------------
+# Testing
+#-----------------------------------------------------------------------------
+if( iMSTK_BUILD_TESTING )        
+    add_subdirectory( Testing )
+endif()
diff --git a/Source/Geometry/Particles/imstkRenderParticleEmitter.cpp b/Source/Animation/Particles/imstkRenderParticleEmitter.cpp
similarity index 75%
rename from Source/Geometry/Particles/imstkRenderParticleEmitter.cpp
rename to Source/Animation/Particles/imstkRenderParticleEmitter.cpp
index e080223bde50cfc0f866c151bee1f87b4484b302..52b852867486849ce1c6f614082d4b8acc587cfd 100644
--- a/Source/Geometry/Particles/imstkRenderParticleEmitter.cpp
+++ b/Source/Animation/Particles/imstkRenderParticleEmitter.cpp
@@ -23,20 +23,12 @@
 
 namespace imstk
 {
-RenderParticleEmitter::RenderParticleEmitter(unsigned int maxNumParticles /*=128*/,
-                                             float time /*= 3000*/,
-                                             RenderParticleEmitter::Mode mode /*= Mode::BURST*/)
-    : Geometry(Geometry::Type::RenderParticleEmitter)
+RenderParticleEmitter::RenderParticleEmitter(std::shared_ptr<Geometry> geometry,
+                                             const float time /*= 3000*/,
+                                             RenderParticleEmitter::Mode mode /*= Mode::CONTINUOUS*/)
+    : AnimationModel(geometry)
 {
-    if (maxNumParticles <= 128)
-    {
-        m_maxNumParticles = maxNumParticles;
-    }
-    else
-    {
-        m_maxNumParticles = 128;
-        LOG(WARNING) << "The maximum number of decals is 128";
-    }
+    this->setGeometry(geometry);
 
     m_time = time;
     m_emitTime = m_time;
@@ -57,25 +49,51 @@ RenderParticleEmitter::RenderParticleEmitter(unsigned int maxNumParticles /*=128
     m_keyFrames.push_back(startFrame);
     m_keyFrames.push_back(endFrame);
 
-    m_vertexPositions[0] = glm::vec3(0.5, 0.5, 0);
-    m_vertexPositions[1] = glm::vec3(0.5, -0.5, 0);
-    m_vertexPositions[2] = glm::vec3(-0.5, 0.5, 0);
-    m_vertexPositions[3] = glm::vec3(-0.5, -0.5, 0);
+    this->initializeParticles();
+}
 
-    m_vertexNormals[0] = glm::vec3(0.0, 0.0, 1.0);
-    m_vertexNormals[1] = glm::vec3(0.0, 0.0, 1.0);
-    m_vertexNormals[2] = glm::vec3(0.0, 0.0, 1.0);
-    m_vertexNormals[3] = glm::vec3(0.0, 0.0, 1.0);
+void
+RenderParticleEmitter::setGeometry(
+    std::shared_ptr<Geometry> geometry)
+{
+    if (geometry->getType() != Geometry::Type::RenderParticles)
+    {
+        LOG(FATAL) << "Geometry must be RenderParticles";
+        return;
+    }
 
-    m_vertexUVs[0] = glm::vec2(1.0, 1.0);
-    m_vertexUVs[1] = glm::vec2(1.0, 0);
-    m_vertexUVs[2] = glm::vec2(0, 1.0);
-    m_vertexUVs[3] = glm::vec2(0, 0);
+    m_animationGeometry = geometry;
+    m_particles = &std::static_pointer_cast<RenderParticles>(m_animationGeometry)->getParticles();
+}
 
-    m_triangles[0] = glm::ivec3(1, 0, 3);
-    m_triangles[1] = glm::ivec3(0, 2, 3);
+RenderParticleEmitter::Mode
+RenderParticleEmitter::getEmitterMode() const
+{
+    return m_mode;
+}
 
-    this->initializeParticles();
+void
+RenderParticleEmitter::setEmitterSize(const float size)
+{
+    m_emitterSize = size;
+}
+
+void
+RenderParticleEmitter::setInitialVelocityRange(const Vec3f minDirection,
+                                               const Vec3f maxDirection,
+                                               const float minSpeed,
+                                               const float maxSpeed,
+                                               const float minRotationSpeed,
+                                               const float maxRotationSpeed)
+{
+    m_minDirection = minDirection;
+    m_maxDirection = maxDirection;
+    m_minDirection.normalize();
+    m_maxDirection.normalize();
+    m_minSpeed = minSpeed;
+    m_maxSpeed = maxSpeed;
+    m_minRotationSpeed = minRotationSpeed;
+    m_maxRotationSpeed = maxRotationSpeed;
 }
 
 bool
@@ -90,27 +108,63 @@ RenderParticleEmitter::addKeyFrame(RenderParticleKeyFrame keyFrame)
     return true;
 }
 
-RenderParticleEmitter::Mode
-RenderParticleEmitter::getEmitterMode()
+RenderParticleKeyFrame *
+RenderParticleEmitter::getStartKeyFrame()
 {
-    return m_mode;
+    unsigned int index = 0;
+
+    for (unsigned int i = 0; i < m_keyFrames.size(); i++)
+    {
+        if (m_keyFrames[i].m_time < m_keyFrames[index].m_time)
+        {
+            index = i;
+        }
+    }
+
+    return &m_keyFrames[index];
 }
 
-void
-RenderParticleEmitter::setEmitterSize(float size)
+RenderParticleKeyFrame *
+RenderParticleEmitter::getEndKeyFrame()
 {
-    m_emitterSize = size;
+    unsigned int index = 0;
+
+    for (unsigned int i = 0; i < m_keyFrames.size(); i++)
+    {
+        if (m_keyFrames[i].m_time > m_keyFrames[index].m_time)
+        {
+            index = i;
+        }
+    }
+
+    return &m_keyFrames[index];
+}
+
+std::vector<RenderParticleKeyFrame>&
+RenderParticleEmitter::getKeyFrames()
+{
+    return m_keyFrames;
 }
 
 void
-RenderParticleEmitter::setParticleSize(float size)
+RenderParticleEmitter::reset()
 {
-    m_particleSize = size;
+    if (m_mode != Mode::BURST)
+    {
+        return;
+    }
+
+    auto renderParticles = std::static_pointer_cast<RenderParticles>(m_geometry);
+    renderParticles->reset();
+
+    this->initializeParticles();
 }
 
 void
-RenderParticleEmitter::updateParticleEmitter(Vec3d cameraPosition)
+RenderParticleEmitter::update()
 {
+    auto renderParticles = std::static_pointer_cast<RenderParticles>(m_geometry);
+
     if (!m_started)
     {
         m_stopWatch.start();
@@ -121,7 +175,7 @@ RenderParticleEmitter::updateParticleEmitter(Vec3d cameraPosition)
     float dt = (float)(time - m_lastUpdateTime);
     m_lastUpdateTime = time;
 
-    for (auto&& particle : m_particles)
+    for (auto&& particle : (*m_particles))
     {
         auto startKeyFrameTemp = *this->getStartKeyFrame();
         auto startKeyFrame = &startKeyFrameTemp;
@@ -134,7 +188,7 @@ RenderParticleEmitter::updateParticleEmitter(Vec3d cameraPosition)
         {
             particle->m_created = true;
             this->emitParticle(particle);
-            m_numParticles++;
+            renderParticles->incrementNumOfParticles();
         }
         else if (particle->m_age < 0)
         {
@@ -179,17 +233,32 @@ RenderParticleEmitter::updateParticleEmitter(Vec3d cameraPosition)
         particle->m_scale = (alpha * endKeyFrame->m_scale)
                             + ((1.0f - alpha) * startKeyFrame->m_scale);
 
-        interpolateColor(particle->m_color,
+        this->interpolateColor(particle->m_color,
             endKeyFrame->m_color,
             startKeyFrame->m_color,
             alpha);
     }
 }
 
+void
+RenderParticleEmitter::initializeParticles()
+{
+    m_particles->clear();
+
+    auto particles = std::static_pointer_cast<RenderParticles>(m_animationGeometry);
+
+    for (unsigned int i = 0; i < particles->getMaxNumParticles(); i++)
+    {
+        m_particles->push_back(std::unique_ptr<RenderParticle>(new RenderParticle()));
+        (*m_particles)[i]->m_age = -(i / (float)(particles->getMaxNumParticles())) * m_emitTime;
+        (*m_particles)[i]->m_created = false;
+    }
+}
+
 void
 RenderParticleEmitter::emitParticle(std::unique_ptr<RenderParticle>& particle)
 {
-    auto position = this->getTranslation();
+    auto position = m_animationGeometry->getTranslation();
 
     if (m_shape == Shape::CUBE)
     {
@@ -226,9 +295,9 @@ RenderParticleEmitter::emitParticle(std::unique_ptr<RenderParticle>& particle)
 
 void
 RenderParticleEmitter::interpolateColor(Color& destination,
-                                        Color& sourceA,
-                                        Color& sourceB,
-                                        float alpha)
+                                        const Color& sourceA,
+                                        const Color& sourceB,
+                                        const float alpha)
 {
     destination.r = (sourceA.r * alpha) + (sourceB.r * (1.0f - alpha));
     destination.g = (sourceA.g * alpha) + (sourceB.g * (1.0f - alpha));
@@ -236,103 +305,9 @@ RenderParticleEmitter::interpolateColor(Color& destination,
     destination.a = (sourceA.a * alpha) + (sourceB.a * (1.0f - alpha));
 }
 
-unsigned int
-RenderParticleEmitter::getNumParticles()
-{
-    return m_numParticles;
-}
-
-std::vector<std::unique_ptr<RenderParticle>>&
-RenderParticleEmitter::getParticles()
-{
-    return m_particles;
-}
-
-std::vector<RenderParticleKeyFrame>&
-RenderParticleEmitter::getKeyFrames()
-{
-    return m_keyFrames;
-}
-
-RenderParticleKeyFrame *
-RenderParticleEmitter::getStartKeyFrame()
-{
-    unsigned int index = 0;
-
-    for (unsigned int i = 0; i < m_keyFrames.size(); i++)
-    {
-        if (m_keyFrames[i].m_time < m_keyFrames[index].m_time)
-        {
-            index = i;
-        }
-    }
-
-    return &m_keyFrames[index];
-}
-
-RenderParticleKeyFrame *
-RenderParticleEmitter::getEndKeyFrame()
-{
-    unsigned int index = 0;
-
-    for (unsigned int i = 0; i < m_keyFrames.size(); i++)
-    {
-        if (m_keyFrames[i].m_time > m_keyFrames[index].m_time)
-        {
-            index = i;
-        }
-    }
-
-    return &m_keyFrames[index];
-}
-
-void
-RenderParticleEmitter::setInitialVelocityRange(Vec3f minDirection,
-                                               Vec3f maxDirection,
-                                               float minSpeed,
-                                               float maxSpeed,
-                                               float minRotationSpeed,
-                                               float maxRotationSpeed)
-{
-    m_minDirection = minDirection;
-    m_maxDirection = maxDirection;
-    m_minDirection.normalize();
-    m_maxDirection.normalize();
-    m_minSpeed = minSpeed;
-    m_maxSpeed = maxSpeed;
-    m_minRotationSpeed = minRotationSpeed;
-    m_maxRotationSpeed = maxRotationSpeed;
-}
-
 float
 RenderParticleEmitter::getRandomNormalizedFloat()
 {
     return (float)std::rand() / RAND_MAX;
 }
-
-void
-RenderParticleEmitter::initializeParticles()
-{
-    m_particles.clear();
-
-    for (unsigned int i = 0; i < m_maxNumParticles; i++)
-    {
-        m_particles.push_back(std::make_unique<RenderParticle>());
-        m_particles[i]->m_age = -(i / (float)(m_maxNumParticles)) * m_emitTime;
-        m_particles[i]->m_created = false;
-    }
-}
-
-void
-RenderParticleEmitter::reset()
-{
-    if (m_mode != Mode::BURST)
-    {
-        return;
-    }
-
-    m_numParticles = 0;
-
-    this->initializeParticles();
 }
-}
\ No newline at end of file
diff --git a/Source/Geometry/Particles/imstkRenderParticleEmitter.h b/Source/Animation/Particles/imstkRenderParticleEmitter.h
similarity index 61%
rename from Source/Geometry/Particles/imstkRenderParticleEmitter.h
rename to Source/Animation/Particles/imstkRenderParticleEmitter.h
index d5a3a878f39ddf5095865e1288f366b1e83dab6a..746156b4dddadf726ce5285a5241bd53d83415bd 100644
--- a/Source/Geometry/Particles/imstkRenderParticleEmitter.h
+++ b/Source/Animation/Particles/imstkRenderParticleEmitter.h
@@ -1,250 +1,213 @@
-/*=========================================================================
-
-   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.
-
-=========================================================================*/
-
-#ifndef imstkRenderParticleEmitter_h
-#define imstkRenderParticleEmitter_h
-
-#include <vector>
-#include <climits>
-#include <memory>
-
-#include "glm/glm.hpp"
-
-#include "imstkGeometry.h"
-#include "imstkMath.h"
-#include "imstkColor.h"
-#include "imstkTimer.h"
-
-namespace imstk
-{
-struct RenderParticle
-{
-    Vec3f m_position = Vec3f(0, 0, 0);
-    Vec3f m_velocity = Vec3f(0, 0, 0);
-    Vec3f m_acceleration = Vec3f(0, 0, 0);
-    Color m_color = Color::White;
-    float m_age = 0;
-    bool m_created = false;
-    float m_scale = 1.0f;
-    float m_rotation = 0;
-    float m_rotationalVelocity = 0;
-    float m_rotationalAcceleration = 0;
-};
-
-struct RenderParticleKeyFrame
-{
-    float m_time = 0;
-    Color m_color = Color::White;
-    Vec3f m_acceleration = Vec3f(0, 0, 0);
-    float m_rotationalAcceleration = 0;
-    float m_scale = 1.0f;
-};
-
-///
-/// \class RenderParticleEmitter
-///
-/// \brief Particle emitter
-///
-class RenderParticleEmitter : public Geometry
-{
-public:
-    ///
-    /// \brief Shape of emitter
-    ///
-    enum class Shape
-    {
-        CUBE
-    };
-
-    ///
-    /// \brief Mode of emitter
-    ///
-    enum class Mode
-    {
-        CONTINUOUS, ///< Emitter continuously releases/recycles particles
-        BURST       ///< Emitter releases particles once until manually reset
-    };
-
-    ///
-    /// \brief Constructor
-    /// \param maxNumParticles Number of particles this emitter can produce
-    /// \param time Lifespan of each particle (in milliseconds)
-    /// \param mode Mode for emitter
-    ///
-    RenderParticleEmitter(unsigned int maxNumParticles = 128,
-                          float time = 3000.0f,
-                          Mode mode = Mode::CONTINUOUS);
-
-    ///
-    /// \brief Add keyframe to particle emitter
-    /// \param keyFrame key frame to add
-    /// \returns True if key frame added, false if too many key frames
-    ///
-    bool addKeyFrame(RenderParticleKeyFrame keyFrame);
-
-    ///
-    /// \brief Get mode of emitter
-    /// \returns mode Mode of emitter
-    ///
-    RenderParticleEmitter::Mode getEmitterMode();
-
-    ///
-    /// \brief Set size of emitter
-    /// \param size Width of emitter
-    ///
-    void setEmitterSize(float size);
-
-    ///
-    /// \brief Set size of particle
-    /// \param size Particle size, this determines how much each keyframe
-    ///        scales by
-    ///
-    void setParticleSize(float size);
-
-    ///
-    /// \brief Update function
-    ///
-    void updateParticleEmitter(Vec3d cameraPosition);
-
-    ///
-    /// \brief Emit particle
-    ///
-    void emitParticle(std::unique_ptr<RenderParticle>& particle);
-
-    ///
-    /// \brief Get number of particles
-    ///
-    unsigned int getNumParticles();
-
-    ///
-    /// \brief Get particles
-    /// \returns particles
-    ///
-    std::vector<std::unique_ptr<RenderParticle>>& getParticles();
-
-    ///
-    /// \brief Get start and end frames
-    ///
-    RenderParticleKeyFrame * getStartKeyFrame();
-    RenderParticleKeyFrame * getEndKeyFrame();
-
-    ///
-    /// \brief Get key frames
-    /// \returns key frames that are unsorted
-    ///
-    std::vector<RenderParticleKeyFrame>& getKeyFrames();
-
-    ///
-    /// \brief Set velocity range
-    /// This functions sets minimum and maximum rotation values for determining
-    /// the initial trajectory of the particles. The values are randomly
-    /// selected (according to a uniform distribution) between the min and max
-    /// values. If the values are the same, then the particle direction will
-    /// not behave randomly.
-    /// \param minDirection Maximum initial angle of trajectory
-    /// \param maxDirection Minimum initial angle of trajectory
-    /// \param minSpeed Minimum initial speed
-    /// \param maxSpeed Maximum initial speed
-    /// \param minRotationSpeed Minimum initial rotation speed
-    /// \param maxRotationSpeed Maximum initial rotation speed
-    ///
-    void setInitialVelocityRange(Vec3f minDirection,
-                                 Vec3f maxDirection,
-                                 float minSpeed,
-                                 float maxSpeed,
-                                 float minRotationSpeed,
-                                 float maxRotationSpeed);
-
-    ///
-    /// \brief Get uniformly-distributed float
-    /// \returns float in the range of [0, 1]
-    ///
-    float getRandomNormalizedFloat();
-
-    ///
-    /// \brief Get volume
-    /// As these are particles, the volume is 0
-    ///
-    double getVolume() const override { return 0; };
-
-    ///
-    /// \brief Reset the emitter
-    /// Only works for burst particles
-    ///
-    void reset();
-
-protected:
-    friend class VulkanParticleRenderDelegate;
-
-    ///
-    /// \brief Interpolate color
-    ///
-    void interpolateColor(Color& destination,
-                          Color& sourceA,
-                          Color& sourceB,
-                          float alpha);
-
-    ///
-    /// \brief Initialize particles
-    ///
-    void initializeParticles();
-
-    const int c_maxNumKeyFrames = 16; ///< Maximum key frames
-    unsigned int m_maxNumParticles = 128; ///< Maximum particles
-
-    RenderParticleEmitter::Mode m_mode
-        = RenderParticleEmitter::Mode::CONTINUOUS;
-    RenderParticleEmitter::Shape m_shape
-        = RenderParticleEmitter::Shape::CUBE;
-    float m_emitterSize = 1.0f;
-    float m_particleSize = 0.1f;
-
-    std::vector<std::unique_ptr<RenderParticle>> m_particles; ///< Particle objects
-    std::vector<RenderParticleKeyFrame> m_keyFrames; ///< Particle keyframes
-    imstk::StopWatch m_stopWatch;
-    glm::vec3 m_vertexPositions[4];
-    glm::vec3 m_vertexNormals[4];
-    glm::vec3 m_vertexTangents[4];
-    glm::vec2 m_vertexUVs[4];
-    glm::ivec3 m_triangles[2];
-
-    Vec3f m_minDirection;
-    Vec3f m_maxDirection;
-    float m_minSpeed;
-    float m_maxSpeed;
-    float m_minRotationSpeed;
-    float m_maxRotationSpeed;
-
-    void applyTranslation(const Vec3d t) override {};
-    void applyRotation(const Mat3d r) override {};
-    void applyScaling(const double s) override {};
-    virtual void updatePostTransformData() override {};
-
-    float m_time; ///< total time for particle system
-    float m_emitTime;
-
-    unsigned int m_numParticles = 0;
-    double m_lastUpdateTime = 0.0;
-    bool m_started = false;
-};
-}
-
-#endif
\ No newline at end of file
+/*=========================================================================
+
+   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.
+
+=========================================================================*/
+
+#ifndef imstkRenderParticleEmitter_h
+#define imstkRenderParticleEmitter_h
+
+
+#include <memory>
+#include <vector>
+
+#include "g3log/g3log.hpp"
+
+#include "imstkMath.h"
+#include "imstkColor.h"
+#include "imstkTimer.h"
+#include "imstkAnimationModel.h"
+#include "imstkRenderParticles.h"
+
+namespace imstk
+{
+///
+/// \struct RenderParticleKeyFrame
+///
+/// \brief Keyframe for particle animation
+///
+struct RenderParticleKeyFrame
+{
+    float m_time = 0;
+    Color m_color = Color::White;
+    Vec3f m_acceleration = Vec3f(0, 0, 0);
+    float m_rotationalAcceleration = 0;
+    float m_scale = 1.0f;
+};
+
+///
+/// \class RenderParticleEmitter
+///
+/// \brief Animation method for rendering particles
+/// Common use cases include smoke and fire.
+///
+class RenderParticleEmitter : public AnimationModel
+{
+public:
+    ///
+    /// \brief Shape of emitter
+    ///
+    enum class Shape
+    {
+        CUBE
+    };
+
+    ///
+    /// \brief Mode of emitter
+    ///
+    enum class Mode
+    {
+        CONTINUOUS, ///< Emitter continuously releases/recycles particles
+        BURST       ///< Emitter releases particles once until manually reset
+    };
+
+    ///
+    /// \brief Constructor
+    ///
+    RenderParticleEmitter(std::shared_ptr<Geometry> geometry,
+                          const float time = 3000.0f,
+                          Mode mode = Mode::CONTINUOUS);
+
+    ///
+    /// \brief Set animation geometry
+    /// \param renderParticles particles for rendering
+    ///
+    virtual void setGeometry(std::shared_ptr<Geometry> renderParticles);
+
+    ///
+    /// \brief Get mode of emitter
+    /// \returns mode Mode of emitter
+    ///
+    RenderParticleEmitter::Mode getEmitterMode() const;
+
+    ///
+    /// \brief Set size of emitter
+    /// \param size Width of emitter
+    ///
+    void setEmitterSize(const float size);
+
+    ///
+    /// \brief Set velocity range
+    /// This functions sets minimum and maximum rotation values for determining
+    /// the initial trajectory of the particles. The values are randomly
+    /// selected (according to a uniform distribution) between the min and max
+    /// values. If the values are the same, then the particle direction will
+    /// not behave randomly.
+    /// \param minDirection Maximum initial angle of trajectory
+    /// \param maxDirection Minimum initial angle of trajectory
+    /// \param minSpeed Minimum initial speed
+    /// \param maxSpeed Maximum initial speed
+    /// \param minRotationSpeed Minimum initial rotation speed
+    /// \param maxRotationSpeed Maximum initial rotation speed
+    ///
+    void setInitialVelocityRange(const Vec3f minDirection,
+                                 const Vec3f maxDirection,
+                                 const float minSpeed,
+                                 const float maxSpeed,
+                                 const float minRotationSpeed,
+                                 const float maxRotationSpeed);
+
+    ///
+    /// \brief Add keyframe to particle emitter
+    /// \param keyFrame key frame to add
+    /// \returns True if key frame added, false if too many key frames
+    ///
+    bool addKeyFrame(RenderParticleKeyFrame keyFrame);
+
+    ///
+    /// \brief Get start and end frames
+    ///
+    RenderParticleKeyFrame * getStartKeyFrame();
+    RenderParticleKeyFrame * getEndKeyFrame();
+
+    ///
+    /// \brief Get key frames
+    /// \returns key frames that are unsorted
+    ///
+    std::vector<RenderParticleKeyFrame>& getKeyFrames();
+
+    ///
+    /// \brief Reset the emitter
+    /// Only works for burst particles
+    ///
+    virtual void reset();
+
+    ///
+    /// \brief Update
+    ///
+    virtual void update();
+
+protected:
+    friend class VulkanParticleRenderDelegate;
+
+    ///
+    /// \brief Initialize particles
+    ///
+    void initializeParticles();
+
+    ///
+    /// \brief Interpolate color
+    ///
+    void interpolateColor(Color& destination,
+                          const Color& sourceA,
+                          const Color& sourceB,
+                          const float alpha);
+
+    ///
+    /// \brief Emit particle
+    ///
+    void emitParticle(std::unique_ptr<RenderParticle>& particle);
+
+    ///
+    /// \brief Get uniformly-distributed float
+    /// \returns float in the range of [0, 1]
+    ///
+    float getRandomNormalizedFloat();
+
+    std::vector<RenderParticleKeyFrame> m_keyFrames; ///< Particle keyframes
+
+    RenderParticleEmitter::Mode m_mode
+        = RenderParticleEmitter::Mode::CONTINUOUS;
+    RenderParticleEmitter::Shape m_shape
+        = RenderParticleEmitter::Shape::CUBE;
+
+    Vec3f m_minDirection;
+    Vec3f m_maxDirection;
+    float m_minSpeed;
+    float m_maxSpeed;
+    float m_minRotationSpeed;
+    float m_maxRotationSpeed;
+
+    float m_time; ///< total time for particle system
+    float m_emitTime;
+
+    imstk::StopWatch m_stopWatch;
+
+    double m_lastUpdateTime = 0.0;
+    bool m_started = false;
+
+    float m_emitterSize = 1.0f;
+
+    const int c_maxNumKeyFrames = 16; ///< Maximum key frames
+
+    std::shared_ptr<Geometry> m_animationGeometry = nullptr;
+    std::vector<std::unique_ptr<RenderParticle>> * m_particles;
+};
+} // imstk
+
+#endif // ifndef imstkRenderParticleEmitter_h
diff --git a/Source/Geometry/Particles/imstkRenderParticles.cpp b/Source/Geometry/Particles/imstkRenderParticles.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1b3c4cede9a18ad718ff8dce0ee80090569cd238
--- /dev/null
+++ b/Source/Geometry/Particles/imstkRenderParticles.cpp
@@ -0,0 +1,93 @@
+/*=========================================================================
+
+   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 "imstkRenderParticles.h"
+
+namespace imstk
+{
+RenderParticles::RenderParticles(const unsigned int maxNumParticles /*=128*/)
+    : Geometry(Geometry::Type::RenderParticles)
+{
+    if (maxNumParticles <= 128)
+    {
+        m_maxNumParticles = maxNumParticles;
+    }
+    else
+    {
+        m_maxNumParticles = 128;
+        LOG(WARNING) << "The maximum number of decals is 128";
+    }
+
+    m_vertexPositions[0] = glm::vec3(0.5, 0.5, 0);
+    m_vertexPositions[1] = glm::vec3(0.5, -0.5, 0);
+    m_vertexPositions[2] = glm::vec3(-0.5, 0.5, 0);
+    m_vertexPositions[3] = glm::vec3(-0.5, -0.5, 0);
+
+    m_vertexNormals[0] = glm::vec3(0.0, 0.0, 1.0);
+    m_vertexNormals[1] = glm::vec3(0.0, 0.0, 1.0);
+    m_vertexNormals[2] = glm::vec3(0.0, 0.0, 1.0);
+    m_vertexNormals[3] = glm::vec3(0.0, 0.0, 1.0);
+
+    m_vertexUVs[0] = glm::vec2(1.0, 1.0);
+    m_vertexUVs[1] = glm::vec2(1.0, 0);
+    m_vertexUVs[2] = glm::vec2(0, 1.0);
+    m_vertexUVs[3] = glm::vec2(0, 0);
+
+    m_triangles[0] = glm::ivec3(1, 0, 3);
+    m_triangles[1] = glm::ivec3(0, 2, 3);
+}
+
+void
+RenderParticles::setParticleSize(const float size)
+{
+    m_particleSize = size;
+}
+
+std::vector<std::unique_ptr<RenderParticle>>&
+RenderParticles::getParticles()
+{
+    return m_particles;
+}
+
+void
+RenderParticles::reset()
+{
+    m_numParticles = 0;
+}
+
+void
+RenderParticles::incrementNumOfParticles()
+{
+    m_numParticles++;
+}
+
+unsigned int
+RenderParticles::getNumParticles()
+{
+    return m_numParticles;
+}
+
+unsigned int
+RenderParticles::getMaxNumParticles()
+{
+    return m_maxNumParticles;
+}
+}
\ No newline at end of file
diff --git a/Source/Geometry/Particles/imstkRenderParticles.h b/Source/Geometry/Particles/imstkRenderParticles.h
new file mode 100644
index 0000000000000000000000000000000000000000..4cc55443abacbb9148c855ec6f4890e2a58a854b
--- /dev/null
+++ b/Source/Geometry/Particles/imstkRenderParticles.h
@@ -0,0 +1,136 @@
+/*=========================================================================
+
+   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.
+
+=========================================================================*/
+
+#ifndef imstkRenderParticles_h
+#define imstkRenderParticles_h
+
+#include <vector>
+#include <climits>
+#include <memory>
+
+#include "glm/glm.hpp"
+
+#include "imstkGeometry.h"
+#include "imstkMath.h"
+#include "imstkColor.h"
+#include "imstkTimer.h"
+
+namespace imstk
+{
+///
+/// \struct RenderParticle
+///
+/// \brief Particle data
+///
+struct RenderParticle
+{
+    Vec3f m_position = Vec3f(0, 0, 0);
+    Vec3f m_velocity = Vec3f(0, 0, 0);
+    Vec3f m_acceleration = Vec3f(0, 0, 0);
+    Color m_color = Color::White;
+    float m_age = 0;
+    bool m_created = false;
+    float m_scale = 1.0f;
+    float m_rotation = 0;
+    float m_rotationalVelocity = 0;
+    float m_rotationalAcceleration = 0;
+};
+
+///
+/// \class RenderParticles
+///
+/// \brief Particles for rendering
+///
+class RenderParticles : public Geometry
+{
+public:
+
+    ///
+    /// \brief Constructor
+    /// \param maxNumParticles Number of particles this emitter can produce
+    /// \param time Lifespan of each particle (in milliseconds)
+    /// \param mode Mode for emitter
+    ///
+    RenderParticles(const unsigned int maxNumParticles = 128);
+
+    ///
+    /// \brief Set size of particle
+    /// \param size Particle size, this determines how much each keyframe
+    ///        scales by
+    ///
+    void setParticleSize(const float size);
+
+    ///
+    /// \brief Get particles
+    /// \returns particles
+    ///
+    std::vector<std::unique_ptr<RenderParticle>>& getParticles();
+
+    ///
+    /// \brief Reset number of particles
+    ///
+    void reset();
+
+    ///
+    /// \brief Increment number of particles
+    ///
+    void incrementNumOfParticles();
+
+    ///
+    /// \brief Get number of particles
+    ///
+    unsigned int getNumParticles();
+
+    ///
+    /// \brief Get maximum number of particles
+    ///
+    unsigned int getMaxNumParticles();
+
+    ///
+    /// \brief Get volume
+    /// As these are particles, the volume is 0
+    ///
+    double getVolume() const override { return 0; };
+
+protected:
+    friend class VulkanParticleRenderDelegate;
+    friend class RenderParticles;
+
+    unsigned int m_maxNumParticles = 128; ///< Maximum particles
+    float m_particleSize = 0.1f;
+
+    std::vector<std::unique_ptr<RenderParticle>> m_particles; ///< Particle objects
+    glm::vec3 m_vertexPositions[4];
+    glm::vec3 m_vertexNormals[4];
+    glm::vec3 m_vertexTangents[4];
+    glm::vec2 m_vertexUVs[4];
+    glm::ivec3 m_triangles[2];
+
+    unsigned int m_numParticles = 0;
+
+    void applyTranslation(const Vec3d t) override {};
+    void applyRotation(const Mat3d r) override {};
+    void applyScaling(const double s) override {};
+    virtual void updatePostTransformData() override {};
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/Geometry/imstkGeometry.h b/Source/Geometry/imstkGeometry.h
index 32f397f6c6da4bcb635bd37d9277ce000b01da78..978a7ca3b8995c69a99847e9cbafe7529ea0abf1 100644
--- a/Source/Geometry/imstkGeometry.h
+++ b/Source/Geometry/imstkGeometry.h
@@ -54,7 +54,7 @@ public:
         Capsule,
         Decal,
         DecalPool,
-        RenderParticleEmitter
+        RenderParticles
     };
 
     ///
diff --git a/Source/Rendering/CMakeLists.txt b/Source/Rendering/CMakeLists.txt
index b45833cb13cc38039c5ccc72692cc3bd7afb3dda..afb083e2e9d3628e7ee4a6ea033edb69ee6099c6 100644
--- a/Source/Rendering/CMakeLists.txt
+++ b/Source/Rendering/CMakeLists.txt
@@ -123,6 +123,7 @@ imstk_add_library( Rendering
     ${VTK_LIBRARIES}
     ${RENDERING_DEPENDENCIES}
     Scene
+    Animation
     glm
   #VERBOSE
   )
diff --git a/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanParticleRenderDelegate.cpp b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanParticleRenderDelegate.cpp
index 2332acb712d09774ed53572425026c73f928afaa..0ed4c5e8739ad05df2089bea70260022119679b2 100644
--- a/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanParticleRenderDelegate.cpp
+++ b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanParticleRenderDelegate.cpp
@@ -29,7 +29,7 @@ VulkanParticleRenderDelegate::VulkanParticleRenderDelegate(std::shared_ptr<Visua
 {
     this->initialize(visualModel);
 
-    auto geometry = std::static_pointer_cast<RenderParticleEmitter>(visualModel->getGeometry());
+    auto geometry = std::static_pointer_cast<RenderParticles>(visualModel->getGeometry());
 
     m_numVertices = 4;
     m_numTriangles = 2;
@@ -49,7 +49,7 @@ void
 VulkanParticleRenderDelegate::updateVertexBuffer()
 {
     auto vertices = (VulkanBasicVertex *)m_vertexBuffer->getVertexMemory();
-    auto geometry = std::static_pointer_cast<RenderParticleEmitter>(m_visualModel->getGeometry());
+    auto geometry = std::static_pointer_cast<RenderParticles>(m_visualModel->getGeometry());
 
     for (unsigned i = 0; i < m_numVertices; i++)
     {
@@ -87,9 +87,7 @@ VulkanParticleRenderDelegate::update(const uint32_t frameIndex, std::shared_ptr<
 {
     unsigned int index = 0;
 
-    auto geometry = std::static_pointer_cast<RenderParticleEmitter>(m_visualModel->getGeometry());
-
-    geometry->updateParticleEmitter(camera->getPosition());
+    auto particles = std::static_pointer_cast<RenderParticles>(m_visualModel->getGeometry());
 
     auto mat = this->getVisualModel()->getRenderMaterial();
     auto cameraPosition = glm::vec3(camera->getPosition()[0],
@@ -101,9 +99,9 @@ VulkanParticleRenderDelegate::update(const uint32_t frameIndex, std::shared_ptr<
 
     auto matColor = mat->getColor();
 
-    this->sortParticles(geometry->getParticles(), geometry->getNumParticles(), cameraPosition);
+    this->sortParticles(particles->getParticles(), particles->getNumParticles(), cameraPosition);
 
-    for (unsigned int i = 0; i < geometry->getNumParticles(); i++)
+    for (unsigned int i = 0; i < particles->getNumParticles(); i++)
     {
         auto particlePosition = glm::vec3(m_particles[i]->m_position[0],
             m_particles[i]->m_position[1],
@@ -117,7 +115,7 @@ VulkanParticleRenderDelegate::update(const uint32_t frameIndex, std::shared_ptr<
         transformation = transformation * billboardTransformation;
         transformation = glm::rotate(transformation, m_particles[i]->m_rotation, glm::vec3(0, 0, 1.0f));
         transformation = glm::scale(transformation,
-            glm::vec3(geometry->m_particleSize) * m_particles[i]->m_scale);
+            glm::vec3(particles->m_particleSize) * m_particles[i]->m_scale);
         m_particleVertexUniforms.transform[i] = transformation;
 
         m_particleFragmentUniforms.receivesShadows[i] = mat->getReceivesShadows() ? 1 : 0;
diff --git a/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanParticleRenderDelegate.h b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanParticleRenderDelegate.h
index 13710c300e1c9e0d76fadfa73e87c885ff6444e8..ed5e7b30b717ce2e0fcd26eb95c1f4b3d714791b 100644
--- a/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanParticleRenderDelegate.h
+++ b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanParticleRenderDelegate.h
@@ -28,6 +28,11 @@
 
 namespace imstk
 {
+///
+/// \class VulkanParticleRenderDelegate
+///
+/// \brief Billboard render delegate for RenderParticles
+///
 class VulkanParticleRenderDelegate : public VulkanRenderDelegate {
 public:
 
diff --git a/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanRenderDelegate.cpp b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanRenderDelegate.cpp
index 8ecbd70e1f9dd6ba73e40c60b346e308b4796b1a..9c8795eb2b267f3eb2a45f39d19dbd9337c685d0 100644
--- a/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanRenderDelegate.cpp
+++ b/Source/Rendering/VulkanRenderer/RenderDelegate/imstkVulkanRenderDelegate.cpp
@@ -101,7 +101,7 @@ VulkanRenderDelegate::make_delegate(std::shared_ptr<VisualModel> visualModel,
     {
         return std::make_shared<VulkanDecalRenderDelegate>(visualModel, type, memoryManager);
     }
-    case Geometry::Type::RenderParticleEmitter:
+    case Geometry::Type::RenderParticles:
     {
         return std::make_shared<VulkanParticleRenderDelegate>(visualModel, type, memoryManager);
     }
diff --git a/Source/Rendering/VulkanRenderer/imstkVulkanRenderer.cpp b/Source/Rendering/VulkanRenderer/imstkVulkanRenderer.cpp
index 1901cbb3dea2ee5227f18213f2990543fe14e8ba..6dc7e90662fba3f13b09a1fabcad5448b7f690f2 100644
--- a/Source/Rendering/VulkanRenderer/imstkVulkanRenderer.cpp
+++ b/Source/Rendering/VulkanRenderer/imstkVulkanRenderer.cpp
@@ -659,6 +659,15 @@ VulkanRenderer::renderFrame()
 
     this->loadAllVisualModels();
 
+    for (auto sceneObject : m_scene->getSceneObjects())
+    {
+        if (sceneObject->getType() == SceneObject::Type::Animation)
+        {
+            auto animatedObject = std::static_pointer_cast<AnimationObject>(sceneObject);
+            animatedObject->getAnimationModel()->update();
+        }
+    }
+
     // Update global uniforms
     this->updateGlobalUniforms(nextImageIndex);
 
@@ -670,7 +679,7 @@ VulkanRenderer::renderFrame()
             auto decalPool = std::dynamic_pointer_cast<VulkanDecalRenderDelegate>(m_renderDelegates[renderDelegateIndex]);
             decalPool->update(nextImageIndex, m_scene->getCamera());
         }
-        else if (m_renderDelegates[renderDelegateIndex]->getVisualModel()->getGeometry()->getType() == Geometry::Type::RenderParticleEmitter)
+        else if (m_renderDelegates[renderDelegateIndex]->getVisualModel()->getGeometry()->getType() == Geometry::Type::RenderParticles)
         {
             auto particleEmitter = std::dynamic_pointer_cast<VulkanParticleRenderDelegate>(m_renderDelegates[renderDelegateIndex]);
             particleEmitter->update(nextImageIndex, m_scene->getCamera());
@@ -746,7 +755,7 @@ VulkanRenderer::renderFrame()
             auto material = m_renderDelegates[renderDelegateIndex]->m_shadowMaterial;
 
             if (m_renderDelegates[renderDelegateIndex]->getVisualModel()->getGeometry()->getType() == Geometry::Type::DecalPool
-                || m_renderDelegates[renderDelegateIndex]->getVisualModel()->getGeometry()->getType() == Geometry::Type::RenderParticleEmitter
+                || m_renderDelegates[renderDelegateIndex]->getVisualModel()->getGeometry()->getType() == Geometry::Type::RenderParticles
                 || !m_renderDelegates[renderDelegateIndex]->getVisualModel()->getRenderMaterial()->getCastsShadows()
                 || !m_renderDelegates[renderDelegateIndex]->getVisualModel()->isVisible())
             {
@@ -787,7 +796,7 @@ VulkanRenderer::renderFrame()
     for (unsigned int renderDelegateIndex = 0; renderDelegateIndex < m_renderDelegates.size(); renderDelegateIndex++)
     {
         if (m_renderDelegates[renderDelegateIndex]->getVisualModel()->getGeometry()->getType() == Geometry::Type::DecalPool
-            || m_renderDelegates[renderDelegateIndex]->getVisualModel()->getGeometry()->getType() == Geometry::Type::RenderParticleEmitter
+            || m_renderDelegates[renderDelegateIndex]->getVisualModel()->getGeometry()->getType() == Geometry::Type::RenderParticles
             || !m_renderDelegates[renderDelegateIndex]->getVisualModel()->isVisible())
         {
             continue;
@@ -866,7 +875,7 @@ VulkanRenderer::renderFrame()
     for (unsigned int renderDelegateIndex = 0; renderDelegateIndex < m_renderDelegates.size(); renderDelegateIndex++)
     {
         if (m_renderDelegates[renderDelegateIndex]->getVisualModel()->getGeometry()->getType() == Geometry::Type::DecalPool
-            || m_renderDelegates[renderDelegateIndex]->getVisualModel()->getGeometry()->getType() == Geometry::Type::RenderParticleEmitter
+            || m_renderDelegates[renderDelegateIndex]->getVisualModel()->getGeometry()->getType() == Geometry::Type::RenderParticles
             || !m_renderDelegates[renderDelegateIndex]->getVisualModel()->isVisible())
         {
             continue;
@@ -938,13 +947,13 @@ VulkanRenderer::renderFrame()
 
     for (unsigned int renderDelegateIndex = 0; renderDelegateIndex < m_renderDelegates.size(); renderDelegateIndex++)
     {
-        if (m_renderDelegates[renderDelegateIndex]->getVisualModel()->getGeometry()->getType() != Geometry::Type::RenderParticleEmitter
+        if (m_renderDelegates[renderDelegateIndex]->getVisualModel()->getGeometry()->getType() != Geometry::Type::RenderParticles
             || !m_renderDelegates[renderDelegateIndex]->getVisualModel()->isVisible())
         {
             continue;
         }
 
-        auto geometry = std::dynamic_pointer_cast<RenderParticleEmitter>(m_renderDelegates[renderDelegateIndex]->getVisualModel()->getGeometry());
+        auto geometry = std::dynamic_pointer_cast<RenderParticles>(m_renderDelegates[renderDelegateIndex]->getVisualModel()->getGeometry());
         auto material = m_renderDelegates[renderDelegateIndex]->m_material;
         vkCmdBindPipeline(m_renderCommandBuffer[nextImageIndex], VK_PIPELINE_BIND_POINT_GRAPHICS, material->m_pipeline);
         this->setCommandBufferState(&m_renderCommandBuffer[nextImageIndex], m_width, m_height);
diff --git a/Source/Rendering/VulkanRenderer/imstkVulkanRenderer.h b/Source/Rendering/VulkanRenderer/imstkVulkanRenderer.h
index 15e2c2d0869adbad2df7b64d2846735de80edc54..b9af56bbac8780bc1f3e6baa7e483bd56d9c3b33 100644
--- a/Source/Rendering/VulkanRenderer/imstkVulkanRenderer.h
+++ b/Source/Rendering/VulkanRenderer/imstkVulkanRenderer.h
@@ -41,6 +41,7 @@
 #include "imstkDecalPool.h"
 #include "imstkRenderParticleEmitter.h"
 #include "imstkTextureManager.h"
+#include "imstkAnimationObject.h"
 
 #include "imstkVulkanValidation.h"
 #include "imstkVulkanVertexBuffer.h"
diff --git a/Source/SceneElements/Objects/imstkAnimationModel.cpp b/Source/SceneElements/Objects/imstkAnimationModel.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2b61db821ed7fac8c5c447f609daf14ce3125905
--- /dev/null
+++ b/Source/SceneElements/Objects/imstkAnimationModel.cpp
@@ -0,0 +1,42 @@
+/*=========================================================================
+
+   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 "imstkAnimationModel.h"
+
+namespace imstk
+{
+AnimationModel::AnimationModel(std::shared_ptr<Geometry> geometry)
+{
+    m_geometry = geometry;
+}
+
+std::shared_ptr<Geometry>
+AnimationModel::getGeometry()
+{
+    return m_geometry;
+}
+
+void
+AnimationModel::setGeometry(std::shared_ptr<Geometry> geometry)
+{
+    m_geometry = geometry;
+}
+}
\ No newline at end of file
diff --git a/Source/SceneElements/Objects/imstkAnimationModel.h b/Source/SceneElements/Objects/imstkAnimationModel.h
new file mode 100644
index 0000000000000000000000000000000000000000..188f4ea4fa3b0474c41817c78e7654dc7cbfbedc
--- /dev/null
+++ b/Source/SceneElements/Objects/imstkAnimationModel.h
@@ -0,0 +1,70 @@
+/*=========================================================================
+
+   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.
+
+=========================================================================*/
+
+#ifndef imstkAnimationModel_h
+#define imstkAnimationModel_h
+
+#include <memory>
+
+#include "imstkGeometry.h"
+
+namespace imstk
+{
+///
+/// \class AnimationModel
+///
+/// \brief Contains geometric and animation render information
+///
+class AnimationModel
+{
+public:
+    ///
+    /// \brief Constructor
+    ///
+    AnimationModel(std::shared_ptr<Geometry> geometry);
+
+    AnimationModel() = delete;
+
+    ///
+    /// \brief Get/set geometry
+    ///
+    std::shared_ptr<Geometry> getGeometry();
+    virtual void setGeometry(std::shared_ptr<Geometry> geometry);
+
+    ///
+    /// \brief Update animation
+    ///
+    virtual void update() {};
+
+    ///
+    /// \brief Reset animation
+    ///
+    virtual void reset() {};
+
+protected:
+    friend class VulkanRenderer;
+    friend class VTKRenderer;
+
+    std::shared_ptr<Geometry> m_geometry = nullptr;
+};
+} // imstk
+
+#endif // ifndef imstkAnimationModel_h
\ No newline at end of file
diff --git a/Source/SceneElements/Objects/imstkAnimationObject.cpp b/Source/SceneElements/Objects/imstkAnimationObject.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a6c408129c89a7cdc7b3d6873bb33d7ab4564e72
--- /dev/null
+++ b/Source/SceneElements/Objects/imstkAnimationObject.cpp
@@ -0,0 +1,37 @@
+/*=========================================================================
+
+   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 "imstkAnimationObject.h"
+
+namespace imstk
+{
+std::shared_ptr<AnimationModel>
+AnimationObject::getAnimationModel() const
+{
+    return m_animationModel;
+}
+
+void
+AnimationObject::setAnimationModel(std::shared_ptr<AnimationModel> model)
+{
+    m_animationModel = model;
+}
+} // imstk
diff --git a/Source/SceneElements/Objects/imstkAnimationObject.h b/Source/SceneElements/Objects/imstkAnimationObject.h
new file mode 100644
index 0000000000000000000000000000000000000000..ee77ccaddab0f6a3227de4fa6950e972daa69467
--- /dev/null
+++ b/Source/SceneElements/Objects/imstkAnimationObject.h
@@ -0,0 +1,86 @@
+/*=========================================================================
+
+   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.
+
+=========================================================================*/
+
+#ifndef imstkAnimationObject_h
+#define imstkAnimationObject_h
+
+#include <memory>
+
+#include "imstkSceneObject.h"
+#include "imstkGeometryMap.h"
+#include "imstkAnimationModel.h"
+#include "imstkMath.h"
+
+namespace imstk
+{
+class Geometry;
+
+class AnimationObject : public SceneObject
+{
+public:
+    ///
+    /// \brief Constructor
+    ///
+    AnimationObject(const std::string& name) : SceneObject(name)
+    {
+        m_type = Type::Animation;
+    }
+
+    ///
+    /// \brief Constructor
+    ///
+    AnimationObject(std::string&& name) : SceneObject(std::move(name))
+    {
+        m_type = Type::Animation;
+    }
+
+    ///
+    /// \brief Default destructor
+    ///
+    virtual ~AnimationObject() = default;
+
+    ///
+    /// \brief Set/get animation model
+    ///
+    std::shared_ptr<AnimationModel> getAnimationModel() const;
+    void setAnimationModel(std::shared_ptr<AnimationModel> model);
+
+    ///
+    /// \brief Initialize the scene object
+    ///
+    virtual bool initialize()
+    {
+        if (SceneObject::initialize())
+        {
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+
+protected:
+    std::shared_ptr<AnimationModel> m_animationModel;
+};
+} // imstk
+
+#endif // ifndef imstkAnimationObject_h
diff --git a/Source/SceneElements/Objects/imstkSceneObject.h b/Source/SceneElements/Objects/imstkSceneObject.h
index 0456eb2f0089412c55b60f48a7c4333e60a1337b..35532d248b318d03000eb030e94345ef217effc3 100644
--- a/Source/SceneElements/Objects/imstkSceneObject.h
+++ b/Source/SceneElements/Objects/imstkSceneObject.h
@@ -44,6 +44,7 @@ public:
     enum class Type
     {
         Visual,
+        Animation,
         Colliding,
         Rigid,
         FEMDeformable,