diff --git a/docs/CHANGES.txt b/docs/CHANGES.txt index 1bbfc8550..c0a746335 100644 --- a/docs/CHANGES.txt +++ b/docs/CHANGES.txt @@ -6,7 +6,7 @@ Copyright (c) 2002-2024 Various contributors (see below, and AUTHORS.txt) https://tuxpaint.org/ -2024.January.2 (0.9.32) +2024.January.7 (0.9.32) * Improvements to Magic tools: ---------------------------- * Support for complexity levels in Magic tools via the plugin API. @@ -54,6 +54,19 @@ https://tuxpaint.org/ (https://freesound.org/people/mudflea2/sounds/708182/) Creative Commons 0 by mudflea2 + * WIP - Epitrochoid and Hypotrochoid magic tools: + + Tools to draw centered trochoids; to create art similar to a + Spirograph or Wondergraph. Drag left/right to adjust the + radius of the stator (fixed circle). Drag up/down to adjust + radius of the rotator (the rolling circle). Size option + adjusts the position of the virtual pen within the rotator. + + If "--nomagicsizes" is set, three versions each of + epitrochoid and hypotrochoid drawing tools will be made + available, each with differing pen positions. + + Code: Bill Kendrick + + WIP - Needs icons + + WIP - Needs sounds + * Improvements to "Text" & "Label" tools: --------------------------------------- * The name and size of the chosen font is shown in the instructions diff --git a/magic/src/trochoids.c b/magic/src/trochoids.c new file mode 100644 index 000000000..395cebfe6 --- /dev/null +++ b/magic/src/trochoids.c @@ -0,0 +1,499 @@ +/* trochoids.c + + Magic tools to draw various centered trochoids; + similar to art generated by devices like the + Spirograph and Wondergraph. + + by Bill Kendrick + + January 6, 2024 - January 7, 2024 +*/ + +#include +#include +#include + +#include "tp_magic_api.h" +#include "SDL_image.h" +#include "SDL_mixer.h" + +#define deg_cos(x) cos((float) (x) * M_PI / 180.0) +#define deg_sin(x) sin((float) (x) * M_PI / 180.0) + +/* All _possible_ tools */ +enum +{ + TOOL_EPITROCHOID_SIZES, + TOOL_EPITROCHOID_NOSIZES_1, + TOOL_EPITROCHOID_NOSIZES_2, + TOOL_EPITROCHOID_NOSIZES_3, + TOOL_HYPOTROCHOID_SIZES, + TOOL_HYPOTROCHOID_NOSIZES_1, + TOOL_HYPOTROCHOID_NOSIZES_2, + TOOL_HYPOTROCHOID_NOSIZES_3, + NUM_TOOLS +}; + +Uint8 tp_offers_sizes = 1; + +int num_tools[2] = { + 6, /* when sizes not available */ + 2, /* when sizes available */ +}; + +int * which_to_tool; + +int which_to_tool_per_size_availability[2][6] = { + /* when sizes not available */ + { + TOOL_EPITROCHOID_NOSIZES_1, + TOOL_EPITROCHOID_NOSIZES_2, + TOOL_EPITROCHOID_NOSIZES_3, + TOOL_HYPOTROCHOID_NOSIZES_1, + TOOL_HYPOTROCHOID_NOSIZES_2, + TOOL_HYPOTROCHOID_NOSIZES_3, + }, + /* when sizes available */ + { + TOOL_EPITROCHOID_SIZES, + TOOL_HYPOTROCHOID_SIZES, + -1, + -1, + -1, + -1, + }, +}; + +#define SIZE_WELLINSIDE 0.33 +#define SIZE_INSIDE 0.66 +#define SIZE_EDGE 1.00 +#define SIZE_OUTSIDE 1.50 +#define SIZE_WELLOUTSIDE 2.00 +#define NUM_SIZES 5 + +float trochoids_sizes_per_size_setting[NUM_SIZES] = { + SIZE_WELLINSIDE, + SIZE_INSIDE, + SIZE_EDGE, + SIZE_OUTSIDE, + SIZE_WELLOUTSIDE, +}; +/* Default to "SIZE_EDGE" */ +#define DEFAULT_SIZE 3 + +float trochoids_sizes_per_tool[NUM_TOOLS] = { + 0, // N/A; size controls available + SIZE_INSIDE, + SIZE_EDGE, + SIZE_WELLOUTSIDE, + 0, // N/A; size controls available + SIZE_INSIDE, + SIZE_EDGE, + SIZE_WELLOUTSIDE, +}; + +const char *icon_filenames[NUM_TOOLS] = { + "1pt_persp_select.png", /* FIXME */ + "1pt_persp_select.png", /* FIXME */ + "1pt_persp_select.png", /* FIXME */ + "1pt_persp_select.png", /* FIXME */ + "1pt_persp_select.png", /* FIXME */ + "1pt_persp_select.png", /* FIXME */ + "1pt_persp_select.png", /* FIXME */ + "1pt_persp_select.png", /* FIXME */ +}; + + +const char *tool_names[NUM_TOOLS] = { + gettext_noop("Epitrochoid"), + gettext_noop("Epitrochoid 1"), + gettext_noop("Epitrochoid 2"), + gettext_noop("Epitrochoid 3"), + gettext_noop("Hypotrochoid"), + gettext_noop("Hypotrochoid 1"), + gettext_noop("Hypotrochoid 2"), + gettext_noop("Hypotrochoid 3"), +}; + + +const char *tool_descriptions[NUM_TOOLS] = { + /* Epitrochoids */ + gettext_noop("Click to start drawing an epitrochoid. Drag left/right to change the size of the fixed circle, and up/down to change the size of the circle rolling outside of it. Use the size option to change where the pen is."), + gettext_noop("Click to start drawing an epitrochoid. Drag left/right to change the size of the fixed circle, and up/down to change the size of the circle rolling outside of it. Then pen is within the rolling circle."), + gettext_noop("Click to start drawing an epitrochoid. Drag left/right to change the size of the fixed circle, and up/down to change the size of the circle rolling outside of it. Then pen is on the edge of the rolling circle."), + gettext_noop("Click to start drawing an epitrochoid. Drag left/right to change the size of the fixed circle, and up/down to change the size of the circle rolling outside of it. Then pen is outside the rolling circle."), + /* Hypotrochoids */ + gettext_noop("Click to start drawing a hypotrochoid. Drag left/right to change the size of the fixed circle, and up/down to change the size of the circle rolling inside it. Use the size option to change where the pen is."), + gettext_noop("Click to start drawing a hypotrochoid. Drag left/right to change the size of the fixed circle, and up/down to change the size of the circle rolling inside it. Then pen is within the rolling circle."), + gettext_noop("Click to start drawing a hypotrochoid. Drag left/right to change the size of the fixed circle, and up/down to change the size of the circle rolling inside it. Then pen is on the edge of the rolling circle."), + gettext_noop("Click to start drawing a hypotrochoid. Drag left/right to change the size of the fixed circle, and up/down to change the size of the circle rolling inside it. Then pen is outside the rolling circle."), +}; + + +/* Sound effects (same for everyone) */ +enum { + SND_DRAW_CLICK, + SND_DRAW_RELEASE, + NUM_SNDS +}; + +Mix_Chunk *sound_effects[NUM_SNDS]; + +const char *sound_filenames[NUM_SNDS] = { + "n_pt_persp_click.ogg", // FIXME + "n_pt_persp_release.ogg", // FIXME +}; + +Uint8 trochoids_size = 1; +int trochoids_x, trochoids_y, dragged = 0; +Uint32 trochoids_color = 0x00000000; +int rotator_anim_a = 0; + +/* Function prototypes: */ +Uint32 trochoids_api_version(void); +int trochoids_init(magic_api * api, Uint8 disabled_features, Uint8 complexity_level); +int trochoids_get_tool_count(magic_api * api); +SDL_Surface *trochoids_get_icon(magic_api * api, int which); +char *trochoids_get_name(magic_api * api, int which); +int trochoids_get_group(magic_api * api, int which); +char *trochoids_get_description(magic_api * api, int which, int mode); +int trochoids_requires_colors(magic_api * api, int which); +int trochoids_modes(magic_api * api, int which); +Uint8 trochoids_accepted_sizes(magic_api * api, int which, int mode); +Uint8 trochoids_default_size(magic_api * api, int which, int mode); +void trochoids_shutdown(magic_api * api); +void trochoids_click(magic_api * api, int which, int mode, + SDL_Surface * canvas, SDL_Surface * snapshot, int x, int y, + SDL_Rect * update_rect); +void trochoids_drag(magic_api * api, int which, + SDL_Surface * canvas, SDL_Surface * snapshot, + int old_x, int old_y, int x, int y, + SDL_Rect * update_rect); +void trochoids_work(magic_api * api, int which, + SDL_Surface * canvas, SDL_Surface * snapshot, + int x, int y, SDL_Rect * update_rect, int guides); +void trochoids_release(magic_api * api, int which, + SDL_Surface * canvas, SDL_Surface * snapshot, int x, int y, + SDL_Rect * update_rect); +void trochoids_set_color(magic_api * api, int which, SDL_Surface * canvas, + SDL_Surface * snapshot, Uint8 r, Uint8 g, Uint8 b, + SDL_Rect * update_rect); +void trochoids_set_size(magic_api * api, int which, int mode, + SDL_Surface * canvas, SDL_Surface * last, + Uint8 size, SDL_Rect * update_rect); +void trochoids_line_callback(void *pointer, int tool, SDL_Surface * canvas, + SDL_Surface * snapshot, int x, int y); +void trochoids_xorline_callback(void *pointer, int tool, SDL_Surface * canvas, + SDL_Surface * snapshot, int x, int y); +void trochoids_switchin(magic_api * api, int which, int mode, + SDL_Surface * canvas); +void trochoids_switchout(magic_api * api, int which, int mode, + SDL_Surface * canvas); +int calc_lcm(int a, int b); + + +Uint32 trochoids_api_version(void) +{ + return (TP_MAGIC_API_VERSION); +} + + +int trochoids_init(magic_api * api, Uint8 disabled_features, Uint8 complexity_level ATTRIBUTE_UNUSED) +{ + int i; + char filename[1024]; + + for (i = 0; i < NUM_SNDS; i++) { + sound_effects[i] = NULL; + } + + if (disabled_features & MAGIC_FEATURE_SIZE) { + tp_offers_sizes = 0; + } else { + tp_offers_sizes = 1; + } + + which_to_tool = which_to_tool_per_size_availability[tp_offers_sizes]; + + for (i = 0; i < NUM_SNDS; i++) + { + snprintf(filename, sizeof(filename), "%s/sounds/magic/%s", api->data_directory, + sound_filenames[i]); + sound_effects[i] = Mix_LoadWAV(filename); + } + + return (1); +} + + +int trochoids_get_tool_count(magic_api * api ATTRIBUTE_UNUSED) +{ + return (num_tools[tp_offers_sizes]); +} + + +SDL_Surface *trochoids_get_icon(magic_api * api, int which) +{ + char filename[1024]; + + snprintf(filename, sizeof(filename), "%s/images/magic/%s", + api->data_directory, icon_filenames[which_to_tool[which]]); + + return (IMG_Load(filename)); +} + + +char *trochoids_get_name(magic_api * api ATTRIBUTE_UNUSED, int which) +{ + return (strdup(gettext(tool_names[which_to_tool[which]]))); +} + + +int trochoids_get_group(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED) +{ + return (MAGIC_TYPE_ARTISTIC); +} + + +char *trochoids_get_description(magic_api * api ATTRIBUTE_UNUSED, int which, int mode ATTRIBUTE_UNUSED) +{ + return (strdup(gettext(tool_descriptions[which_to_tool[which]]))); +} + + +int trochoids_requires_colors(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED) +{ + return 1; +} + + +int trochoids_modes(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED) +{ + return MODE_PAINT; +} + +Uint8 trochoids_accepted_sizes(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED, int mode ATTRIBUTE_UNUSED) +{ + return NUM_SIZES; +} + + +Uint8 trochoids_default_size(magic_api * api ATTRIBUTE_UNUSED, + int which ATTRIBUTE_UNUSED, int mode ATTRIBUTE_UNUSED) +{ + return DEFAULT_SIZE; +} + + +void trochoids_shutdown(magic_api * api ATTRIBUTE_UNUSED) +{ + int i; + + for (i = 0; i < NUM_SNDS; i++) { + if (sound_effects[i] != NULL) { + Mix_FreeChunk(sound_effects[i]); + } + } +} + + +void trochoids_click(magic_api * api, int which, int mode ATTRIBUTE_UNUSED, + SDL_Surface * canvas, SDL_Surface * snapshot, int x, int y, + SDL_Rect * update_rect) +{ + trochoids_x = x; + trochoids_y = y; + trochoids_drag(api, which, canvas, snapshot, x, y, x + (canvas->w / 20), y + (canvas->h / 20), update_rect); + dragged = 0; +} + + +/* Affect the canvas on drag: */ +void trochoids_drag(magic_api * api, int which, + SDL_Surface * canvas, SDL_Surface * snapshot, + int old_x ATTRIBUTE_UNUSED, int old_y ATTRIBUTE_UNUSED, + int x, int y, SDL_Rect * update_rect) +{ + dragged = 1; + trochoids_work(api, which, canvas, snapshot, x, y, update_rect, 1); +} + +void trochoids_work(magic_api * api, int which, + SDL_Surface * canvas, SDL_Surface * snapshot, + int x, int y, SDL_Rect * update_rect, int guides) { + int R, r, d, LCM; + int px, py, px2, py2; + float a, r_ratio, size; + + which = which_to_tool[which]; + + /* Drag left/rigth to change radius of stator (fixed circle) */ + R = abs(trochoids_x - x); + if (R < 20) { + R = 20; + } + + R = (R / 10) * 10; + + /* Drag down to increase radius of rotator (rolling circle) */ + r = abs(y - trochoids_y); + if (r < 10) { + r = 10; + } + + r = (r / 10) * 10; + + /* Epitrochoid: rotator is outside the stator; + Hypotrochoid: rotator is inside the stator */ + if (which == TOOL_HYPOTROCHOID_SIZES || + which == TOOL_HYPOTROCHOID_NOSIZES_1 || + which == TOOL_HYPOTROCHOID_NOSIZES_2 || + which == TOOL_HYPOTROCHOID_NOSIZES_3) { + r = -r - 2; + } + + r_ratio = (float) (R + r) / (float) r; + + /* Size option (or use of alternate tools, if --nomagicsizes) + determines the distance from the center of the rotator + that the pen draws */ + if (tp_offers_sizes) { + size = trochoids_sizes_per_size_setting[trochoids_size]; + } else { + size = trochoids_sizes_per_tool[which]; + } + d = r * size; + + /* Erase old before drawing new */ + + /* FIXME */ + update_rect->x = 0; + update_rect->y = 0; + update_rect->w = canvas->w; + update_rect->h = canvas->h; + + SDL_BlitSurface(snapshot, update_rect, canvas, update_rect); + + /* Draw the lines */ + LCM = calc_lcm(r, R); + for (a = 0; a < 360.0 * (float) (LCM / R); a++) { + float a2 = (a + 1); + + px = trochoids_x + (((R + r) * deg_cos(a)) + (d * deg_cos(r_ratio * a))); + py = trochoids_y + (((R + r) * deg_sin(a)) - (d * deg_sin(r_ratio * a))); + px2 = trochoids_x + (((R + r) * deg_cos((a2))) + (d * deg_cos(r_ratio * a2))); + py2 = trochoids_y + (((R + r) * deg_sin((a2))) - (d * deg_sin(r_ratio * a2))); + api->line((void *)api, which, canvas, snapshot, px, py, px2, py2, (20 * guides) + 1, trochoids_line_callback); + } + + if (guides) { + /* When still dragging (before release), draw some "guides", + showing the mechanism that would be used to generate the pattern */ + rotator_anim_a = (int) (atan2(y - trochoids_y, x - trochoids_x) / M_PI * 180.0); + + for (a = 0; a < 360; a = a + 2) { + /* Stator (fixed circle) */ + px = (int) ((float) trochoids_x + ((float) R * deg_cos(a))); + py = (int) ((float) trochoids_y - ((float) R * deg_sin(a))); + api->xorpixel(canvas, px, py); + api->xorpixel(canvas, px + 1, py); + api->xorpixel(canvas, px, py + 1); + api->xorpixel(canvas, px + 1, py + 1); + + /* Rotator (rolling circle) */ + px = (int) ((float) trochoids_x + ((R + r) * deg_cos(rotator_anim_a)) + ((float) r * deg_cos(a))); + py = (int) ((float) trochoids_y + ((R + r) * deg_sin(rotator_anim_a)) - ((float) r * deg_sin(a))); + api->xorpixel(canvas, px, py); + api->xorpixel(canvas, px + 1, py); + api->xorpixel(canvas, px, py + 1); + api->xorpixel(canvas, px + 1, py + 1); + + } + px = (int) ((float) trochoids_x + ((R + r) * deg_cos(rotator_anim_a)) + ((float) d * deg_cos(0))); + py = (int) ((float) trochoids_y + ((R + r) * deg_sin(rotator_anim_a)) - ((float) d * deg_sin(0))); + for (int yy = -2; yy <= 2; yy++) { + for (int xx = -2; xx <= 2; xx++) { + api->putpixel(canvas, px + xx, py + yy, trochoids_color); + } + } + } +} + + +void trochoids_release(magic_api * api, int which, + SDL_Surface * canvas, SDL_Surface * snapshot, + int x, int y, SDL_Rect * update_rect) +{ + if (dragged == 0) { + x += (canvas->w / 20); + y += (canvas->h / 20); + } + + trochoids_work(api, which, canvas, snapshot, x, y, update_rect, 0); +} + + +void trochoids_set_color(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED, + SDL_Surface * canvas ATTRIBUTE_UNUSED, + SDL_Surface * snapshot ATTRIBUTE_UNUSED, + Uint8 r, Uint8 g, Uint8 b, + SDL_Rect * update_rect ATTRIBUTE_UNUSED) +{ + trochoids_color = SDL_MapRGB(canvas->format, r, g, b); +} + + +void trochoids_set_size(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED, int mode ATTRIBUTE_UNUSED, + SDL_Surface * canvas ATTRIBUTE_UNUSED, SDL_Surface * last ATTRIBUTE_UNUSED, + Uint8 size, SDL_Rect * update_rect ATTRIBUTE_UNUSED) +{ + trochoids_size = (size - 1); /* array index is 0-based, but Tux Paint returns between 1...{accepted sizes} */ +} + + +void trochoids_line_callback(void *pointer ATTRIBUTE_UNUSED, int tool ATTRIBUTE_UNUSED, + SDL_Surface * canvas, SDL_Surface * snapshot ATTRIBUTE_UNUSED, + int x, int y) +{ + magic_api *api = (magic_api *) pointer; + + api->putpixel(canvas, x, y, trochoids_color); +} + +void trochoids_xorline_callback(void *pointer ATTRIBUTE_UNUSED, int tool ATTRIBUTE_UNUSED, + SDL_Surface * canvas, SDL_Surface * snapshot ATTRIBUTE_UNUSED, + int x, int y) +{ + magic_api *api = (magic_api *) pointer; + + api->xorpixel(canvas, x, y); + api->xorpixel(canvas, x + 1, y); + api->xorpixel(canvas, x, y + 1); + api->xorpixel(canvas, x + 1, y + 1); +} + +void trochoids_switchin(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED, int mode ATTRIBUTE_UNUSED, + SDL_Surface * canvas ATTRIBUTE_UNUSED) +{ +} + +void trochoids_switchout(magic_api * api ATTRIBUTE_UNUSED, int which ATTRIBUTE_UNUSED, int mode ATTRIBUTE_UNUSED, + SDL_Surface * canvas ATTRIBUTE_UNUSED) +{ +} + +int calc_lcm(int a, int b) { + int max; + + if (a > b) { + max = a; + } else { + max = b; + } + + while ((max % a) != 0 || (max % b) != 0) { + max++; + } + + return max; +}