Эффект движения паровоза

-2.png

Задача: нарисовать паровоз и придать ему эффект движения;
Использованный API: GTK/GDK, Cairo;
Среда разработки: Code::Blocks 8.02;

В этой картинке 3 анимированых объекта:
1. Толкательная система
2. Дым
3. Шпалы

Толкательная система движется при помощи функций sin и cos, делая круговые движения. Координаты стыков палок толкательной системы вычисляются по формулам:

x = cx + r1*cos(p)
y = cy + r2*sin(p)

где cx, cy - координаты первоначального положения стыков
p - позиция толкательной системы
r1, r2 - диапазон движения стыков

Очевидно, что это окружность. Но при r2=0 стык между палками будет делать движения только влево-вправо.

Дым движется по зеркальному отражению функции y=sqrt(x) описывая достаточно реалистичную траекторию, свойственную настоящему дыму. Чем дальше клуб дыма от трубы, тем больше его радиус и прозрачность. В каждый момент, когда из трубы должен выйти очередной клуб дыма, стартовая позиция рисования дыма сдвигается в самое начало. Со шпалами ещё проще - это просто прорисовка прямоугольников вдоль прямой, стартовая позиция рисования так же сдвигается когда справа должна показаться новая шпала.

#include <stdlib.h>
#include <gtk/gtk.h>
#include <math.h>
 
typedef struct dat dat;
struct dat
{
    GtkWidget *wid; //виджет
    float pos; //позиция толкательной системы паровоза
    char drawed; //переменная необходима для проверки состояния нарисованости неподвижных частей анимации
    GdkPixbuf *pb; //буфер для перерисовки неподвижных частей картинки
    cairo_t *cr; //переменная для рисования cairo
    gint start_shpala, start_dym; //позиция шпал и дыма
    float color[3]; //цвет заливки паровоза
};
 
gboolean on_draw(GtkWidget *wid, GdkEventExpose *event, gpointer data) //функция рисования
{
    dat *d=(dat*)data;
    d->cr = gdk_cairo_create(wid->window); //инициализация cairo
    gint i;
    if(d->drawed == 0) //если эта часть кода не нарисована, то рисуем
    {
        cairo_move_to(d->cr, 0, 0); //ставим кисть в точку (0,0) экрана
        cairo_line_to(d->cr, wid->allocation.width, 0);
        cairo_line_to(d->cr, wid->allocation.width, wid->allocation.height);
        cairo_line_to(d->cr, 0, wid->allocation.height);
        cairo_line_to(d->cr, 0, 0);
        cairo_set_source_rgb(d->cr, 0.7, 0.8, 1);
        cairo_fill(d->cr); //заливаем выделеную область
 
        cairo_set_source_rgb(d->cr, 0.5, 0.5, 0.5);
        cairo_set_line_width (d->cr, 2);
 
        cairo_rectangle(d->cr, 515, 335, 60, 80);
        cairo_set_source_rgb(d->cr, 0, 0, 0);
        cairo_stroke_preserve(d->cr);
        cairo_set_source_rgb(d->cr, d->color[0], d->color[1], d->color[2]);
        cairo_fill(d->cr);
 
        cairo_arc (d->cr, 605, 425, 25, 0, 2*M_PI); //-----------------
        cairo_set_line_width(d->cr, 1);             // Маленькое колесо 
        cairo_set_source_rgb(d->cr, 0, 0, 0);
        cairo_stroke_preserve(d->cr);
        cairo_set_source_rgb(d->cr, 0.7, 0.7, 0.7);
        cairo_fill(d->cr);
        cairo_arc (d->cr, 605, 425, 20, 0, 2*M_PI); 
        cairo_set_source_rgb(d->cr, 0, 0, 0);
        cairo_stroke_preserve(d->cr);
        cairo_set_source_rgb(d->cr, 0.4, 0.4, 0.4);
        cairo_fill(d->cr);
 
        cairo_set_source_rgb(d->cr, 0, 0, 0);
        cairo_arc (d->cr, 605, 425, 3, 0, 2*M_PI); //серцевина мальенкого колеса
        cairo_fill(d->cr);                         //-------------------------
 
        cairo_set_line_width(d->cr, 2);//-------------
        cairo_move_to(d->cr, 535, 340);//Красный обод над колёсами
        cairo_line_to(d->cr, 195, 340);
        cairo_arc (d->cr, 222, 340, 27, M_PI, 3*M_PI/2);
        cairo_line_to(d->cr, 597, 313);
        cairo_arc (d->cr, 540, 415, 117, 3*M_PI/2 + M_PI/6, M_PI/14);
        cairo_line_to(d->cr, 649, 440);
        cairo_line_to(d->cr, 623, 340);
        cairo_line_to(d->cr, 535, 340);
        cairo_set_source_rgb(d->cr, 0, 0, 0);
        cairo_stroke_preserve(d->cr);
        cairo_set_source_rgb(d->cr, 0.8, 0, 0);
        cairo_fill(d->cr);//-----------------------------------------
 
        cairo_arc (d->cr, 545, 440, 100, 3*M_PI/2, 0); //Внутренняя сторона щитка
        cairo_line_to(d->cr, 654, 441);
        cairo_line_to(d->cr, 625, 340);
        cairo_line_to(d->cr, 545, 340);
        cairo_set_source_rgb(d->cr, 0.8, 0, 0);
        cairo_fill(d->cr);
        cairo_arc (d->cr, 545, 440, 100, 3*M_PI/2 + M_PI/29, 0);
        cairo_set_line_width(d->cr, 1);
        cairo_set_source_rgb(d->cr, 0, 0, 0);
        cairo_stroke(d->cr);
 
        cairo_set_line_width(d->cr, 2);
        cairo_set_source_rgb(d->cr, 0, 0, 0);
 
        cairo_rectangle(d->cr, 106, 330, 20, 10);
        cairo_rectangle(d->cr, 96, 320, 10, 30);
        cairo_fill(d->cr);
 
        cairo_move_to(d->cr, 108, 116);
        cairo_arc (d->cr, 0, 180, 126, 3*M_PI/2 + M_PI/3, 0); //Левый верхний угол
        cairo_line_to(d->cr, 126, 354);
        cairo_arc (d->cr, 0, 425, 145, 3*M_PI/2 + M_PI/3 + M_PI/40, 0); //Нижний левый угол паровоза
        cairo_line_to(d->cr, 195, 425);
        cairo_line_to(d->cr, 195, 340);
        cairo_arc (d->cr, 222, 340, 27, M_PI, 3*M_PI/2);
        cairo_line_to(d->cr, 599, 313);
        cairo_line_to(d->cr, 550, 195);
        cairo_line_to(d->cr, 530, 195);
        cairo_line_to(d->cr, 550, 135); // Труба
        cairo_line_to(d->cr, 490, 135);
        cairo_line_to(d->cr, 510, 195);
        cairo_line_to(d->cr, 254, 195);
 
        cairo_stroke_preserve(d->cr);
        cairo_set_source_rgb(d->cr, d->color[0], d->color[1], d->color[2]);
        cairo_line_to(d->cr, 254, 194);
        cairo_line_to(d->cr, 110, 116);
        cairo_fill(d->cr);
        cairo_arc (d->cr, 550, 264, 69, 3*M_PI/2, M_PI/4); //Передок
        cairo_set_source_rgb(d->cr, 0, 0, 0);
        cairo_stroke_preserve(d->cr);
        cairo_set_source_rgb(d->cr, d->color[0], d->color[1], d->color[2]);
        cairo_fill(d->cr);
        cairo_move_to(d->cr, 599, 313);
        cairo_line_to(d->cr, 550, 195);
        cairo_stroke(d->cr);
 
        cairo_move_to(d->cr, 108, 116);
        cairo_line_to(d->cr, 238, 116);
        cairo_arc (d->cr, 34, 195, 220, 3*M_PI/2 + M_PI/3 + M_PI/20, 0); //Лобовое стекло
        cairo_set_source_rgb(d->cr, 0, 0, 0);
        cairo_stroke_preserve(d->cr);
        cairo_set_source_rgb(d->cr, d->color[0], d->color[1], d->color[2]);
        cairo_fill(d->cr);
 
        cairo_arc (d->cr, 24, 194, 220, 3*M_PI/2 + M_PI/3 + M_PI/15, 0);
        cairo_line_to(d->cr, 138, 194);
        cairo_line_to(d->cr, 138, 126);
        cairo_line_to(d->cr, 234, 126);
        cairo_set_source_rgb(d->cr, 0, 0, 0);
        cairo_stroke_preserve(d->cr);
        cairo_set_source_rgba(d->cr, 0.5, 0.5, 0.5, 0.5);
        cairo_fill(d->cr);
 
        cairo_arc (d->cr, 370, 195, 20, M_PI, 0); //Песочница
        cairo_set_source_rgb(d->cr, 0, 0, 0);
        cairo_stroke_preserve(d->cr);
        cairo_set_source_rgb(d->cr, d->color[0], d->color[1], d->color[2]);
        cairo_fill(d->cr);
 
        cairo_stroke(d->cr);
 
    	cairo_set_source_rgb(d->cr, 0, 0, 0);
    	cairo_set_line_width (d->cr, 1);
 
    for (i=0; i<3; i++)
    {
        cairo_arc (d->cr, 250 + i*105, 400, 50, 0, 2*M_PI); //------------
        cairo_stroke_preserve(d->cr);                       //Большие колёса
        cairo_set_source_rgb(d->cr, 0.7, 0.7, 0.7);         //
        cairo_fill(d->cr);
        cairo_arc (d->cr, 250 + i*105, 400, 45, 0, 2*M_PI);
        cairo_set_source_rgb(d->cr, 0, 0, 0);
        cairo_stroke_preserve(d->cr);
        cairo_set_source_rgb(d->cr, 0.4, 0.4, 0.4);
        cairo_fill(d->cr);
        cairo_set_source_rgb(d->cr, 0, 0, 0);
        cairo_arc (d->cr, 250 + i*105, 400, 5, 0, 2*M_PI);
        cairo_fill(d->cr);
    }                                                       //---------------
        cairo_set_source_rgb(d->cr, 0,0, 20.0/255.0);
        cairo_select_font_face(d->cr, "Impact",CAIRO_FONT_SLANT_ITALIC, CAIRO_FONT_WEIGHT_NORMAL);
        cairo_set_font_size(d->cr, 24);
        cairo_move_to(d->cr, 450, 310);
        cairo_show_text(d->cr, "SteamMonster"); //Надпись на паровозе
 
        cairo_rectangle(d->cr,0, 445,wid->allocation.width, 10); //рельса
        cairo_set_source_rgb(d->cr, 0.5, 0.3, 0.1);
        cairo_fill(d->cr);
 
        cairo_rectangle(d->cr,0, 460,wid->allocation.width, wid->allocation.height); //земля
        cairo_set_source_rgb(d->cr, 0, 0, 0);
        cairo_fill(d->cr);
 
        d->drawed = 1; //отметка о том что неподвижная часть анимации нарисована
        if (d->pb != NULL)
            g_object_unref(d->pb);
        d->pb = gdk_pixbuf_get_from_drawable(NULL,wid->window,NULL,0,0,0,0,wid->allocation.width,wid->allocation.height); //помещение картинки в пиксбаф
 
    }
    else // Подвижная часть картинки
    {
    GdkGC * gc = wid->style->fg_gc[GTK_WIDGET_STATE (wid)];
    gdk_draw_pixbuf(wid->window,gc,d->pb,0,0,0,0,wid->allocation.width,wid->allocation.height,0,0,0); //Перерисовываем неподвижную часть картинки
 
    cairo_set_source_rgb(d->cr, 0, 0, 0);
    cairo_set_line_width (d->cr, 4);
    cairo_move_to(d->cr, 515, 400);
    cairo_line_to(d->cr, 485 + 25*cos(d->pos), 400); //1ая палка толкательной системы
    cairo_line_to(d->cr, 355 + 25*cos(d->pos), 400 + 25*sin(d->pos));//2ая палка которая двигается по внешнему радиусу среднего колеса
    cairo_line_to(d->cr, 355 + 7*cos(d->pos-1), 400 + 7*sin(d->pos-1)); //3я, по внутренему радиусу среднего колеса
    cairo_line_to(d->cr, 408 + 10*cos(d->pos), 390); //4ая, движется на весу
    cairo_line_to(d->cr, 408 - 10*cos(d->pos), 340); //последняя, которая уходит в верхнюю часть паровоза
 
    cairo_move_to(d->cr, 250 + 25*cos(d->pos), 400 + 25*sin(d->pos));//соединительная палка между колёсами
    cairo_line_to(d->cr, 460 + 25*cos(d->pos), 400 + 25*sin(d->pos));//--------------------
    cairo_stroke(d->cr);
    cairo_arc(d->cr, 485 + 25*cos(d->pos), 400, 3, 0, 2*M_PI);//соединенительный крепёж между 1ой и 2ой палкой
    cairo_arc(d->cr, 355 + 7*cos(d->pos-1), 400 + 7*sin(d->pos-1), 4, 0, 2*M_PI);//между 2ой и 3ей
    cairo_fill(d->cr);
    cairo_arc(d->cr, 408 + 10*cos(d->pos), 390, 3, 0, 2*M_PI);// и т. д.
    cairo_fill(d->cr);
    cairo_arc(d->cr, 355 + 25*cos(d->pos), 400 + 25*sin(d->pos), 4, 0, 2*M_PI);
    cairo_arc(d->cr, 250 + 25*cos(d->pos), 400 + 25*sin(d->pos), 4, 0, 2*M_PI);
    cairo_arc(d->cr, 460 + 25*cos(d->pos), 400 + 25*sin(d->pos), 4, 0, 2*M_PI);
    cairo_fill(d->cr);
 
    i = d->start_shpala;
    cairo_set_source_rgb(d->cr, 0.5, 0.5, 0.5);
    while (i < wid->allocation.width) {  
        cairo_rectangle(d->cr, i, 455, 15, 10); //рисование шпал вдоль рельсы пока не закончится область рисования
        cairo_fill(d->cr);
        i+=30;//расстояние между шпалами 30 пикселей
    }
    if (d->start_shpala > -55)
        d->start_shpala -= 5; 
        else d->start_shpala = 0; //обнуление стартовой позиции расстановки шпал когда справа появляется новая шпала
 
    i = d->start_dym;
    while (i < 85) {
        cairo_set_source_rgba(d->cr, 0.5, 0.5, 0.5, 0.4 - i*0.004 ); //установка цвета и прозрачности дыма. Прозрачность зависит от позиции: чем дальше дым, тем более он прозрачен
        cairo_arc(d->cr, 520 - i*5 , 135 - sqrt(i*380), 20 + i/3, 0, 2*M_PI); //Рисование клубов дыма. Чем дальше дым тем больше радиус круга
        cairo_fill(d->cr);
        i+=5;
    }
    if (d->start_dym < 4)
        d->start_dym += 1;
        else d->start_dym = 0; //обнуление стартовой позиции прорисовки дыма, когда очередному клубу дыма пора выйти из трубы
    }
    cairo_set_source_rgb(d->cr, 1, 1, 1);
    cairo_move_to(d->cr, 530, 195); //------------------------------------------------
    cairo_line_to(d->cr, 550, 135); //Перерисовываем трубу чтобы дым её не перекрывал
    cairo_line_to(d->cr, 490, 135);
    cairo_line_to(d->cr, 510, 195);
    cairo_set_source_rgb(d->cr, 0, 0, 0);
    cairo_stroke_preserve(d->cr);
    cairo_set_source_rgb(d->cr, d->color[0], d->color[1], d->color[2]);
    cairo_fill(d->cr);//------------------------------------------------
    cairo_destroy(d->cr);
    return TRUE;
}
 
gboolean steam(gpointer data)
{
    dat *d=(dat*)data;
    d->pos+=0.1; //сдвиг позиции толкательной системы
    if (d->pos>=2*M_PI) d->pos=0.0; //обнуление позиции толкательной системы когда она сделает полный оборот
    gtk_widget_queue_draw(d->wid);//перерисовка паровоза
    return TRUE;
}
 
int main (int argc, char *argv[])
{
  GtkWidget *win = NULL;
 
  /* Initialize GTK+ */
  g_log_set_handler ("Gtk", G_LOG_LEVEL_WARNING, (GLogFunc) gtk_false, NULL);
  gtk_init (&argc, &argv);
  g_log_set_handler ("Gtk", G_LOG_LEVEL_WARNING, g_log_default_handler, NULL);
 
  /* Create the main window */
  win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_container_set_border_width (GTK_CONTAINER (win), 8);
  gtk_window_set_title (GTK_WINDOW (win), "Паравозег");
  gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_CENTER);
  gtk_widget_set_size_request(win, 700, 500);
  gtk_widget_realize (win);
  g_signal_connect (win, "destroy", gtk_main_quit, NULL);
 
  dat *d=NULL;
  d=(dat *)malloc(sizeof(dat));
  d->pos=0.0;
  d->wid=win;
  d->drawed = 0;
  d->pb = NULL;
  d->cr = NULL;
  d->start_shpala = 0;
  d->start_dym = 0;
  d->color[0]=0;
  d->color[1]=0.4;
  d->color[2]=0.6;
  g_timeout_add(50, steam, (gpointer)d);
  g_signal_connect (G_OBJECT (win), "expose_event", G_CALLBACK (on_draw), (gpointer)d);
 
  /* Enter the main loop */
  gtk_widget_show_all (win);
  gtk_main ();
  return 0;
}

Ключевые слова: 
анимация, движение по окружности, паровоз
ВложениеРазмер
main.c.rar3.4 кб