Bon, il y a une solution simple: déclarer une variable
struct {
float x;
float y;
} float_point[10];
(ou employer des double) et dans le while(!quit), faire les calculs sur float_point, puis copier les float_point[i] dans un tableau de structures SDL_Point
Maintenant les points restes parfaitement bien alignés, merci à tous pour votre aide, je me demande quand même pourquoi stocker la solution des calculs directement dans un tableau de SDL_Point sa ne marche pas mais stocker la solution des calculs dans des tableau de floats puis ensuite y mettre dans un tableau de SDL_Point sa marche.
Et pour répondre à ta question, j'utilise windows.
.... je me demande quand même pourquoi stocker la solution des calculs directement dans un tableau de SDL_Point sa ne marche pas mais stocker la solution des calculs dans des tableau de floats puis ensuite y mettre dans un tableau de SDL_Point sa marche.
C'est très simple: les membres .x et .y d'une structure SDL_Point sont des int. Et donc les décimales du calcul sont perdues à chaque itération. Ce qui n'arrive pas dans la solution que je propose, puisque les décimales sont conservées d'une itération à l'autre.
- Edité par edgarjacobs 21 novembre 2023 à 21:09:18
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent
Les calculs se font avec la position courante et avec les arrondis (il y a beaucoup d'entier) la position se décale et s’amplifie à chaque calcul.
Le calcul avec des entiers n'est pas assez précis. Quand tu passes de float en int tu perds tout ce qui est derrière la virgule. Et à chaque pas, le problème s’amplifie.
ok mais même dans cette solution, on envoi un nombre décimale dans SDL_Point à un moment, donc sa supprime quand même les nombres après la virgule donc sa devrait poser le même problème si le problème est que sa supprime les nombres après la virgule.
Ça arrondit (si on fait ça bien) pour obtenir les adresses des pixels, qui sont des entiers. Mais le calcul des positions se fait avec la précision des float ou des double.
En général, on a intérêt à bien distinguer, dans les programmes
Les coordonnées du problème
Les coordonnées du système d'affichage
Et donc s'abstenir d'utiliser des SDL_Point pour les coordonnées "probleme". C'est évident quand le problème se passe dans un monde 3D, ou qu'on veut faire des zooms, etc
- Edité par michelbillaud 22 novembre 2023 à 7:35:35
J'ai bricolé à partir de l'exemple présenté plus haut pour, essentiellement,
avoir une séparation nette entre le "monde" (où il y a des points qui tournent autour de l'origine) et le "dessin" (l'origine du monde apparaît au centre)
gérer le redimensionnement qui change les coordonnées du centre haha.
une animation par un timer + callback qui envoie régulièrement des événements qui déclenchent animation et affichage (plutot que des attentes actives)
+ des bricoles, parce qu'il faut bien que j'apprenne comment faire
/**
* Essais SDL, avec une pompe à évènements pour l'animation
*/
#include <math.h>
#include <stdbool.h>
#include <SDL.h>
#include <SDL_timer.h>
#define W_WIDTH 600
#define W_HEIGHT 400
#define NB_DOTS 10
#define DOT_SPACING 10
#define ROTATION_SPEED (M_PI / 180.0) // radians par frame
#define ANIMATION_DELAY_MS 20
struct Point {
float x, y;
};
struct World {
struct Point dot[NB_DOTS];
};
void init_world(struct World *world)
{
for(int i = 0; i < NB_DOTS; i++) {
world->dot[i] = (struct Point) {
.x = 0,
.y = i * DOT_SPACING
};
}
}
void animate_world(struct World *w)
{
double s = sin(ROTATION_SPEED),
c = cos(ROTATION_SPEED);
for (int i = 0; i < NB_DOTS; i++) {
w->dot[i] = (struct Point) {
.x = c * w->dot[i].x - s * w->dot[i].y,
.y = s * w->dot[i].x + c * w->dot[i].y
};
}
}
struct Drawing {
SDL_Window *window;
SDL_Renderer *renderer;
};
Uint32 timer_callback(Uint32 interval, void *param)
{
SDL_Event event = {
.user = {
.type = SDL_USEREVENT // ici pas besoin des autres champs
}
};
SDL_PushEvent(&event);
return interval;
}
void init_drawing(struct Drawing *drawing)
{
drawing->window = SDL_CreateWindow("test",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
W_WIDTH, W_HEIGHT,
SDL_WINDOW_RESIZABLE);
drawing->renderer = SDL_CreateRenderer(drawing->window, -1, SDL_RENDERER_ACCELERATED);
}
void draw_world( struct World *world, struct Drawing *drawing)
{
// Fond noir
SDL_SetRenderDrawColor(drawing->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
SDL_RenderClear(drawing->renderer);
// points en rouge
SDL_SetRenderDrawColor(drawing->renderer, 255, 0, 0, SDL_ALPHA_OPAQUE);
// la fenêtre est redimensionnable, quelles sont ses dimensions ?
int width, height;
SDL_GetWindowSize(drawing->window, &width, &height);
int center_x = width / 2, center_y = height / 2;
for (int i = 0; i < NB_DOTS; i++) {
// l'origine du monde est au centre du dessin -> translation
SDL_RenderDrawPoint(drawing->renderer,
world->dot[i].x + center_x,
world->dot[i].y + center_y);
}
SDL_RenderPresent(drawing->renderer);
}
int main(int argc, char *argv[])
{
struct World world;
init_world(&world);
struct Drawing drawing;
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
init_drawing(&drawing);
// le timer ajoute des "user events" qui déclenchent l'animation
SDL_TimerID my_timer_id = SDL_AddTimer(ANIMATION_DELAY_MS, timer_callback, NULL);
for (bool running = true; running; ) {
SDL_Event event;
SDL_WaitEvent(& event);
switch (event.type) {
case SDL_USEREVENT:
animate_world(&world);
draw_world(&world, &drawing);
break;
case SDL_QUIT :
running = false;
break;
case SDL_KEYDOWN :
if (event.key.keysym.sym == 'q') {
running = false;
}
break;
default:
break;
}
}
return 0;
}
Noel approche, profitez de notre offre exceptionnelle, le Makefile (on sait jamais) qui marche sous Linux / Debian avec gestion automatique des dépendances etc.
CFLAGS = -std=c17
CFLAGS += -Wall -Wextra -pedantic -Werror -Wno-unused
CFLAGS += -MMD
CFLAGS += -D_XOPEN_SOURCE=700
CFLAGS += -g
CFLAGS += $(shell sdl2-config --cflags)
LDLIBS = -export-dynamic $(shell sdl2-config --libs)
EXECS = exemple-rotation
TESTS = exemple-rotation
all : $(EXECS) $(TESTS)
run : $(TESTS)
$(foreach p, $(TESTS), ./$(p))
# les objets qui participent à la création de l'exécutable
exemple-rotation: exemple-rotation.o
-include $(wildcard *.d) # correction
clean:
$(RM) *~ *.o *.d
mrproper: clean
$(RM) $(EXECS)
PS: dans le code il faut ajouter - pour le principe - la fermeture de SDL à la fin, et ça fera penser à la libération des ressources (fenetre et renderer). On verra dans un autre exemple en préparation, avec gestion d'un zoom + affichage de texte
- Edité par michelbillaud 30 novembre 2023 à 7:28:20
Comme SLD2 met aussi dans SDL_Event les événements liés à la fenêtre, tu peux les traiter pour détecter les changements de dimensions de la fenêtre et même récupérer les nouvelles dimensions dans les données data1 et data2 véhiculées par l'événement.
Du coup, tu peux recalculer le nouveau centre à cette occasion, au lieu de le recalculer à chaque appel de la fonction de dessin, et le stocker, par exemple dans la struct drawing :
struct Drawing {
SDL_Window *window;
SDL_Renderer *renderer;
int center_x;
int center_y;
};
void draw_world( struct World *world, struct Drawing *drawing)
{
// Fond noir
SDL_SetRenderDrawColor(drawing->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
SDL_RenderClear(drawing->renderer);
// points en rouge
SDL_SetRenderDrawColor(drawing->renderer, 255, 0, 0, SDL_ALPHA_OPAQUE);
for (int i = 0; i < NB_DOTS; i++) {
// l'origine du monde est au centre du dessin -> translation
SDL_RenderDrawPoint(drawing->renderer,
world->dot[i].x + drawing->center_x,
world->dot[i].y + drawing->center_y);
}
SDL_RenderPresent(drawing->renderer);
}
dans ta boucle de traitement des événements, tu peux rajouter le traitement des événements de redimensionnement, et récupérer les nouvelles dimensions pour précalculer le nouveau centre :
switch (event.type) {
case SDL_WINDOWEVENT:
if (event.window.event == SDL_WINDOWEVENT_RESIZED ||
event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
drawing.center_x = event.window.data1 / 2;
drawing.center_y = event.window.data2 / 2;
}
break;
case SDL_USEREVENT:
(...)
Il manque une parenthèse fermante à ton Makefile en ligne 23.
Et voila, avec un peu de redécoupage / nettoyage, + affichage d'un texte + libération des ressources.
Avec valgrind on détecte des fuites mémoire à l'exécution, mais #cépamoua, pas dans mon code, c'est la faute aux bibliothèques SDL / OpenGL qui allouent des trucs au départ et ne les libèrent jamais (chargement de bibliothèques dynamiques, par exemple)
/**
* Essais SDL, avec une pompe à évènements pour l'animation
* et un zoom (qui change avec la touche z)
*/
#include <math.h>
#include <stdbool.h>
#include <SDL.h>
#include <SDL_timer.h>
#include <SDL_ttf.h>
#define W_WIDTH 600
#define W_HEIGHT 400
#define FONT_FILE "/usr/share/fonts/truetype/open-sans/OpenSans-Regular.ttf"
#define FONT_SIZE 24
#define NB_DOTS 10
#define DOT_SPACING 10
#define ROTATION_SPEED (M_PI / 180.0) // radians par frame
#define ANIMATION_DELAY_MS 20
#define UNUSED void
struct Point {
float x, y;
};
struct World {
struct Point dot[NB_DOTS];
bool still_alive;
};
struct Drawing {
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Point origin; // où dessiner le point (0, 0) ?
int zoom; // facteur multiplicatif, de 1 à 4
TTF_Font *font;
};
// -----------------------------------------------------------------
//
// Actions sur World
//
void init_world(struct World *world)
{
for(int i = 0; i < NB_DOTS; i++) {
world->dot[i] = (struct Point) {
.x = 0,
.y = i * DOT_SPACING
};
}
world->still_alive = true;
}
void animate_world(struct World *w)
{
double s = sin(ROTATION_SPEED),
c = cos(ROTATION_SPEED);
for (int i = 0; i < NB_DOTS; i++) {
w->dot[i] = (struct Point) {
.x = c * w->dot[i].x - s * w->dot[i].y,
.y = s * w->dot[i].x + c * w->dot[i].y
};
}
}
// -------------------------------------------------------
//
// actions sur Drawing
//
void change_zoom(struct Drawing *drawing)
{
drawing->zoom = drawing->zoom % 4 + 1;
}
void init_drawing(struct Drawing *drawing)
{
drawing->window = SDL_CreateWindow("test",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
W_WIDTH, W_HEIGHT,
SDL_WINDOW_RESIZABLE);
drawing->renderer = SDL_CreateRenderer(drawing->window, -1, SDL_RENDERER_ACCELERATED);
drawing->zoom = 1;
drawing->origin.x = W_WIDTH / 2;
drawing->origin.y = W_HEIGHT / 2;
drawing->font = TTF_OpenFont(FONT_FILE, FONT_SIZE);
if (drawing->font == NULL) {
fprintf(stderr, "police %s absente\n", FONT_FILE);
exit (0);
}
}
void drawing_destroy(struct Drawing *drawing)
{
TTF_CloseFont(drawing->font);
SDL_DestroyRenderer(drawing->renderer);
SDL_DestroyWindow(drawing->window);
}
// ----------------------------------------------------------
//
// dessin
//
void draw_world( struct World *world, struct Drawing *drawing)
{
// Fond noir
SDL_SetRenderDrawColor(drawing->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
SDL_RenderClear(drawing->renderer);
// points en rouge
SDL_SetRenderDrawColor(drawing->renderer, 255, 0, 0, SDL_ALPHA_OPAQUE);
for (int i = 0; i < NB_DOTS; i++) {
// l'origine du monde est au centre du dessin -> translation
SDL_RenderDrawPoint(drawing->renderer,
drawing->zoom * world->dot[i].x + drawing->origin.x,
drawing->zoom * world->dot[i].y + drawing->origin.y);
}
static const SDL_Color text_color = {0, 255, 255, 0 };
char text[10];
sprintf(text,"zoom %d%%", 100 * drawing->zoom);
SDL_Surface* text_surface = TTF_RenderText_Solid(
drawing->font,
text,
text_color);
SDL_Texture* text_texture = SDL_CreateTextureFromSurface(drawing->renderer, text_surface);
static const SDL_Rect text_rect = { .x = 20, .y=20, .w=200, .h=50 };
SDL_RenderCopy(drawing->renderer, text_texture, NULL, &text_rect);
SDL_RenderPresent(drawing->renderer);
SDL_DestroyTexture(text_texture);
SDL_FreeSurface(text_surface);
}
// gestion du timer et des evènements
Uint32 timer_callback(Uint32 interval, void *param)
{
(UNUSED) param;
SDL_Event event = {
.user = {
.type = SDL_USEREVENT // ici pas besoin des autres champs
}
};
SDL_PushEvent(&event);
return interval;
}
void process_keyboard_event(SDL_KeyboardEvent *event, struct World *world, struct Drawing *drawing)
{
switch (event->keysym.sym) {
case 'q':
world->still_alive = false;
break;
case 'z':
change_zoom(drawing);
break;
}
}
void process_window_event(struct SDL_WindowEvent *event, struct World *world, struct Drawing *drawing)
{
(UNUSED) world;
switch (event->event) {
case SDL_WINDOWEVENT_RESIZED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
drawing->origin.x = event->data1 / 2;
drawing->origin.y = event->data2 / 2;
break;
}
}
// -------------------------------------------------------
//
// et nous y voila
//
int main(int argc, char *argv[])
{
(UNUSED) argc;
(UNUSED) argv;
struct World world;
init_world(&world);
struct Drawing drawing;
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
TTF_Init();
init_drawing(&drawing);
// le timer ajoute des "user events" qui déclenchent l'animation
SDL_AddTimer(ANIMATION_DELAY_MS, timer_callback, NULL);
world.still_alive = true;
while (world.still_alive) {
SDL_Event event;
SDL_WaitEvent(& event);
switch (event.type) {
case SDL_USEREVENT:
animate_world(&world);
draw_world(&world, &drawing);
break;
case SDL_QUIT :
world.still_alive = false;
break;
case SDL_KEYDOWN :
process_keyboard_event(& event.key, &world, &drawing);
break;
case SDL_WINDOWEVENT:
process_window_event(&event.window, &world, &drawing);
break;
default:
break;
}
}
drawing_destroy(&drawing);
TTF_Quit();
SDL_Quit();
return 0;
}
modification du makefile : on met le ompilateur en mode casse-bonbons en lui demandant de signaler les variables non utilisées à l'insu de mon plein gré (il faut les "flagger" avec la macro UNUSED, que je trouve plus explicite que void). + correction de la parenthèse mangée par accident.
Exercice : gérer les touches de direction pour que ça décale le dessin
Complément : pour afficher une chaine codée en UTF-8 (c'est le progrès), utiliser
TTF_RenderUTF8_Solid
au lieu de TTF_RenderText_Solid. Là y a pas de problème parce qu'on ne fait pas afficher de caractères accentués, mais bon, un jour vous voudrez faire un jeu qui affiche "Gagné !".... Ca donne
avec une chaine littérale u8"...." pour bien signaler que ce qu'il y a dedans doit être codé en UTF-8, même si le source utilise un autre encodage, genre latin1 ou CPxyz.
Dans le Makefile, il faut aussi ajouter -lSDL2_ttf à LDLIBS pour ton nouveau programme.
Je n'avais jamais vu l'utilisation de SDL_AddTimer pour gérer la temporisation sur la boucle principale.
C'est une solution que je trouve simple pour gérer une animation peu complexe et adaptée au programme concerné, là tout fonctionne au rythme du Timer.
Elle trouve ses limites quand on doit afficher et gérer de très nombreux objets et animations et où le temps nécessaire pour ces calculs peut être très variable en fonction de la complexité de ce qui doit être géré et dessiné, comme dans un jeu un peu complexe.
C'est pourquoi, en général, les exemples que l'on trouve sur Internet de boucles d'animation utilisent SDL_GetTicks() (il y a aussi SDL_GetPerformanceCounter() mais SDL_GetTicks() en général suffit), en gros cela veux dire :
on note SDL_GetTicks()
on fait les différents traitements
quand on a terminé, on consulte SDL_GetTicks() de nouveau et s'il nous reste du temps, on temporise avec SDL_Delay()
Cela permet de fixer un maximum et de traiter de façon découplée la fréquence d'affichage (on parle de FPS - c'est à dire d'images par secondes) par rapport à l'exécution des autres traitements, afin qu'elle puisse être variable et adaptée selon le temps nécessaire au traitement.
Dans un jeu, tu as aussi typiquement des animations de sprites à gérer (par exemple une action d'un personnage correspond à une animation de 13 images censés s'afficher idéalement sur une certaine période de temps, une autre action peut nécessiter une animation avec plus ou moins de sprites pour une même période ou une période différente). Lorsque la charge de traitement est lourde cela peut être utile de sauter des images pour que l'action du personnage se produise dans le temps prévu (autant que possible). Tu auras une animation un peu moins parfaite, mais au moins le jeu ne semblera pas "ralentit".
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent