Rotating sprite with a center of rotation in SDL2 - c

I want to rotate a sprite in C and SDL2, with a set center of rotation, and without scaling or anti-aliasing.
My game resolution is 320x240, and the display is scaled up when I set the game to full screen, because I'm using SDL_RenderSetLogicalSize(renderer, 320, 240).
Using SDL2's SDL_RenderCopyEx() (or SDL_RenderCopyExF()) to rotate a SDL_Texture.
As shown in this example ( https://imgur.com/UGNDfEY ) when the window is set to full screen, the texture is scaled up and at much higher resolution. Is would like the final 320x240 rendering to be scaled up, not the individual textures.
Using SDL_gfx's rotozoomSurface() was a possible alternative.
However, as shown in this example ( https://imgur.com/czPEUhv ), while this method give the intended low-resolution and aliased look, it has no center of rotation, and renders the transparency color as half-transparent black.
Is there a function that does what I'm looking for? Are there some tricks to get around that?

What I would do is to render what you want to in a SDL_Texture, and then print this texture into the renderer, using something like :
// Set 'your_texture' as target
SDL_SetRenderTarget(your_renderer, your_texture);
// We are now printing the rotated image on the texture
SDL_RenderCopyEx(your_renderer, // we still use the renderer; it will be automatically printed into the texture 'your_texture'
your_image,
&srcrect,
&dstrect,
angle,
&center,
SDL_FLIP_NONE); // unless you want to flip vertically / horizontally
// Set the renderer as target and print the previous texture
SDL_SetRenderTarget(your_renderer, NULL);
SDL_RenderClear(your_renderer);
SDL_RenderCopy (your_renderer, your_texture, NULL, NULL); // here the scale is automatically done
SDL_RenderPresent(your_renderer);
It works, but I don't know if it is very efficient.
Don't forget to define your_texture with a SDL_TEXTUREACCESS_TARGET access.
Hope this helps,
Durza42

Thanks to #Durza42, here's the solution to my problem:
#define kScreenWidth 320
#define kScreenHeight 240
SDL_Window* g_window = NULL;
SDL_Texture* g_texture = NULL;
SDL_Renderer* g_renderer = NULL;
SDL_Texture* g_sprite = NULL;
double g_sprite_angle = 0.0;
SDL_FRect g_sprite_frect = {
.x = 50.0f,
.y = 50.0f,
.w = 32.0f,
.h = 32.0f,
};
void
sdl_load(void)
{
SDL_Init(SDL_INIT_VIDEO);
g_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, kScreenWidth, kScreenHeight, 0);
g_renderer = SDL_CreateRenderer(g_window, -1, SDL_RENDERER_PRESENTVSYNC);
g_texture = SDL_CreateTexture(g_renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, kScreenWidth, kScreenHeight);
SDL_RenderSetLogicalSize(g_renderer, kScreenWidth, kScreenHeight);
}
void
sprite_load(void)
{
g_sprite = IMG_LoadTexture(g_renderer, "./sprite.png");
}
void
draw(void)
{
SDL_SetRenderTarget(g_renderer, g_texture);
SDL_RenderClear(g_renderer);
SDL_RenderCopyExF(g_renderer, g_sprite, NULL, &g_sprite_frect, g_sprite_angle, NULL, SDL_FLIP_NONE);
SDL_SetRenderTarget(g_renderer, NULL);
SDL_RenderClear(g_renderer);
SDL_RenderCopy(g_renderer, g_texture, NULL, NULL);
SDL_RenderPresent(g_renderer);
}

Related

Blank screen after rendering text

I have opened a window which shows two rects on the screen then using SDL_TTF to show the mouse position on the screen.
The bit I am having hard time understanding is why after rendering text the the two rects before it do not show up.
I am using SDL_RenderFillRect to draw two rects on screen
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderFillRect(renderer, rect1);
SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
SDL_RenderFillRect(renderer, rect2);
Code for rendering the text is
// define string with mouse x, y coords
sprintf(someString, "x: %d, y: %d", mouse.x, mouse.y);
SDL_Point textPos = {10, 10};
WriteText(renderer, font, someString, textPos, (SDL_Color){255, 255, 255, 255});
SDL_Surface *fontSurface = TTF_RenderText_Blended(font, someString, COLOR_BLACK); // create font surface
SDL_Texture *fontTexture = SDL_CreateTextureFromSurface(renderer, fontSurface); // create the texture
// get clip width and height from fontsurface clip rect
SDL_Rect *fontRect = &fontSurface->clip_rect;
fontRect->x = pos.x;
fontRect->y = pos.y;
SDL_RenderCopy(renderer, fontTexture, NULL, fontRect); // copy text to the renderer
// delete surface and texture
SDL_FreeSurface(fontSurface);
SDL_DestroyTexture(fontTexture);
It to shows the mouse positon top left corner of the window. However this makes the rest of the window blank.
To prevent this my work around is having to draw something on the screen after calling SDL_RendererCopy (and weirdly before calling SDL_DestroyTexture too) For example drawing single point
...
SDL_RenderCopy(renderer, fontTexture, NULL, fontRect); // copy text to the renderer
// why is this needed??
SDL_RenderDrawPoint(renderer, 0, 0);
// delete surface and texture
SDL_FreeSurface(fontSurface);
SDL_DestroyTexture(fontTexture); // have to draw a point before this
...
This then shows the two rects rendered before the text
If I set dstRect to NULL when calling SDL_RenderCopy then the text spans the whole window but I can see what was rendered before underneath the text.
Why am I having to draw a point after calling SDL_RenderCopy to stop what was rendered before from not showing up?
NOTE: Link to full source code https://pastebin.com/tRSFT0PV
This is a bug in SDL 2.0.10. It's fixed by https://hg.libsdl.org/SDL/rev/6ee12b88beed and this fix will ship with 2.0.11. Sorry about that!

How do I properly tell SDL to interpret the alpha channel correctly from a pixel map?

I'm not able to get the alpha channel to work properly in SDL using a pixel map. Essentially it seems to get fairly transparent at low values, but as the alpha values approach 0, the pixels become black.
I've tried setting the blend mode for both the texture and the renderer, and searched around for other possible solutions. So far the documentation hasn't helped me figure out a solution. So here is a minimal example I made:
#include "SDL.h"
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600
unsigned int* PixelMap;
SDL_Window* Window;
SDL_Renderer* Renderer;
SDL_Texture* Texture;
int
SDL_main(int argc, char* args[])
{
PixelMap = (unsigned int*)malloc(SCREEN_WIDTH * SCREEN_HEIGHT * sizeof(*PixelMap));
SDL_CreateWindowAndRenderer(SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN, &Window, &Renderer);
Texture = SDL_CreateTexture(Renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, SCREEN_WIDTH, SCREEN_HEIGHT);
SDL_SetTextureBlendMode(Texture, SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawBlendMode(Renderer, SDL_BLENDMODE_BLEND);
while(1)
{
for(int I = 0; I < SCREEN_WIDTH * SCREEN_HEIGHT; ++I)
{
PixelMap[I] = 0xFFFCCFFF;
}
for(int I = SCREEN_WIDTH/2 - 100; I < SCREEN_WIDTH/2 + 100; ++I)
{
PixelMap[I + (SCREEN_WIDTH * SCREEN_HEIGHT/2)] = 0x00000000;
}
SDL_UpdateTexture(Texture, 0, PixelMap, SCREEN_WIDTH * sizeof(*PixelMap));
SDL_RenderClear(Renderer);
SDL_RenderCopy(Renderer, Texture, 0, 0);
SDL_RenderPresent(Renderer);
}
}
This isn't the exact code I'm using, but it seems to produce either the same or a similar issue. Here I would expect the line drawn to be transparent since I'm assigning 0 to the location, the blend mode has been set, and the pixel format is set to RGBA8888. But I'm seeing a black line instead.
What am I missing?
Thanks for reading!
EDIT: Oh, duh! I'm overwriting the data, so it has nothing to blend with-- I guess the default background color is black, so it's blending with that. So the solution would be to render the background first, then add the changed pixels on top of that.
I'll leave this up in case it helps anyone else in the future.

Is it possible to create an XOR pen like DrawFocusRect()?

The Win32 GDI DrawFocusRect(HDC, const RECT*) function draws the dotted outline of a rectangle on the desired devince context. The cool thing about this function is it draws the dots using an XOR function so that when you call it a second time on the same device context and rectangle, it erases itself:
RECT rc = { 0, 0, 100, 100 };
DrawFocusRect(hdc, &rc); // draw rectangle
DrawFocusRect(hdc, &rc); // erase the rectangle we just drew
I want to achieve the same dotted line effect as DrawFocusRect() but I just want a line, not a whole rectangle. I tried doing this by passing a RECT of height 1 to DrawFocusRect() but this doesn't work because it XORs the "bottom line" of the rectange on top of the top line so nothing gets painted.
Can I create a plain HPEN that achieves the same effect as DrawFocusRect() so I can draw just a single line?
As #IInspectable commented, you want to use SetROP2(). The other half of the battle is creating the correct pen. Here is how the whole thing shakes out:
HPEN create_focus_pen()
{
LONG width(1);
SystemParametersInfo(SPI_GETFOCUSBORDERHEIGHT, 0, &width, 0);
LOGBRUSH lb = { }; // initialize to zero
lb.lbColor = 0xffffff; // white
lb.lbStyle = BS_SOLID;
return ExtCreatePen(PS.GEOMETRIC | PS.DOT, width, &lb, 0, 0);
}
void draw_focus_line(HDC hdc, HPEN hpen, POINT from, POINT to)
{
HPEN old_pen = SelectObject(hdc, hpen);
int old_rop = SetROP2(R2_XORPEN);
MoveToEx(hdc, from.x, from.y, nullptr);
LineTo(hdc, to.x, to.y);
SelectObject(hdc, old_pen);
SetROP2(old_rop);
}

SDL2 messed up image with manipulating pixels and SDL_UpdateTexture()

I'm trying to make a simple image viewer. I basically load an image into a surface and then create a texture from it.
At the end, I do the usual SDL_RenderClear(), SDL_RenderCopy() and SDL_RenderPresent() as per the migration guide.
This works fine, except that if I call SDL_UpdateTexture() before the 3 render calls above, I get a messed up image:
I am calling SDL_UpdateTexture() like this:
SDL_UpdateTexture(texture, NULL, image->pixels, image->pitch)
Where image is the surface I loaded for the image and texture is the texture I created from that. Attempts to vary the pitch result in differently messed up images. I also tried using a rect for the second parameter, but results are the same if the rect has the same dimensions as the image. If the dimensions are larger (e.g. same as the window), the update doesn't happen, but there are no errors.
The full code is available.
I would like to manipulate pixels of the surface directly via image->pixels and then call SDL_UpdateTexture(), but just calling SDL_UpdateTexture() without any tampering is enough to mess things up.
I think there is something wrong with the pitch or the SDL_Rect parameters,
but there is another SDL function which might help:
SDL_Texture* SDL_CreateTextureFromSurface(SDL_Renderer* renderer,
SDL_Surface* surface)
Could you maybe try the following. It should replace any pink (r=255,g=0,b=255) pixels to be transparent. You would simply change the pixel32 manipulation to accommodate your needs.
SDL_Surface* image = IMG_Load(filename);
SDL_Surface* imageFomatted = SDL_ConvertSurfaceFormat(image,
SDL_PIXELFORMAT_RGBA8888,
NULL);
texture = SDL_CreateTexture(renderer,
SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCESS_STREAMING,
imageFomatted->w, imageFomatted->h);
void* pixels = NULL;
int pitch = 0;
SDL_LockTexture(texture, &imageFomatted->clip_rect, &pixels, &pitch);
memcpy(pixels, imageFomatted->pixels, (imageFomatted->pitch * imageFomatted->h));
int width = imageFomatted->w;
int height = imageFomatted->h;
Uint32* pixels32 = (Uint32*)pixels;
int pixelCount = (pitch / 4) * height;
Uint32 colorKey = SDL_MapRGB(imageFomatted->format, 0xFF, 0x00, 0xFF);
Uint32 transparent = SDL_MapRGBA(imageFomatted->format, 0xFF, 0x00, 0xFF, 0x00);
for (int i = 0; i < pixelCount; i++) {
if (pixels32[i] == colorKey) {
pixels32[i] = transparent;
}
}
SDL_UnlockTexture(texture);
SDL_FreeSurface(imageFormatted);
SDL_FreeSurface(image);
pixels = NULL;
pitch = 0;
width = 0;
height = 0;

opengl failing to draw mesh

SOLVED: I'm not really sure how though... thanks for all your help guys.
I tried glDisable(GL_CULL_FACE); but the mesh is still not visible.
Basically I'm trying to draw a mesh (made from verts, normals, and texture coords) in OpenGL, using a display list. The mesh is on .obj format (exported from 3ds max 2013)
The problem is that the mesh is not visible.
To draw the display list I'm just using glCallLists (list, 1);
I have verified that I can draw things to the screen by drawing a point in the center of the screen and that works fine.
Could it be possible that the camera is positioned inside the mesh? If so is there an OpenGL state that I could enable to allow me to see the inside of a set of verts?
I know that the data I have is all valid, verified by printing each vert, normal and texture coord to a file before adding it to the display list, it looks valid.
I have dont no glTranslatef or anything like that, my projection matrix is setup like this:
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluPerspective (45.0, (float)1024/(float)768, -9999, 9999);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
If you want to have a look at the .obj file, here it is: http://pastebin.com/PpG3vG5e
This is how I create the display list:
list = glGenLists (1);
glNewList (list, GL_COMPILE);
glBegin (GL_TRIANGLES);
for (i = 0; i < data.face_count; i++)
{
// first vert
normal[0][0] = (float)data.vertex_normal_list[data.face_list[i]->normal_index[0]]->e[0];
normal[0][1] = (float)data.vertex_normal_list[data.face_list[i]->normal_index[0]]->e[1];
normal[0][2] = (float)data.vertex_normal_list[data.face_list[i]->normal_index[0]]->e[2];
tex[0][0] = (float)data.vertex_texture_list[data.face_list[i]->texture_index[0]]->e[0];
tex[0][1] = (float)data.vertex_texture_list[data.face_list[i]->texture_index[0]]->e[1];
tex[0][2] = (float)data.vertex_texture_list[data.face_list[i]->texture_index[0]]->e[2];
vert[0][0] = (float)data.vertex_list[data.face_list[i]->vertex_index[0]]->e[0];
vert[0][1] = (float)data.vertex_list[data.face_list[i]->vertex_index[0]]->e[1];
vert[0][2] = (float)data.vertex_list[data.face_list[i]->vertex_index[0]]->e[2];
// second vert
normal[1][0] = (float)data.vertex_normal_list[data.face_list[i]->normal_index[1]]->e[0];
normal[1][1] = (float)data.vertex_normal_list[data.face_list[i]->normal_index[1]]->e[1];
normal[1][2] = (float)data.vertex_normal_list[data.face_list[i]->normal_index[1]]->e[2];
tex[1][0] = (float)data.vertex_texture_list[data.face_list[i]->texture_index[1]]->e[0];
tex[1][1] = (float)data.vertex_texture_list[data.face_list[i]->texture_index[1]]->e[1];
tex[1][2] = (float)data.vertex_texture_list[data.face_list[i]->texture_index[1]]->e[2];
vert[1][0] = (float)data.vertex_list[data.face_list[i]->vertex_index[1]]->e[0];
vert[1][1] = (float)data.vertex_list[data.face_list[i]->vertex_index[1]]->e[1];
vert[1][2] = (float)data.vertex_list[data.face_list[i]->vertex_index[1]]->e[2];
// third vert
normal[2][0] = (float)data.vertex_normal_list[data.face_list[i]->normal_index[2]]->e[0];
normal[2][1] = (float)data.vertex_normal_list[data.face_list[i]->normal_index[2]]->e[1];
normal[2][2] = (float)data.vertex_normal_list[data.face_list[i]->normal_index[2]]->e[2];
tex[2][0] = (float)data.vertex_texture_list[data.face_list[i]->texture_index[2]]->e[0];
tex[2][1] = (float)data.vertex_texture_list[data.face_list[i]->texture_index[2]]->e[1];
tex[2][2] = (float)data.vertex_texture_list[data.face_list[i]->texture_index[2]]->e[2];
vert[2][0] = (float)data.vertex_list[data.face_list[i]->vertex_index[2]]->e[0];
vert[2][1] = (float)data.vertex_list[data.face_list[i]->vertex_index[2]]->e[1];
vert[2][2] = (float)data.vertex_list[data.face_list[i]->vertex_index[2]]->e[2];
for (j = 0; j < 3; j++)
{
glNormal3f (normal[j][0], normal[j][1], normal[j][2]);
glTexCoord3f (tex[j][0], tex[j][1], tex[j][2]);
glVertex3f (vert[j][0], vert[j][1], vert[j][2]);
}
}
glEnd ();
glEndList ();
EDIT:
I've tried things like:
glTranslatef (0, 0, 5);
glCallList (mesh);
glTranslatef (0, 0, 0);
but they don't work either :(
EDIT:
#datenwolf
Here is the code I use to draw it:
Draw_Begin ();
Mdl_Draw (list, 0.0f, 0.0f, 0.0f);
Draw_End ();
This
gluPerspective (45.0, (float)1024/(float)768, -9999, 9999);
is wrong. In a perspective projection both the near and the far plane distance must be of the same sign, i.e. both positive or both negative. Also the absolute value of the near plane must be smaller than the absolute value of the far plane. And the near plane distance must be nonzero. In mathematical notation:
sgn(near) = sgn(far) ^ 0 < |near| < |far|
Usually both near and far are chosen positive. Also as a rule of thumb the near clipping plane should be chosen as fer away as possible. The far plane can be placed at infinity (exploting some of the properties of homogenous matrices), but usually is placed as close as possible to max out depth buffer resolution.

Resources