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

Author Topic: sprite/texture/image bending  (Read 3510 times)

0 Members and 1 Guest are viewing this topic.

Elusive

  • Newbie
  • *
  • Posts: 10
    • View Profile
sprite/texture/image bending
« on: October 21, 2011, 11:10:10 pm »
I am trying to decorate big circle with multiple small rectangular images. They are supposed to fully cover the surface of the circle. This is how i first tried to align them:



It is possible to build it like that, but it requires tons of manual adjustment and tweaking to make sure that it fits properly at the intersection. Another Problem is that i would like to use the same images for bigger circles, too. Those would consist out of more of these small images to compensate for the bigger radius, but they would end up overlapping at another angle. That is where this idea comes in:



That way i would only need to adjust the images once (and it would be much simpler because i can put two images side-by-side in my image-editor instead of firing up the app each time i changed something).

How can I transform the sprites like that? I thought of Shaders (which I did not use very much before), but i think it would be better not to do it in realtime, since transformed sprites do not need to change once they are generated for a particular angle. Can someone point me in the right direction here?

sbroadfoot90

  • Jr. Member
  • **
  • Posts: 77
    • View Profile
sprite/texture/image bending
« Reply #1 on: October 21, 2011, 11:25:11 pm »
I think the easiest solutions is just to produce the images as curved already and use some trigonometry to place them. Manual tweaking shouldn't be an issue if you get your maths right

Elusive

  • Newbie
  • *
  • Posts: 10
    • View Profile
sprite/texture/image bending
« Reply #2 on: October 21, 2011, 11:43:55 pm »
Placing them at the correct position is not a problem. I am already doing that. The main problem is that the images are not always curved the same way. If the decorated circle is bigger, the angle at which they need to be bent needs to be smaller.


If i would use sprite 1 multiple times to form a full circle, it would be much smaller than the one for 2, since 1 is much more curved than 2. The intersections would differ, resulting in a manually tweaked image for every possible circle size. I would like to avoid that, if possible.

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
sprite/texture/image bending
« Reply #3 on: October 22, 2011, 07:44:21 am »
It's currently not possible (or at least not easy), but with the new API that will be available soon, you will be able to define your own geometry.
Laurent Gomila - SFML developer

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
sprite/texture/image bending
« Reply #4 on: October 22, 2011, 02:26:43 pm »
Quote from: "Elusive"
How can I transform the sprites like that?
This is an interesting problem, so I've taken some time to implement it :)

First, you need to reflect how to achieve the functionality with the SFML 2 API. I operate on two sf::Image objects and use a texture mapping transform. That is, every pixel of the destination image is mapped to one of the source image. Actually, we are not mapping a rectangle to a circle sector piece, but performing the inverse transform. If you are not familiar with std::function, take a look at the Boost.Function documentation.
Code: [Select]
#include <SFML/Graphics/Image.hpp>
#include <functional>

// Function that maps every pixel of dest to source, according to the transform
// transform: Takes pixel coordinates, returns texture coordinate vector with components in [0.f, 1.f]
void ApplyTransform(const sf::Image& source, sf::Image& dest, std::function<sf::Vector2f(unsigned int, unsigned int)> transform)
{
for (unsigned int x = 0; x < dest.GetWidth(); ++x)
for (unsigned int y = 0; y < dest.GetHeight(); ++y)
{
sf::Vector2f texCoords = transform(x, y);

// Invalid texture coordinates lead to transparent pixels
sf::Color color(0, 0, 0, 0);
if (texCoords != sf::Vector2f(-1.f, -1.f))
{
color = source.GetPixel(
static_cast<unsigned int>(source.GetWidth() * texCoords.x),
static_cast<unsigned int>(source.GetHeight() * texCoords.y));
}

dest.SetPixel(x, y, color);
}
}

Now we can pass any callable object as third argument. The core functionality is implemented in the following functor. Additional to SFML, I use polar vectors from Thor because I want to avoid raw trigonometry.
Code: [Select]
#include <Thor/Vectors/PolarVector.hpp>

// Functor to transform every point in a circle sector piece to a square with width 1
struct CircleSectorPiece
{
// center: Center position of the circle
// r1:     Small radius, > 0
// r2:     Big radius, > r1
// phi1:   Begin angle, in [-180, 180[
// phi2:   End angle, in ]phi1, 180]
CircleSectorPiece(sf::Vector2f center, float r1, float r2, float phi1, float phi2)
: center(center)
, r1(r1)
, r2(r2)
, phi1(phi1)
, phi2(phi2)
{
}

sf::Vector2f operator() (unsigned int x, unsigned int y)
{
const sf::Vector2f pixelCoords(sf::Vector2i(x, y));

// Transform difference between current pixel and circle center to polar coordinates
const thor::PolarVector2f vec = pixelCoords - center;

// The actual transform, if point is inside the circle sector piece
if (vec.r > r1 && vec.r < r2 && vec.phi > phi1 && vec.phi < phi2)
{
return sf::Vector2f(
     (vec.phi - phi1) / (phi2 - phi1),
1.f - (vec.r   - r1)   / (r2   - r1));
}

// Point is outside, return invalid texture coordinate
else
{
return sf::Vector2f(-1.f, -1.f);
}
};

sf::Vector2f center;
float r1;
float r2;
float phi1;
float phi2;
};

As you see, the code performing the actual transform is very short. The rest is either boilerplate code (functor) or interop with SFML. A use case might look like this:
Code: [Select]
sf::Image source, dest;
source.LoadFromFile("image.jpg");
dest.Create(500, 500);
ApplyTransform(source, dest, CircleSectorPiece(sf::Vector2f(200.f, 200.f), 50.f, 300.f, -150.f, -30.f));



Quote from: "Laurent"
It's currently not possible (or at least not easy).
As you see, it is ;)

But out of interest: Does the new API allow such circular transforms? Or would one approximate it with multiple trapezes?
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
sprite/texture/image bending
« Reply #5 on: October 22, 2011, 02:33:24 pm »
Quote
As you see, it is

I was focusing on geometry, I didn't think about transforming the source image instead ;)

Quote
But out of interest: Does the new API allow such circular transforms? Or would one approximate it with multiple trapezes?

OpenGL only understands triangles, so any curved geometry has to be approximated. But at least the new API will allow to create textured trapezes ;)
Laurent Gomila - SFML developer

Elusive

  • Newbie
  • *
  • Posts: 10
    • View Profile
sprite/texture/image bending
« Reply #6 on: October 22, 2011, 10:21:46 pm »
Just... wow, Nexus! This is exactly what i was looking for. Thanks a lot! It works like a charm. I think i'll need to take a closer look at your library ;)

I wonder if it would be possible to add bilinear interpolation, so that it looks smoother.

Elusive

  • Newbie
  • *
  • Posts: 10
    • View Profile
sprite/texture/image bending
« Reply #7 on: October 23, 2011, 03:50:32 am »
Okay, I've implemented a simple bilinear filter, which makes it even more awesome.

Before:


After:


My implementation (assuming the surrounding code by Nexus):
Code: [Select]
sf::Vector2f pixelPos(source.GetWidth() * texCoords.x, source.GetHeight() * texCoords.y);
sf::Vector2i roundedPixelPos(pixelPos);
sf::Vector2f posFraction(pixelPos.x - roundedPixelPos.x, pixelPos.y - roundedPixelPos.y);
sf::Vector2f invertedPosFraction(1.0f - posFraction.x, 1.0f - posFraction.y);

sf::Color color1 = source.GetPixel(roundedPixelPos.x,                         roundedPixelPos.y                         );
sf::Color color2 = source.GetPixel(roundedPixelPos.x + 1 % source.GetWidth(), roundedPixelPos.y                         );
sf::Color color3 = source.GetPixel(roundedPixelPos.x,                         roundedPixelPos.y + 1 % source.GetHeight());
sf::Color color4 = source.GetPixel(roundedPixelPos.x + 1 % source.GetWidth(), roundedPixelPos.y + 1 % source.GetHeight());

sf::Color interpolatedColor;

// red
float r1 = invertedPosFraction.x * color1.r + posFraction.x * color2.r;
float r2 = invertedPosFraction.x * color3.r + posFraction.x * color4.r;
interpolatedColor.r = invertedPosFraction.y * r1 + posFraction.y * r2;

// green
float g1 = invertedPosFraction.x * color1.g + posFraction.x * color2.g;
float g2 = invertedPosFraction.x * color3.g + posFraction.x * color4.g;
interpolatedColor.g = invertedPosFraction.y * g1 + posFraction.y * g2;

// blue
float b1 = invertedPosFraction.x * color1.b + posFraction.x * color2.b;
float b2 = invertedPosFraction.x * color3.b + posFraction.x * color4.b;
interpolatedColor.b = invertedPosFraction.y * b1 + posFraction.y * b2;

// alpha
float a1 = invertedPosFraction.x * color1.a + posFraction.x * color2.a;
float a2 = invertedPosFraction.x * color3.a + posFraction.x * color4.a;
interpolatedColor.a = invertedPosFraction.y * a1 + posFraction.y * a2;

dest.SetPixel(x, y, interpolatedColor);


I am pretty happy with the result!