Wall crawling + EdgeShape normals

Topics: User Forum
Feb 7, 2011 at 9:41 AM

First time I've posted here even though I've been using Farseer for a few months now. But I'm a bit stuck so I have a quick question to ask.

 

I'm trying to get a ball to crawl up walls and across ceilings. My approach so far has been to average the contact normals (in world space) of the contacts in the chatacter's contactList that belong to my terrain collision category. I then apply a force in the opposite direction to the averaged normal (with ignoreGravity set to true).

 

This works fine for crawling around recangles, but gives me strange results when the character is crawling over uneven terrain which I make using a chain of EdgeShapes. In this case the character jumps about rather than sticking to the surface.

So there are two parts to my question. 1) Am I going about this the wrong way, and 2) Is there a reason I'd get odd normals between a circle and EdgeShapes?

 

Thanks in advance!

Coordinator
Feb 7, 2011 at 10:37 AM

What version of FPE are you using? I would recommend you try out the latest.

Feb 7, 2011 at 10:56 AM

Sorry, should have said, I'm using FPE 3.2. Any clue where I might be going wrong?

Feb 7, 2011 at 4:39 PM

Apologies for the double post. When you said latest version, did you mean the latest release (I.e. 3.2 on 1 Jan) or the latest changeset in the source control?

Coordinator
Feb 7, 2011 at 6:23 PM

I meant the latest version in the download section: FPE 3.2 - the one you are already using.

Take a look at the EdgeShapes test from the Testbed. It raycasts against a lot of edge shapes that makes up a terrain. If you zoom in (use x and z), you will see the normal pointing the right way.

Feb 7, 2011 at 6:34 PM

Ok, thanks. Its probably something I'm doing somewhere else then. I'll test it against basic EdgeShapes to see if its being caused by the circle being in contact with mutliple EdgeShapes at once. If I can't figure it out I might post my code up here later today.

Thanks again

Feb 13, 2011 at 4:49 PM

Just wanted to update on this issue for my own peace of mind, and to check that I haven't uncovered a bug. The issue was that sometimes when the circle was in contact with two edgeshapes at the same time, manifold.PointCount == 0 was returning true, even though the contact appeared in the circles contactList. In this case, GetWorldManifold returned Vector2.UnitY for the normal, which in my simulation is directly up (yes, I know it's upside down!) causing the circle to bounce or hover. 

 

So I can avoid this in future, in what circumstances will a contact in a body's contactList have manifold.PointCount == 0?

 

Thanks again

Coordinator
Feb 13, 2011 at 4:57 PM

Could you work up a Testbed sample that shows the issue? I can't test for any bugs unless I have something to test against.

Feb 13, 2011 at 10:26 PM

I thought I'd share a little about how I am doing crawling.

I use a slightly inflated circle shape sensor fixture around my player circle shape. This I use as a phantom to report close fixtures. My control code then maintains a revolute joint that it updates each frame while crawling, based on the close fixtures. It biases the anchor points in the direction of the thumbstick to achieve movement. Another important thing to remember is to allow collision between the joint bodies.

As an aside, I also use the phantom fixtures to limit the fixtures I cast against looking for the ground.

Not sure if that helps you, but I did use an approach similiar to yours initially. I find the joint works better if you have non-static entities that you wish to crawl on (ie. walking on the underside of a see-saw will cause the see-saw ends to drop as you crawl under them).

Feb 14, 2011 at 10:17 AM

Genbox, I will put together a testbed next weekend (only have access to a mac this week), just wanted to check there was nothing obvious causing manifold.PointCount to be zero.

Shivajs - I'd be very interested in hearing more about your approach (I didn't quite follow how you decide where to anchor the joint). My plan for none static objects was to apply a force opposite to the y component of the force applied to the character at the collision point to try and cancel it out. Otherwise I can do without crawling on dynamic objects!

Feb 18, 2011 at 7:45 PM

Genbox, I've modified the Edge testbed to illustrate what I was talking about. I don't know that its a bug, just that it was unexpected behaviour to me.

 

/*
* Farseer Physics Engine based on Box2D.XNA port:
* Copyright (c) 2010 Ian Qvist
* 
* Box2D.XNA port of Box2D:
* Copyright (c) 2009 Brandon Furtwangler, Nathan Furtwangler
*
* Original source Box2D:
* Copyright (c) 2006-2009 Erin Catto http://www.gphysics.com 
* 
* This software is provided 'as-is', without any express or implied 
* warranty.  In no event will the authors be held liable for any damages 
* arising from the use of this software. 
* Permission is granted to anyone to use this software for any purpose, 
* including commercial applications, and to alter it and redistribute it 
* freely, subject to the following restrictions: 
* 1. The origin of this software must not be misrepresented; you must not 
* claim that you wrote the original software. If you use this software 
* in a product, an acknowledgment in the product documentation would be 
* appreciated but is not required. 
* 2. Altered source versions must be plainly marked as such, and must not be 
* misrepresented as being the original software. 
* 3. This notice may not be removed or altered from any source distribution. 
*/

using FarseerPhysics.Collision.Shapes;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Factories;
using FarseerPhysics.TestBed.Framework;
using FarseerPhysics.Dynamics.Contacts;
using FarseerPhysics.Common;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace FarseerPhysics.TestBed.Tests
{
    public class EdgeTest : Test
    {
        private Fixture _circleFixture;

        private float spinSpeed = -15f;

        private EdgeTest()
        {
            {
                Body ground = BodyFactory.CreateBody(World);

                Vector2 v1 = new Vector2(-10.0f, 0.0f);
                Vector2 v2 = new Vector2(-8.0f, -0.5f);
                Vector2 v3 = new Vector2(-4.0f, -0.3f);
                Vector2 v4 = new Vector2(0f, -0.7f);
                Vector2 v5 = new Vector2(4.0f, -0.2f);
                Vector2 v6 = new Vector2(8.0f, -0.8f);
                Vector2 v7 = new Vector2(10.0f, -0.6f);
                Vector2 v8 = new Vector2(12.0f, -0.0f);
                Vector2 v9 = new Vector2(15.0f, -0.2f);

                EdgeShape shape = new EdgeShape(v1, v2);
                shape.HasVertex3 = true;
                shape.Vertex3 = v3;
                ground.CreateFixture(shape);

                shape.Set(v2, v3);
                shape.HasVertex0 = true;
                shape.HasVertex3 = true;
                shape.Vertex0 = v1;
                shape.Vertex3 = v4;
                ground.CreateFixture(shape);

                shape.Set(v3, v4);
                shape.HasVertex0 = true;
                shape.HasVertex3 = true;
                shape.Vertex0 = v2;
                shape.Vertex3 = v5;
                ground.CreateFixture(shape);

                shape.Set(v4, v5);
                shape.HasVertex0 = true;
                shape.HasVertex3 = true;
                shape.Vertex0 = v3;
                shape.Vertex3 = v6;
                ground.CreateFixture(shape);

                shape.Set(v5, v6);
                shape.HasVertex0 = true;
                shape.HasVertex3 = true;
                shape.Vertex0 = v4;
                shape.Vertex3 = v7;
                ground.CreateFixture(shape);

                shape.Set(v6, v7);
                shape.HasVertex0 = true;
                shape.HasVertex3 = true;
                shape.Vertex0 = v5;
                shape.Vertex3 = v8;
                ground.CreateFixture(shape);

                shape.Set(v7, v8);
                shape.HasVertex0 = true;
                shape.HasVertex3 = true;
                shape.Vertex0 = v6;
                shape.Vertex3 = v9;
                ground.CreateFixture(shape);

                shape.Set(v8, v9);
                shape.HasVertex0 = true;
                shape.Vertex0 = v7;
                ground.CreateFixture(shape);

            }

            {
                Body body = BodyFactory.CreateBody(World, new Vector2(-0.5f, 0.6f));
                body.BodyType = BodyType.Dynamic;
                body.SleepingAllowed = false;

                CircleShape shape = new CircleShape(0.5f, 1);
                _circleFixture = body.CreateFixture(shape);
            }

            /*{
                Body body = BodyFactory.CreateBody(World, new Vector2(1.0f, 0.6f));
                body.BodyType = BodyType.Dynamic;
                body.SleepingAllowed = false;

                PolygonShape shape = new PolygonShape(1);
                shape.SetAsBox(0.5f, 0.5f);

                body.CreateFixture(shape);
            }*/
        }

        public override void Update(GameSettings settings, GameTime gameTime)
        {
            DebugView.DrawString(50, TextLine, "Rotation: " + _circleFixture.Body.Rotation);
            TextLine += 15;
            DebugView.DrawString(50, TextLine, "Revolutions: " + _circleFixture.Body.Revolutions);

            GamePadState state = GamePad.GetState(PlayerIndex.One);
            if (state.ThumbSticks.Left.X > 0.1f)
            {
                _circleFixture.Body.AngularVelocity = spinSpeed;
            }
            else if (state.ThumbSticks.Left.X < -0.1f)
            {
                _circleFixture.Body.AngularVelocity = -spinSpeed;
            }

            ContactEdge cEdge = _circleFixture.Body.ContactList;

            while (cEdge != null)
            {
                if (cEdge.Contact.Manifold.PointCount == 0)
                {
                    TextLine += 15;
                    DebugView.DrawString(50, TextLine, "Contact found with zero Manifold.PointCount");
                }
                cEdge = cEdge.Next;
            }


            base.Update(settings, gameTime);
        }


        internal static Test Create()
        {
            return new EdgeTest();
        }
    }
}

Coordinator
Feb 18, 2011 at 8:34 PM

Thanks a lot for the test.

Now I see what you mean. I have two notes:

1. It is indeed expected that you get a Manifold with 0 points as the manifold (contact actually) gets created when the AABBs in the broad phase touches each other. You can see it illustrated by pressing F4 when running the test you just created.

2. The contact is only populated with contact points when it touches something, and I can understand why it puzzles you that it still contains manifolds with 0 points when touching something. I will take a look at it and see if I can do.

Feb 18, 2011 at 10:11 PM

Genbox, thanks for the explanation, that makes a lot more sense now (especially with the F4 tip). 

Essentially I misunderstood the nature of a contact. I now see that the comment at the head of the Contact class would have cleared it up, I just didn't think to look there! 

Thanks again.

Mar 5, 2011 at 11:53 PM

Sorry about the delay, but here's an explanation of my (updated) approach :)

I have a phantom, which is just a wrapper class around an IsSensor fixture. It maintains a list of overlapping fixtures by subscribing to the phantom fixture's OnCollision/OnSeparation events.

The phantom then has a method to return the closest overlapping fixture using Farseer's distance query:

    public bool GetClosestOverlappingFixture(out Fixture a_fixture, out DistanceOutput a_output)
    {
      Fixture bestFixture = null;
      DistanceOutput bestOutput = new DistanceOutput();

      // resolve some information about the phantom
      Fixture phantomFixture = m_fixture.Fixture;
      Transform phantomTransform;
      phantomFixture.Body.GetTransform(out phantomTransform);

      // start building the distance input data
      DistanceInput input = new DistanceInput();
      input.TransformA = phantomTransform;

      for (int i=0; i<phantomFixture.Shape.ChildCount; ++i)
      {
        // set the distance input data for this shape
        input.ProxyA.Set(phantomFixture.Shape, i);

        foreach (Fixture fixture in m_overlappingFarseerFxitures)
        {
          // resolve some information about this fixture
          Transform transform;
          fixture.Body.GetTransform(out transform);

          // set the fixtures input data
          input.TransformB = transform;

          for (int j = 0; j < fixture.Shape.ChildCount; ++j)
          {
            // set the distance input data for this shape
            input.ProxyB.Set(fixture.Shape, j);

            // make the distance query
            SimplexCache cache;
            DistanceOutput output;
            Distance.ComputeDistance(out output, out cache, ref input);

            if (bestFixture == null || output.Distance < bestOutput.Distance)
            {
              bestFixture = fixture;
              bestOutput = output;
            }
          }
        }
      }

      a_fixture = bestFixture;
      a_output = bestOutput;

      return bestFixture != null;
    }

 

My player controller then uses the result (the closest fixture) to manage a revolute joint.

Creating the joint:

          Fixture closestFixture = null;
          DistanceOutput closestOutput = new DistanceOutput();

          if (m_phantom.GetClosestOverlappingFixture(out closestFixture, out closestOutput))
          {
            Vector2 direction = closestOutput.PointB - body.Body.Position;
            float length = direction.Length();
            direction.Normalize();

            // there is geometry to grab
            m_grabJoint = new RevoluteJoint(body.Body, closestFixture.Body, body.Body.GetLocalPoint(body.Body.Position + direction), closestFixture.Body.GetLocalPoint(body.Body.Position + direction * length));
            m_grabJoint.CollideConnected = true;
            Game.Singleton.Instance.GameWorld.PhysicsWorld.World.AddJoint(m_grabJoint);
          }

And updating joint:

          Fixture closestFixture = null;
          DistanceOutput closestOutput = new DistanceOutput();

          if (m_phantom.GetClosestOverlappingFixture(out closestFixture, out closestOutput))
          {
            // there is a fixture to grab
            Vector2 direction = closestOutput.PointB - body.Body.Position;
            float length = direction.Length();
            direction.Normalize();

            // change joint bodies if necessary
            if (m_grabJoint.BodyB == closestFixture.Body)
            {
              // closest fixture still belongs to the same body, update anchors
              m_grabJoint.LocalAnchorA = body.Body.GetLocalPoint(body.Body.Position + direction);
              m_grabJoint.LocalAnchorB = closestFixture.Body.GetLocalPoint(body.Body.Position + direction * length);
            }
            else
            {
              // closest fixture belongs to a different body, recreate joint
              PhysicsWorld world = Game.Singleton.Instance.GameWorld.PhysicsWorld;
              world.World.RemoveJoint(m_grabJoint);
              m_grabJoint = new RevoluteJoint(body.Body, closestFixture.Body, body.Body.GetLocalPoint(body.Body.Position + direction), closestFixture.Body.GetLocalPoint(body.Body.Position + direction * length));
              m_grabJoint.CollideConnected = true;
              world.World.AddJoint(m_grabJoint);
            }

            m_orientation = (float)(System.Math.Atan2(-1f, 0f) - System.Math.Atan2(direction.Y, direction.X));
          }

 

Movement is achieved by controlling a motor on a separate revolute joint on my player bodies. All of this is done relative to the orientation of the surface the player is crawling on:

        Matrix rotation = Matrix.CreateRotationZ(m_orientation);
        Vector2 rotatedThumbStick = Vector2.TransformNormal(pad.State.ThumbSticks.Left, rotation);

        float stickValue = rotatedThumbStick.X;
        if (System.Math.Abs(rotatedThumbStick.X) < 0.1f)
        {
          stickValue = 0f;
        }
        const float characterSpeed = float.MaxValue;
        MotorisedRevoluteJoint.RevoluteJoint.MotorSpeed = stickValue * characterSpeed;

 

I'm still actively working on tuning the feel of all this, and there are plenty of improvements I plan to make in this area. I'm confident that this approach is both simple and robust, but of course YYMV :)

If you like, I can upload a short video of this in all its physics debug view glory :p

May 19, 2011 at 2:19 AM

i for one would like to see the video

May 19, 2011 at 9:41 AM

As requested: http://www.youtube.com/watch?v=I_eRFmT4PSk

Let me know if you have any questions about anything, or wanted to see more of something in particular :)

May 22, 2011 at 6:22 PM

looks pretty sweet, thanks for the vid

Jul 3, 2011 at 6:37 PM

hello,

shivajs, i really like the way you've implemented wall crawling :D although im having some issue intergrating your example into my project.

The biggest issue im having is that when the grabJoint is created (i have it set up in my test project to create when the (A) button is pressed) it

grabs the isSensor fixture and not the player Circle Fixture. Since the player Fixture is never in contact with the surface its grabing i cant move the player with

the revolute joint motor. Also, im not sure if this is a huge deal, but in the GetClosestOverlappingFixture method, the line Fixture phantomFixture = m_fixture.Fixture;, i

commented out that line and made a fixture and body member field in the Phantom class named phantomFixture (which has IsSensor = true) and phantomBody. I attach the

sensor phantom fixture to my player fixture using another revolute joint. is this correct? how are you creating the sensor fixture? How/Where are you creating the grabJoint? 

 Im really lost on this, i could REALLY use any help from you or anyone else that has implemented these methods into there project <3.

-Josh-

 

Jul 5, 2011 at 9:05 AM

I'm not sure I totally understand what you've done, but I'll give it a shot anyway :)

Firstly, for joint creation, you should be creating a joint between your player's body and the closest fixture's body. The anchors are placed on a unit circle (for the player body, since in my case my player has a radius of 1) and at the contact position (for the other body, it might look a little confusing that I rebuild the contact position from the player's body position, the direction vector and the magnitude, but I experimented with tweaking the length at various times).

What do you mean when you say 'it grabs the isSensor fixture'? Are you saying your phantom (sensor) fixture is finding another sensor fixture to try and grab? Or is the body you are passing to the joint constructor the sensor's body and not the player's body?

Storing the isSensor fixture on the phantom is fine, any instance of body.Body or fixture.Fixture in my code is merely me accessing the Farseer type through my own type (I wrap for my editor and content pipeline).

Attaching the phantom body to the player body with another joint is fine.

 

If I haven't helped, a code snippet might help me diagnose the problem.