diff --git a/Blarg.GameFramework/Blarg.GameFramework.csproj b/Blarg.GameFramework/Blarg.GameFramework.csproj
index cc0f5e3..c320851 100644
--- a/Blarg.GameFramework/Blarg.GameFramework.csproj
+++ b/Blarg.GameFramework/Blarg.GameFramework.csproj
@@ -184,6 +184,8 @@
+
+
diff --git a/Blarg.GameFramework/Graphics/Atlas/TextureAtlasAnimator.cs b/Blarg.GameFramework/Graphics/Atlas/TextureAtlasAnimator.cs
new file mode 100644
index 0000000..d18017f
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/Atlas/TextureAtlasAnimator.cs
@@ -0,0 +1,183 @@
+using System;
+using System.Collections.Generic;
+using Blarg.GameFramework.Content;
+using Blarg.GameFramework.Support;
+
+namespace Blarg.GameFramework.Graphics.Atlas
+{
+ public class TextureAtlasAnimator : IDisposable
+ {
+ Dictionary _animations;
+ ContentManager _contentManager;
+
+ public TextureAtlasAnimator()
+ {
+ _contentManager = Framework.Services.Get();
+ if (_contentManager == null)
+ throw new InvalidOperationException("Could not find ContentManager object.");
+
+ _animations = new Dictionary();
+ }
+
+ public void AddSequence(string name, TextureAtlas atlas, int tileToBeAnimated, int start, int stop, float delay, bool loop)
+ {
+ if (String.IsNullOrEmpty(name))
+ throw new ArgumentException("name");
+ if (atlas == null)
+ throw new ArgumentNullException("atlas");
+ if (start >= atlas.NumTiles)
+ throw new InvalidOperationException();
+ if (stop >= atlas.NumTiles)
+ throw new InvalidOperationException();
+ if (start >= stop)
+ throw new InvalidOperationException();
+ if (tileToBeAnimated >= start && tileToBeAnimated <= stop)
+ throw new InvalidOperationException();
+
+ if (_animations.ContainsKey(name))
+ return;
+
+ var sequence = new TextureAtlasTileAnimation();
+ sequence.Atlas = atlas;
+ sequence.AnimatingIndex = tileToBeAnimated;
+ sequence.Start = start;
+ sequence.Stop = stop;
+ sequence.Delay = delay;
+ sequence.IsAnimating = true;
+ sequence.Loop = loop;
+ sequence.Frames = new Image[sequence.NumFrames];
+ sequence.Name = name;
+
+ sequence.Current = sequence.Start;
+ sequence.CurrentFrameTime = 0.0f;
+
+ // since we can't read a texture back from OpenGL after we've uploaded it
+ // (??? or can we somehow .. ?), we need to load the image again so that
+ // we can copy out the image data for tiles "start" to "stop"
+ string textureFilename = _contentManager.GetNameOf(atlas.Texture);
+ if (String.IsNullOrEmpty(textureFilename))
+ throw new InvalidOperationException("Texture atlas is using a texture not loaded via ContentManager. Cannot automatically obtain backing Image asset.");
+ var textureImage = _contentManager.Get(textureFilename);
+ if (textureImage == null)
+ throw new InvalidOperationException("Could not obtain backing Image asset for this texture atlas.");
+
+ // first, copy the original tile image that's at the "tileToBeAnimated" spot
+ // in the atlas texture so we can restore it back again if necessary
+ var originalTile = atlas.GetTile(sequence.AnimatingIndex);
+ sequence.OriginalAnimatingTile = new Image(textureImage, originalTile.Dimensions);
+
+ // copy each frame ("start" to "stop") from the source texture image
+ for (int i = 0; i < sequence.NumFrames; ++i)
+ {
+ var tile = atlas.GetTile(i + sequence.Start);
+ sequence.Frames[i] = new Image(textureImage, tile.Dimensions);
+ }
+
+ _animations.Add(name, sequence);
+ }
+
+ public void ResetSequence(string name)
+ {
+ if (String.IsNullOrEmpty(name))
+ throw new ArgumentException("name");
+
+ var sequence = _animations.Get(name);
+ if (sequence == null)
+ throw new InvalidOperationException("Sequence not found.");
+
+ sequence.IsAnimating = true;
+ sequence.Current = sequence.Start;
+ sequence.CurrentFrameTime = 0.0f;
+
+ UpdateTextureWithCurrentTileFrame(sequence);
+ }
+
+ public void StopSequence(string name, bool restoreOriginalTile = false)
+ {
+ if (String.IsNullOrEmpty(name))
+ throw new ArgumentException("name");
+
+ var sequence = _animations.Get(name);
+ if (sequence == null)
+ throw new InvalidOperationException("Sequence not found.");
+
+ sequence.IsAnimating = false;
+ sequence.Current = sequence.Stop;
+ sequence.CurrentFrameTime = 0.0f;
+
+ if (restoreOriginalTile)
+ RestoreTextureWithOriginalTile(sequence);
+ else
+ UpdateTextureWithCurrentTileFrame(sequence);
+ }
+
+ public void ToggleSequence(string name, bool enable)
+ {
+ if (String.IsNullOrEmpty(name))
+ throw new ArgumentException("name");
+
+ var sequence = _animations.Get(name);
+ if (sequence == null)
+ throw new InvalidOperationException("Sequence not found.");
+
+ sequence.IsAnimating = enable;
+ sequence.CurrentFrameTime = 0.0f;
+
+ UpdateTextureWithCurrentTileFrame(sequence);
+ }
+
+ public void OnUpdate(float delta)
+ {
+ foreach (var i in _animations)
+ {
+ var sequence = i.Value;
+
+ if (!sequence.IsAnimationFinished && sequence.IsAnimating)
+ {
+ sequence.CurrentFrameTime += delta;
+
+ if (sequence.CurrentFrameTime >= sequence.Delay)
+ {
+ // move to the next frame
+ sequence.CurrentFrameTime = 0.0f;
+
+ ++sequence.Current;
+ if (sequence.Current > sequence.Stop)
+ sequence.Current = sequence.Start;
+
+ UpdateTextureWithCurrentTileFrame(sequence);
+ }
+ }
+ }
+ }
+
+ public void OnNewContext()
+ {
+ foreach (var i in _animations)
+ UpdateTextureWithCurrentTileFrame(i.Value);
+ }
+
+ private void UpdateTextureWithCurrentTileFrame(TextureAtlasTileAnimation sequence)
+ {
+ int frameIndex = sequence.Current - sequence.Start;
+ var frameImage = sequence.Frames[frameIndex];
+ var tile = sequence.Atlas.GetTile(sequence.AnimatingIndex);
+
+ sequence.Atlas.Texture.Update(frameImage, tile.Dimensions.Left, tile.Dimensions.Top);
+ }
+
+ private void RestoreTextureWithOriginalTile(TextureAtlasTileAnimation sequence)
+ {
+ var tile = sequence.Atlas.GetTile(sequence.AnimatingIndex);
+ sequence.Atlas.Texture.Update(sequence.OriginalAnimatingTile, tile.Dimensions.Left, tile.Dimensions.Top);
+ }
+
+ public void Dispose()
+ {
+ foreach (var i in _animations)
+ RestoreTextureWithOriginalTile(i.Value);
+ _animations.Clear();
+ }
+ }
+}
+
diff --git a/Blarg.GameFramework/Graphics/Atlas/TextureAtlasTileAnimation.cs b/Blarg.GameFramework/Graphics/Atlas/TextureAtlasTileAnimation.cs
new file mode 100644
index 0000000..ad78285
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/Atlas/TextureAtlasTileAnimation.cs
@@ -0,0 +1,31 @@
+using System;
+
+namespace Blarg.GameFramework.Graphics.Atlas
+{
+ internal class TextureAtlasTileAnimation
+ {
+ public TextureAtlas Atlas;
+ public int AnimatingIndex;
+ public int Start;
+ public int Stop;
+ public int Current;
+ public float Delay;
+ public float CurrentFrameTime;
+ public bool IsAnimating;
+ public bool Loop;
+ public Image OriginalAnimatingTile;
+ public Image[] Frames = null;
+ public string Name;
+
+ public int NumFrames
+ {
+ get { return Stop - Start + 1; }
+ }
+
+ public bool IsAnimationFinished
+ {
+ get { return (IsAnimating && !Loop && Current == Stop); }
+ }
+ }
+}
+