I am looking for a function that draws a filled circle using SDL2 without using a renderer at all. I currently have this:
void Circle(int center_x, int center_y, int radius, SDL_Color color) {
eraseOldCircle();
uint32_t *pixels = (uint32_t *) windowSurface->pixels;
SDL_PixelFormat *windowFormat = windowSurface->format;
SDL_LockSurface(windowSurface); // Lock surface for direct pixel access capability
int radiussqrd = radius * radius;
for(int x=center_x-radius; x<=center_x+radius; x++) {
int dx = center_x - x;
for(int y=center_y-radius; y<=center_y+radius; y++) {
int dy = center_y - y;
if((dy * dy + dx * dx) <= radiussqrd) {
pixels[(y * WIDTH + x)] = SDL_MapRGB(windowFormat, color.r, color.g, color.b);
}
}
}
SDL_UnlockSurface(windowSurface);
SDL_UpdateWindowSurface(window);
}
which has been adapted from another function I found here, it draws the pixels directly to the windowSurface after calling eraseOldCircle (which puts the game's background image back to the previous position of the circle, effectively erasing it from there.) but it is still too slow for what I need (probably the maths?). What would be the fastest way to draw a circle using direct pixel access? I need it to be high speed so I can use it in a 2D game. I haven't been able to find anything until now, everything I see uses SDL_Renderer, but I should strictly never use it.
Here is eraseOldCircle() in case it helps:
void eraseOldCircle() {
//Calculate previous position of ball
SDL_Rect pos = {circlePosition.x-(radius+steps), circlePosition.y-(radius+steps), radius*radius, radius*2+steps};
SDL_BlitSurface(backgroundImage, &pos, windowSurface, &pos);
}
I'm not too sure how to do it with surfaces and memory management and all that, but if this helps, here is a version using an SDL_Renderer that runs pretty quickly:
void draw_circle(SDL_Renderer *renderer, int x, int y, int radius, SDL_Color color)
{
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
for (int w = 0; w < radius * 2; w++)
{
for (int h = 0; h < radius * 2; h++)
{
int dx = radius - w; // horizontal offset
int dy = radius - h; // vertical offset
if ((dx*dx + dy*dy) <= (radius * radius))
{
SDL_RenderDrawPoint(renderer, x + dx, y + dy);
}
}
}
}
If you draw many circles, I would guess SDL_UpdateWindowSurface is where you spend the most time. Try this instead
SDL_LockSurface
// erase and draw all circles (possibly >1000)
SDL_UnlockSurface
SDL_UpdateWindowSurface
You can optimize your circle drawing code a bit, but it is probably fast enough. I also think that SDL_Renderer is probably fast enough.
The documentation for SDL_UpdateWindowSurface says it will copy the surface to the screen. You only need to do this once per frame.
Related
I'm trying to create a rather simple game which largely involves drawing circles with SDL2. I discovered SDL lacks and built-in method to draw circles to an SDL_Renderer, but upon searching for a method, I discovered this helpful answer which details using the Midpoint Circle Algorithm to accomplish this. Since I wanted a filled circle, I wrote a rather simple function that just draws a lot of slightly smaller circles to give the appearance of a filled circle. Unfortunately, this resulted in circles being drawn with gaps that form a sort of 'X' pattern on the circle, as shown:
Here is my draw_hollow_circle function:
void draw_hollow_circle(SDL_Renderer *renderer, int centerx, int centery, int radius)
{
// Draws a hollow circle with the given position and radius
const int diameter = (radius * 2);
int x = radius - 1;
int y = 0;
int tx = 1;
int ty = 1;
int error = tx - diameter;
while (x >= y)
{
// Each renders an octant of the circle
SDL_RenderDrawPoint(renderer, centerx + x, centery + y);
SDL_RenderDrawPoint(renderer, centerx + x, centery - y);
SDL_RenderDrawPoint(renderer, centerx - x, centery + y);
SDL_RenderDrawPoint(renderer, centerx - x, centery - y);
SDL_RenderDrawPoint(renderer, centerx + y, centery - x);
SDL_RenderDrawPoint(renderer, centerx + y, centery + x);
SDL_RenderDrawPoint(renderer, centerx - y, centery - x);
SDL_RenderDrawPoint(renderer, centerx - y, centery + x);
if (error <= 0)
{
++y;
error += ty;
ty += 2;
}
if (error > 0)
{
--x;
tx += 2;
error += (tx - diameter);
}
}
}
And here is my draw_circle function:
void draw_circle(SDL_Renderer *renderer, int x, int y, int radius, int r, int g, int b)
{
// Draws a filled circle with the given position, radius, and color
SDL_SetRenderDrawColor(renderer, r, g, b, SDL_ALPHA_OPAQUE);
// Draw a lot of slightly smaller hollow circles to give the impression of a filled circle
while (radius >= 0)
{
draw_hollow_circle(renderer, x, y, radius);
--radius;
}
}
Now, this is rather annoying, and I would like a method of avoiding such gaps and getting just a pure red circle, but I have unfortunately not been able to find any method of doing so. I tried a different method involving drawing many radii going from the center of the circle to the edge, but this resulted in a similar problem, albeit with the gaps in slightly different places. Any type of answer would be fine, be it an algorithm better suited for filled circles, an error in the math of my code, etc.
The holes are artifacts of round-off errors. Precise positioning of each point would use real numbers (think floating point), but pixel coordinates must be integers, hence the rounding. Yes, you get similar artifacts when drawing diagonal lines for a similar reason. The only lines that could be drawn without rounding of some sort are horizontal, vertical, those with slope +1, and those with slope −1.
A simple approach to fill the circle is to draw rectangles instead of points. Each iteration of the loop in draw_hollow_circle draws eight points. The first four would be the corners of one rectangle, while the second four form another rectangle. Try drawing those two rectangles instead of the eight points. (You no longer need to iterate over radii.)
(Stick to fully opaque colors for this since many of the points will be drawn multiple times. That would interfere with partial transparency.)
You can connect the points on the circle circumference with either horizontal or vertical lines to achieve a filled circle. This does not create overlap like the method described by #JaMit, so blending should be possible:
void fill_circle(SDL_Renderer *renderer, int centerX, int centerY, int radius) {
const int diameter = (radius * 2);
int x = (radius - 1);
int y = 0;
int tx = 1;
int ty = 1;
int error = (tx - diameter);
while (x >= y)
{
// top
SDL_RenderDrawLine(renderer, centerX - y, centerY - x, centerX + y, centerY - x);
// top-center piece
SDL_RenderDrawLine(renderer, centerX - x, centerY - y, centerX + x, centerY - y);
// lower-center piece
SDL_RenderDrawLine(renderer, centerX - x, centerY + y, centerX + x, centerY + y);
// lower piece
SDL_RenderDrawLine(renderer, centerX - y, centerY + x, centerX + y, centerY + x);
if (error <= 0)
{
++y;
error += ty;
ty += 2;
}
if (error > 0)
{
--x;
tx += 2;
error += (tx - diameter);
}
}
}
You can comment out the draw-line statements, one-by-one, to see the effects.
I am having a small problem in essentially creating a path tracer.
In my project, I have an object which constantly moves around quite organically through an update function done in the while loop. I use immediate mode and represent the player as a square, I would like to make it so that every update the object is drawn in its current position, but also for it to draw it's previous position(s), so etching dots towards the path the object is going. I'm pretty sure we can do this by drawing the position as normal but not clearing everything up after this instance in the while loop, but I have no knowledge on how to do this.
Edit: For those who want the code, do understand that this code, in particular, is not adherent to the question and that I made a ton of generalizations (such as the particle(s) being referred to an object) so that the general gist of the question is understandable:
#include "PerlinNoise.hpp"
#include "Particle.hpp"
#include <iostream>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <cmath>
#include <vector>
using namespace siv;
float map(float oValue, float oMin, float oMax, float nMin, float nMax)
{
float oRange = (oMax - oMin);
float nRange = (nMax - nMin);
return(((oValue - oMin) * nRange)/oRange) + nMin;
}
void drawRectangle(float x, float y, float xr, float yr, float R, float G, float B)
{
glBegin(GL_QUADS);
glColor3f(R,G,B);
glVertex2f(x,y);
glVertex2f(x+xr,y);
glVertex2f(x+xr,y+yr);
glVertex2f(x,y+yr);
glEnd();
}
void drawLine(float x, float y, float xr, float yr, float rotation)
{
float radius = sqrt(xr*xr + yr*yr);
float a0 = asin(yr/radius);
float tangle = a0+rotation;
//std::cout<<tangle*180/M_PI<<std::endl;
glBegin(GL_LINES);
glColor3f(.1,.1,.1);
glVertex2f(x,y);
glVertex2f(x + sin(tangle)*radius,y + cos(tangle)*radius);
glEnd();
}
int main()
{
float inc = 0.1;
int scl = 20;
int cols,rows;
Particle particles[100000];
//V2D flowfield[cols*rows];
GLFWwindow* window;
if (!glfwInit())
return 1;
int width = 800;
int height = 800;
window = glfwCreateWindow(width, height, "Window", NULL, NULL);
cols = floor(width/scl);
rows = floor(height/scl);
V2D flowfield[cols*rows];
float zoff = 0;
if (!window) {
glfwTerminate();
return 1;
}
glfwMakeContextCurrent(window);
if(glewInit()!=GLEW_OK)
std::cout<<"Error"<<std::endl;
glEnable(GL_DEPTH_TEST);
glMatrixMode(GL_PROJECTION);
glfwGetFramebufferSize(window, &width, &height);
glOrtho(0, width*(width/height), height, 0, -2, 2);
PerlinNoise png = PerlinNoise(1);
while(!glfwWindowShouldClose(window)) {
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glClearColor(0.11, 0.14, 0.17, 1);
float yoff = 0;
for(int y = 0; y < rows; y++)
{
float xoff = 0;
for(int x = 0; x < cols; x++)
{
double noise = map(png.noise((double)xoff, (double)yoff, (double)zoff),-1,1,0,1);
double angle = noise * 8 *M_PI;
//std::cout<<angle/(2*M_PI)<<std::endl;
int index = x + y * cols;
V2D v = V2D(cos(angle), sin(angle));
v.normalize();
v = V2D(v.x*5,v.y*5);
flowfield[index] = v;
//drawLine(x*scl, y*scl, scl, 0, atan2(v.x, v.y));
//drawRectangle(x*scl,y*scl,scl,scl,noise,noise,noise);
xoff += inc;
}
yoff += inc;
zoff += 0.0001;
}
for(int i = 0; i < 100000; i++)
{
particles[i].follow(flowfield);
particles[i].update();
particles[i].show();
}
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
}
When drawing directly to a window (be it double buffered or not doesn't make a difference) you must not make any assumptions about its contents being persistent between drawing. Heck, strictly speaking the contents may become damaged mid draw, before things even finished up; of course in practice this isn't very likely to happen and given modern compositing graphics systems it's practically eliminated.
Your application screams for drawing to an intermediary framebuffer object. FBOs are guaranteed to retain their contents no matter what happens; also you can add further drawing to the backing buffer of an FBO at any time.
The official OpenGL wiki describes FBOs at https://www.khronos.org/opengl/wiki/Framebuffer_Object
Also ages ago I wrote a simple codesample (using a lot of outdated, legacy OpenGL); drawing is legacy, but the FBO parts are done today as it was 10 years ago: https://github.com/datenwolf/codesamples/blob/master/samples/OpenGL/minimalfbo/minimalfbo.c (I implemented it using render to texture; a render to renderbuffer and buffer blit to main framebuffer would work for you, too).
I've been trying to draw a circle in c++ using openGL. So far i have a compresses circle and it just has a random line going across the screen.
This is the function I'm using to get this shape.
void Sprite::init(int x, int y, int width, int height, Type mode, float scale) {
_x = x;
_y = y;
_width = width;
_height = height;
//generate buffer if it hasn't been generated
if (_vboID == 0) {
glGenBuffers(1, &_vboID);
}
Vertex vertexData[360];
if (mode == Type::CIRCLE) {
float rad = 3.14159;
for (int i = 0; i < 359; i++) {
vertexData[i].setPosition((rad * scale) * cos(i), (rad * scale) * sin(i));
}
}
//Tell opengl to bind our vertex buffer object
glBindBuffer(GL_ARRAY_BUFFER, _vboID);
//Upload the data to the GPU
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
//Unbind the buffer
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
What is causing the line? Why is my circle being compressed?
Sorry if this is a dumb question or if this question doesn't belong on this website I'm very new to both c++ as well as this website.
It is difficult to be sure without testing the code myself, but I'll guess anyway.
Your weird line is probably caused by the buffer not being fully initialized. This is wrong:
Vertex vertexData[360];
for (int i = 0; i < 359; i++) {
It should be:
for (int i = 0; i < 360; i++) {
or else the position at vertexData[359] is left uninitialized and contains some far away point.
About the ellipse instead of a circle, that is probably caused by your viewport not having the same scale horizontally and vertically. If you configure the viewport plus transformation matrices to have a viewing frustum of X=-10..10, Y=-10..10, but the actual viewport is X=0..800 and the Y=0..600, for example, then the scale would be different and you'll get your image distorted.
The solution would be one of:
Create a square viewport instead of rectangular. Check your arguments to glViewport().
Define a view matrix to consider the same ratio your viewport has. You don't show how you set the view/world matrix, maybe you are not even using matrices... If that is the case, you should probably use one.
I don't understand, exactly, what you want obtain but... cos() and sin() receive a radiant argument; so, instead of cos(i) and sin(i), I suppose you need cos((2*rad*i)/360.0)) and sin((2*rad*i)/360.0)) or, semplified, cos((rad*i)/180.0)) and cos((rad*i)/180.0))
And what about the center and the radious of the circle?
(x, y) should be the center of the circle?
scale is the radious?
In this case, I suppose you should write something like (caution: not tested)
Vertex vertexData[360];
float rad = 3.14159;
if (mode == Type::CIRCLE) {
for (int i = 0; i < 359; ++i) {
float angle = (rad / 180) * i; // (thanks Rodrigo)
vertexData[i].setPosition(x + scale * cos(angle), y + scale * sin(angle));
}
}
or, loosing precision but avoidind some moltiplication,
Vertex vertexData[360];
float rad = 3.14159;
float angIncr = rad / 180.0;
if (mode == Type::CIRCLE) {
for (int i = 0, float angle = 0.0; i < 359; ++i, angle += angIncr) {
vertexData[i].setPosition(x + scale * cos(angle), y + scale * sin(angle));
}
}
But what about width and heigth?
p.s.: sorry for my bad English.
--- modified with suggestion from Rodrigo --
How do I make a 2D square move on the screen? I try to move it but it just stays there.
int x = 100;
int y = 100;
int width = 50;
int height = 50;
x += 1;
glBegin(GL_QUADS);
glColor3f(r, g, b);
glVertex2f(x, y);
glVertex2f(x + width, y);
glVertex2f(x + width, y + height);
glVertex2f(x, y + height);
glEnd();
It all loads fine, it draws the square and everything, but it just doesn't move the square, I'm using SDL to draw the window incase you want to know.
OpenGL expects you to send relative coordinates between 0 and 1. Moreover, you create new variables every frame, so they can't really be incremented along all frames.
// box parameters in pixels
int boxleft = 100,
boxbottom = 100;
int boxwidth = 50,
boxheight = 50;
// window dimensions
int screenwidth = 1920,
screenheight = 1080;
for(;;)
{
// clear last frame
glClear(GL_COLOR_BUFFER_BIT);
// calculate screen space coordinates
float left = (float)boxleft / screenwidth,
right = left + (float)boxwidth / screenwidth,
bottom = (float)boxbottom / screenheight,
top = bottom + (float)boxheight / screenheight;
// draw the box
glBegin(GL_QUADS);
glColor3f(r, g, b);
glVertex2f(left, top);
glVertex2f(right, top);
glVertex2f(right, bottom);
glVertex2f(left, bottom);
glEnd();
// shift box for next frame
boxleft++;
}
Update: Okay, you say the square draws fine with your coordinates, so you might not change that. But defining the variables outside your draw loop is essential. Tell me if this works for you.
Assuming that's all one function, the issue is that the begining of the function is constantly resetting your value for x to 100. Move your variable definitions out of the function. As an example:
int x = 100;
int y = 100;
int width = 50;
int height = 50;
function drawSquare()
{
x += 1;
glBegin(GL_QUADS);
glColor3f(r, g, b);
glVertex2f(x, y);
glVertex2f(x + width, y);
glVertex2f(x + width, y + height);
glVertex2f(x, y + height);
glEnd();
}
Each time you call that function, the square will have x incremented by one and so will move progressively over.
I have to draw a conical gradient in Qt C++ but I can not use the QConicalGradient. I did have a linear gradient, but I do not know how to make a conical gradient. I do not want the finished code, but I ask for a simple algorithm.
for(int y = 0; y < image.height(); y++){
QRgb *line = (QRgb *)image.scanLine(y);
for(int x = 0; x < image.width(); x++){
QPoint currentPoint(x, y);
QPoint relativeToCenter = currentPoint - centerPoint;
float angle = atan2(relativeToCenter.y(), relativeToCenter.x);
// I have a problem in this line because I don't know how to set a color:
float hue = map(-M_PI, angle, M_PI, 0, 255);
line[x] = (red << 16) + (grn << 8) + blue;
}
}
Can you help me?
Here is some pseudo code:
Given some area to paint on, and a defined center for your gradient...
For each point that you are painting on in the area, calculate the angle to the center of your gradient.
// QPoint currentPoint; // created/populated with a x, y value by two for loops
QPoint relativeToCenter = currentPoint - centerPoint;
angle = atan2(relativeToCenter.y(), relativeToCenter.x());
Then map that angle to a color using your linear gradient, or some sort of mapping function.
float hue = map(-PI, angle, PI, 0, 255); // convert angle in radians to value
// between 0 and 255
Paint that pixel, and repeat for every pixel in your area.
EDIT: Depending on the pattern of the gradient, you will want to create a different QColor pixel. For example if you had a "rainbow" gradient, just going from one hue to the next, you could use a linear mapping function like this:
float map(float x1, float x, float x2, float y1, float y2)
{
if(true){
if(x<x1)
x = x1;
if(x>x2)
x = x2;
}
return y1 + (y2-y1)/(x2-x1)*(x-x1);
}
Then you create a QColor object using the outputted value:
float hue = map(-PI, angle, PI, 0, 255); // convert angle in radians to value
// between 0 and 255
QColor c;
c.setHsl( (int) hue, 255, 255);
Then use this QColor object with your QPainter or QBrush or QPen that you are using. Or if you are putting a qRgb value back in:
line[x] = c.rgb();
http://qt-project.org/doc/qt-4.8/qcolor.html
Hope that helps.