Now with an actual bouncing yoda.
Now with an actual bouncing yoda.

--- a/sandbox/sandbox/DrawingFunctions.cs
+++ b/sandbox/sandbox/DrawingFunctions.cs
@@ -17,12 +17,12 @@
 
 			GL.Color4(1f, 1f, 1f, 1f);
 			GL.TexCoord2(0, 0); GL.Vertex3(x, y, z);
-			GL.TexCoord2(1, 1); GL.Vertex3(x + tex.Width, y + tex.Height, z);
-			GL.TexCoord2(0, 1); GL.Vertex3(x, y + tex.Height, z);
+			GL.TexCoord2(1, 1); GL.Vertex3(x + width, y + height, z);
+			GL.TexCoord2(0, 1); GL.Vertex3(x, y + height, z);
 
 			GL.TexCoord2(0, 0); GL.Vertex3(x, y, z);
-			GL.TexCoord2(1, 0); GL.Vertex3(x + tex.Width, y, z);
-			GL.TexCoord2(1, 1); GL.Vertex3(x + tex.Width, y + tex.Height, z);
+			GL.TexCoord2(1, 0); GL.Vertex3(x + width, y, z);
+			GL.TexCoord2(1, 1); GL.Vertex3(x + width, y + height, z);
 
 			GL.End();
 		}
@@ -35,12 +35,12 @@
 
 			GL.Color4(1f, 1f, 1f, 1f);
 			GL.TexCoord2(0, 0); GL.Vertex3(x, y, z);
-			GL.TexCoord2(1, 1); GL.Vertex3(x + tex.Width, y + tex.Height, z);
-			GL.TexCoord2(0, 1); GL.Vertex3(x, y + tex.Height, z);
+			GL.TexCoord2(1, 1); GL.Vertex3(x + width, y + height, z);
+			GL.TexCoord2(0, 1); GL.Vertex3(x, y + height, z);
 
 			GL.TexCoord2(0, 0); GL.Vertex3(x, y, z);
-			GL.TexCoord2(1, 0); GL.Vertex3(x + tex.Width, y, z);
-			GL.TexCoord2(1, 1); GL.Vertex3(x + tex.Width, y + tex.Height, z);
+			GL.TexCoord2(1, 0); GL.Vertex3(x + width, y, z);
+			GL.TexCoord2(1, 1); GL.Vertex3(x + width, y + height, z);
 
 			GL.End();
 		}
@@ -65,8 +65,8 @@
 
 		public static void DrawTexturedQuadCenteredAtPoint(Texture2D tex, float x, float y, float z, float width, float height)
 		{
-			float fx = x - (float)tex.Width / 2f;
-			float fy = y - (float)tex.Height / 2f;
+			float fx = x - (float)width / 2f;
+			float fy = y - (float)height / 2f;
 
 			DrawTexturedQuadAtPoint(tex, fx, fy, z, width, height);
 		}

--- a/sandbox/sandbox/Game.cs
+++ b/sandbox/sandbox/Game.cs
@@ -61,7 +61,7 @@
 
 		public Game(int width, int height)
 		{
-			this.Window = new GameWindow(width, height, new GraphicsMode(32, 8, 0, 0));
+			this.Window = new GameWindow(width, height, new GraphicsMode(32, 8, 0, 0), "Operation: Bouncing Yoda");
 
 			this.Window.Load += this.OnWindowLoad;
 			this.Window.RenderFrame += this.OnRenderFrame;

--- /dev/null
+++ b/sandbox/sandbox/Types/Entity.cs
@@ -1,1 +1,42 @@
+using System;
+using System.Collections.Generic;
+using OpenTK;
+using OpenTK.Graphics;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Input;
 
+namespace sandbox
+{
+	/// <summary>
+	/// An entity is a collidable game object.  For now, we're going to keep this small.
+	/// </summary>
+	public class Entity
+	{
+		public UInt64 id;
+		public Texture2D texture;
+		public Rect extents;
+		public Vector2 velocity;
+		public float speed;
+		public float mass;
+		public bool collides;
+
+		public void Init()
+		{
+			// 0xffffffffffffffff is code for "we're full".
+			this.id = 0xffffffffffffffff;
+			this.texture = null;
+			this.extents = Rect.Zero;
+			this.velocity = Vector2.Zero;
+			this.speed = 0f;
+			this.mass = 0f;
+			this.collides = true;
+		}
+
+		public Entity()
+		{
+			this.Init();
+		}
+	}
+}
+
+

--- /dev/null
+++ b/sandbox/sandbox/Types/Rect.cs
@@ -1,1 +1,88 @@
+using System;
+using OpenTK;
 
+namespace sandbox
+{
+	public struct Rect
+	{
+		public static readonly Rect Zero = new Rect(Vector2.Zero, Vector2.Zero);
+
+		public Vector2 position;
+		public Vector2 size;
+
+		public float top
+		{
+			get
+			{
+				return position.Y;
+			}
+			set
+			{
+				position.Y = value;
+			}
+		}
+
+		public float right
+		{
+			get
+			{
+				return position.X + size.X;
+			}
+			set
+			{
+				position.X = value - size.X;
+			}
+		}
+
+		public float bottom
+		{
+			get
+			{
+				return position.Y + size.Y;
+			}
+			set
+			{
+				position.Y = value - size.Y;
+			}
+		}
+
+		public float left
+		{
+			get
+			{
+				return position.X;
+			}
+			set
+			{
+				position.X = value;
+			}
+		}
+
+		public Vector2 center
+		{
+			get
+			{
+				return new Vector2(position.X + size.X / 2f, position.Y + size.Y / 2f);
+			}
+			set
+			{
+				position.X = value.X - size.X / 2f;
+				position.Y = value.Y - size.Y / 2f;
+			}
+		}
+
+		public Rect(Vector2 pos, Vector2 siz)
+		{
+			this.position = pos;
+			this.size = siz;
+		}
+
+		public Rect(Rect other)
+		{
+			this.position = other.position;
+			this.size = other.size;
+		}
+	}
+}
+
+

--- a/sandbox/sandbox/Yoda.cs
+++ b/sandbox/sandbox/Yoda.cs
@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using OpenTK;
 using OpenTK.Graphics;
 using OpenTK.Graphics.OpenGL;
@@ -8,11 +9,110 @@
 {
 	public class Yoda : IGameModule
 	{
+		static Stack<Entity> entityPool = new Stack<Entity>();
+
+		static Entity GetEntity()
+		{
+			if (entityPool.Count > 0)
+			{
+				return entityPool.Pop();
+			}
+			else
+			{
+				return new Entity();
+			}
+		}
+
+		static void PutEntity(Entity e)
+		{
+			e.Init();
+			entityPool.Push(e);
+		}
+
+		static bool RectCollidesOther(Rect rect, Rect other)
+		{
+			return (other.top >= rect.top && other.top <= rect.bottom) ||
+			(other.bottom >= rect.top && other.bottom <= rect.bottom) ||
+			(other.left >= rect.left && other.left <= rect.left) ||
+			(other.right >= rect.left && other.right <= rect.left);
+		}
+
+		static bool RectWillCollideOther(Rect rect, Rect other, Vector2 velRect, Vector2 velOther)
+		{
+			rect.position += velRect;
+
+			other.position += velOther;
+
+			return RectCollidesOther(rect, other);
+		}
+
+		static bool RectCollidesTopEdge(Rect rect)
+		{
+			return rect.top <= 0;
+		}
+
+		static bool RectCollidesRightEdge(Rect rect)
+		{
+			return rect.right >= Game.ScreenWidth;
+		}
+
+		static bool RectCollidesBottomEdge(Rect rect)
+		{
+			return rect.bottom >= Game.ScreenHeight;
+		}
+
+		static bool RectCollidesLeftEdge(Rect rect)
+		{
+			return rect.left <= 0;
+		}
+
+		static bool RectWillCollideTopEdge(Rect rect, Vector2 vel)
+		{
+			return rect.top + vel.Y <= 0;
+		}
+
+		static bool RectWillCollideRightEdge(Rect rect, Vector2 vel)
+		{
+			return rect.right + vel.X >= Game.ScreenWidth;
+		}
+
+		static bool RectWillCollideBottomEdge(Rect rect, Vector2 vel)
+		{
+			return rect.bottom + vel.Y >= Game.ScreenHeight;
+		}
+
+		static bool RectWillCollideLeftEdge(Rect rect, Vector2 vel)
+		{
+			return rect.left + vel.X <= 0;
+		}
+
+		static bool RectWillCollideYEdge(Rect rect, Vector2 vel)
+		{
+			return rect.top + vel.Y <= 0 || rect.bottom + vel.Y >= Game.ScreenHeight;
+		}
+
+		static bool RectWillCollideXEdge(Rect rect, Vector2 vel)
+		{
+			return rect.left + vel.X <= 0 || rect.right + vel.X >= Game.ScreenWidth;
+		}
+
+		static void ApplyForceToEntity(Entity e, Vector2 force)
+		{
+			if (e.mass != 0)
+			{
+				Vector2 deltaV = force / e.mass;
+				e.velocity += deltaV;
+			}
+			else
+			{
+				e.velocity = (e.velocity.Normalized() + force).Normalized() * e.speed;
+			}
+		}
+
 		private Texture2D yodaTexture;
 
-		private Vector2 yodaPos;
-		private float yodaSpd;
-		private Vector2 yodaVel;
+		Entity yoda;
+		Entity ball;
 
 		private InputSubscription MoveUp;
 		private InputSubscription MoveRight;
@@ -28,12 +128,24 @@
 
 		public void LoadModule()
 		{
-			yodaPos = Vector2.Zero;
-
-			yodaTexture = Loaders.LoadTexture("data/Yoda-276x276.jpg");
-			yodaPos = new Vector2(Game.ScreenWidth / 2f, Game.ScreenHeight / 2f);
-			yodaSpd = 5;
-			yodaVel = Vector2.Zero;
+			yoda = GetEntity();
+
+			yoda.texture = Loaders.LoadTexture("data/Yoda-276x276.jpg");
+
+			yoda.extents.size = new Vector2((float)yoda.texture.Width, (float)yoda.texture.Height);
+			yoda.extents.center = new Vector2(Game.ScreenWidth / 2f, Game.ScreenHeight / 2f);
+
+			yoda.speed = 5;
+
+			ball = GetEntity();
+
+			ball.texture = yoda.texture;
+
+			ball.extents.size = new Vector2(16f, 16f);
+
+			ball.speed = 10;
+			ball.velocity = new Vector2(1, 1) * ball.speed;
+			ball.mass = 1;
 
 			this.MoveUp = new InputSubscription(
 				"Move Up",
@@ -106,7 +218,13 @@
 
 		public void Dispose()
 		{
-			GL.DeleteTexture(yodaTexture.ID);
+			foreach (Entity e in entityPool)
+			{
+				if (GL.IsTexture(e.texture.ID))
+				{
+					GL.DeleteTexture(e.texture.ID);
+				}
+			}
 		}
 
 		private void UpdateCtrlVector()
@@ -126,33 +244,69 @@
 
 			if (yodaCtrlVector.LengthSquared != 0)
 			{
-				yodaVel = yodaCtrlVector.Normalized() * yodaSpd;
+				yoda.velocity = yodaCtrlVector.Normalized() * yoda.speed;
 			}
 			else
 			{
-				yodaVel = Vector2.Zero;
+				yoda.velocity = Vector2.Zero;
 			}
 		}
 
 		private void OnUpdate(object sender, FrameEventArgs e)
 		{
-			if (yodaVel.LengthSquared != 0)
-			{
-				yodaPos += yodaVel;
-
-				yodaPos.X %= Game.ScreenWidth;
-				yodaPos.Y %= Game.ScreenHeight;
-
-				if (yodaPos.X < 0)
-					yodaPos.X += Game.ScreenWidth;
-				if (yodaPos.Y < 0)
-					yodaPos.Y += Game.ScreenHeight;
+			if (yoda.velocity.LengthSquared != 0)
+			{
+				if (RectWillCollideYEdge(yoda.extents, yoda.velocity))
+				{
+					yoda.velocity.Y = 0;
+				}
+				if (RectWillCollideXEdge(yoda.extents, yoda.velocity))
+				{
+					yoda.velocity.X = 0;
+				}
+
+				yoda.extents.position += yoda.velocity;
+			}
+
+			if (ball.velocity.LengthSquared != 0)
+			{
+				Console.WriteLine(
+					"Ball {{ top: {0}, left: {1}, bottom: {2}, right: {3}, velocity: {4}, center: {5} }}",
+					ball.extents.top,
+					ball.extents.left,
+					ball.extents.bottom,
+					ball.extents.right,
+					ball.velocity,
+					ball.extents.center
+				);
+				ball.extents.position += ball.velocity;
+
+				if (RectWillCollideTopEdge(ball.extents, ball.velocity))
+				{
+					ApplyForceToEntity(ball, new Vector2(0f, ball.mass * Math.Abs(ball.velocity.Y) * 2f));
+				}
+
+				if (RectWillCollideBottomEdge(ball.extents, ball.velocity))
+				{
+					ApplyForceToEntity(ball, new Vector2(0f, -ball.mass * Math.Abs(ball.velocity.Y) * 2f));
+				}
+
+				if (RectWillCollideLeftEdge(ball.extents, ball.velocity))
+				{
+					ApplyForceToEntity(ball, new Vector2(ball.mass * Math.Abs(ball.velocity.X) * 2f, 0));
+				}
+
+				if (RectWillCollideRightEdge(ball.extents, ball.velocity))
+				{
+					ApplyForceToEntity(ball, new Vector2(-ball.mass * Math.Abs(ball.velocity.X) * 2f, 0f));
+				}
 			}
 		}
 
 		private void OnRender(object sender, FrameEventArgs e)
 		{
-			DrawingFunctions.DrawTexturedQuadCenteredAtPoint(yodaTexture, yodaPos.X, yodaPos.Y, -1);
+			DrawingFunctions.DrawTexturedQuadCenteredAtPoint(yoda.texture, yoda.extents.center.X, yoda.extents.center.Y, -1);
+			DrawingFunctions.DrawTexturedQuadCenteredAtPoint(ball.texture, ball.extents.center.X, ball.extents.center.Y, -1, ball.extents.size.X, ball.extents.size.Y);
 		}
 	}
 }

--- a/sandbox/sandbox/sandbox.csproj
+++ b/sandbox/sandbox/sandbox.csproj
@@ -48,6 +48,8 @@
     <Compile Include="Tools.cs" />
     <Compile Include="TextWriter.cs" />
     <Compile Include="JustSomeText.cs" />
+    <Compile Include="Types\Entity.cs" />
+    <Compile Include="Types\Rect.cs" />
   </ItemGroup>
   <ItemGroup>
     <Reference Include="System.Drawing" />
@@ -81,5 +83,6 @@
     <Folder Include="data\" />
     <Folder Include="Input\" />
     <Folder Include="API\" />
+    <Folder Include="Types\" />
   </ItemGroup>
 </Project>