1
0
Fork 0
forked from len0rd/rockbox

puzzles: antialiased line drawing and optional "shortcuts" for undo/redo

- line drawing should eventually be moved to xlcd, but for now it's very nonportable code
- fixes a minor issue with the configuration screens

Change-Id: I897f01b7210cbbec32665c2bc67920c965ea0bec
This commit is contained in:
Franklin Wei 2017-01-13 19:11:33 -05:00
parent 79e8cd4cfe
commit 8e4429853d
3 changed files with 319 additions and 111 deletions

View file

@ -43,7 +43,7 @@ ROCKS += $(PUZZLES_ROCKS)
endif
# Hack to suppress all warnings:
PUZZLESFLAGS = $(filter-out -O%,$(PLUGINFLAGS)) -Os \
PUZZLESFLAGS = $(filter-out -O%,$(PLUGINFLAGS)) -O3 \
-Wno-unused-parameter -Wno-sign-compare -Wno-strict-aliasing -w \
-DFOR_REAL -I$(PUZZLES_SRCDIR)
ifdef PUZZLES_COMBINED

View file

@ -30,6 +30,7 @@
#include "lib/playback_control.h"
#endif
#include "lib/xlcd.h"
#include "fixedpoint.h"
/* how many ticks between timer callbacks */
#define TIMER_INTERVAL (HZ / 50)
@ -75,7 +76,7 @@ extern bool audiobuf_available;
static struct settings_t {
int slowmo_factor;
bool bulk, timerflash, clipoff;
bool bulk, timerflash, clipoff, shortcuts, no_aa;
} settings;
/* clipping is implemented through viewports and offsetting
@ -179,14 +180,129 @@ static void rb_draw_rect(void *handle, int x, int y, int w, int h, int color)
rb->lcd_fillrect(x, y, w, h);
}
#define SWAP(a, b, t) do { t = a; a = b; b = t; } while(0);
#define fp_fpart(f, bits) ((f) & ((1 << (bits)) - 1))
#define fp_rfpart(f, bits) ((1 << (bits)) - fp_fpart(f, bits))
#define FRACBITS 16
/* most of our time drawing lines is spent in this function! */
static inline void plot(unsigned x, unsigned y, unsigned long a,
unsigned long r1, unsigned long g1, unsigned long b1,
unsigned cl, unsigned cr, unsigned cu, unsigned cd)
{
/* This is really quite possibly the least efficient way of doing
this. A better way would be in draw_antialiased_line(), but the
problem with that is that the algorithms I investigated at
least were incorrect at least part of the time and didn't make
drawing much faster overall. */
if(!(cl <= x && x < cr && cu <= y && y < cd))
return;
fb_data *ptr = rb->lcd_framebuffer + y * LCD_WIDTH + x;
fb_data orig = *ptr;
unsigned long r2, g2, b2;
r2 = RGB_UNPACK_RED(orig);
g2 = RGB_UNPACK_GREEN(orig);
b2 = RGB_UNPACK_BLUE(orig);
unsigned long r, g, b;
r = ((r1 * a) + (r2 * (256 - a))) >> 8;
g = ((g1 * a) + (g2 * (256 - a))) >> 8;
b = ((b1 * a) + (b2 * (256 - a))) >> 8;
*ptr = LCD_RGBPACK(r, g, b);
}
#undef ABS
#define ABS(a) ((a)<0?-(a):(a))
/* speed benchmark: 34392 lines/sec vs 112687 non-antialiased
* lines/sec at full optimization on ipod6g */
/* expects UN-OFFSET coordinates, directly access framebuffer */
static void draw_antialiased_line(int x0, int y0, int x1, int y1)
{
/* fixed-point Wu's algorithm, modified for integer-only endpoints */
/* passed to plot() to avoid re-calculation */
unsigned short l = 0, r = LCD_WIDTH, u = 0, d = LCD_HEIGHT;
if(clipped)
{
l = clip_rect.x;
r = clip_rect.x + clip_rect.width;
u = clip_rect.y;
d = clip_rect.y + clip_rect.height;
}
bool steep = ABS(y1 - y0) > ABS(x1 - x0);
int tmp;
if(steep)
{
SWAP(x0, y0, tmp);
SWAP(x1, y1, tmp);
}
if(x0 > x1)
{
SWAP(x0, x1, tmp);
SWAP(y0, y1, tmp);
}
int dx, dy;
dx = x1 - x0;
dy = y1 - y0;
if(!(dx << FRACBITS))
return; /* bail out */
long gradient = fp_div(dy << FRACBITS, dx << FRACBITS, FRACBITS);
long intery = (y0 << FRACBITS);
unsigned color = rb->lcd_get_foreground();
unsigned long r1, g1, b1;
r1 = RGB_UNPACK_RED(color);
g1 = RGB_UNPACK_GREEN(color);
b1 = RGB_UNPACK_BLUE(color);
/* main loop */
if(steep)
{
for(int x = x0; x <= x1; ++x, intery += gradient)
{
unsigned y = intery >> FRACBITS;
unsigned alpha = fp_fpart(intery, FRACBITS) >> (FRACBITS - 8);
plot(y, x, (1 << 8) - alpha, r1, g1, b1, l, r, u, d);
plot(y + 1, x, alpha, r1, g1, b1, l, r, u, d);
}
}
else
{
for(int x = x0; x <= x1; ++x, intery += gradient)
{
unsigned y = intery >> FRACBITS;
unsigned alpha = fp_fpart(intery, FRACBITS) >> (FRACBITS - 8);
plot(x, y, (1 << 8) - alpha, r1, g1, b1, l, r, u, d);
plot(x, y + 1, alpha, r1, g1, b1, l, r, u, d);
}
}
}
static void rb_draw_line(void *handle, int x1, int y1, int x2, int y2,
int color)
{
LOGF("rb_draw_line(%d, %d, %d, %d, %d)", x1, y1, x2, y2, color);
rb_color(color);
if(settings.no_aa)
{
offset_coords(&x1, &y1);
offset_coords(&x2, &y2);
rb_color(color);
rb->lcd_drawline(x1, y1, x2, y2);
}
else
draw_antialiased_line(x1, y1, x2, y2);
}
/*
@ -351,12 +467,15 @@ static void rb_draw_poly(void *handle, int *coords, int npoints,
y1 = coords[2 * (i - 1) + 1];
x2 = coords[2 * i];
y2 = coords[2 * i + 1];
if(settings.no_aa)
{
offset_coords(&x1, &y1);
offset_coords(&x2, &y2);
rb->lcd_drawline(x1, y1,
x2, y2);
//rb->lcd_update();
//rb->sleep(HZ/2);
}
else
draw_antialiased_line(x1, y1, x2, y2);
}
int x1, y1, x2, y2;
@ -364,11 +483,16 @@ static void rb_draw_poly(void *handle, int *coords, int npoints,
y1 = coords[1];
x2 = coords[2 * (npoints - 1)];
y2 = coords[2 * (npoints - 1) + 1];
if(settings.no_aa)
{
offset_coords(&x1, &y1);
offset_coords(&x2, &y2);
rb->lcd_drawline(x1, y1,
x2, y2);
}
else
draw_antialiased_line(x1, y1, x2, y2);
}
static void rb_draw_circle(void *handle, int cx, int cy, int radius,
@ -394,25 +518,6 @@ struct blitter {
struct bitmap bmp;
};
static blitter *rb_blitter_new(void *handle, int w, int h)
{
LOGF("rb_blitter_new");
blitter *b = snew(blitter);
b->bmp.width = w;
b->bmp.height = h;
b->bmp.data = smalloc(w * h * sizeof(fb_data));
b->have_data = false;
return b;
}
static void rb_blitter_free(void *handle, blitter *bl)
{
LOGF("rb_blitter_free");
sfree(bl->bmp.data);
sfree(bl);
return;
}
/* originally from emcc.c */
static void trim_rect(int *x, int *y, int *w, int *h)
{
@ -430,10 +535,10 @@ static void trim_rect(int *x, int *y, int *w, int *h)
y1 = *y + *h;
/* Clip each coordinate at both extremes of the canvas */
x0 = (x0 < 0 ? 0 : x0 > LCD_WIDTH ? LCD_WIDTH : x0);
x1 = (x1 < 0 ? 0 : x1 > LCD_WIDTH ? LCD_WIDTH : x1);
y0 = (y0 < 0 ? 0 : y0 > LCD_HEIGHT ? LCD_HEIGHT : y0);
y1 = (y1 < 0 ? 0 : y1 > LCD_HEIGHT ? LCD_HEIGHT : y1);
x0 = (x0 < 0 ? 0 : x0 > LCD_WIDTH - 1 ? LCD_WIDTH - 1: x0);
x1 = (x1 < 0 ? 0 : x1 > LCD_WIDTH - 1 ? LCD_WIDTH - 1: x1);
y0 = (y0 < 0 ? 0 : y0 > LCD_HEIGHT - 1 ? LCD_HEIGHT - 1: y0);
y1 = (y1 < 0 ? 0 : y1 > LCD_HEIGHT - 1 ? LCD_HEIGHT - 1: y1);
/* Transform back into x,y,w,h to return */
*x = x0;
@ -442,6 +547,25 @@ static void trim_rect(int *x, int *y, int *w, int *h)
*h = y1 - y0;
}
static blitter *rb_blitter_new(void *handle, int w, int h)
{
LOGF("rb_blitter_new");
blitter *b = snew(blitter);
b->bmp.width = w;
b->bmp.height = h;
b->bmp.data = smalloc(w * h * sizeof(fb_data));
b->have_data = false;
return b;
}
static void rb_blitter_free(void *handle, blitter *bl)
{
LOGF("rb_blitter_free");
sfree(bl->bmp.data);
sfree(bl);
return;
}
/* copy a section of the framebuffer */
static void rb_blitter_save(void *handle, blitter *bl, int x, int y)
{
@ -623,8 +747,6 @@ void get_random_seed(void **randseed, int *randseedsize)
*randseed = snew(long);
long seed = *rb->current_tick;
rb->memcpy(*randseed, &seed, sizeof(seed));
//*(long*)*randseed = 42; // debug
//rb->splash(HZ, "DEBUG SEED ON");
*randseedsize = sizeof(long);
}
@ -683,8 +805,7 @@ static int list_choose(const char *list_str, const char *title)
}
}
/* return value is only meaningful when type == C_STRING */
static bool do_configure_item(config_item *cfg)
static void do_configure_item(config_item *cfg)
{
switch(cfg->type)
{
@ -698,11 +819,11 @@ static bool do_configure_item(config_item *cfg)
if(rb->kbd_input(newstr, MAX_STRLEN) < 0)
{
sfree(newstr);
return false;
break;
}
sfree(cfg->sval);
cfg->sval = newstr;
return true;
break;
}
case C_BOOLEAN:
{
@ -726,7 +847,6 @@ static bool do_configure_item(config_item *cfg)
fatal("bad type");
break;
}
return false;
}
const char *config_formatter(int sel, void *data, char *buf, size_t len)
@ -737,11 +857,13 @@ const char *config_formatter(int sel, void *data, char *buf, size_t len)
return buf;
}
static void config_menu(void)
static bool config_menu(void)
{
char *title;
config_item *config = midend_get_config(me, CFG_SETTINGS, &title);
bool success = false;
if(!config)
{
rb->splash(HZ, "Nothing to configure.");
@ -782,22 +904,16 @@ static void config_menu(void)
config_item old;
int pos = rb->gui_synclist_get_sel_pos(&list);
memcpy(&old, config + pos, sizeof(old));
char *old_str;
if(old.type == C_STRING)
old_str = dupstr(old.sval);
bool freed_str = do_configure_item(config + pos);
do_configure_item(config + pos);
char *err = midend_set_config(me, CFG_SETTINGS, config);
if(err)
{
rb->splash(HZ, err);
memcpy(config + pos, &old, sizeof(old));
if(freed_str)
config[pos].sval = old_str;
}
else if(old.type == C_STRING)
else
{
/* success, and we duplicated the old string, so free it */
sfree(old_str);
success = true;
}
break;
}
@ -813,6 +929,7 @@ static void config_menu(void)
done:
sfree(title);
free_cfg(config);
return success;
}
const char *preset_formatter(int sel, void *data, char *buf, size_t len)
@ -824,12 +941,12 @@ const char *preset_formatter(int sel, void *data, char *buf, size_t len)
return buf;
}
static void presets_menu(void)
static bool presets_menu(void)
{
if(!midend_num_presets(me))
{
rb->splash(HZ, "No presets!");
return;
return false;
}
/* display a list */
@ -843,9 +960,8 @@ static void presets_menu(void)
int current = midend_which_preset(me);
rb->gui_synclist_select_item(&list, current >= 0 ? current : 0);
bool done = false;
rb->gui_synclist_set_title(&list, "Game Type", NOICON);
while (!done)
while(1)
{
rb->gui_synclist_draw(&list);
int button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK);
@ -860,13 +976,11 @@ static void presets_menu(void)
game_params *params;
midend_fetch_preset(me, sel, &junk, &params);
midend_set_params(me, params);
done = true;
break;
return true;
}
case ACTION_STD_PREV:
case ACTION_STD_CANCEL:
done = true;
break;
return false;
default:
break;
}
@ -948,6 +1062,37 @@ static void init_default_settings(void)
settings.slowmo_factor = 1;
settings.bulk = false;
settings.timerflash = false;
settings.clipoff = false;
settings.shortcuts = false;
settings.no_aa = false;
}
static void bench_aa(void)
{
rb->sleep(0);
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
rb->cpu_boost(true);
#endif
int next = *rb->current_tick + HZ;
int i = 0;
while(*rb->current_tick < next)
{
draw_antialiased_line(0, 0, 20, 31);
++i;
}
rb->splashf(HZ, "%d AA lines/sec", i);
next = *rb->current_tick + HZ;
int j = 0;
while(*rb->current_tick < next)
{
rb->lcd_drawline(0, 0, 20, 31);
++j;
}
rb->splashf(HZ, "%d normal lines/sec", j);
rb->splashf(HZ, "Efficiency: %d%%", 100 * i / j);
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
rb->cpu_boost(false);
#endif
}
static void debug_menu(void)
@ -958,6 +1103,9 @@ static void debug_menu(void)
"Toggle bulk update",
"Toggle flash pixel on timer",
"Toggle clip",
"Toggle shortcuts",
"Toggle antialias",
"Benchmark antialias",
"Back");
bool quit = false;
int sel = 0;
@ -988,6 +1136,15 @@ static void debug_menu(void)
settings.clipoff = !settings.clipoff;
break;
case 5:
settings.shortcuts = !settings.shortcuts;
break;
case 6:
settings.no_aa = !settings.no_aa;
break;
case 7:
bench_aa();
break;
case 8:
default:
quit = true;
break;
@ -1055,6 +1212,23 @@ static int pausemenu_cb(int action, const struct menu_item_ex *this_item)
return action;
}
static void clear_and_draw(void)
{
rb->lcd_clear_display();
rb->lcd_update();
midend_force_redraw(me);
draw_title();
}
static void reset_drawing(void)
{
rb->lcd_set_viewport(NULL);
rb->lcd_set_backdrop(NULL);
rb->lcd_set_foreground(LCD_BLACK);
rb->lcd_set_background(BG_COLOR);
}
static int pause_menu(void)
{
#define static auto
@ -1139,19 +1313,27 @@ static int pause_menu(void)
playback_control(NULL);
break;
case 9:
presets_menu();
if(presets_menu())
{
midend_new_game(me);
fix_size();
reset_drawing();
clear_and_draw();
quit = true;
}
break;
case 10:
debug_menu();
break;
case 11:
config_menu();
if(config_menu())
{
midend_new_game(me);
fix_size();
reset_drawing();
clear_and_draw();
quit = true;
}
break;
#ifdef COMBINED
case 12:
@ -1257,8 +1439,8 @@ static int process_input(int tmo)
}
LOGF("accepting event 0x%08x", button);
}
/* not inertia: events fire on presses */
else
/* default is to ignore repeats except for untangle */
else if(strcmp("Untangle", midend_which_game(me)->name))
{
/* start accepting input again after a release */
if(!button)
@ -1267,6 +1449,7 @@ static int process_input(int tmo)
return 0;
}
/* ignore repeats */
/* Untangle gets special treatment */
if(!accept_input)
return 0;
accept_input = false;
@ -1316,7 +1499,34 @@ static int process_input(int tmo)
case BTN_FIRE:
state = CURSOR_SELECT;
break;
default:
break;
}
if(settings.shortcuts)
{
static bool shortcuts_ok = true;
switch(button)
{
case BTN_LEFT | BTN_FIRE:
if(shortcuts_ok)
midend_process_key(me, 0, 0, 'u');
shortcuts_ok = false;
break;
case BTN_RIGHT | BTN_FIRE:
if(shortcuts_ok)
midend_process_key(me, 0, 0, 'r');
shortcuts_ok = false;
break;
case 0:
shortcuts_ok = true;
break;
default:
break;
}
}
LOGF("process_input done");
LOGF("------------------");
return state;
@ -1403,13 +1613,23 @@ static void write_wrapper(void *ptr, void *buf, int len)
rb->write(fd, buf, len);
}
static void clear_and_draw(void)
static void init_colors(void)
{
rb->lcd_clear_display();
rb->lcd_update();
float *floatcolors = midend_colors(me, &ncolors);
midend_force_redraw(me);
draw_title();
/* convert them to packed RGB */
colors = smalloc(ncolors * sizeof(unsigned));
unsigned *ptr = colors;
float *floatptr = floatcolors;
for(int i = 0; i < ncolors; ++i)
{
int r = 255 * *(floatptr++);
int g = 255 * *(floatptr++);
int b = 255 * *(floatptr++);
LOGF("color %d is %d %d %d", i, r, g, b);
*ptr++ = LCD_RGBPACK(r, g, b);
}
sfree(floatcolors);
}
static char *init_for_game(const game *gm, int load_fd, bool draw)
@ -1431,26 +1651,9 @@ static char *init_for_game(const game *gm, int load_fd, bool draw)
fix_size();
float *floatcolors = midend_colors(me, &ncolors);
init_colors();
/* convert them to packed RGB */
colors = smalloc(ncolors * sizeof(unsigned));
unsigned *ptr = colors;
float *floatptr = floatcolors;
for(int i = 0; i < ncolors; ++i)
{
int r = 255 * *(floatptr++);
int g = 255 * *(floatptr++);
int b = 255 * *(floatptr++);
LOGF("color %d is %d %d %d", i, r, g, b);
*ptr++ = LCD_RGBPACK(r, g, b);
}
sfree(floatcolors);
rb->lcd_set_viewport(NULL);
rb->lcd_set_backdrop(NULL);
rb->lcd_set_foreground(LCD_BLACK);
rb->lcd_set_background(BG_COLOR);
reset_drawing();
if(draw)
{
@ -1690,22 +1893,26 @@ enum plugin_status plugin_start(const void *param)
playback_control(NULL);
break;
case 5:
presets_menu();
if(!load_success)
if(presets_menu())
{
midend_new_game(me);
fix_size();
init_colors();
reset_drawing();
clear_and_draw();
goto game_loop;
}
quit = true;
break;
case 6:
config_menu();
if(!load_success)
if(config_menu())
{
midend_new_game(me);
fix_size();
init_colors();
reset_drawing();
clear_and_draw();
goto game_loop;
}
quit = true;
break;
case 8:
if(load_success)

View file

@ -1162,18 +1162,17 @@ static char *interpret_move(const game_state *state, game_ui *ui,
{
if(ui->dragpoint < 0)
{
if(ui->cursorpoint < 0)
{
ui->cursorpoint = 0;
return "";
}
/* We're selecting a point here. */
/* Search all the points and find the closest one (2-D) in
* the given direction. */
int i, best;
long bestd;
if(ui->cursorpoint < 0)
{
ui->cursorpoint = 0;
}
/*
* Begin drag. We drag the vertex _nearest_ to the pointer,
* just in case one is nearly on top of another and we want
@ -1196,7 +1195,7 @@ static char *interpret_move(const game_state *state, game_ui *ui,
/* Figure out if this point falls into a 90 degree
* range extending from the current point */
float angle = atan2(-dy, dx); /* adjust for raster coordinates */
float angle = atan2(-dy, dx); /* negate y to adjust for raster coordinates */
/* offset to [0..2*PI] */
if(angle < 0)
@ -1494,6 +1493,8 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
ds->bg = bg;
game_compute_size(&state->params, ds->tilesize, &w, &h);
clip(dr, 0, 0, w, h);
draw_rect(dr, 0, 0, w, h, bg);
/*