Introduction
In the last tutorial we learnt how to load and render png files with alpha layers. This time we're gonna dive into rotating textures. Or more presicely ; rendering them rotated.
Today e'll be making a simple, but working, analog clock. This will show how to rotate textures in SDL2. We'll also improve how we represent textures
SDL_RenderCopyEx
Until now, we have been rendering using SDL_RencerCopy. But in order to rotate, we need a different function ; int SDL_RenderCopyEx it takes a couple more arguments. I'll try to explain all of them.
SDL_RenderCopyEx
(
SDL_Renderer* renderer,
SDL_Texture* texture,
const SDL_Rect* srcrect,
const SDL_Rect* dstrect
const double angle,
const SDL_Point* center,
const SDL_RendererFlip flip
)
renderer
- the SDL_Renderer we always use for renderingtexture
- the SDL_Texture we want to rendersrcrect
- which part of the SDL_Texture to render. null for everything.dstrect
- where to render it. Used exactly like in SDL_RenderFillRect)angle
- angle of rotationcenter
- the center of rotationflip
- flipping of texture ( vertical, horizontal, no flip )
- 0 on success
As you can see, the first four parameters are identical to
SDL_RenderCopy
you can read more about them in part 6.Texture flip
Texture flip is represented as an SDL_TextureFlip
It is simply an enum with the following three values :
- SDL_FLIP_NONE - don't flip at all
- SDL_FLIP_HORIZONTAL - flip horizontaly
- SDL_FLIP_VERTICAL - flip vertically
For now, we will just be using SDL_FLIP_NONE.
Center of rotation
The center of rotation is given as a position seen from the top-left of the texture ( remember; in SDL 0,0 is the top-left of a texture. ) So a center of 0,0 will mean you rotate it from the top-left corner.
Finding the center of rotation is usualy quite simple, it'll usually just be the center of the texture you are using. For a circle, like a spinning wheel, the center of rotation will simply be the center of the wheel. Or for a Tetris piece it will be the center of the retris piece. The center of a texture is easy to find. Since a center of any rectangle will be halfway along its x axis and halfway along its y axis, the position can be calculate as follows ;
// The rect of the texure.
// Assume it is filled out with pos(x, y) and size( w, h )
SDL_Rect textureRect;
SDL_Point center;
// Calculate center pos by setting
// ->x pos to half of witdh
// ->y pos to half of height
center.x = textureRect.w / 2;
center.y = textureRect.h / 2;
The type of
center
is one we haven't used before, SDL_Point
. But an SDL_Point
is simply just a struct with an x and y value.In our case we are going to make a working analog wall clock. So we can't rotate it around the middle ( that would be a weird clock! ) What we need to do, is find the base of the hands.
Here is a picture of on our clock hands. The white hole in the middle is where we want the center of rotation to be. All three hands look very similar to this, all hands have a hole in the base, and all of them rotate around that point.
If you look closely at the green dots, you'll see that the distance to the hole from either of the sides and the bottom is all the same ( three green dots. ) The green dots span the entire width of the base. So to find the center x value, we simply take half of the width. Looking at the picture, you'll see that the distance from the bottom to the hole is the same as the distance from the sides subtract half of the width to find the y position of the hole.
So the code for finding the center of rotation will be something like this :
// Assume textureRect is filled out with pos and size
SDL_Rect textureRect;
SDL_Point textureCenter;
int halfWidth = textureRect.w / 2;
textureCenter.x = halfWidth;
// Center.y = half of the width above texture bottom.
textureCenter.y = textureRect.h - halfWidth;
Putting things together
Previously we have worked with simply storing the
SDL_Texture
and SDL_Rect
as separate objects in our main.cpp. But now we also need to store the center of rotation and the angle. So that's four variables for each texture. With so many variables, it's a good idea to split it into a separate struct :
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Texture.h - headerphile.blogspot.no | |
// Contains basics of a rotable texture + extra code specifc to clock hands | |
// Any code marked as specific to clock hands can be removed to get a bare bones Texture class | |
// | |
#pragma once // Seme as standard inclusion guard ( #ifndef _TEXTURE_ #define _TEXTURE_ #endif ) | |
// Specific to clock hands, can be removed | |
// ================================================================== | |
#include <ctime> | |
enum class HandType | |
{ | |
Hour, | |
Minute, | |
Second | |
}; | |
// ================================================================== | |
// | |
struct Texture | |
{ | |
// Initializes a texture, including size and position | |
void LoadTexture( SDL_Renderer *renderer, const std::string &str, const SDL_Rect &windowRect, HandType handType_ ) | |
{ | |
SDL_Surface* surface = IMG_Load( str.c_str() ); | |
texture = SDL_CreateTextureFromSurface( renderer, surface ); | |
SDL_FreeSurface( surface ); | |
handType = handType_; | |
SetInitialPosition( windowRect ); | |
} | |
// Renders the texture with rotation | |
void Render( SDL_Renderer* renderer ) | |
{ | |
SDL_RenderCopyEx( renderer, texture, nullptr, &rect, angle, ¢er, SDL_FLIP_NONE ); | |
} | |
private: | |
// Sets position and center | |
void SetInitialPosition( const SDL_Rect &windowRect ) | |
{ | |
// Find width and height of texture | |
int32_t width = 0; | |
int32_t height = 0; | |
SDL_QueryTexture( texture, nullptr, nullptr, &width, &height ); | |
// Set size | |
rect.w = width; | |
rect.h = height; | |
// Center hands | |
rect.x = windowRect.w * 0.5; | |
rect.y = ( windowRect.h * 0.5 ) - rect.h; | |
// Calculate center | |
center.x = rect.w * 0.5; | |
center.y = rect.h - ( rect.w * 0.5 ); | |
angle = 0; | |
} | |
double angle; | |
SDL_Texture* texture; | |
SDL_Rect rect; | |
SDL_Point center; | |
// Code specific to our clock hands - can be removed | |
// ================================================================== | |
public: | |
void UpdateHandPositions() | |
{ | |
// time( 0 ) returns current time as UNIX timestamp / epoch ( seconds since 1.1.1970 ) | |
auto timeEpoch = time( 0 ); | |
// Loceltime takes the time as epoch and returns the pointer to a tm containing the time as a tm | |
// A is a struct containing variables like time in seconds, minutes andhours. | |
// Note: | |
// Seconds are represented in the range 0 - 60 ( since C++11 ) | |
// Minutes are represented in the range 0 - 59 | |
// Hours are represented in the range 0 - 23 | |
tm* tmCurrent = localtime( &timeEpoch ); | |
if ( handType == HandType::Hour ) | |
SetAngleForTime( tmCurrent->tm_hour, 12 ); | |
else if ( handType == HandType::Minute ) | |
SetAngleForTime( tmCurrent->tm_min, 59 ); | |
else if ( handType == HandType::Second ) | |
SetAngleForTime( tmCurrent->tm_sec, 60 ); | |
} | |
private: | |
// Set rotation according to input | |
void SetAngleForTime( int32_t value, double maxValue ) | |
{ | |
// Note : | |
// For hours value can be between 0 and 23, but max value is always 12 | |
// This means than when hour is larger than 12, rotation will be more than 1.0 | |
// This means that angle will be larger than 360. | |
// Luckily, SDL handles this and treats it like the corresponding value less than 360 | |
// So for ecample, if angle is 540 ( 360 + 180 ), SDL will treat it as 540 - 360 = 180 | |
double rotation = static_cast< double >( value / maxValue ); | |
angle = 360 * rotation; | |
} | |
HandType handType; | |
// ================================================================== | |
}; |
We also need to change main to make the clock work
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SDL Tutorial 8 - headerphile.blogspot.no | |
// A simple clock using SDL_RenderCopyEx() | |
// Compile with : clang++ main.cpp -o clcok -std=c++11 -lSDL2 -lSDL2_image | |
// Run : ./clock | |
#include <SDL2/SDL.h> | |
#include <SDL2/SDL_image.h> | |
#include <iostream> | |
#include <vector> | |
#include "Texture.h" | |
bool InitEverything(); | |
bool InitSDL(); | |
bool CreateWindow(); | |
bool CreateRenderer(); | |
void SetupRenderer(); | |
void Render(); | |
void RunGame(); | |
void UpdateHandPositions(); | |
void SetHourPosition( int32_t time ); | |
void SetSecondPosition( int32_t time ); | |
void SetMinutePosition( int32_t time ); | |
SDL_Rect windowRect = { 0, 0, 1920, 1080 }; | |
Texture hour; | |
Texture minute; | |
Texture second; | |
SDL_Window* window; | |
SDL_Renderer* renderer; | |
int main( int argc, char* args[] ) | |
{ | |
if ( !InitEverything() ) | |
return -1; | |
hour.LoadTexture( renderer, "hour_hand.png", windowRect, HandType::Hour ); | |
minute.LoadTexture( renderer, "min_hand.png", windowRect, HandType::Minute ); | |
second.LoadTexture( renderer, "sec_hand.png", windowRect, HandType::Second ); | |
RunGame(); | |
} | |
void RunGame() | |
{ | |
bool loop = true; | |
while ( loop ) | |
{ | |
SDL_Event event; | |
while ( SDL_PollEvent( &event ) ) | |
{ | |
if ( event.type == SDL_QUIT ) | |
loop = false; | |
} | |
UpdateHandPositions(); | |
Render(); | |
SDL_Delay( 16 ); | |
} | |
} | |
void UpdateHandPositions() | |
{ | |
hour.UpdateHandPositions(); | |
minute.UpdateHandPositions(); | |
second.UpdateHandPositions(); | |
} | |
void Render() | |
{ | |
// Clear the window and make it all red | |
SDL_RenderClear( renderer ); | |
hour.Render( renderer ); | |
minute.Render( renderer ); | |
second.Render( renderer ); | |
// Render the changes above | |
SDL_RenderPresent( renderer); | |
} | |
// The remaining code is unchanged, so I removed it to shorten the gist. Full code in download url |
As you can see, there is very little code needed in main since a lot of it is moved into
Texture.h
Conclusion
The full code can be downloaded here.
Feel free to comment if you have anything to say or ask questions if anything is unclear. I always appreciate getting comments.
For a full list of my tutorials / posts, click here.
Ingen kommentarer:
Legg inn en kommentar