Search code examples
c++sfmltransformation-matrix

How to turn three square sprites into a cube?


I would like to achieve this effect by combining three sprites (two of which are transformed by shearing and scaling):

Cube

sf::Transform createShearTransform(float shearX, float shearY) {
    sf::Transform transform;
    transform = sf::Transform(
        1, shearX, 0.f,
        shearY, 1, 0.f,
        0.f, 0.f, 1.f
    );
    return transform;
}

Unfortunately, I have a problem with this and I don't know how I could create this and then smoothly move the entire cube.

Some code:

#include <SFML/Graphics.hpp>

sf::Transform createShearTransform(float shearX, float shearY) {

    sf::Transform transform;
    transform = sf::Transform(
        1, shearX, 0.f,
        shearY, 1, 0.f,
        0.f, 0.f, 1.f
    );
    return transform;
}

int main() {
    sf::Texture texture; texture.loadFromFile("tex.png");
    sf::Sprite front(texture);
    sf::Sprite top(texture);
    sf::Sprite right(texture);

    float offset = 64 + 32;
    float size = 64;

    top  .setPosition({ offset       , offset        });
    front.setPosition({ offset       , offset + size });
    right.setPosition({ offset + size, offset + size });

    sf::Transform shear1 = createShearTransform(-1, 0);
    sf::Transform shear2 = createShearTransform(0, -1);
    sf::RenderWindow window( sf::VideoMode(800, 800), "" );
    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed)
                window.close();
        }

        auto mousepos = sf::Vector2f(sf::Mouse::getPosition(window));
        front.setPosition(mousepos);
        right.setPosition(mousepos);
        top.setPosition(mousepos);

        window.clear();
        window.draw(front);
        sf::Transform f1;
        f1.translate({ 64, -128 });
        sf::Transform s1;
        s1.scale({ 1, 0.75 });
        sf::Transform s2;
        s2.scale({ 0.75 , 1 });
        sf::Transform f2;
        f2.translate({ 16, -60 });
        window.draw(top, f1 * top.getTransform() * shear1 * s1);
        window.draw(right, f2 * right.getTransform() * shear2 * s2);
        window.display();
    }
}

Solution

  • Operations of scaling and shearing should be done when origin of sprite has (0,0) coordinate. Now it doesn't happen like this, because when you call window.draw transformation matrix of sprite (that equals to translation matrix set by setPosition(mousepos) invocation) is combined by the matrix passed as second argument of draw method. So you get such order of operations:

    f1 * top.getTransform() * shear1 * s1 * SpriteTranslation
    

    what leads to wrong results. RenderStates states it clearly:

    The transform is a special case: sprites, texts and shapes (and it's a good idea to do it with your own drawable classes too) combine their transform with the one that is passed in the RenderStates structure. So that you can use a "global" transform on top of each object's transform.

    To turn back effect of translation set by setPosition use getInverseTransform. Then make scaling/shearing (its origin will be (0,0) coordinate) and finally you can apply translation to position sprites at mouse pos.

        sf::Transform topScale;
        topScale.scale({1, 0.75});
        
        sf::Transform topTransform;
        topTransform.translate(topScale.transformPoint( 0, -size));
        topTransform.translate(mousepos);
        topTransform.translate(topScale.transformPoint(0,size)); 
        topTransform.combine(shear1);
        topTransform.translate(topScale.transformPoint(0,-size));
        topTransform.combine(topScale);
        topTransform.combine(top.getInverseTransform());
    
        sf::Transform rightScale;
        rightScale.scale({1., 1.});
        
        sf::Transform rightTransform;
        rightTransform.translate({size,0});
        rightTransform.translate(mousepos);
        rightTransform.combine(shear2);
        rightTransform.combine(rightScale);
        rightTransform.combine(right.getInverseTransform());
        
        window.draw(top, topTransform);
        window.draw(right, rightTransform);
    

    Remarks to top: before shearing, sprite is position so that its leftBottom is (0,0). Because in this case Xaxis shear is performed, all translation are done with scaled Ysize value of texture (size).

    FinaEffect