[Box2D][LibGDX] Raycasting bug?

Hi!

I’m messing around with box2d trying to implement a LoS algorithm by casting a ray (from the mouse for now) to every edge point of the box2d bodies.
If the fraction of the RayCastCallback is 1.0 meaning that the ray hit the target without collision then I’ll do another raycast with the starting position as the last raycast’s end position and a random distance in the given direction as the end point.
It is working fine mostly but there are a few cases where the ray ignores collision with certain bodies for no reason.

Here is a picture of the problem:
(The yellow circles are the end points after 1 or 2 raycasts, the blue circle is where the bug occoured. If you trace that ray back to the start you’ll see that it doesnt register collision with the 1st body)

Eventhough it’s messy because it’s only a sandbox project I’ll provide source code.

public class RayCast extends ApplicationAdapter {
	public static final float PPM = 64f;

	OrthographicCamera cam, b2;

	SpriteBatch batch;
	Box2DDebugRenderer dr;
	ShapeRenderer sr;

	World world;
	RayCastCallback rccb;

	Vector2 start;
	Vector2 end;
	Array<Vector2> endPoints;
	Array<Vector2> finalEndPoints;

	@Override
	public void create() {
		batch = new SpriteBatch();

		world = new World(new Vector2(0, 0), false);
		dr = new Box2DDebugRenderer();
		sr = new ShapeRenderer();

		b2 = new OrthographicCamera();
		cam = new OrthographicCamera();

		b2.setToOrtho(false, Gdx.graphics.getWidth() / PPM, Gdx.graphics.getHeight() / PPM);
		cam.setToOrtho(false, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());

		endPoints = new Array<Vector2>();
		finalEndPoints = new Array<Vector2>();

		//		float w = Gdx.graphics.getWidth() / PPM;
		//		float h = Gdx.graphics.getHeight() / PPM;
		//
		//		finalEndPoints.add(new Vector2(0, 0));
		//		finalEndPoints.add(new Vector2(0, h));
		//		finalEndPoints.add(new Vector2(w, h));
		//		finalEndPoints.add(new Vector2(w, 0));

		// Left
		createBody(16, Gdx.graphics.getHeight() / 2, 32, Gdx.graphics.getHeight(), false);
		// Right
		createBody(Gdx.graphics.getWidth() - 16, Gdx.graphics.getHeight() / 2, 32, Gdx.graphics.getHeight(), false);
		// Top
		createBody(Gdx.graphics.getWidth() / 2, Gdx.graphics.getHeight() - 16, Gdx.graphics.getWidth() - 66, 32, false);
		// Bottom
		createBody(Gdx.graphics.getWidth() / 2, 16, Gdx.graphics.getWidth() - 66, 32, false);

		//		createBody(800, 400, 64, 64, true);
		//		createBody(500, 300, 128, 64, true);
		//		createBody(150, 200, 64, 64, true);
		//		createBody(450, 500, 64, 64, true);
		//		createBody(200, 100, 256, 64, true);
		//
		//		createBody(200, 600, 64, 64, true);
		//		createBody(274, 600, 64, 64, true);
		createBody(348, 600, 64, 64, true);
		createBody(422, 600, 64, 64, true);
		start = new Vector2(2, 2);
		end = new Vector2(2, 2);
		tmp = new Vector2();

		for(int i = 0; i < finalEndPoints.size; i++) {
			endPoints.add(new Vector2());
		}

		rccb = new RayCastCallback() {
			@Override
			public float reportRayFixture(Fixture fixture, Vector2 point, Vector2 normal, float fraction) {
				if(fraction == 1.0f) {
					newCast = true;
				}
				;
				endPoints.get(index).set(point);
				return fraction;
			}
		};
	}

	int index = 0;
	boolean newCast = false;
	Vector2 tmp;

	@Override
	public void render() {
		if(Gdx.input.isKeyJustPressed(Keys.ESCAPE))
			Gdx.app.exit();

		Gdx.gl.glClearColor(0, 0, 0, 1);
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

		float mx = Gdx.input.getX() / PPM;
		float my = (Gdx.graphics.getHeight() - Gdx.input.getY()) / PPM;
		start.set(mx, my);

		sr.begin(ShapeType.Line);
		for(index = 0; index < finalEndPoints.size; index++) {
			// First raycast
			world.rayCast(rccb, start, finalEndPoints.get(index));

			tmp.set(endPoints.get(index));
			float angle = (float) (Math.atan2(tmp.y - start.y, tmp.x - start.x));
			angle *= MathUtils.radiansToDegrees;
			if(angle < 0)
				angle += 360;

			// 0, 0
			if(angle > 0 && angle < 90 && index % 4 == 0)
				newCast = false;
			// 0, 1
			else if(angle > 270 && angle < 360 && index % 4 == 1)
				newCast = false;
			// 1, 1
			else if(angle > 180 && angle < 270 && index % 4 == 2)
				newCast = false;
			// 1, 0
			else if(angle > 90 && angle < 180 && index % 4 == 3)
				newCast = false;

			if(!newCast) {
				//				sr.setColor(Color.WHITE);
				//				sr.line(start.x * PPM, start.y * PPM, endPoints.get(index).x * PPM, endPoints.get(index).y * PPM);
			}
			else {
				newCast = false;
				angle *= MathUtils.degreesToRadians;

				// 25 * tileSize distance for the ray cast target 
				float x = (float) Math.cos(angle) * 25f;
				float y = (float) Math.sin(angle) * 25f;

				tmp.add(x, y);
				world.rayCast(rccb, finalEndPoints.get(index), tmp);

				sr.setColor(Color.YELLOW);
				sr.line(finalEndPoints.get(index).x * PPM, finalEndPoints.get(index).y * PPM, endPoints.get(index).x * PPM, endPoints.get(index).y * PPM);

			}
		}
		sr.end();

		dr.render(world, b2.combined);

		sr.begin(ShapeType.Line);
		for(int i = 0; i < endPoints.size; i++) {

			// Start -> end
			sr.setColor(Color.RED);
			sr.line(start.x * PPM, start.y * PPM, endPoints.get(i).x * PPM, endPoints.get(i).y * PPM);
			sr.setColor(Color.YELLOW);
			sr.circle(endPoints.get(i).x * PPM, endPoints.get(i).y * PPM, 10);
		}
		sr.end();

	}

	public Body createBody(float x, float y, float w, float h, boolean add) {
		Body body;
		w /= 2;
		w /= PPM;
		h /= 2;
		h /= PPM;
		x /= PPM;
		y /= PPM;

		if(add) {
			finalEndPoints.add(new Vector2(x - w, y - h));
			finalEndPoints.add(new Vector2(x - w, y + h));
			finalEndPoints.add(new Vector2(x + w, y + h));
			finalEndPoints.add(new Vector2(x + w, y - h));
		}

		BodyDef def = new BodyDef();
		def.type = BodyType.DynamicBody;
		def.fixedRotation = false;
		def.gravityScale = 1f;
		def.bullet = true;
		body = world.createBody(def);

		PolygonShape poly = new PolygonShape();
		poly.setAsBox(w, h);
		body.createFixture(poly, 1);

		body.setTransform(x, y, 0);

		return body;
	}
}

Any help would be greatly appriciated! :wink:

Maybe this is more trouble than you want to go to, but it might make this easier to debug. I’m wondering if you can narrow this down to a single segment-vs.-box test that fails consistently. In your second image, for example, it’d be a segment from the start point in the lower left to the upper-left corner of box 2, tested against box 1 (that appears to be what’s failing, if I’m not mistaken). A self-contained example with a test case that always fails might make this a little easier to dig into.

I know what you mean, and I have tried it and that’s the thing. Its still there and I cant figure out why. By the way it’s not only affecthing that single cornber, because if I have multiple bodies it’s still wrong at some raycasts. Here is the same scenario with multiple bodies. (I didn’t hightlight anything on this picture)

from what I remember from box2d, the raycastcallback is called as many times as there is a hit detected, this way it gives you all the collisions between two points. Looking at your code it seems that you assume that it will call the callback only once on the first collision detected. The callback is called multiple times in no specific order, meaning that sometime the last call you will get will be the first collision, and sometimes the last … random :slight_smile:

Gosh, I feel so stupid now. I inserted one boolean in my callback and it fixed it. :smiley:
Thanks for the help!