Bounding Box

Oct 25, 2011 at 7:03 PM
Edited Oct 25, 2011 at 7:03 PM

Hi,

First of all i must say that Demina is a great lib; it does everything its is supposed to do and even interpolation between animations! Great!

Im a beginner with Demina, and i want to know if there is a way to get the bouding box of an entire animation in a specific key frame, or maybe in the current key frame since i want to automate physics objects creation.

Thanks in advance.

Coordinator
Oct 26, 2011 at 2:08 PM

Thanks! I'm glad you like it.

Unfortunately no, there's nothing like that built into Demina. I think this would be pretty hard to do accurately at runtime, since you'd have to analyze the images and disregard the transparent areas to make a bounding box. A few people (myself included) have talked about building an external tool where you could design hitboxes and collision volumes, but nobody has done any work in that direct yet.

May 3, 2012 at 7:11 PM
Edited May 3, 2012 at 7:12 PM

Hi, i discovered Demina yesterday, great tool :) But collision was important in my game case, so i implement it.

For that i've downloaded Demina 4.0 sources.

Add in AnimatedPlayer.cs :

 

 public int CurrentKeyframeIndex
 {
      get { return currentKeyframeIndex; }
 }

 

Create new class RotatedRectangle in your game project :

 

public class RotatedRectangle
    {
        public Rectangle CollisionRectangle;
        public float Rotation;
        public Vector2 Origin;
        
        public RotatedRectangle(Rectangle theRectangle, float theInitialRotation)
        {
            CollisionRectangle = theRectangle;
            Rotation = theInitialRotation;
            Origin = new Vector2((int)theRectangle.Width / 2, (int)theRectangle.Height / 2);
        }

        public void ChangePosition(int theXPositionAdjustment, int theYPositionAdjustment)
        {
            CollisionRectangle.X += theXPositionAdjustment;
            CollisionRectangle.Y += theYPositionAdjustment;
        }

     
        public bool Intersects(Rectangle theRectangle)
        {
            return Intersects(new RotatedRectangle(theRectangle, 0.0f));
        }


        public bool Intersects(RotatedRectangle theRectangle)
        {
           
            List<Vector2> aRectangleAxis = new List<Vector2>();
            aRectangleAxis.Add(UpperRightCorner() - UpperLeftCorner());
            aRectangleAxis.Add(UpperRightCorner() - LowerRightCorner());
            aRectangleAxis.Add(theRectangle.UpperLeftCorner() - theRectangle.LowerLeftCorner());
            aRectangleAxis.Add(theRectangle.UpperLeftCorner() - theRectangle.UpperRightCorner());

            foreach (Vector2 aAxis in aRectangleAxis)
            {
                if (!IsAxisCollision(theRectangle, aAxis))
                {
                    return false;
                }
            }

            return true;
        }
        
  
        private bool IsAxisCollision(RotatedRectangle theRectangle, Vector2 aAxis)
        {
        
            List<int> aRectangleAScalars = new List<int>();
            aRectangleAScalars.Add(GenerateScalar(theRectangle.UpperLeftCorner(), aAxis));
            aRectangleAScalars.Add(GenerateScalar(theRectangle.UpperRightCorner(), aAxis));
            aRectangleAScalars.Add(GenerateScalar(theRectangle.LowerLeftCorner(), aAxis));
            aRectangleAScalars.Add(GenerateScalar(theRectangle.LowerRightCorner(), aAxis));

     
            List<int> aRectangleBScalars = new List<int>();
            aRectangleBScalars.Add(GenerateScalar(UpperLeftCorner(), aAxis));
            aRectangleBScalars.Add(GenerateScalar(UpperRightCorner(), aAxis));
            aRectangleBScalars.Add(GenerateScalar(LowerLeftCorner(), aAxis));
            aRectangleBScalars.Add(GenerateScalar(LowerRightCorner(), aAxis));

            int aRectangleAMinimum = aRectangleAScalars.Min();
            int aRectangleAMaximum = aRectangleAScalars.Max();
            int aRectangleBMinimum = aRectangleBScalars.Min();
            int aRectangleBMaximum = aRectangleBScalars.Max();

          
            if (aRectangleBMinimum <= aRectangleAMaximum && aRectangleBMaximum >= aRectangleAMaximum)
            {
                return true;
            }
            else if (aRectangleAMinimum <= aRectangleBMaximum && aRectangleAMaximum >= aRectangleBMaximum)
            {
                return true;
            }

            return false;
        }

        private int GenerateScalar(Vector2 theRectangleCorner, Vector2 theAxis)
        {
         
            float aNumerator = (theRectangleCorner.X * theAxis.X) + (theRectangleCorner.Y * theAxis.Y);
            float aDenominator = (theAxis.X * theAxis.X) + (theAxis.Y * theAxis.Y);
            float aDivisionResult = aNumerator / aDenominator;
            Vector2 aCornerProjected = new Vector2(aDivisionResult * theAxis.X, aDivisionResult * theAxis.Y);
            
          
            float aScalar = (theAxis.X * aCornerProjected.X) + (theAxis.Y * aCornerProjected.Y);
            return (int)aScalar;
        }

     
        private Vector2 RotatePoint(Vector2 thePoint, Vector2 theOrigin, float theRotation)
        {
            Vector2 aTranslatedPoint = new Vector2();
            aTranslatedPoint.X = (float)(theOrigin.X + (thePoint.X - theOrigin.X) * Math.Cos(theRotation)
                - (thePoint.Y - theOrigin.Y) * Math.Sin(theRotation));
            aTranslatedPoint.Y = (float)(theOrigin.Y + (thePoint.Y - theOrigin.Y) * Math.Cos(theRotation)
                + (thePoint.X - theOrigin.X) * Math.Sin(theRotation));
            return aTranslatedPoint;
        }
                
        public Vector2 UpperLeftCorner()
        {
            Vector2 aUpperLeft = new Vector2(CollisionRectangle.Left, CollisionRectangle.Top);
            aUpperLeft = RotatePoint(aUpperLeft, aUpperLeft + Origin, Rotation);
            return aUpperLeft;
        }

        public Vector2 UpperRightCorner()
        {
            Vector2 aUpperRight = new Vector2(CollisionRectangle.Right, CollisionRectangle.Top);
            aUpperRight = RotatePoint(aUpperRight, aUpperRight + new Vector2(-Origin.X, Origin.Y), Rotation);
            return aUpperRight;
        }

        public Vector2 LowerLeftCorner()
        {
            Vector2 aLowerLeft = new Vector2(CollisionRectangle.Left, CollisionRectangle.Bottom);
            aLowerLeft = RotatePoint(aLowerLeft, aLowerLeft + new Vector2(Origin.X, -Origin.Y), Rotation);
            return aLowerLeft;
        }

        public Vector2 LowerRightCorner()
        {
            Vector2 aLowerRight = new Vector2(CollisionRectangle.Right, CollisionRectangle.Bottom);
            aLowerRight = RotatePoint(aLowerRight, aLowerRight + new Vector2(-Origin.X, -Origin.Y), Rotation);
            return aLowerRight;
        }

        public int X
        {
            get { return CollisionRectangle.X; }
        }

        public int Y
        {
            get { return CollisionRectangle.Y; }
        }

        public int Width
        {
            get { return CollisionRectangle.Width; }
        }

        public int Height
        {
            get { return CollisionRectangle.Height; }
        }

    }

 

And now, just have to create a new object from AnimationPlayer :

 

class AnimationPlayerCollidable : Demina.AnimationPlayer
    {
        public List<RotatedRectangle> getCollisionBoxes(Vector2 position)
        {

            List<RotatedRectangle> collideBoxes = new List<RotatedRectangle>();
            Animation animation = getAnimation();
            for (int boneIndex = 0; boneIndex < animation.Keyframes[0].Bones.Count; boneIndex++)
            {
                Bone bone = animation.Keyframes[CurrentKeyframeIndex].Bones[boneIndex];
                if (bone.Hidden)
                    continue;

                Rectangle rect = new Rectangle(
                       (int)(position + BoneTransformations[boneIndex].Position - animation.Textures[bone.TextureIndex].TextureBounds.Origin).X,
                       (int)(position + BoneTransformations[boneIndex].Position + animation.Textures[bone.TextureIndex].TextureBounds.Origin).Y,
                       (int)((position + BoneTransformations[boneIndex].Position + new Vector2(animation.Textures[bone.TextureIndex].TextureBounds.Location.Width, animation.Textures[bone.TextureIndex].TextureBounds.Location.Height)) - (position + BoneTransformations[boneIndex].Position)).X,
                       (int)((position + BoneTransformations[boneIndex].Position + new Vector2(animation.Textures[bone.TextureIndex].TextureBounds.Location.Width, 0)) - (position + BoneTransformations[boneIndex].Position + new Vector2(0, animation.Textures[bone.TextureIndex].TextureBounds.Location.Height))).Y);

                RotatedRectangle rotated = new RotatedRectangle(rect, BoneTransformations[boneIndex].Rotation);
                collideBoxes.Add(rotated);
            }
            return collideBoxes;

        }
    }

 

 

And now you can get boundingbox of all bones with for example :

foreach (var item in animationPlayer.getCollisionBoxes(new Vector2(400, 200)))
{
//And check collision with the Intersect method
}

 

Where the vector is the drawing position , like in the Draw method.

Screenshot with bounding boxes :

http://image.noelshack.com/fichiers/2012/18/1336069416-skel.jpg

May 6, 2012 at 4:29 AM

Thats great Nakato! This should commited to demina code.

Coordinator
May 6, 2012 at 12:15 PM

This is pretty neat.

Unfortunately, since it gets the width and height from the input texture, the rectangles don't necessarily match the shape of each bone. You can see that in the screenshot. Every box is two times the size of its contents. However, it wouldn't be too hard to run over the pixel data in the editor to generate bounding boxes.

In general, I think traditional hitboxes would be more useful, but if this is something people find helpful, I can try to add it.