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

Author Topic: Broken TexCoords when zooming sf::View  (Read 5117 times)

0 Members and 1 Guest are viewing this topic.

Glocke

  • Sr. Member
  • ****
  • Posts: 289
  • Hobby Dev/GameDev
    • View Profile
Broken TexCoords when zooming sf::View
« on: June 18, 2016, 11:59:40 am »
Hi, I use a tileset texture with multiple tiles on it and sometimes - when the view is zoomed - a line from the next tile is drawn. So I expect the tex coords to be broken somehow ... but why?

Minimal example code:
#include <iostream>
#include <SFML/Graphics.hpp>

void add(sf::VertexArray& array, sf::Vector2u const & world_pos, unsigned int tile_offset, unsigned int tile_size) {
        sf::Vertex tl, tr, br, bl; // [t]op[r]ight etc.
        // setup positions
        tl.position = sf::Vector2f(             world_pos.x             * tile_size,             world_pos.y    * tile_size);
        tr.position = sf::Vector2f((1 + world_pos.x)    * tile_size,             world_pos.y    * tile_size);
        br.position = sf::Vector2f((1 + world_pos.x)    * tile_size,    (1 + world_pos.y)       * tile_size);
        bl.position = sf::Vector2f(             world_pos.x             * tile_size,    (1 + world_pos.y)       * tile_size);
        // setup texture coords
        tl.texCoords = sf::Vector2f(0.f,                tile_offset                     * tile_size);
        tr.texCoords = sf::Vector2f(tile_size,  tile_offset                     * tile_size);
        br.texCoords = sf::Vector2f(tile_size,  (tile_offset + 1)       * tile_size);
        bl.texCoords = sf::Vector2f(0.f,                (tile_offset + 1)       * tile_size);
        // add tiles
        array.append(tl);
        array.append(tr);
        array.append(br);
        array.append(bl);
}

int main() {
        sf::RenderWindow window{{800u, 600u}, "test"};
        sf::Texture tileset;
        tileset.loadFromFile("tileset.png");
        unsigned int tile_size{16u};
       
        sf::VertexArray array{sf::Quads};
        for (auto y = 0u; y < 20; ++y) {
                for (auto x = 0u; x < 20; ++x) {
                        unsigned int offset{0u};
                        if (y == 0 || y == 19 || x == 0 || x == 19) {
                                offset = 1u;
                        }
                        add(array, {x, y}, offset, tile_size);
                }
        }
       
        auto view = window.getDefaultView();
        float zoom{1.f};
       
        while (window.isOpen()) {
                sf::Event event;
                while (window.pollEvent(event)) {
                        if (event.type == sf::Event::Closed) {
                                window.close();
                        }
                }
               
                window.clear(sf::Color::Black);
               
                if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) { view.move(0, -1); }
                if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) { view.move(0, 1); }
                if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) { view.move(-1, 0); }
                if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { view.move(1, 0); }
                if (sf::Keyboard::isKeyPressed(sf::Keyboard::Q)) { view.zoom(1.01f); zoom *= 1.01f; std::cout << "zoom: " << zoom << "\n"; }
                if (sf::Keyboard::isKeyPressed(sf::Keyboard::E)) { view.zoom(0.99f); zoom *= 0.99f; std::cout << "zoom: " << zoom << "\n"; }
               
                window.setView(view);
                window.draw(array, &tileset);
                window.display();
        }
}
 

Tileset png and screenshots are attached. Any ideas what's wrong with my implementation? (btw using SFML 2.3 from ubuntu wily repos)
« Last Edit: June 18, 2016, 12:13:49 pm by Glocke »
Current project: Racod's Lair - Rogue-inspired Coop Action RPG

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: Broken TexCoords when zooming sf::View
« Reply #1 on: June 18, 2016, 01:06:17 pm »
Nothing's wrong. There's an explanation (and a way to avoid it) at the end of this class documentation.
Laurent Gomila - SFML developer

Glocke

  • Sr. Member
  • ****
  • Posts: 289
  • Hobby Dev/GameDev
    • View Profile
Re: Broken TexCoords when zooming sf::View
« Reply #2 on: June 18, 2016, 01:18:32 pm »
Thx, Laurent. I guess you refer to this, right?
Quote
In order to render a sf::Drawable object pixel-perfectly, make sure the involved coordinates allow a 1:1 mapping of pixels in the window to texels (pixels in the texture). More specifically, this means:

    The object's position, origin and scale have no fractional part
    The object's and the view's rotation are a multiple of 90 degrees
    The view's center and size have no fractional part
From my point of view:
  • The objects (vertices) have positions, origin and scale without fractional part
  • Neither the object nor the view are rotated anyhow.
  • The view's center is modified by integer steps. Hence the center shouldn't have a fractional part
Refers "size" to the original (pre zoom) or final (post zoom) size? If second, how to avoid? Calculate the view's final size before zooming and adjusting the zoom to hit an integer size?
Current project: Racod's Lair - Rogue-inspired Coop Action RPG

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: Broken TexCoords when zooming sf::View
« Reply #3 on: June 18, 2016, 02:47:58 pm »
zoom is just a shortcut to setSize. So all you have to do is to round the size after zooming.
Laurent Gomila - SFML developer

Glocke

  • Sr. Member
  • ****
  • Posts: 289
  • Hobby Dev/GameDev
    • View Profile
Re: Broken TexCoords when zooming sf::View
« Reply #4 on: June 18, 2016, 02:59:14 pm »
zoom is just a shortcut to setSize. So all you have to do is to round the size after zooming.

Ok, ... but doesn't this break the aspect ratio between width and height?
Current project: Racod's Lair - Rogue-inspired Coop Action RPG

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: Broken TexCoords when zooming sf::View
« Reply #5 on: June 18, 2016, 03:50:41 pm »
By changing the size by less than a half pixel? ???
Laurent Gomila - SFML developer

andywood

  • Newbie
  • *
  • Posts: 6
    • View Profile
Re: Broken TexCoords when zooming sf::View
« Reply #6 on: June 19, 2016, 12:14:51 am »
I'm not sure if this is the most correct solution, but I've solved this problem by making the tiles slightly larger than my desired tile size by a fixed, tiny amount.

Mortal

  • Sr. Member
  • ****
  • Posts: 284
    • View Profile
Re: Broken TexCoords when zooming sf::View
« Reply #7 on: June 19, 2016, 02:05:14 am »
me too, i had similar problem, only solution that works for me is by adding half-pixel to tile size.
« Last Edit: June 19, 2016, 02:36:44 am by MORTAL »

Glocke

  • Sr. Member
  • ****
  • Posts: 289
  • Hobby Dev/GameDev
    • View Profile
Re: Broken TexCoords when zooming sf::View
« Reply #8 on: June 19, 2016, 08:22:07 am »
By changing the size by less than a half pixel? ???
If the current resolution is 800x600 and the zoom factor is 1.013525, the resulting size is 810.82x608.115; rounding results to 811x608. gcd(800,600)=200, hence 800/200=4, 600/200=3, thus 4:3 ratio. gcd(811,608)=1, hence 811:608 ratio. Doing this repeated will break the ratio more and more.

I tried the following:
float zoom(sf::View& view, float factor) {
        if (factor == 1.f) {
                return 1.f;
        }
        auto size = sf::Vector2i{view.getSize()};
        auto prev = size;
        int step = factor > 1.f ? 1 : -1;
        // calculate ratio and reduce fraction
        auto ratio = size;
        auto gcd = boost::math::gcd(ratio.x, ratio.y);
        ratio.x /= gcd;
        ratio.y /= gcd;
        // resize until factor exceeded
        float done{1.f};
        do {
                size += ratio * step;
                done = (1.f * size.x) / prev.x;
        } while ((factor > 1.f && done < factor) ||
                         (factor < 1.f && done > factor));
        // fix size
        if (size.x <= 0 || size.y <= 0) {
                size = ratio;
        }
        // apply size
        view.setSize(sf::Vector2f{size});
        return done;
}
 

But this doesn't fix the "render feature"... any thoughts about that?

/EDIT: Optimized version for larger zooming steps (without loop)
(click to show/hide)

/EDIT: Updated program code that will zoom to a factor which causes the lines on my machine
(click to show/hide)
« Last Edit: June 19, 2016, 08:54:59 am by Glocke »
Current project: Racod's Lair - Rogue-inspired Coop Action RPG

Glocke

  • Sr. Member
  • ****
  • Posts: 289
  • Hobby Dev/GameDev
    • View Profile
Re: Broken TexCoords when zooming sf::View
« Reply #9 on: June 21, 2016, 08:33:05 am »
Inspired by the fact, that google let this problem seem a general one, I thought about that "adding pixels to each tile"-solution and came up with this: let the game add the pixels when loading the tileset. It seems to work perfectly ;D

Also the code for preparing the tile changed. Here's my solution:
void scale(sf::Vector2f& v, sf::Vector2u const & tile_size) {
        v.x *= tile_size.x;
        v.y *= tile_size.y;
}

void prepare(sf::Vector2f& tl, sf::Vector2f& tr, sf::Vector2f& br, sf::Vector2f& bl, sf::Vector2u const & offset, sf::Vector2u const & tile_size) {
        tl = sf::Vector2f(offset.x,                     offset.y);
        tr = sf::Vector2f(offset.x + 1.f,       offset.y);
        br = sf::Vector2f(offset.x + 1.f,       offset.y + 1.f);
        bl = sf::Vector2f(offset.x,                     offset.y + 1.f);
        scale(tl, tile_size);
        scale(tr, tile_size);
        scale(br, tile_size);
        scale(bl, tile_size);
}

void add(sf::VertexArray& array, sf::Vector2u const & world_pos, sf::Vector2u const & tile_offset, sf::Vector2u const & tile_size) {
        sf::Vertex tl, tr, br, bl; // [t]op[r]ight etc.
        // setup positions and texture coords
        prepare(tl.position, tr.position, br.position, bl.position, world_pos, tile_size);
        prepare(tl.texCoords, tr.texCoords, br.texCoords, bl.texCoords, tile_offset, tile_size);
        // fix tex coords to suit the modified atlas
        sf::Vector2f delta{1.f, 1.f};
        delta.x += tile_offset.x * 2;
        delta.y += tile_offset.y * 2;
        tl.texCoords += delta;
        tr.texCoords += delta;
        br.texCoords += delta;
        bl.texCoords += delta;
        // add tiles
        array.append(tl);
        array.append(tr);
        array.append(br);
        array.append(bl);
}

sf::Image rebuildAtlas(sf::Image const & source, sf::Vector2u const & tilesize) {
        // determine new size
        auto size = source.getSize();
        assert(size.x % tilesize.x == 0);
        assert(size.y % tilesize.y == 0);
        auto num_x = size.x / tilesize.x;
        auto num_y = size.y / tilesize.y;
        size.x += num_x * 2;
        size.y += num_y * 2;
        sf::Image atlas;
        atlas.create(size.x, size.y);
       
        // create atlas
        sf::Vector2u offset;
        for (offset.y = 0u; offset.y < num_y; ++offset.y) {
                for (offset.x = 0u; offset.x < num_x; ++offset.x) {
                        // copy frame
                        auto destX = 1 + offset.x * (tilesize.x + 2);
                        auto destY = 1 + offset.y * (tilesize.y + 2);
                        sf::IntRect sourceRect;
                        sourceRect.left = offset.x * tilesize.x;
                        sourceRect.top = offset.y * tilesize.y;
                        sourceRect.width = tilesize.x;
                        sourceRect.height = tilesize.y;
                        atlas.copy(source, destX, destY, sourceRect);
                       
                        // create left border
                        --destX;
                        sourceRect.width = 1;
                        atlas.copy(source, destX, destY, sourceRect);
                       
                        // create right border
                        destX += tilesize.x + 1;
                        sourceRect.left += tilesize.x - 1;
                        atlas.copy(source, destX, destY, sourceRect);
                       
                        // create top border (copying from source to source!)
                        destX -= tilesize.x + 1;
                        sourceRect.left = destX;
                        sourceRect.top = destY;
                        sourceRect.width = tilesize.x + 2;
                        sourceRect.height = 1;
                        --destY;
                        atlas.copy(atlas, destX, destY, sourceRect);
                       
                        // create bottom border (copying from source to source!)
                        destY += tilesize.x;
                        sourceRect.top = destY;
                        ++destY;
                        atlas.copy(atlas, destX, destY, sourceRect);
                }
        }
        return atlas;
};
 
Current project: Racod's Lair - Rogue-inspired Coop Action RPG