I have this very weird problem with a C# port of the Box2D physics engine.
I'm basically using the C# port called Box2DX (originally hosted here: https://code.google.com/archive/p/box2dx/ and exported to this GitHub repo: https://github.com/colgreen/box2dx)
Now, the thing is that raycasting works as expected ONLY if all dynamic bodies are in certain positions in the simulated physics world. It is hard to explain, so I created a test class in the TestBed project that comes with the engine in order to precisely re-create the problem.
I set up the world to contain 3 static bodies to represent the ground. I also added a large box to represent the "player" character and a small box. Both are dynamic bodies with a density of 1.
In this image you can see the setup with the raycasting not picking up any of the fixtures (ray represented by red line starting in midair and ending inside the large ground fixture). It should detect the box or at the very least the static ground fixture.
Now, the weird part is, that the raycasting DOES work, the moment the "player" body is positioned "above" the raycast's origin, as seen here:
The same thing happens, whenever the "box" body is moved above the ray.
Notice that I'm also casting three blue rays from the bottom part of the "player" body straight down. These rays also behave strangely. In the first image above, you can see that all three rays detect the ground fixture properly. However, the moment the player's AABB is positioned as in the following image, the raycasts won't work anymore:
Basically, the blue rays stop working the moment the player's AABB is moved over the left edge of any body it stands on.
I've been fiddling with this for countless hours and I can't figure out what causes this strange behavior. I'm at a point where I think it is simply a bug in the world querying within the actual C# port and has nothing to do with my particular setup.
For reference, here is the code of my custom test class from the TestBed project:
public class CustomRayCastTest : Test
{
public static CustomRayCastTest Create() => new CustomRayCastTest();
private Body _player;
public CustomRayCastTest()
{
_world.Gravity = new Box2DX.Common.Vec2(0, -6.25f);
BodyDef islandBodyDefA = new BodyDef
{
AllowSleep = false,
Angle = 0,
FixedRotation = true,
IsBullet = false,
//UserData = new object(),
IsSleeping = false,
Position = new Box2DX.Common.Vec2(1, 15)
//Position = new Box2DX.Common.Vec2(1, -17)
};
PolygonDef polygonDefIslandA = new PolygonDef
{
Density = 0,
Friction = 0.4f,
IsSensor = false,
Restitution = 0
};
polygonDefIslandA.SetAsBox(6, 3);
Body bodyIslandA = _world.CreateBody(islandBodyDefA);
Shape shapeIslandA = bodyIslandA.CreateShape(polygonDefIslandA);
bodyIslandA.SetMassFromShapes();
BodyDef islandBodyDefB = new BodyDef
{
AllowSleep = false,
Angle = 0,
FixedRotation = true,
IsBullet = false,
//UserData = new object(),
IsSleeping = false,
Position = new Box2DX.Common.Vec2(12, 2)
//Position = new Box2DX.Common.Vec2(12, -30)
};
PolygonDef polygonDefIslandB = new PolygonDef
{
Density = 0,
Friction = 0.4f,
IsSensor = false,
Restitution = 0
};
polygonDefIslandB.SetAsBox(9, 4);
Body bodyIslandB = _world.CreateBody(islandBodyDefB);
Shape shapeIslandB = bodyIslandB.CreateShape(polygonDefIslandB);
bodyIslandB.SetMassFromShapes();
BodyDef islandBodyDefC = new BodyDef
{
AllowSleep = false,
Angle = 0,
FixedRotation = true,
IsBullet = false,
//UserData = new object(),
IsSleeping = false,
Position = new Box2DX.Common.Vec2(32, 15)
//Position = new Box2DX.Common.Vec2(32, -17)
};
PolygonDef polygonDefIslandC = new PolygonDef
{
Density = 0,
Friction = 0.4f,
IsSensor = false,
Restitution = 0
};
polygonDefIslandC.SetAsBox(6, 3);
Body bodyIslandC = _world.CreateBody(islandBodyDefC);
Shape shapeIslandC = bodyIslandC.CreateShape(polygonDefIslandC);
bodyIslandC.SetMassFromShapes();
// Box
BodyDef boxBodyDef = new BodyDef
{
AllowSleep = false,
Angle = 0,
FixedRotation = true,
IsBullet = false,
//UserData = new object(),
IsSleeping = false,
Position = new Box2DX.Common.Vec2(18, 25)
//Position = new Box2DX.Common.Vec2(18, -1)
};
PolygonDef boxDef = new PolygonDef
{
Density = 1,
Friction = 0.2f,
IsSensor = false,
Restitution = 0
};
boxDef.SetAsBox(1, 1);
Body boxBody = _world.CreateBody(boxBodyDef);
Shape boxShape = boxBody.CreateShape(boxDef);
boxBody.SetMassFromShapes();
// Player
BodyDef playerBodyDef = new BodyDef
{
AllowSleep = false,
Angle = 0,
FixedRotation = true,
IsBullet = false,
//UserData = new object(),
IsSleeping = false,
Position = new Box2DX.Common.Vec2(5, 21)
//Position = new Box2DX.Common.Vec2(5, -4)
};
PolygonDef playerShapeDef = new PolygonDef
{
Density = 1,
Friction = 0.2f,
IsSensor = false,
Restitution = 0
};
playerShapeDef.SetAsBox(1, 2);
Body playerBody = _world.CreateBody(playerBodyDef);
Shape playerShape = playerBody.CreateShape(playerShapeDef);
playerBody.SetMassFromShapes();
_player = playerBody;
}
public override void Keyboard(Keys key)
{
base.Keyboard(key);
if(key == Keys.D)
{
_player.ApplyForce(new Vec2(7f * _player.GetMass(), 0), _player.GetWorldPoint(Vec2.Zero));
}
else if(key == Keys.A)
{
_player.ApplyForce(new Vec2(-7f * _player.GetMass(), 0), _player.GetWorldPoint(Vec2.Zero));
}
if(key == Keys.W)
{
_player.ApplyImpulse(new Vec2(0, _player.GetMass() * 20f), _player.GetWorldPoint(Vec2.Zero));
}
}
public override void Step(Settings settings)
{
base.Step(settings);
Vec2 rayStart = new Vec2(22, 40);
Vec2 rayEnd = new Vec2(17, 1);
Segment ray = new Segment
{
P1 = rayStart,
P2 = rayEnd
};
var shape = _world.RaycastOne(ray, out float lambda, out Vec2 normal, false, null);
if(shape != null)
{
Vec2 dir = rayEnd - rayStart;
rayEnd = rayStart + dir * lambda;
}
_debugDraw.DrawSegment(rayStart, rayEnd, new Color(1, 0, 0));
CastPlayerRays();
}
private void CastPlayerRays()
{
const int rayCount = 3;
const float playerWidth = 2;
const float playerHeight = 4;
const float inset = 0.2f;
const float rayLength = 2;
for (int i = 0; i < rayCount; i++)
{
Vec2 origin = new Vec2(-(playerWidth / 2 - inset), -(playerHeight / 2 - inset));
origin.X += i * (playerWidth / (rayCount));
origin = _player.GetWorldPoint(origin);
Vec2 end = origin + new Vec2(0, -1) * rayLength;
var shape = _world.RaycastOne(new Segment { P1 = origin, P2 = end }, out float lambda, out Vec2 normal, false, null);
if (shape != null)
{
Vec2 dir = end - origin;
end = origin + dir * lambda;
}
_debugDraw.DrawSegment(origin, end, new Color(0, 1, 1));
}
}
}
Has anyone ever experienced this problem? I have no idea how to solve this. I need the raycasts to work reliably for my game project. Any help would be much appreciated.
Thank you! :)