/* -*- mode: c; c-basic-offset: 2 -*- * To compile: * * gcc -Wall -g $(pkg-config --cflags --libs gtk+-2.0 cairo) \ * akamaru.c main.c -o akamaru */ #include #include #include #include #include #include #include #include #include "akamaru.h" static void model_init_constants (Model *model) { model->elasticity = 0.7; model->friction = 10; model->gravity.x = 0; model->gravity.y = 80; model->k = 10; model->constrain_iterations = 10; } static void model_init_polygons (Model *model) { const double ground_level = 500; model_add_diamond (model, 250, 300); model_add_diamond (model, 400, 150); model_add_rectangle (model, -100, 200, 200, 250); model_add_rectangle (model, -200, ground_level, 1200, ground_level + 400); model_add_rectangle (model, 300, 320, 400, 350); } static void model_init_snake (Model *model) { const int num_objects = 20; Object *object, *prev, *pprev; int i; model_init (model); model_init_constants (model); model_init_polygons (model); prev = NULL; pprev = NULL; for (i = 0; i < num_objects; i++) { object = model_add_object (model, g_random_int_range (100, 300), g_random_int_range (100, 300), 1, NULL); if (pprev != NULL) model_add_stick (model, object, pprev, g_random_int_range (20, 40)); if (prev != NULL) model_add_stick (model, object, prev, g_random_int_range (20, 40)); pprev = prev; prev = object; } } static void model_init_rope (Model *model) { const int num_objects = 20; const int stick_length = 10; Object *object, *prev; int i; model_init (model); model_init_constants (model); model_init_polygons (model); for (i = 0, prev = NULL; i < num_objects; i++, prev = object) { object = model_add_object (model, 200, 40 + i * stick_length, 1, NULL); if (prev != NULL) model_add_stick (model, object, prev, stick_length); } } static void model_init_curtain (Model *model) { const int num_ropes = 5; const int num_rope_objects = 15; const int stick_length = 10; const int rope_offset = 30; Offset *offset; Object *prev, *object; double x, y; int i, j; model_init (model); model_init_constants (model); model_init_polygons (model); offset = model_add_offset (model, num_ropes, rope_offset, 0); for (i = 0; i < num_ropes; i++) { prev = NULL; for (j = 0; j < num_rope_objects; j++) { x = 200 + i * rope_offset; y = 40 + j * stick_length; object = model_add_object (model, x, y, 1, NULL); if (j == 0) offset->objects[i] = object; else model_add_stick (model, prev, object, stick_length); prev = object; } } } static void model_init_grid (Model *model) { const int width = 8, height = 8; const int distance = 20; Offset *offset; Object *last_column[height], *object; int i, j; model_init (model); model_init_constants (model); model_init_polygons (model); offset = model_add_offset (model, width, distance, 0); for (i = 0; i < width; i++) { for (j = 0; j < height; j++) { object = model_add_object (model, 200 + i * distance, 40 + j * distance, 1, NULL); if (i > 0) model_add_string (model, last_column[j], object, distance); if (j == 0) offset->objects[i] = object; else model_add_string (model, last_column[j - 1], object, distance); last_column[j] = object; } } } static void model_init_molecule (Model *model) { const int num_objects = 8; const int spring_length = 50; Object *objects[num_objects]; int i; model_init (model); model_init_constants (model); for (i = 0; i < num_objects; i++) objects[i] = model_add_object (model, g_random_int_range (200, 400), g_random_int_range (200, 400), 0, NULL); for (i = 0; i < num_objects; i++) { model_add_spring (model, objects[i], objects[(i + 1) % num_objects], spring_length); model_add_spring (model, objects[i], objects[(i + 2) % num_objects], spring_length); } } static void model_init_wobbly (Model *model) { const int width = 8, height = 8; const int distance = 30; double x, y; int i, j; Object *object, *last_column[height]; model_init (model); model_init_constants (model); model_init_polygons (model); for (i = 0; i < width; i++) { for (j = 0; j < height; j++) { x = 200 + i * distance; y = 40 + j * distance; object = model_add_object (model, x, y, 0, NULL); if (i > 0) model_add_offset_spring (model, last_column[j], object, distance, 0); if (j > 0) model_add_offset_spring (model, last_column[j - 1], object, 0, distance); last_column[j] = object; } } } typedef struct { Model *model; Object *current; Object *anchor; int distance; } DockInitClosure; static void dock_add_spacer (Object *object, void *data) { DockInitClosure *closure = data; /* Skip the anchor object. */ if (object != closure->anchor && object != closure->current) model_add_spacer (closure->model, object, closure->current, closure->distance); } static void model_init_dock (Model *model) { const int num_objects = 8; const int distance = 30; DockInitClosure closure; int i; model_init (model); model_init_constants (model); model_add_enclosing_rectangle (model, 10, 10, 700, 500); closure.anchor = model_add_object (model, 0, 0, 0, NULL); model_add_anchor (model, closure.anchor, 300, 300); closure.model = model; closure.distance = distance; for (i = 0; i < num_objects; i++) { closure.current = model_add_object (model, 200 + i * distance / 3, 40, 2, NULL); model_add_spring (model, closure.anchor, closure.current, distance); model_for_each_object (model, dock_add_spacer, &closure); } } static void model_init_big_board (Model *model) { const int num_objects = 8; const int distance = 30; DockInitClosure closure; int i; model_init (model); model_init_constants (model); model->elasticity = 0.4; model->friction = 30; model->gravity.x = 0; model->gravity.y = 40; model->k = 2; model->constrain_iterations = 10; model_add_enclosing_rectangle (model, 10, 10, 700, 500); closure.anchor = NULL; closure.model = model; closure.distance = distance; for (i = 0; i < num_objects; i++) { closure.current = model_add_object (model, 200, 40 + i * 4 * distance / 3, 2, NULL); model_for_each_object (model, dock_add_spacer, &closure); } } static void draw_stick (Stick *stick, void *data) { cairo_t *cr = data; cairo_move_to (cr, stick->a->position.x, stick->a->position.y); cairo_line_to (cr, stick->b->position.x, stick->b->position.y); } static void draw_string (String *string, void *data) { cairo_t *cr = data; cairo_move_to (cr, string->a->position.x, string->a->position.y); cairo_line_to (cr, string->b->position.x, string->b->position.y); } static void draw_offset (Offset *offset, void *data) { cairo_t *cr = data; int i; cairo_move_to (cr, offset->objects[0]->position.x, offset->objects[0]->position.y); for (i = 1; i < offset->num_objects; i++) cairo_line_to (cr, offset->objects[i]->position.x, offset->objects[i]->position.y); } static void draw_spring (Spring *spring, void *data) { cairo_t *cr = data; cairo_move_to (cr, spring->a->position.x, spring->a->position.y); cairo_line_to (cr, spring->b->position.x, spring->b->position.y); } static void draw_offset_spring (OffsetSpring *spring, void *data) { cairo_t *cr = data; cairo_move_to (cr, spring->a->position.x, spring->a->position.y); cairo_line_to (cr, spring->b->position.x, spring->b->position.y); } static void draw_polygon (Polygon *polygon, void *data) { cairo_t *cr = data; int i; for (i = 0; i < polygon->num_points; i++) cairo_line_to (cr, polygon->points[i].x, polygon->points[i].y); cairo_close_path (cr); if (polygon->enclosing) cairo_stroke (cr); else cairo_fill (cr); } static void draw_object (Object *object, void *data) { cairo_t *cr = data; #if 0 cairo_arc (cr, object->position.x, object->position.y, 3, 0, 2 * M_PI); cairo_fill (cr); #else int margin = 10; cairo_move_to(cr, object->position.x - margin, object->position.y - margin); cairo_line_to(cr, object->position.x + margin, object->position.y - margin); cairo_line_to(cr, object->position.x + margin, object->position.y + margin); cairo_line_to(cr, object->position.x - margin, object->position.y + margin); cairo_fill (cr); #endif } typedef struct _Closure Closure; struct _Closure { GtkWidget *drawing_area; GtkWidget *fps_label; Model *model; Anchor *mouse_anchor; int frame_count; int i; struct timeval start; }; static void draw_model (GtkWidget *widget, Model *model) { cairo_t *cr; cr = gdk_cairo_create (widget->window); cairo_set_source_rgb (cr, 1, 1, 1); cairo_paint (cr); cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); cairo_set_line_width (cr, 1); cairo_set_source_rgba (cr, 0, 0, 0.5, 1); model_for_each_polygon (model, draw_polygon, cr); cairo_set_source_rgba (cr, 0, 1, 0, 1); model_for_each_string (model, draw_string, cr); cairo_stroke (cr); cairo_set_source_rgba (cr, 0, 0, 0, 0.8); model_for_each_spring (model, draw_spring, cr); cairo_stroke (cr); cairo_set_line_width (cr, 4); cairo_set_source_rgba (cr, 0, 0, 0.5, 0.5); model_for_each_offset (model, draw_offset, cr); cairo_stroke (cr); cairo_set_line_width (cr, 2); cairo_set_source_rgba (cr, 0, 0, 0, 1); model_for_each_stick (model, draw_stick, cr); cairo_stroke (cr); cairo_set_source_rgba (cr, 1, 0, 1, 0.4); model_for_each_offset_spring (model, draw_offset_spring, cr); cairo_stroke (cr); cairo_set_source_rgba (cr, 1, 0, 0, 0.4); model_for_each_object (model, draw_object, cr); cairo_destroy (cr); } static gboolean expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer data) { Closure *closure = data; draw_model (widget, closure->model); return TRUE; } static gboolean button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer data) { Closure *closure = data; Object *object; if (event->button != 1) return TRUE; object = model_find_nearest (closure->model, event->x, event->y); closure->mouse_anchor = model_add_anchor (closure->model, object, event->x, event->y); return TRUE; } static gboolean button_release_event (GtkWidget *widget, GdkEventButton *event, gpointer data) { Closure *closure = data; if ((event->state & GDK_BUTTON1_MASK) == 0) return TRUE; model_delete_anchor (closure->model, closure->mouse_anchor); closure->mouse_anchor = NULL; return TRUE; } static gboolean motion_notify_event (GtkWidget *widget, GdkEventMotion *event, gpointer data) { Closure *closure = data; int x, y; GdkModifierType state; gdk_window_get_pointer (event->window, &x, &y, &state); if (closure->mouse_anchor != NULL) { closure->mouse_anchor->x = x + 0.5; closure->mouse_anchor->y = y + 0.5; } return TRUE; } typedef void (*ModelInitFunc) (Model *model); static void model_changed (GtkComboBox *combo, gpointer user_data) { Closure *closure = user_data; GtkTreeIter iter; GtkTreeModel *tree_model; ModelInitFunc init; char *name; tree_model = gtk_combo_box_get_model (combo); if (!gtk_combo_box_get_active_iter (combo, &iter)) return; gtk_tree_model_get (tree_model, &iter, 0, &name, 1, &init, -1); model_fini (closure->model); (*init) (closure->model); } static GtkTreeModel * create_model_store (void) { static struct { const char *name; ModelInitFunc init; } models[] = { { "Rope", model_init_rope }, { "Snake", model_init_snake }, { "Curtain", model_init_curtain }, { "Grid", model_init_grid }, { "Molecule", model_init_molecule }, { "Wobbly", model_init_wobbly }, { "Dock", model_init_dock }, { "Big Board", model_init_big_board } }; GtkTreeIter iter; GtkTreeStore *store; gint i; store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_POINTER); for (i = 0; i < G_N_ELEMENTS(models); i++) { gtk_tree_store_append (store, &iter, NULL); gtk_tree_store_set (store, &iter, 0, models[i].name, 1, models[i].init, -1); } return GTK_TREE_MODEL (store); } static GtkWidget * create_model_combo (Closure *closure) { GtkWidget *hbox; GtkWidget *combo, *label; GtkTreeModel *store; GtkCellRenderer *renderer; hbox = gtk_hbox_new (FALSE, 8); label = gtk_label_new_with_mnemonic ("_Model:"); gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); store = create_model_store (); combo = gtk_combo_box_new_with_model (store); gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0); g_object_unref (store); renderer = gtk_cell_renderer_text_new (); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE); gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer, "text", 0, NULL); gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo); gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0); g_signal_connect (combo, "changed", G_CALLBACK (model_changed), closure); label = gtk_label_new ("Frames per second: 0"); gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); closure->fps_label = label; return hbox; } static void create_window (Closure *closure) { GtkWidget *window; GtkWidget *frame; GtkWidget *vbox; GtkWidget *da; GtkWidget *model_combo; window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "赤丸"); g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), &window); gtk_container_set_border_width (GTK_CONTAINER (window), 8); vbox = gtk_vbox_new (FALSE, 8); gtk_container_set_border_width (GTK_CONTAINER (vbox), 8); gtk_container_add (GTK_CONTAINER (window), vbox); /* * Create the drawing area */ frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0); da = gtk_drawing_area_new (); /* set a minimum size */ gtk_widget_set_size_request (da, 200, 200); gtk_container_add (GTK_CONTAINER (frame), da); /* Signals used to handle backing pixmap */ g_signal_connect (da, "expose_event", G_CALLBACK (expose_event), closure); /* Event signals */ g_signal_connect (da, "motion_notify_event", G_CALLBACK (motion_notify_event), closure); g_signal_connect (da, "button_press_event", G_CALLBACK (button_press_event), closure); g_signal_connect (da, "button_release_event", G_CALLBACK (button_release_event), closure); /* Ask to receive events the drawing area doesn't normally * subscribe to */ gtk_widget_set_events (da, gtk_widget_get_events (da) | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK); model_combo = create_model_combo (closure); gtk_box_pack_start (GTK_BOX (vbox), model_combo, FALSE, FALSE, 0); closure->drawing_area = da; } static gint timeout_callback (gpointer data) { Closure *closure = data; int i; for (i = 0; i < 10; i++) model_step (closure->model, 0.005); closure->i++; if (closure->i == 3) { gtk_widget_queue_draw (closure->drawing_area); closure->i = 0; closure->frame_count++; } if (closure->frame_count == 200) { struct timeval end, elapsed; double total; char text[50]; closure->frame_count = 0; gettimeofday (&end, NULL); if (closure->start.tv_usec > end.tv_usec) { end.tv_usec += 1000000; end.tv_sec--; } elapsed.tv_usec = end.tv_usec - closure->start.tv_usec; elapsed.tv_sec = end.tv_sec - closure->start.tv_sec; total = elapsed.tv_sec + ((double) elapsed.tv_usec / 1e6); if (total < 0) { total = 0; } closure->start = end; snprintf (text, sizeof text, "Frames per second: %.2f", 200 / total); gtk_label_set_text (GTK_LABEL (closure->fps_label), text); } return TRUE; } int main (int argc, char *argv[]) { Closure closure; Model model; gtk_init (&argc, &argv); model_init_rope (&model); create_window (&closure); closure.i = 0; closure.model = &model; closure.mouse_anchor = NULL; closure.frame_count = 0; gettimeofday (&closure.start, NULL); g_timeout_add (20, timeout_callback, &closure); gtk_widget_show_all (gtk_widget_get_toplevel (closure.drawing_area)); gtk_main (); return 0; }