Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: SFML Separating Axis Theorem testing  (Read 3927 times)

0 Members and 1 Guest are viewing this topic.

AveryBibeau

  • Newbie
  • *
  • Posts: 7
    • View Profile
SFML Separating Axis Theorem testing
« on: July 01, 2013, 12:29:49 am »
I've put together a small implementation of the Separate Axis Theorem in SFML with VertexArrays but wanted to see if anyone could look it over. I'm pretty good with C++ but by no means a master so I'd love to know how I could improve this. It is an implementation of the ActionScript3 version found here :
http://rocketmandevelopment.com/2010/05/19/separation-of-axis-theorem-for-collision-detection/

My code:

Object.h
class Object : public sf::Drawable, public sf::Transformable
{
public:
        Object() : shape( sf::TrianglesStrip, 6 ), verts( 4 )
        {
                shape[0].position = sf::Vector2f( 0, 0 );
                shape[1].position = sf::Vector2f( 100, 0 );
                shape[2].position = sf::Vector2f( 0, 50 );
                shape[3].position = sf::Vector2f( -50, 0 );
                shape[4].position = sf::Vector2f( 0, -50 );
                shape[5].position = sf::Vector2f( 100, 0 );

                for( int i = 0; i < 4; i++ )
                {
                        verts[i].setRadius( 4 );
                        verts[i].setFillColor( sf::Color::Red );
                        verts[i].setOrigin( verts[i].getGlobalBounds().width/2, verts[i].getGlobalBounds().height/2 );
                        verts[i].setPosition( shape[i+1].position );
                }
        }
        sf::VertexArray& getShape()
        {return shape;}
private:
        virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const
    {
                states.transform *= getTransform();
                target.draw( shape, states);
                for( int i = 0; i < verts.size(); i++ )
                {
                        target.draw( verts[i], states);
                }
    }
        sf::VertexArray shape;
        std::vector<sf::CircleShape> verts;
};

Functions.h
sf::Vector2f normalize( sf::Vector2f& input )
{
        if( sqrt(input.x*input.x + input.y*input.y) == 0 )
        {
                input.x = 1;
                return input;
        }
        float length = sqrt(input.x*input.x + input.y*input.y);

        input.x /= length;
        input.y /= length;
        return input;
}

sf::Vector2f getNormalAxis( sf::VertexArray& shape, int index )
{
        sf::Vector2f vector1 = shape[index].position;
        sf::Vector2f vector2;
        if( index >= shape.getVertexCount() - 1 )
                vector2 = shape[0].position;
        else
                vector2 = shape[index+1].position;

        sf::Vector2f normalAxis( -(vector2.y - vector1.y), vector2.x - vector1.x );
        normalAxis = normalize( normalAxis );
        return normalAxis;
}

float dotProduct( sf::Vector2f& vector1, sf::Vector2f& vector2 )
{
        return vector1.x*vector2.x + vector1.y*vector2.y;
}

bool sat( Object& shape1, Object& shape2 )
{
        sf::Vector2f vectorOffset( shape1.getPosition().x - shape2.getPosition().x, shape1.getPosition().y - shape2.getPosition().y );

        for( int i = 0; i < shape1.getShape().getVertexCount(); i++ )
        {
                sf::Vector2f axis = getNormalAxis( shape1.getShape(), i );

                float min1 = dotProduct( axis, shape1.getShape()[0].position );
                float max1 = min1;

                for( int j = 1; j < shape1.getShape().getVertexCount(); j++ )
                {
                        float testNum = dotProduct( axis, shape1.getShape()[j].position );
                        if( testNum < min1 )
                                min1 = testNum;
                        if( testNum > max1 )
                                max1 = testNum;
                }

                float min2 = dotProduct( axis, shape2.getShape()[0].position );
                float max2 = min2;

                for( int j = 1; j < shape2.getShape().getVertexCount(); j++ )
                {
                        float testNum = dotProduct( axis, shape2.getShape()[j].position );
                        if( testNum < min2 )
                                min2 = testNum;
                        if( testNum > max2 )
                                max2 = testNum;
                }

                float offset = dotProduct( axis, vectorOffset );
                min1 += offset;
                max1 += offset;

                float test1 = min1 - max2;
                float test2 = min2 - max1;

                if( test1 > 0 || test2 > 0 )
                        return 0;
        }
        return 1;
}

My basic implementation just uses two shapes that are the same, one of which is controlled by the mouse. It seems to work fine so far; I haven't tested it extensively with more complex objects/numbers. So I wanted to see if anyone with experience in this area can provide some tips in terms of improving this. My next step is to of course calculate resultant vectors and implement this with freely moving objects.

Edit: On an unrelated note, does anyone have an argument for using TriangleStrips over TriangleFans or vice versa?
« Last Edit: July 01, 2013, 02:10:04 am by AveryBibeau »

AveryBibeau

  • Newbie
  • *
  • Posts: 7
    • View Profile
Re: SFML Separating Axis Theorem testing
« Reply #1 on: July 01, 2013, 04:57:44 am »
I found a big problem with this being used with sf::Transformable classes, it doesn't work with them. I'm not sure if there's a way to apply transformations relatively to a VertexArray automatically but I tried implementing this code:

bool sat( Object& shape1, Object& shape2 )
{
        sf::Vector2f vectorOffset( shape1.getPosition().x - shape2.getPosition().x, shape1.getPosition().y - shape2.getPosition().y );

        sf::VertexArray newshape1( sf::TrianglesFan, shape1.getShape().getVertexCount() );

        for( int i = 0; i < shape1.getShape().getVertexCount(); i++ )
        {
                float length = sqrt( shape1.getShape()[i].position.x*shape1.getShape()[i].position.x + shape1.getShape()[i].position.y*shape1.getShape()[i].position.y );
                newshape1[i].position.x = length*cos( (shape1.getRotation()*(PI/180) ) + atan2( shape1.getShape()[i].position.y, shape1.getShape()[i].position.x ) );
                newshape1[i].position.y = length*sin( (shape1.getRotation()*(PI/180) ) + atan2( shape1.getShape()[i].position.y, shape1.getShape()[i].position.x ) );
                //std::cout << newshape1[i].position.x << " " << newshape1[i].position.y << std::endl;
        }
        //std::cout << std::endl;

        for( int i = 0; i < shape1.getShape().getVertexCount(); i++ )
        {
                sf::Vector2f axis = getNormalAxis( newshape1, i );

                float min1 = dotProduct( axis, newshape1[0].position );
                float max1 = min1;

                for( int j = 1; j < shape1.getShape().getVertexCount(); j++ )
                {
                        float testNum = dotProduct( axis, newshape1[j].position );
                        if( testNum < min1 )
                                min1 = testNum;
                        if( testNum > max1 )
                                max1 = testNum;
                }

                float min2 = dotProduct( axis, shape2.getShape()[0].position );
                float max2 = min2;

                for( int j = 1; j < shape2.getShape().getVertexCount(); j++ )
                {
                        float testNum = dotProduct( axis, shape2.getShape()[j].position );
                        if( testNum < min2 )
                                min2 = testNum;
                        if( testNum > max2 )
                                max2 = testNum;
                }

                float offset = dotProduct( axis, vectorOffset );
                min1 += offset;
                max1 += offset;

                float test1 = min1 - max2;
                float test2 = min2 - max1;

                if( test1 > 0 || test2 > 0 )
                        return 0;

                //std::cout << i << std::endl;
        }
        return 1;
}
 

It sort of works, the rotation is somewhat detected but there are sometimes it detects collisions when one hasn't occurred and I'm not sure if it's a decimal error (unlikely?) or something else