Farseer-based Skeleton

Topics: User Forum
Feb 21, 2013 at 6:19 PM
Edited Feb 21, 2013 at 6:32 PM
I created my own simple Skeleton-system using the farseer engine. It looks great, though I still need to learn the guy how to keep his balance lol, and I have no idea how to do this.

It is far from complete, and things should be made more modular (work with limbs instead of seperate bodyparts and let the limbs contain top-bottom-feet etc. more animations (crouch, stand up, lie down))

So this guy starts to walk and topples over within a few steps, (looks really fun though lol)

So what I want to know is firstly if this is a good way to handle this, and second, how the f* can I make sure that this dude can keep his balance, without 'hacking' then engine and keep things realistically simulated (I tried with another Body Container, which would keep the guy in place and let him not actually touch the ground, applying forces to the container.)

Just to be clear, I'm not asking you to 'solve my code', I'm asking for directions on this :) (Feel free to use this, I feel it's a good class with some tweaks)

```
class Skeleton
{
    private Body Container;
    private BodyPart Head;
    private BodyPart Torso;
    private BodyPart BottomLeftArm;
    private BodyPart BottomRightArm;
    private BodyPart TopLeftArm;
    private BodyPart TopRightArm;
    private BodyPart TopLeftLeg;
    private BodyPart BottomLeftLeg;
    private BodyPart TopRightLeg;
    private BodyPart BottomRightLeg;
    private BodyPart LeftFoot;
    private BodyPart RightFoot;

    private int direction;

    public Vector2 Location
    {
        get { return Torso.body.Position; }
    }

    public Skeleton(World world, Vector2 location)
    {
        this.Container = BodyFactory.CreateRectangle(world, 0.1f, 0.1f, 0.00000f, location);
        this.Container.BodyType = BodyType.Dynamic;
        this.Container.Friction = 0.3f;

        this.Torso = new BodyPart(world,
                                    BodyFactory.CreateRectangle(world, 0.5f, 0.8f, Global.HumanDensity, location));
        //JointFactory.CreateRevoluteJoint(world, this.Container, this.Torso.body, Vector2.Zero);
        //AngleJoint angle = JointFactory.CreateAngleJoint(world, this.Torso.body, this.Container);
        //angle.TargetAngle = 0;

        this.TopRightArm = new BodyPart(world,
                                    BodyFactory.CreateRectangle(world, 0.1f, 0.4f, Global.HumanDensity, this.Torso.body.Position + new Vector2(-0.25f, -0.6f)),
                                    this.Torso.body,
                                    new Vector2(-0.25f, -0.35f));
        this.TopRightArm.AngleJoint.TargetAngle = MathHelper.PiOver2;

        this.BottomRightArm = new BodyPart(world,
                                    BodyFactory.CreateRectangle(world, 0.1f, 0.5f, Global.HumanDensity, this.TopRightArm.body.Position + new Vector2(0f, -0.6f)),
                                    this.TopRightArm.body,
                                    new Vector2(0f, -0.3f));


        this.TopLeftArm = new BodyPart(world,
                                    BodyFactory.CreateRectangle(world, 0.1f, 0.4f, Global.HumanDensity, this.Torso.body.Position + new Vector2(0.25f, -0.6f)),
                                    this.Torso.body,
                                    new Vector2(0.25f, -0.35f));
        this.TopLeftArm.AngleJoint.TargetAngle = -MathHelper.PiOver2;

        this.BottomLeftArm = new BodyPart(world,
                                    BodyFactory.CreateRectangle(world, 0.1f, 0.5f, Global.HumanDensity, this.TopLeftArm.body.Position + new Vector2(0f, -0.6f)),
                                    this.TopLeftArm.body,
                                    new Vector2(0f, -0.3f));


        this.TopRightLeg = new BodyPart(world,
                                    BodyFactory.CreateRectangle(world, 0.1f, 0.5f, Global.HumanDensity, this.Torso.body.Position + new Vector2(-0.25f, 0.8f)),
                                    this.Torso.body,
                                    new Vector2(-0.25f, 0.5f));
        this.BottomRightLeg = new BodyPart(world,
                                    BodyFactory.CreateRectangle(world, 0.1f, 0.5f, Global.HumanDensity, TopRightLeg.body.Position + new Vector2(0f, 0.6f)),
                                    this.TopRightLeg.body,
                                    new Vector2(0f, 0.3f));
        this.RightFoot = new BodyPart(world,
                                    BodyFactory.CreateRectangle(world, 0.3f, 0.1f, Global.HumanDensity, BottomRightLeg.body.Position + new Vector2(0.1f, 0.2f)),
                                    this.BottomRightLeg.body,
                                    new Vector2(0f, 0.1f));
        this.RightFoot.body.Friction = 5f;

        this.TopLeftLeg = new BodyPart(world,
                                    BodyFactory.CreateRectangle(world, 0.1f, 0.5f, Global.HumanDensity, this.Torso.body.Position + new Vector2(0.25f, 0.8f)),
                                    this.Torso.body,
                                    new Vector2(0.25f, 0.5f));
        this.BottomLeftLeg = new BodyPart(world,
                                    BodyFactory.CreateRectangle(world, 0.1f, 0.5f, Global.HumanDensity, TopLeftLeg.body.Position + new Vector2(0f, 0.6f)),
                                    this.TopLeftLeg.body,
                                    new Vector2(0f, 0.3f));
        this.LeftFoot = new BodyPart(world,
                                    BodyFactory.CreateRectangle(world, 0.3f, 0.1f, Global.HumanDensity, BottomLeftLeg.body.Position + new Vector2(0.1f, 0.2f)),
                                    this.BottomLeftLeg.body,
                                    new Vector2(0f, 0.1f));
        this.LeftFoot.body.Friction = 5f;

        this.faceFront();

        this.ignoreColissions();

    }

    public void Update()
    {
        if (this.direction == 0)
            this.doNothing();

        if (this.direction == 1)
            this.walkRight();

        if (this.direction == 2)
            this.walkLeft();

        this.Torso.body.BodyType = BodyType.Static;
        this.doStuff();
    }

    /// <summary>
    /// Changes the direction the skeleton faces
    /// </summary>
    /// <param name="world">Farseer's world</param>
    /// <param name="direction">0: Face forward, 1: Face right, 2: Face left</param>
    public void ChangeDirection(World world, int direction)
    {
        if (this.direction == direction)
            return;

        this.direction = direction;

        /*
        if (direction == 0)
            this.faceFront();
        else
            this.faceSide();
         */

        this.ignoreColissions();
    }

    private void faceFront()
    {
        FixtureFactory.AttachRectangle(0.5f, 0.8f, Global.HumanDensity, Vector2.Zero, this.Torso.body);
        FixtureFactory.AttachRectangle(0.1f, 0.5f, Global.HumanDensity, Vector2.Zero, this.TopRightLeg.body);
        FixtureFactory.AttachRectangle(0.1f, 0.5f, Global.HumanDensity, Vector2.Zero, this.BottomRightLeg.body);
        FixtureFactory.AttachRectangle(0.1f, 0.5f, Global.HumanDensity, Vector2.Zero, this.TopLeftLeg.body);
        FixtureFactory.AttachRectangle(0.1f, 0.5f, Global.HumanDensity, Vector2.Zero, this.BottomLeftLeg.body);
    }

    private void faceSide()
    {
        FixtureFactory.AttachRectangle(0.2f, 0.8f, Global.HumanDensity, Vector2.Zero, this.Torso.body);
        FixtureFactory.AttachRectangle(0.1f, 0.5f, Global.HumanDensity, Vector2.Zero, this.TopRightLeg.body);
        FixtureFactory.AttachRectangle(0.1f, 0.5f, Global.HumanDensity, Vector2.Zero, this.BottomRightLeg.body);
        FixtureFactory.AttachRectangle(0.1f, 0.5f, Global.HumanDensity, Vector2.Zero, this.TopLeftLeg.body);
        FixtureFactory.AttachRectangle(0.1f, 0.5f, Global.HumanDensity, Vector2.Zero, this.BottomLeftLeg.body);
    }

    private void destroyFixtures()
    {
        this.Torso.body.DestroyFixture(this.Torso.body.FixtureList[0]);
        this.TopRightLeg.body.DestroyFixture(this.TopRightLeg.body.FixtureList[0]);
        this.BottomRightLeg.body.DestroyFixture(this.BottomRightLeg.body.FixtureList[0]);
        this.TopLeftLeg.body.DestroyFixture(this.TopLeftLeg.body.FixtureList[0]);
        this.BottomLeftLeg.body.DestroyFixture(this.BottomLeftLeg.body.FixtureList[0]);
    }

    private void destroyJoints()
    {

    }

    private void destroyBody(World world)
    {
        this.Torso.Destroy(world);
        this.TopRightLeg.Destroy(world);
        this.TopLeftLeg.Destroy(world);
        this.BottomRightLeg.Destroy(world);
        this.BottomLeftLeg.Destroy(world);
    }

    private void doNothing()
    {
        this.TopRightLeg.MoveBetween(0);
        this.TopLeftLeg.MoveBetween(0);
        this.BottomRightLeg.MoveBetween(0);
        this.BottomLeftLeg.MoveBetween(0);
        this.RightFoot.MoveBetween(0);
        this.LeftFoot.MoveBetween(0);

        this.TopRightArm.MoveBetween(MathHelper.Pi - MathHelper.PiOver4 / 2);
        this.BottomRightArm.MoveBetween(0);

        this.TopLeftArm.MoveBetween(-MathHelper.Pi + MathHelper.PiOver4 / 2);
        this.BottomLeftArm.MoveBetween(0);
    }
Feb 21, 2013 at 6:23 PM
Edited Feb 21, 2013 at 6:25 PM
private void walkRight()
        {
            this.Container.ApplyLinearImpulse(new Vector2(0.2f, 0));
            float speed = 0.06f;

            this.TopRightLeg.MoveBetween(MathHelper.PiOver4, -MathHelper.PiOver4, speed);
            this.TopLeftLeg.MoveBetween(-MathHelper.PiOver4, MathHelper.PiOver4, speed);

            this.BottomRightLeg.MoveBetween(0, -MathHelper.PiOver2, speed);
            this.BottomLeftLeg.MoveBetween(TopLeftLeg.AngleJoint.TargetAngle, -MathHelper.PiOver2, speed);

            this.RightFoot.MoveBetween(MathHelper.PiOver4, -MathHelper.PiOver2, speed * 2);
            this.LeftFoot.MoveBetween(-MathHelper.PiOver2, MathHelper.PiOver4, speed * 2);

            this.TopRightArm.MoveBetween(-MathHelper.PiOver4 + MathHelper.Pi, MathHelper.PiOver4 + MathHelper.Pi, speed);
            this.BottomRightArm.MoveBetween(MathHelper.Pi - MathHelper.PiOver4, MathHelper.Pi - MathHelper.PiOver2, speed);

            this.TopLeftArm.MoveBetween(MathHelper.PiOver4 - MathHelper.Pi, -MathHelper.PiOver4 - MathHelper.Pi, speed);
            this.BottomLeftArm.MoveBetween(MathHelper.Pi - MathHelper.PiOver4, MathHelper.Pi - MathHelper.PiOver2, speed);
        }

        private void walkLeft()
        {
            this.Container.ApplyLinearImpulse(new Vector2(-0.2f, 0));
            float speed = 0.06f;

            this.TopRightLeg.MoveBetween(-MathHelper.PiOver4, MathHelper.PiOver4, speed);
            this.TopLeftLeg.MoveBetween(MathHelper.PiOver4, -MathHelper.PiOver4, speed);

            this.BottomRightLeg.MoveBetween(0, MathHelper.PiOver2, speed);
            this.BottomLeftLeg.MoveBetween(-TopLeftLeg.AngleJoint.TargetAngle, MathHelper.PiOver2, speed);

            this.RightFoot.MoveBetween(MathHelper.PiOver4 - MathHelper.PiOver2, -MathHelper.PiOver2 - MathHelper.PiOver2, speed * 2);
            this.LeftFoot.MoveBetween(-MathHelper.PiOver2 - MathHelper.PiOver2, MathHelper.PiOver4 - MathHelper.PiOver2, speed * 2);

            this.TopRightArm.MoveBetween(MathHelper.PiOver4 + MathHelper.Pi, -MathHelper.PiOver4 + MathHelper.Pi, speed);
            this.BottomRightArm.MoveBetween(MathHelper.Pi + MathHelper.PiOver4, MathHelper.Pi + MathHelper.PiOver2, speed);

            this.TopLeftArm.MoveBetween(MathHelper.PiOver4 + MathHelper.Pi, MathHelper.PiOver4 + MathHelper.Pi, speed);
            this.BottomLeftArm.MoveBetween(MathHelper.Pi + MathHelper.PiOver4, MathHelper.Pi + MathHelper.PiOver2, speed);
        }

        private void ignoreColissions()
        {
            this.Container.IgnoreCollisionWith(this.Torso.body);
            this.Container.IgnoreCollisionWith(this.BottomLeftArm.body);
            this.Container.IgnoreCollisionWith(this.BottomRightArm.body);
            this.Container.IgnoreCollisionWith(this.TopLeftArm.body);
            this.Container.IgnoreCollisionWith(this.TopRightArm.body);
            this.Container.IgnoreCollisionWith(this.TopLeftLeg.body);
            this.Container.IgnoreCollisionWith(this.BottomLeftLeg.body);
            this.Container.IgnoreCollisionWith(this.LeftFoot.body);
            this.Container.IgnoreCollisionWith(this.TopRightLeg.body);
            this.Container.IgnoreCollisionWith(this.BottomRightLeg.body);
            this.Container.IgnoreCollisionWith(this.RightFoot.body);


            this.Torso.body.IgnoreCollisionWith(this.TopRightArm.body);
            this.Torso.body.IgnoreCollisionWith(this.BottomRightArm.body);
            this.Torso.body.IgnoreCollisionWith(this.TopLeftArm.body);
            this.Torso.body.IgnoreCollisionWith(this.BottomLeftArm.body);

            this.BottomRightArm.body.IgnoreCollisionWith(this.TopLeftArm.body);
            this.BottomRightArm.body.IgnoreCollisionWith(this.BottomLeftArm.body);
            this.BottomRightArm.body.IgnoreCollisionWith(this.TopRightLeg.body);
            this.BottomRightArm.body.IgnoreCollisionWith(this.TopLeftLeg.body);

            this.BottomLeftArm.body.IgnoreCollisionWith(this.TopRightArm.body);
            this.BottomLeftArm.body.IgnoreCollisionWith(this.TopLeftLeg.body);
            this.BottomLeftArm.body.IgnoreCollisionWith(this.TopRightLeg.body);

            this.TopLeftLeg.body.IgnoreCollisionWith(this.TopRightLeg.body);
            this.TopLeftLeg.body.IgnoreCollisionWith(this.BottomRightLeg.body);
            this.TopLeftLeg.body.IgnoreCollisionWith(this.RightFoot.body);

            this.BottomLeftLeg.body.IgnoreCollisionWith(this.TopRightLeg.body);
            this.BottomLeftLeg.body.IgnoreCollisionWith(this.BottomRightLeg.body);
            this.BottomLeftLeg.body.IgnoreCollisionWith(this.RightFoot.body);

            this.LeftFoot.body.IgnoreCollisionWith(this.TopRightLeg.body);
            this.LeftFoot.body.IgnoreCollisionWith(this.BottomRightLeg.body);
            this.LeftFoot.body.IgnoreCollisionWith(this.RightFoot.body);
        }

        private void doStuff()
        {
            this.TopRightLeg.Move();
            this.TopLeftLeg.Move();
            this.BottomRightLeg.Move();
            this.BottomLeftLeg.Move();
            this.RightFoot.Move();
            this.LeftFoot.Move();

            this.TopRightArm.Move();
            this.BottomRightArm.Move();
            this.TopLeftArm.Move();
            this.BottomLeftArm.Move();
        }

        public void Draw()
        {
            //sp.Draw(this.texture, Global.MetersToPixels(this.body.Position), null, Color.White, this.body.Rotation,
            //    new Vector2(this.texture.Width / 2, this.texture.Height / 2), 1f, SpriteEffects.None, 0f);
        }
    }
}
Feb 21, 2013 at 6:25 PM
Edited Feb 21, 2013 at 6:27 PM
Class BodyPart
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Joints;
using FarseerPhysics.Factories;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Scavenger.Classes.SkeletonSpace
{
    class BodyPart
    {
        public Body body;
        private Body masterBody;
        public RevoluteJoint RevoluteJoint;
        public AngleJoint AngleJoint;

        private float[] moveBetween;
        private int targetValue;

        private float speed;

        public BodyPart(World world, Body self)
        {
            this.body = self;
            this.body.BodyType = BodyType.Dynamic;
        }

        public BodyPart(World world, Body self, Body masterBody)
        {
            this.body = self;
            this.masterBody = masterBody;
            this.body.BodyType = BodyType.Dynamic;
            this.body.IgnoreCollisionWith(this.masterBody);
        }

        public BodyPart(World world, Body self, Body masterBody, Vector2 jointLocation)
        {
            this.body = self;
            this.masterBody = masterBody;

            this.RevoluteJoint = JointFactory.CreateRevoluteJoint(world, this.body, this.masterBody, jointLocation);

            this.AngleJoint = JointFactory.CreateAngleJoint(world, this.body, this.masterBody);
            this.AngleJoint.TargetAngle = 0;

            this.body.BodyType = BodyType.Dynamic;
            this.body.IgnoreCollisionWith(this.masterBody);

            this.speed = 0.03f;
            this.moveBetween = new float[2] { 0, 0 };
        }

        public void Move()
        {
            this.getIfNearTarget();

            if (!Global.AlmostEquals(this.AngleJoint.TargetAngle, this.moveBetween[targetValue], this.speed))
                this.AngleJoint.TargetAngle += this.getDirection(moveBetween[targetValue]);
        }

        public void MoveBetween(float float1)
        {
            this.moveBetween = new float[] { float1, float1 };
            this.speed = 0.03f;
            this.targetValue = 0;
        }

        public void MoveBetween(float float1, float float2, float speed)
        {
            this.moveBetween = new float[] { float1, float2 };
            this.speed = speed;
        }

        private void getIfNearTarget()
        {
            if (Global.AlmostEquals(this.moveBetween[targetValue], this.AngleJoint.TargetAngle, this.speed))
                this.addToTargetValue();
        }

        private void addToTargetValue()
        {
            if (targetValue == 0)
                targetValue++;
            else
                targetValue = 0;
        }

        private float getDirection(float target)
        {
            if (target > this.AngleJoint.TargetAngle)
                return this.speed;

            return -this.speed;
        }

        public void Destroy(World world)
        {
            world.RemoveBody(this.body);
        }
    }
}
<Same story as above>
I created my own simple Skeleton-system using the farseer engine. It looks great, though I still need to learn the guy how to keep his balance lol, and I have no idea how to do this.

It is far from complete, and things should be made more modular (work with limbs instead of seperate bodyparts and let the limbs contain top-bottom-feet etc. more animations (crouch, stand up, lie down))

So this guy starts to walk and topples over within a few steps, (looks really fun though lol)

So what I want to know is firstly if this is a good way to handle this, and second, how the f* can I make sure that this dude can keep his balance, without 'hacking' then engine and keep things realistically simulated (I tried with another Body Container, which would keep the guy in place and let him not actually touch the ground, applying forces to the container.)

Just to be clear, I'm not asking you to 'solve my code', I'm asking for directions on this :) (Feel free to use this, I feel it's a good class with some tweaks)
Coordinator
Feb 24, 2013 at 2:16 AM
I assume you are using joints for all the body joints. To make him keep his balance in reaction to forces, you need to find out where the mass of the body is distributed in relation to a rod going from the ground and up into the center of mass. When the mass is shifted, you adjust the joints to accommodate for the change.

Let me give an example:

You have a torso, 2 arms and 2 legs viewed form the side. Together, all of this weighs 70 kg, and most of the mass is in the torso. When the biped has his arms straight down, all the mass is pushed towards the ground (due to gravity) and he is upright because he has an even mass distribution. If he were to take both arms and extend them parallel to the ground, he will probably fall due to mass that has been moved. To correct for this, you need to move one of the legs towards the mass displacement to keep the ragdoll stable.

Search youtube/google for something like 'biped box2d' or 'biped farseer'. This has been been done a couple of times before with varying degrees of success.
Feb 24, 2013 at 9:56 AM
Edited Feb 24, 2013 at 9:57 AM
Yes, I'm using anglejoints with revolutejoints. I tried a couple of different joints and different combinations, but this combination seems to give me the best 'humanlike' joint. If you look at my bodypart class you can quickly see how I move the ragdoll, actually, most of the work is done there. The body just tells the bodyparts between what angles, with what speed, to move. This bodies movements are based on timing, which I'm not too happy about. That makes him a complete moron if e.g. his foot is stuck.

So I guess what is best for me to do is create some classes, LeftLeg, RightLeg etc. and define movements per bodypart per action (walkleft, walkright etc). And compensate for the Center Of Mass dynamically in some way.

What I am looking for is the MaxAngleJoint, It seems to be present in the older versions of the engine because I found traces of it on google, but not in the engine itself. Can I write these back easily? Perhaps with the older code? Why were they removed (If they were removed)?

What I am thinking now is to tell the body part to either move the revolutejoint 'left', or 'right' and change the direction of movement if the bodypart has reached his maximum angle, then change the maximum angle based on the center of mass. Would this be a good setup?

What would be best to determine the center of mass?

EDIT: Here I found some C++ example code http://code.google.com/p/box2d/source/browse/trunk/Contributions/Tests/?r=146