Character Controller Setup Question

Discuss any questions about BEPUphysics or problems encountered.
Post Reply
meathelix
Posts: 14
Joined: Tue Oct 19, 2010 6:39 pm

Character Controller Setup Question

Post by meathelix »

I've been hesitating to post this for a while because I feel like the answer probably lies in some of the source code, but I've hit a wall and my project has been stagnating for a while. Any directional bump right now would be most helpful.

I'm attempting to create an FPS using a capsule as player collision, but I'm getting a lot of erratic behavior. The player bounces up and down as he moves across the gameworld and slides up walls as I press against them. Since I migrated from XNA 3.1 to 4, the player has also begun to fall out of the world a lot as well.

The player is represented by a capsule. The settings are as follows:

Code: Select all

collisionCyl = new  Capsule(position + new Vector3(0, 1, 0), 0.75f, 0.5f,5.0f);
collisionCyl.IsAlwaysActive = true;
collisionCyl.Bounciness = 0;
It's called collisionCyl because I was using a cylinder in a previous iteration (to no avail).

I'm getting the horizontal movement value from the left thumbstick of the gamepad and applying it directly as velocity like so:

Code: Select all

        public void updateInput(GamePadState gps, GameTime elapsed)        
        {
            float deltaT = (float)elapsed.ElapsedGameTime.TotalMilliseconds;
            oldGPS = newGPS;
            newGPS = gps;

            pitch += newGPS.ThumbSticks.Right.Y * deltaT * rotSpeed;
            yaw -= newGPS.ThumbSticks.Right.X * deltaT * rotSpeed;

            pitch = MathHelper.Clamp(pitch, MathHelper.ToRadians(pitchMin), MathHelper.ToRadians(pitchMax));

            moveRaw = Vector3.Zero;
            moveRaw.Z = newGPS.ThumbSticks.Left.Y * deltaT * speed;
            moveRaw.X = -newGPS.ThumbSticks.Left.X * deltaT * speed;

            Matrix cameraMoveRotationMatrix = Matrix.CreateRotationY(yaw);
            
            //NEED TO ACCOUNT FOR GRAVITY HERE            
            Vector3 tempVec = Vector3.Transform(moveRaw, cameraMoveRotationMatrix);
            collisionCyl.LinearVelocity = new Vector3(tempVec.X,collisionCyl.LinearVelocity.Y,tempVec.Z);                                   
            
        }
The relevant part being at the bottom where I set the LinearVelocity value manually.

The game world is comprised of tiles. For the time being, each of these tiles has a floor and ceiling, so I'm just adding boxes to represent those. I'm also attempting to add a triangleMeshGroup for each of the wall models for each tile.

Code: Select all

foreach (ShipTile3d st3d in testShip.tiles3d)
            {
                foreach (KeyValuePair<tileWallLocation, ShipTileWall> stw in st3d.tileTile.walls)
                {
                    //ASSUMING FLOOR AND CEILING FOR NOW
                    testShip.interiorSpace.Add(new Box(st3d.tileEntity.WorldTransform.Translation+new Vector3(0,0.5f,0), 6, 1, 6));
                    testShip.interiorSpace.Add(new Box(st3d.tileEntity.WorldTransform.Translation + new Vector3(0, 5, 0), 6, 1, 6));
                    if ((wallModels.ContainsKey(stw.Value.type)) && (tileWallOffsets.ContainsKey(stw.Key)) && (tileWallRotations.ContainsKey(stw.Key)))
                    {                        
                        StaticTriangleGroup.GetVerticesAndIndicesFromModel(wallModels[stw.Value.type], out verts, out indices);
                        triangleMesh = new TriangleMesh(verts, indices);
                        triangleGroup = new StaticTriangleGroup(triangleMesh);

                        triangleGroup.WorldMatrix = Matrix.CreateRotationY(MathHelper.ToRadians(tileWallRotations[stw.Key])) * st3d.tileEntity.WorldTransform;                        
                        testShip.interiorSpace.Add(triangleGroup);
                    }
                }
            }


So this is where I'm at. I was thinking of digging up the CharacterController example again, but I don't know where it is and whether or not it will be applicable to the 1st person thing I'm doing. Does anyone have advice for where to look from here? I don't even need full realized solutions as much as a general area to start poking around.

Thanks in advance to anyone that has read this far.
Norbo
Site Admin
Posts: 4929
Joined: Tue Jul 04, 2006 4:45 am

Re: Character Controller Setup Question

Post by Norbo »

I'm not sure what the source of the erratic behavior is, but it could partly be related to a combination of bumping on box edges, unstable character entity, and velocity control issues. Once the character is 'nailed down' these problems should be easier to track down.

That said, here's some miscellaneous notes that might be helpful:

-The deltaT of the updateInput method is in milliseconds instead of seconds, which increases everything by a factor of 1000. I assume it has been compensated for in the rotSpeed/speed values, but to avoid future confusion it might be a good idea to change that.

-The linear velocity is being changed using a buffered property. At a glance, it doesn't look like you are re-using value of it anywhere, so this probably doesn't cause a problem. However, it's very common to run into an unintuitive situation where the read value from a buffered property isn't what is expected since the read buffer hasn't been updated yet. Using non-buffered properties (like InternalLinearVelocity) protects against this possibility. (Entity properties in v0.15.0 are all unbuffered to avoid this confusion; buffered properties are accessible through a clearly named BufferedStates property).

-The SimpleCharacterController/CharacterController show a different way to manage velocity that may be more robust. Instead of directly setting the velocity, it finds a support location, computes a goal velocity, and then changes the velocities to meet that goal. This provides a little more power and flexibility, allowing for accelerations/decelerations, walking on moving objects, jumping etc. The relevant code can be found in the FindSupport and HandleHorizontalMotion methods. I'd recommend starting with the SimpleCharacterController, since it just uses a single raycast to find its support. The 'normal' CharacterController uses a fairly complex series of convex casts to determine its support which could be pointlessly confusing.

-It appears the velocity calculation is only designed to handle a character that is upright. Assuming this is the case, the orientation of the character should be locked so that it doesn't rotate due to collisions. You can set the entity's local inertia tensor inverse to the zero matrix to lock the orientation. Later, if you need dynamically changing orientations, you could unlock the inertia and use a SingleEntityAngularMotor to control the orientation.

-I would recommend bundling as much as you can into a single StaticTriangleGroup. In v0.14.3 and before, having lots of separate meshes in the space at once is quite a bit slower than one big one. v0.15.0 improves on this a lot, but if it's possible/reasonable to batch them into one big one, it is still a good idea to do so.
meathelix
Posts: 14
Joined: Tue Oct 19, 2010 6:39 pm

Re: Character Controller Setup Question

Post by meathelix »

Incredible turnaround time. I read this last night shortly after the reply, which was only shortly after the post. It wasn't until tonight that I could do something about the feedback.

Here's what I've modified:
Changed the update deltaT to seconds and updated the speed and rotation speed accordingly. No noticeable difference there, but it's good to iron that out under the hood. Rookie mistake on my part there.

I changed the local inertia tensor inverse to the zero matrix and this immediately stopped all the bumping up and down as I moved the cylinder around. Excellent.

I have yet to look into the legitimate controller class, but I think I want this to be my next step, as it looks more robust. Is that included as part of the base API, or is there an example that has the class in it somewhere? I'm ultimately going to want jumping and potential zero-g situations, so this seems like a must have.

Ever since I moved to XNA 4, the walls stopped providing collision. I wired up the BEPUPhysicsDrawer and noticed that the StaticTriangleGroups are not getting added to my physics space. What would cause this to occur? Is there a difference between XNA versions or an update that changed how this worked? The groups and meshes within those groups are not null, so how do I figure out where the problem is occurring here?

Thanks again for the prompt and thorough response. I should probably divulge that my project is probably not going to be that profitable so you can scale your efforts accordingly. :)
meathelix
Posts: 14
Joined: Tue Oct 19, 2010 6:39 pm

Re: Character Controller Setup Question

Post by meathelix »

I did some digging in the forums and I think the disappearing walls are related to the change in the convenience function for getting the verts from models to put into the triangle meshes.
http://bepu-games.com/forums/viewtopic.php?f=4&t=1048
I'll poke around with fixing this and see if it at least addresses that problem. Then it's on to figuring out how to set up a CharacterController.

[EDIT] I don't think I'm having the same problems, as the GetVerticesAndIndicesFromModel method is returning data, and I put a Try/Catch around the space's Add method to see if it was having problems adding the group and it threw no exceptions. Back to square one I suppose.
Norbo
Site Admin
Posts: 4929
Joined: Tue Jul 04, 2006 4:45 am

Re: Character Controller Setup Question

Post by Norbo »

I have yet to look into the legitimate controller class, but I think I want this to be my next step, as it looks more robust. Is that included as part of the base API, or is there an example that has the class in it somewhere? I'm ultimately going to want jumping and potential zero-g situations, so this seems like a must have.
The full source for the CharacterController and SimpleCharacterController is in the BEPUphysicsDemos source (http://www.bepu-games.com/BEPUphysics/d ... Source.zip), in the Alternate Movement folder.
[EDIT] I don't think I'm having the same problems, as the GetVerticesAndIndicesFromModel method is returning data, and I put a Try/Catch around the space's Add method to see if it was having problems adding the group and it threw no exceptions. Back to square one I suppose.
Interesting. I can't think of anything off the top of my head that would have changed from an XNA 3.1 to 4.0 version with regard to meshes other than the convenience method change.
Ever since I moved to XNA 4, the walls stopped providing collision. I wired up the BEPUPhysicsDrawer and noticed that the StaticTriangleGroups are not getting added to my physics space.
If you haven't already, I'd try manually adding the involved TriangleMesh objects (not the Group object) to the ModelDrawer. If they render properly, then it's not a data problem- though I don't know what the alternative would be at the moment. If the Space property of the StaticTriangleGroup isn't null and the data is correct, it should work.

If the meshes do not appear to render properly, it may be a bug in the convenience method or something similar. Using a custom content processor would solve such an issue, though I'd expect the convenience method to throw an exception if it can't figure something out.
meathelix
Posts: 14
Joined: Tue Oct 19, 2010 6:39 pm

Re: Character Controller Setup Question

Post by meathelix »

I got the collision on the walls to work. It was mainly a matter of manually extracting the vert and index data from the model and not using the pre-existing convenience method. I never got the ModelDrawer to display the TriangleMeshes. I manually added them to the drawer and everything, but to no avail. Not sure what's going on there, but it's a secondary goal since I got my collision to function.

The vertex and index extractor I used was here: http://www.discoverthat.co.uk/games/xna ... helper.htm

I modified it to work with bEPUs TriangleMeshVertex format and a straight list of ints for the index list. It uses lists, but you can use their ToArray<> methods when you make Meshes and Groups out of them.

Here's the modified code for easier use directly with bEPU. Again, the credit goes to the link above. I left all of his credits and whatnot intact.

Code: Select all

#region File Description
//-----------------------------------------------------------------------------
// Author: JCBDigger
// URL: http://Games.DiscoverThat.co.uk
//-----------------------------------------------------------------------------
// To extract the individual polygons from a model class
//-----------------------------------------------------------------------------
// Based on articles and samples including:
//  http://www.enchantedage.com/node/30
//  http://www.ziggyware.com/readarticle.php?article_id=248
//  http://rhysyngsun.spaces.live.com/Blog/cns!FBBD62480D87119D!129.entry
// Modifies for XNA 4 from the following:
//  http://forums.create.msdn.com/forums/p/62209/382778.aspx
//  http://forums.xna.com/forums/t/58238.aspx
//  http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.content.pipeline.graphics.geometrycontent.aspx
//  Each mesh part has it's own VertexBuffer instead of one for the whole model.
//  http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.graphics.modelmeshpart_members.aspx
//  http://blogs.msdn.com/b/shawnhar/archive/2010/04/19/vertex-data-in-xna-game-studio-4-0.aspx
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using BEPUphysics;
using BEPUphysics.DataStructures;
using BEPUphysics.Entities;

#endregion

namespace Engine
{
    public static class VertexHelper
    {               

        /// <summary>
        /// Extract the vertices and indices from the specified model
        /// </summary>
        /// <param name="vertices">Output the list of vertices</param>
        /// <param name="indices">Output the list of indices</param>
        /// <param name="worldPosition">The models world position or use Matrix.Identity for object space</param>
        public static void ExtractTrianglesFrom(Model modelToUse, List<StaticTriangleGroup.StaticTriangleGroupVertex> vertices, List<int> indices, Matrix worldPosition)
        {
            Matrix transform = Matrix.Identity;
            foreach (ModelMesh mesh in modelToUse.Meshes)
            {
                // If the model has bones the vertices have to be transformed by the bone position
                transform = Matrix.Multiply(GetAbsoluteTransform(mesh.ParentBone), worldPosition);
                ExtractModelMeshData(mesh, ref transform, vertices, indices);
            }
        }

        /// <summary>
        /// Transform by a bone position or Identity if no bone is supplied
        /// </summary>
        public static Matrix GetAbsoluteTransform(ModelBone bone)
        {
            if (bone == null)
            {
                return Matrix.Identity;
            }
            return bone.Transform * GetAbsoluteTransform(bone.Parent);
        }

        /// <summary>
        /// Get all the triangles from all mesh parts
        /// </summary>
        public static void ExtractModelMeshData(ModelMesh mesh, ref Matrix transform,
             List<StaticTriangleGroup.StaticTriangleGroupVertex> vertices, List<int> indices)
        {
            foreach (ModelMeshPart meshPart in mesh.MeshParts)
            {
                ExtractModelMeshPartData(meshPart, ref transform, vertices, indices);
            }
        }

        /// <summary>
        /// Get all the triangles from each mesh part (Changed for XNA 4)
        /// </summary>
        public static void ExtractModelMeshPartData(ModelMeshPart meshPart, ref Matrix transform,
           List<StaticTriangleGroup.StaticTriangleGroupVertex> vertices, List<int> indices)
        {
            // Before we add any more where are we starting from
            int offset = vertices.Count;

            // == Vertices (Changed for XNA 4.0)

            // Read the format of the vertex buffer
            VertexDeclaration declaration = meshPart.VertexBuffer.VertexDeclaration;
            VertexElement[] vertexElements = declaration.GetVertexElements();
            // Find the element that holds the position
            VertexElement vertexPosition = new VertexElement();
            foreach (VertexElement vert in vertexElements)
            {
                if (vert.VertexElementUsage == VertexElementUsage.Position &&
                    vert.VertexElementFormat == VertexElementFormat.Vector3)
                {
                    vertexPosition = vert;
                    // There should only be one
                    break;
                }
            }
            // Check the position element found is valid
            if (vertexPosition == null ||
                vertexPosition.VertexElementUsage != VertexElementUsage.Position ||
                vertexPosition.VertexElementFormat != VertexElementFormat.Vector3)
            {
                throw new Exception("Model uses unsupported vertex format!");
            }
            // This where we store the vertices until transformed
            Vector3[] allVertex = new Vector3[meshPart.NumVertices];
            StaticTriangleGroup.StaticTriangleGroupVertex[] allSTGVertex = new StaticTriangleGroup.StaticTriangleGroupVertex[meshPart.NumVertices];
            // Read the vertices from the buffer in to the array
            meshPart.VertexBuffer.GetData<Vector3>(
                meshPart.VertexOffset * declaration.VertexStride + vertexPosition.Offset,
                allVertex,
                0,
                meshPart.NumVertices,
                declaration.VertexStride);
            // Transform them based on the relative bone location and the world if provided
            for (int i = 0; i != allVertex.Length; ++i)
            {
                Vector3.Transform(ref allVertex[i], ref transform, out allVertex[i]);
                allSTGVertex[i] = new StaticTriangleGroup.StaticTriangleGroupVertex(allVertex[i]);
            }
            // Store the transformed vertices with those from all the other meshes in this model
            vertices.AddRange(allSTGVertex);

            // == Indices (Changed for XNA 4)

            // Find out which vertices make up which triangles
            if (meshPart.IndexBuffer.IndexElementSize != IndexElementSize.SixteenBits)
            {
                // This could probably be handled by using int in place of short but is unnecessary
                throw new Exception("Model uses 32-bit indices, which are not supported.");
            }
            // Each primitive is a triangle
            short[] indexElements = new short[meshPart.PrimitiveCount * 3];
            meshPart.IndexBuffer.GetData<short>(
                meshPart.StartIndex * 2,
                indexElements,
                0,
                meshPart.PrimitiveCount * 3);
            // Each TriangleVertexIndices holds the three indexes to each vertex that makes up a triangle
            int[] tvi = new int[meshPart.PrimitiveCount*3];
            for (int i = 0; i != tvi.Length; ++i)
            {
                // The offset is becuase we are storing them all in the one array and the 
                // vertices were added to the end of the array.
                tvi[i] = indexElements[i] + offset;
                /*tvi[i].B = indexElements[i * 3 + 1] + offset;
                tvi[i].C = indexElements[i * 3 + 2] + offset;*/
            }
            // Store our triangles
            indices.AddRange(tvi);
        }


    }
}
Norbo
Site Admin
Posts: 4929
Joined: Tue Jul 04, 2006 4:45 am

Re: Character Controller Setup Question

Post by Norbo »

Just a quick warning to anyone targeting phone hardware; it looks like that code uses the VertexBuffer.GetData which has a bug on the WP7 device (at least as of my most recent tests). The engine used a method very similar to that in the past, but it was changed to be consistent across all platforms.

But if it works better for your target platform, go for it :)

I'll see if I can find what was causing the convenience method to silently fail later.
Post Reply