From 758f31020accdd5904c4ceb517f296fe2e22977e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Kristian=20H=C3=B8gsberg?= Date: Fri, 19 May 2006 22:11:56 -0400 Subject: [PATCH] First commit of akamaru. --- COPYING | 20 ++ Makefile | 12 ++ akamaru.c | 571 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 603 insertions(+) create mode 100644 COPYING create mode 100644 Makefile create mode 100644 akamaru.c diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..76b4213 --- /dev/null +++ b/COPYING @@ -0,0 +1,20 @@ +Akamaru is Copyright © 2005 Kristian Høgsberg + +Permission to use, copy, modify, distribute, and sell this software +and its documentation for any purpose is hereby granted without fee, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Kristian Høgsberg not +be used in advertising or publicity pertaining to distribution of the +software without specific, written prior permission. Kristian Høgsberg +makes no representations about the suitability of this software for +any purpose. It is provided "as is" without express or implied +warranty. + +KRISTIAN HØGSBERG DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS +SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL KRISTIAN HØGSBERG BE LIABLE FOR ANY +SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER +RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f16a831 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +CFLAGS = -Wall -g +CPPFLAGS = $(shell pkg-config --cflags gtk+-2.0 cairo) +LDLIBS = $(shell pkg-config --libs gtk+-2.0 cairo) +LDFLAGS = -g + +target = akamaru +objs = akamaru.o + +$(target) : $(objs) + +clean : + rm $(target) $(objs) diff --git a/akamaru.c b/akamaru.c new file mode 100644 index 0000000..7e3c348 --- /dev/null +++ b/akamaru.c @@ -0,0 +1,571 @@ +/* -*- mode: c; c-basic-offset: 2 -*- + * To compile: + * + * gcc -Wall -g $(pkg-config --cflags --libs gtk+-2.0 cairo) \ + * akamaru.c -o akamaru + * + * See: + * + * http://en.wikipedia.org/wiki/Verlet_integration + * http://www.teknikus.dk/tj/gdc2001.htm + * + * TODO: + * + * - Fix box collision test + * - Add stick objects instead of hardcoding sticks + * - Add code to add boxes + * - Add circle object + */ + +#include +#include +#include +#include +#include + +const double stick_length = 10; +const double ground_friction = 0.1, ground_level = 400; +const double box_left = 200, box_top = 200, box_bottom = 210; +const double elasticity = 0.7; +const double edge_fuzz = 1; + +typedef struct _xy_pair Point; +typedef struct _xy_pair Vector; +struct _xy_pair { + double x, y; +}; + +typedef struct _Object Object; +typedef struct _Stick Stick; +typedef struct _Model Model; + +struct _Object { + Vector force; + + Point position; + Point previous_position; + Vector velocity; + + double mass; + double theta; +}; + +struct _Stick { + Object *a, *b; +}; + +struct _Model { + int num_objects; + Object *objects; + int num_sticks; + Stick *sticks; + double k; + double friction; + + Object *anchor_object; + Vector anchor_position; + + double theta; +}; + +static void +model_init (Model *model) +{ + const int num_objects = 20; + const int num_sticks = 40 - 3; + int i; + + model->objects = g_new (Object, num_objects); + model->num_objects = num_objects; + model->sticks = g_new (Stick, num_sticks); + model->num_sticks = num_sticks; + + for (i = 0; i < num_objects; i++) { + model->objects[i].position.x = 200; + model->objects[i].position.y = i * stick_length + 40; + model->objects[i].previous_position.x = 200; + model->objects[i].previous_position.y = i * stick_length + 40; + + if (i + 1 < num_objects) { + model->sticks[i * 2].a = &model->objects[i]; + model->sticks[i * 2].b = &model->objects[i + 1]; + } + if (i + 2 < num_objects) { + model->sticks[i * 2 + 1].a = &model->objects[i]; + model->sticks[i * 2 + 1].b = &model->objects[i + 2]; + } + } + + model->anchor_object = NULL; +} + +static void +model_accumulate_forces (Model *model) +{ + int i; + + for (i = 0; i < model->num_objects; i++) { + model->objects[i].force.x = 0; + model->objects[i].force.y = 3; + } +} + +static void +model_integrate (Model *model, double step) +{ + double x, y; + Object *o; + int i; + + for (i = 0; i < model->num_objects; i++) { + o = &model->objects[i]; + x = o->position.x; + y = o->position.y; + + o->position.x = + x + (x - o->previous_position.x) + o->force.x * step * step; + o->position.y = + y + (y - o->previous_position.y) + o->force.y * step * step; + + o->previous_position.x = x; + o->previous_position.y = y; + } +} + +static void +model_constrain (Model *model, double step) +{ + double dx, dy, x, y, distance, fraction, squared; + int i, j; + + /* Anchor object constraint. */ + if (model->anchor_object != NULL) { + model->anchor_object->position.x = model->anchor_position.x; + model->anchor_object->position.y = model->anchor_position.y; + model->anchor_object->previous_position.x = model->anchor_position.x; + model->anchor_object->previous_position.y = model->anchor_position.y; + } + + /* FIXME: this should be "is point inside box" test instead. Figure + * out from previous_position which edge the point has passed + * through and reflect in that. */ + for (i = 0; i < model->num_objects; i++) { + x = model->objects[i].position.x; + y = model->objects[i].position.y; + if (box_top - edge_fuzz <= y && + model->objects[i].previous_position.y <= box_top + edge_fuzz && + x < box_left) { + model->objects[i].position.y = box_top - (y - box_top) * elasticity; + model->objects[i].previous_position.y = + box_top - (model->objects[i].previous_position.y - box_top) * elasticity; + } + } + + /* Ground collision detection constraints. This puts a ground level + * in to make sure the points don't fall off the screen. */ + for (i = 0; i < model->num_objects; i++) { + x = model->objects[i].position.x; + y = model->objects[i].position.y; + + if (model->objects[i].position.y > ground_level) { + model->objects[i].position.y = + ground_level - (model->objects[i].position.y - ground_level) * elasticity; + model->objects[i].previous_position.y = + ground_level - (model->objects[i].previous_position.y - ground_level) * elasticity; + + /* Friction on impact */ + model->objects[i].position.x = + model->objects[i].position.x * (1 - ground_friction) + + model->objects[i].previous_position.x * ground_friction; + } + } + +#if 1 + /* Stick constraints. */ + for (i = 0; i < model->num_sticks; i++) { + x = model->sticks[i].a->position.x; + y = model->sticks[i].a->position.y; + dx = model->sticks[j].b->position.x - x; + dy = model->sticks[j].b->position.y - y; + distance = sqrt (dx * dx + dy * dy); + fraction = (distance - stick_length) / distance / 2; + model->sticks[i].a->position.x = x + dx * fraction; + model->sticks[i].a->position.y = y + dy * fraction; + model->sticks[j].b->position.x = x + dx * (1 - fraction); + model->sticks[j].b->position.y = y + dy * (1 - fraction); + } +#else + /* Stick constraints, without square roots. */ + squared = stick_length * stick_length; + for (i = 0; i < model->num_objects - 1; i++) { + j = i + 1; + x = model->objects[i].position.x; + y = model->objects[i].position.y; + dx = model->objects[j].position.x - x; + dy = model->objects[j].position.y - y; + fraction = squared / (dx * dx + dy * dy + squared) - 0.5; + model->objects[i].position.x = x + dx * fraction; + model->objects[i].position.y = y + dy * fraction; + model->objects[j].position.x = x + dx * (1 - fraction); + model->objects[j].position.y = y + dy * (1 - fraction); + } +#endif +} + +static void +model_step (Model *model, double delta_t) +{ + int i; + + model_accumulate_forces (model); + model_integrate (model, delta_t); + + for (i = 0; i < 10; i++) + model_constrain (model, delta_t); + + model->theta += delta_t; +} + +static double +object_distance (Object *object, double x, double y) +{ + double dx, dy; + + dx = object->position.x - x; + dy = object->position.y - y; + + return sqrt (dx*dx + dy*dy); +} + +static Object * +model_find_nearest (Model *model, double x, double y) +{ + Object *object; + double distance, min_distance; + int i; + + for (i = 0; i < model->num_objects; i++) { + distance = object_distance (&model->objects[i], x, y); + if (i == 0 || distance < min_distance) { + min_distance = distance; + object = &model->objects[i]; + } + } + + return object; +} + +typedef struct _Color Color; +struct _Color { + double red, green, blue; +}; + +static void +draw_star (cairo_t *cr, + gdouble cx, + gdouble cy, + double theta, + Color *color) +{ + const int spike_count = 5; + const int inner_radius = 2; + const int outer_radius = 4; + double x, y; + int i; + + cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5); + cairo_new_path (cr); + for (i = 0; i < spike_count; i++) { + x = cx + cos ((i * 2) * M_PI / spike_count + theta) * inner_radius; + y = cy + sin ((i * 2) * M_PI / spike_count + theta) * inner_radius; + + if (i == 0) + cairo_move_to (cr, x, y); + else + cairo_line_to (cr, x, y); + + x = cx + cos ((i * 2 + 1) * M_PI / spike_count + theta) * outer_radius; + y = cy + sin ((i * 2 + 1) * M_PI / spike_count + theta) * outer_radius; + + cairo_line_to (cr, x, y); + } + cairo_fill (cr); +} + +static void +draw_lines (cairo_t *cr, + Model *model, + Color *color) +{ + int i; + + cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5); + cairo_new_path (cr); + cairo_set_line_width (cr, 4); + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + + for (i = 0; i < model->num_objects; i++) { + cairo_line_to (cr, + model->objects[i].position.x, + model->objects[i].position.y); + } + + cairo_stroke (cr); +} + +static void +draw_constraints (cairo_t *cr, + Model *model, + Color *color) +{ + cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5); + + cairo_move_to (cr, 0, ground_level); + cairo_line_to (cr, 1500, ground_level); + cairo_line_to (cr, 1500, ground_level + 10); + cairo_line_to (cr, 0, ground_level + 10); + cairo_close_path (cr); + + cairo_move_to (cr, 0, box_top); + cairo_line_to (cr, box_left, box_top); + cairo_line_to (cr, box_left, box_bottom); + cairo_line_to (cr, 0, box_bottom); + cairo_close_path (cr); + + cairo_fill (cr); +} + +static Color blue = { 0, 0, 1 }; +static Color red = { 1, 0, 0 }; + +static gboolean +sproing_expose_event (GtkWidget *widget, + GdkEventExpose *event, + gpointer data) +{ + Model *model = data; + int i; + cairo_t *cr; + + cr = gdk_cairo_create (widget->window); + + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_paint (cr); + + draw_constraints (cr, model, &red); + draw_lines (cr, model, &blue); + +#if 0 + for (i = 0; i < model->num_objects; i++) { + draw_star (widget, model->objects[i].position.x, + model->objects[i].position.y, model->objects[i].theta, &blue); + } +#endif + + cairo_destroy (cr); + + return TRUE; +} + +static gboolean +sproing_button_press_event (GtkWidget *widget, + GdkEventButton *event, + gpointer data) +{ + Model *model = data; + + if (event->button != 1) + return TRUE; + + model->anchor_position.x = event->x; + model->anchor_position.y = event->y; + model->anchor_object = model_find_nearest (model, event->x, event->y); + + return TRUE; +} + +static gboolean +sproing_button_release_event (GtkWidget *widget, + GdkEventButton *event, + gpointer data) +{ + Model *model = data; + + if ((event->state & GDK_BUTTON1_MASK) == 0) + return TRUE; + + model->anchor_object = NULL; + + return TRUE; +} + +static gboolean +sproing_motion_notify_event (GtkWidget *widget, + GdkEventMotion *event, + gpointer data) +{ + Model *model = data; + int x, y; + GdkModifierType state; + + gdk_window_get_pointer (event->window, &x, &y, &state); + + model->anchor_position.x = x + 0.5; + model->anchor_position.y = y + 0.5; + + return TRUE; +} + +static void +spring_constant_changed (GtkSpinButton *spinbutton, gpointer user_data) +{ + Model *model = user_data; + + model->k = gtk_spin_button_get_value (spinbutton); +} + +static void +friction_changed (GtkSpinButton *spinbutton, gpointer user_data) +{ + Model *model = user_data; + + model->friction = gtk_spin_button_get_value (spinbutton); +} + +static GtkWidget * +create_spinners (Model *model) +{ + GtkWidget *hbox; + GtkWidget *spinner, *label; + + hbox = gtk_hbox_new (FALSE, 8); + + label = gtk_label_new_with_mnemonic ("_Spring constant:"); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + spinner = gtk_spin_button_new_with_range (0.05, 30.00, 0.05); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), spinner); + gtk_box_pack_start (GTK_BOX (hbox), spinner, FALSE, FALSE, 0); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), model->k); + g_signal_connect (spinner, "value-changed", + G_CALLBACK (spring_constant_changed), model); + + label = gtk_label_new_with_mnemonic ("_Friction:"); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + spinner = gtk_spin_button_new_with_range (0.05, 15.00, 0.05); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), spinner); + gtk_box_pack_start (GTK_BOX (hbox), spinner, FALSE, FALSE, 0); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), model->friction); + g_signal_connect (spinner, "value-changed", + G_CALLBACK (friction_changed), model); + + return hbox; +} + +static GtkWidget * +create_window (Model *model) +{ + GtkWidget *window; + GtkWidget *frame; + GtkWidget *vbox; + GtkWidget *da; + GtkWidget *spinners; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (window), "Drawing Area"); + + 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, 600, 500); + + gtk_container_add (GTK_CONTAINER (frame), da); + + /* Signals used to handle backing pixmap */ + + g_signal_connect (da, "expose_event", + G_CALLBACK (sproing_expose_event), model); + + /* Event signals */ + + g_signal_connect (da, "motion_notify_event", + G_CALLBACK (sproing_motion_notify_event), model); + g_signal_connect (da, "button_press_event", + G_CALLBACK (sproing_button_press_event), model); + g_signal_connect (da, "button_release_event", + G_CALLBACK (sproing_button_release_event), model); + + /* 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); + + spinners = create_spinners (model); + gtk_box_pack_start (GTK_BOX (vbox), spinners, FALSE, FALSE, 0); + + return da; +} + +typedef struct _Closure Closure; +struct _Closure { + GtkWidget *drawing_area; + Model *model; + int i; +}; + +static gint +timeout_callback (gpointer data) +{ + Closure *closure = data; + int i; + + for (i = 0; i < 3; i++) + model_step (closure->model, 0.5); + + closure->i++; + if (closure->i == 1) { + gtk_widget_queue_draw (closure->drawing_area); + closure->i = 0; + } + + return TRUE; +} + +int +main (int argc, char *argv[]) +{ + Closure closure; + Model model; + + gtk_init (&argc, &argv); + model_init (&model); + closure.drawing_area = create_window (&model); + closure.i = 0; + gtk_widget_show_all (gtk_widget_get_toplevel (closure.drawing_area)); + closure.model = &model; + g_timeout_add (100, timeout_callback, &closure); + gtk_main (); + + return 0; +} -- 2.43.0