4
\$\begingroup\$

I'm casting a ray in Box2D using b2World.rayCast how can I get the normal of the wall which was hit? Or better yet - the line start and end points?

The information returned is: Fixture (fixture hit, and parent body)
Point ( Location of hit )
Normal (Normal pointing at new direction of ray - not the normal of the edge hit)
Fraction (0.0 - 1.0) of the point location along the array

However lets say I wanted to place something along the edge hit, previously I just iterated through the segments and did a line intersection test. However if the body is rotated this does not work.

How can I retrieve the line/edge information for the edge the ray hits with. For example, if i wanted to place texture along the wall or some kind of decal.

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

Here's what I ended up doing incase anyone else stumbled onto this.
This implementation will only work on rectangular shapes. Since those are stored in the same order in the shape every time. Im sure there's a way to figure out what the vertices map to in a custom shape though as well, but I don't know it.

After you perform the raycast, and retrieve the fixture information from box2d, perform your own a line-segment hit test on each of the edges in the fixture, first rotate the lines by the angle of the b2Body.

To perform this raytest, create a linesegment represented from your player to the point recieved from the raycast. If you like you can extrapolate it a tiny tiny bit more, just to make sure it connects.

Create 4 line segments from the fixture such that:

     /**
         * Creates a linesegment between points A and B and offsets them by a point
         * @param offset
         * @param a
         * @param b
         */
        FROM_VECTORS_AND_OFFSET: function( offset, a, b ) {
            var pointA = new b2Vec2(offset.x + a.x, offset.y + a.y);
            var pointB = new b2Vec2(offset.x + b.x, offset.y + b.y);
            return new LineSegment(pointA, pointB);
        },


        /**
         * Creates an array lines from a b2Body
         * @param {Box2D.Dynamics.b2Body} aBody
         * @return {Array} An array of lines
         */
        FROM_BODY: function( aBody ) {
            var bodyPos = aBody.GetPosition();
            var shape = aBody.GetFixtureList().GetShape();

            var lineA = FROM_VECTORS_AND_OFFSET( bodyPos, shape.m_vertices[0], shape.m_vertices[1] );
            var lineB = FROM_VECTORS_AND_OFFSET( bodyPos, shape.m_vertices[1], shape.m_vertices[2] );
            var lineC = FROM_VECTORS_AND_OFFSET( bodyPos, shape.m_vertices[2], shape.m_vertices[3] );
            var lineD = FROM_VECTORS_AND_OFFSET( bodyPos, shape.m_vertices[3], shape.m_vertices[0] );

            return [lineA, lineB, lineC, lineD];
        },

Rotate each line by the angle of the body such that:

rotateLinesegment: function(pointA, pointB, angle) {

    // Find the mid-point between A and B - i make a copy of pointA just to be safe
    var center = pointA.Copy();
    center.Add( pointB );
    center.Multiply( 0.5 );

    var na = pointA.Copy();
    na.Subtract(center);

    var nb = pointB.Copy();
    nb.Subtract(center);

    // Rotate each component in the vector by angle
    var rotateVector = function( v, angle ) {
        var s = Math.sin(angle);
        var c = Math.cos(angle);

        var nx = c * v.x - s * v.y;
        var ny = s * v.x + c * v.y;

        v.x = nx;
        v.y = ny;
    };

    // Rotate, then untranslate it back to center
    rotateVector(na, angle);
    na.Add( center );

    // Rotate, then untranslate it back to center
    rotateVector(nb, angle);
    nb.Add( center );

    pointA = na;
    pointB = nb;

    return {pointA: pointA, pointB: pointB}
}

Perform a line-line intersection on each line.

/**
* Intersects two lines
* @param line1
* @param line2
* @return Array of points where the lines intersect
*/
INTERSECT_LINES: function( line1, line2 ) {
    function intersectLineLine(a1, a2, b1, b2) {
        var result = [];
        var ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x);
        var ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x);
        var u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);

        if (u_b != 0) {
            var ua = ua_t / u_b;
            var ub = ub_t / u_b;


            // See if T is between 0.0 - 1.0
            if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
                var x = a1.x + ua * (a2.x - a1.x);
                var y = a1.y + ua * (a2.y - a1.y);
                var point = new Box2D.Common.Math.b2Vec2(x, y);
                result.push(point);
            } else { // Lines are coincident... count anyway?

            }
        } else {
        }
        return result;
}

return intersectLineLine(line1._a, line1._b, line2._a, line2._b);
}

     // Somewhere after creating the lines in the raycast call
 for(var i = 0; i < lines.length; i++) {
    var aLine = lines[i];
    aLine.rotate( otherBody.GetAngle() );
    var result = INTERSECT_LINES( playerToTracer, aLine );
    if(result) {
        contactLine = aLine
    }
}

After all these steps, you will have the information about which line you collided with in the fixture.

\$\endgroup\$
2
\$\begingroup\$

Firstly, the normal returned by the raycast should in fact be the normal of the fixture hit, at the point it was hit - maybe something is going wrong if this is not the case.

If you just want to attach something to the fixture that was hit, at the correct location and orientation, you should be able to do it like this:

b2Fixture* hitFixture; //from raycast result
b2Vec2 hitPoint, hitNormal; //from raycast result

b2Vec2 bodyCoordOfHitPoint = hitFixture->GetBody()->GetLocalPoint( hitPoint );
b2Vec2 bodyCoordOfHitNormal = hitFixture->GetBody()->GetLocalVector( hitNormal );

Thereafter you can draw the decal using this body coord position, transformed by the current body transform. This would be enough if you just want to display something 'stuck onto' the fixture like you mentioned in the first post.

However if you really need to know which edge of the fixture was hit for some reason, then yeah you will need to do something more, like what you did. Since the raycast already knows which side of the polygon was hit, you could probably alter the Box2D source code to pass this back as part of the raycast results if you were concerned about efficiency.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .