SFML community forums
Help => Graphics => Topic started by: T.T.H. on February 05, 2010, 03:05:13 pm
-
I'm using branches/sfml2 revision 1369 and I noticed that when creating and drawing lines with sf::Shape::Line the final position of those lines seems to be dependent on the size of the render window. In addition there seem to be issues with the visibility of diagonal (angular/beveled/sloped?) lines when their thickness is too small.
When running my demo application (see next post for code) you'll notice the following:
- the window size randomly changes all the time (intended)
- the blue rectangle always remains on the very same spot (intended)
- the red horizontal and vertical lines are "wobbling" despite the fact that their positions are not changed in the code (bad)
- the red diagonal line is not visible at all (bad)
As I can't imagine any use for positions of lines being dependent on the render window size I'd really appreciate if at least the first issue will get fixed.
Please note that the second issue was already mentioned here:
http://www.sfml-dev.org/forum/viewtopic.php?t=1828
-
// small SFML demo written by T.T.H. on 4. Feb. 2010 to demonstrate:
// 1. line position "wobbling" based on render window size
// 2. line thickness "invisibility" with diagonal lines
// (already mentioned here: http://www.sfml-dev.org/forum/viewtopic.php?t=1828 )
//
// forum post:
// http://www.sfml-dev.org/forum/viewtopic.php?t=2145
//
// I'm using:
// - branches/sfml2 revision 1369 being statically linked
// - Visual C++ .NET 2003 (compiler version 7.1.6030, "Professional" edition)
// - German Windows XP SP3 32 Bit
// - NVIDIA Quadro NVS 285 (some onboard thingie with some default driver)
#include <SFML/Graphics.hpp>
int myrand(int max)
{
return (int) ((float) max * (rand() / (RAND_MAX + 1.0)));
}
int main(int argc, char** argv)
{
// create render window
sf::RenderWindow MyRenderWindow(sf::VideoMode(300, 300), "My SFML Window");
// create image
sf::Image MyImage;
MyImage.Create(200, 200, sf::Color(0, 0, 255, 255));
// create sprite
sf::Sprite MySprite(MyImage);
MySprite.SetPosition(50.0f, 50.0f);
// create lines
float X(50.0f);
float Y(50.0f);
float Length(200.0f);
sf::Shape MyLineHorizontal(sf::Shape::Line(X, Y, X + Length, Y , 1.0f, sf::Color(255, 0, 0)));
sf::Shape MyLineVertical (sf::Shape::Line(X, Y, X , Y + Length, 1.0f, sf::Color(255, 0, 0)));
sf::Shape MyLineDiagonal (sf::Shape::Line(X, Y, X + Length, Y + Length, 1.0f, sf::Color(255, 0, 0)));
// the big loop
srand((unsigned int) time(NULL));
bool Running(true);
while (Running)
{
// randomly set render window size
MyRenderWindow.SetSize(300 + myrand(50), 300 + myrand(50));
// update view of window to avoid wicked streching and enforce pixel precision
MyRenderWindow.SetView(sf::View(sf::FloatRect(0, 0, (float) MyRenderWindow.GetWidth(), (float) MyRenderWindow.GetHeight())));
// clear window
MyRenderWindow.Clear(sf::Color(0, 255, 0));
// draw sprite
MyRenderWindow.Draw(MySprite);
// draw lines
MyRenderWindow.Draw(MyLineHorizontal);
MyRenderWindow.Draw(MyLineVertical);
MyRenderWindow.Draw(MyLineDiagonal);
// display window
MyRenderWindow.Display();
// process events
sf::Event MyEvent;
while (MyRenderWindow.GetEvent(MyEvent))
{
// window closed
if (MyEvent.Type == sf::Event::Closed) Running = false;
// escape key pressed
if ((MyEvent.Type == sf::Event::KeyPressed) && (MyEvent.Key.Code == sf::Key::Escape)) Running = false;
}
}
return EXIT_SUCCESS;
}
-
Dude, you should update to the latest revision before spending so much time writing test code, I fixed it in revision 1390 :wink:
But your feedback was not useless, I tested your code and it confirms that the fix works well.
By the way, this is the kind of code that I love: I copied, compiled, ran it and wrote my answer in 30 sec :mrgreen:
-
D'OH
I remember I checked the log on 2. February.
You made revision 1390 on 3. February.
I stumbled about the issue on 4. February.
Regarding the sample code: I know that if one gives you simple, obvious, working sample code you're usually pretty fast with fixing bugs. So I really polished it up - and it worked - a bit wicked in the temporal order or events but it worked :D
Anyway, my weekend is like only 2 minutes ahead, so I'll update to the latest revision and test it at Monday. By the way, what exactly do you mean with...
New try for pixel-perfect rendering -- waiting for feedbacks
-
Regarding the sample code: I know that if one gives you simple, obvious, working sample code you're usually pretty fast with fixing bugs. So I really polished it up - and it worked - a bit wicked in the temporal order or events but it worked
:lol:
By the way, what exactly do you mean with...
New try for pixel-perfect rendering -- waiting for feedbacks
Well, it's like my 10th attempt at getting pixel-perfect rendering, so I wasn't very positive about this new modification. On top of that, this modification consists mainly in getting rid of everything that was trying to make the rendering better. Too beautiful to work.
However, it passed all my tests (which consists of all the code samples of people that once complained about rendering), that's why I commited it.
It actually works and solves all the remaining bugs, but it won't produce a perfect result with coordinates not aligned with pixels (i.e. integer coordinates, if you don't play with views).
-
I just updated to branches/sfml2 revision 1392 and tried it out (with VS2003).
The good news:
- no more line "wobbling" when the render window is resized
- the diagonal line is visible with exactly one pixel width (non-antialiased)
- the diagonal line is exactly where I want it to be (same position and size as the rectangle)
The bad news:
- the horizontal line is one pixel "too far up"
- the vertical line is one pixel "too far left"
When putting the following code in my former example...
int main(int argc, char** argv)
{
// create render window
sf::RenderWindow MyRenderWindow(sf::VideoMode(300, 300), "My SFML Window");
// position
float X(1.0f);
float Y(1.0f);
float Length(200.0f);
// create image
sf::Image MyImage;
MyImage.Create((int) Length, (int) Length, sf::Color(0, 0, 255, 255));
// create sprite
sf::Sprite MySprite(MyImage);
MySprite.SetPosition(X, Y);
// create lines
sf::Shape MyLineHorizontal(sf::Shape::Line(X, Y, X + Length, Y , 1.0f, sf::Color(255, 0, 0)));
sf::Shape MyLineVertical (sf::Shape::Line(X, Y, X , Y + Length, 1.0f, sf::Color(255, 0, 0)));
sf::Shape MyLineDiagonal (sf::Shape::Line(X, Y, X + Length, Y + Length, 1.0f, sf::Color(255, 0, 0)));
// the big loop
/* -- SNIP -- */
...you can see that the top left corner of the blue rectangle is at 1/1 but the horizontal line starts at 1/0 and the vertical line starts at 0/1 despite the fact that all drawables in my examples are positioned at 1/1. Maybe it's a rounding issue?
Regarding code size: when I develop new features for a project the code gets bigger at first and then the code gets smaller again and at the end of the day I have to refrain from judging myself by the sheer amount of new code because "good code" even means "simple and short code, doing exactly what was required, nothing more and nothing less". A funny quote I did recently read in an online resume was "my contribution to the project's code size was -40.000 lines of code".
-
The bad news:
- the horizontal line is one pixel "too far up"
- the vertical line is one pixel "too far left"
This is a very particular situation.
Let's take the example of your horizontal line at Y = 1. With a thickness of 1, it will extend from Y = 0.5 to Y = 1.5. In other words, it is located exactly between the center of pixel 0 and the center of pixel 1, and due to strict rounding rules, OpenGL chooses to draw it on pixel 0. You can easily confirm this by adding a very small amount to the Y coordinate (like 0.01): it is enough to make it appear at pixel 1 ;)
If my explanations are not clear enough I can try to draw a picture to show you what happens.
-
Your explanation is clear, thanks. My answer is "GNAAGNAAGNAAWHATAMESS". Those rounding issues in 3D interfaces can get really annoying.
I could "fix" it like this in my demo project:
// position
// note: +0.001 because of OpenGL rounding issues regarding lines
float X(1.001f);
float Y(1.001f);
Some related issue: when "zooming" the view of the render window the lines become "un-pixel-precise" again:
MyRenderWindow.SetView(sf::View(sf::FloatRect(0, 0, (float) MyRenderWindow.GetWidth() / 3.0f, (float) MyRenderWindow.GetHeight() / 3.0f)));
Notice the / 3.0f in the code and the missing pixel in the top left corner of the three pixel thick lines. This even happens without the +0.001f trick.
Anyway. Since I only need horizontal and vertical lines in my current project I will now simply use one pixel thick rectangles instead of one pixel thick lines: so far I did not notice any of those aforementioned issues when using rectangles instead of lines.
Nevertheless thanks for your help!
-
I could "fix" it like this in my demo project:
// position
// note: +0.001 because of OpenGL rounding issues regarding lines
float X(1.001f);
float Y(1.001f);
Well, you really need to fix it because your code is wrong ;)
What you want is a line that is drawn between Y = 1 and Y = 2, not between 0.5 and 1.5. Given that the line coordinates represent its center, what you want is really 1.5, not 1.0.
Some related issue: when "zooming" the view of the render window the lines become "un-pixel-precise" again:
This is intended. One consequence of my last modification is that pixel-perfect rendering now requires the objects coordinates to be aligned with pixels. Basically, when you use decimal coordinates for your objects and/or your view, anything can happen and I'm not responsible for that ;)
If you're wondering why I removed the automatic rounding that happened before my last modification, this is because it caused a bug: objects moving/rotating/scaling slowly were crappy, the rounding stuff removed all the smoothness.
Anyway. Since I only need horizontal and vertical lines in my current project I will now simply use one pixel thick rectangles instead of one pixel thick lines: so far I did not notice any of those aforementioned issues when using rectangles instead of lines.
You don't need to do that, just remember that lines coordinates are their center, and you will have the exact same results as rectangles.
-
What you want is a line that is drawn between Y = 1 and Y = 2, not between 0.5 and 1.5. Given that the line coordinates represent its center, what you want is really 1.5, not 1.0.
Ok. But that +0.5 is only for lines, right?
[...] now requires the objects coordinates to be aligned with pixels. Basically, when you use decimal coordinates for your objects and/or your view, anything can happen and I'm not responsible for that ;)
That is something I don't understand yet.
You don't need to do that, just remember that lines coordinates are their center, and you will have the exact same results as rectangles.
When adding 0.5 to the position of the line while there is no zoom in the view it looks like I want it to look. But when the zoom changes it doesn't. Try the following code and make the lines matched on the rectangle with different values for the Zoom variable: 0.5f, 1.0f, 4.0f
int main(int argc, char** argv)
{
// create render window
sf::RenderWindow MyRenderWindow(sf::VideoMode(900, 900), "My SFML Window");
// position
float X(1.0f);
float Y(1.0f);
float Length(200.0f);
float Zoom(4.0f); // <-- change this 0.5f, 1.0f, 4.0f
// create image
sf::Image MyImage;
MyImage.Create((int) Length, (int) Length, sf::Color(0, 0, 255, 255));
MyImage.SetSmooth(false);
// create sprite
sf::Sprite MySprite(MyImage);
MySprite.SetPosition(X, Y);
// line position adaption
X += 0.5f;
Y += 0.5f;
// create lines
sf::Shape MyLineHorizontal(sf::Shape::Line(X, Y, X + Length, Y , 1.0f, sf::Color(255, 0, 0)));
sf::Shape MyLineVertical (sf::Shape::Line(X, Y, X , Y + Length, 1.0f, sf::Color(255, 0, 0)));
sf::Shape MyLineDiagonal (sf::Shape::Line(X, Y, X + Length, Y + Length, 1.0f, sf::Color(255, 0, 0)));
// the big loop
srand((unsigned int) time(NULL));
bool Running(true);
while (Running)
{
// randomly set render window size
// ### MyRenderWindow.SetSize(300 + myrand(50), 300 + myrand(50));
// update view of window to avoid wicked streching and enforce pixel precision
MyRenderWindow.SetView(sf::View(sf::FloatRect(0, 0, (float) MyRenderWindow.GetWidth() / Zoom, (float) MyRenderWindow.GetHeight() / Zoom)));
// clear window
MyRenderWindow.Clear(sf::Color(0, 255, 0));
// draw sprite
MyRenderWindow.Draw(MySprite);
// draw lines
MyRenderWindow.Draw(MyLineHorizontal);
MyRenderWindow.Draw(MyLineVertical);
MyRenderWindow.Draw(MyLineDiagonal);
// display window
MyRenderWindow.Display();
// process events
sf::Event MyEvent;
while (MyRenderWindow.GetEvent(MyEvent))
{
// window closed
if (MyEvent.Type == sf::Event::Closed) Running = false;
// escape key pressed
if ((MyEvent.Type == sf::Event::KeyPressed) && (MyEvent.Key.Code == sf::Key::Escape)) Running = false;
}
// sleep
_sleep(100);
}
return EXIT_SUCCESS;
}
-
Ok. But that +0.5 is only for lines, right?
That 0.5 is thickness/2 which is added on each side of the line. So yes, it is only for lines.
That is something I don't understand yet.
I'm just saying that pixel perfect rendering requires that you don't mess too much with decimal coordinates ;)
To be more precise, coordinates must match pixels of the RenderTarget. In most situations this simply means using integer coordinates.
When adding 0.5 to the position of the line while there is no zoom in the view it looks like I want it to look. But when the zoom changes it doesn't. Try the following code and make the lines matched on the rectangle with different values for the Zoom variable: 0.5f, 1.0f, 4.0f
This is because you add 0.5 to each coordinate of each line. You just need to add it to Y for the horizontal line and to X for the vertical line.
-
This is because you add 0.5 to each coordinate of each line. You just need to add it to Y for the horizontal line and to X for the vertical line.
Ah, ok, now I understood it. Seems to work fine now.
Thanks again, your help is greatly appreciated.
-
You're welcome, I love demonstrating that the new code works flawlessly :lol: