diff --git a/libraries/pico_vector/pico_vector.cpp b/libraries/pico_vector/pico_vector.cpp index a3cb6bfce..ce3b4ecc0 100644 --- a/libraries/pico_vector/pico_vector.cpp +++ b/libraries/pico_vector/pico_vector.cpp @@ -1,5 +1,6 @@ #define PP_IMPLEMENTATION #define AF_IMPLEMENTATION +#define PPP_IMPLEMENTATION #include "pico_vector.hpp" #include diff --git a/libraries/pico_vector/pico_vector.hpp b/libraries/pico_vector/pico_vector.hpp index db0a3412a..f3b11f77d 100644 --- a/libraries/pico_vector/pico_vector.hpp +++ b/libraries/pico_vector/pico_vector.hpp @@ -17,6 +17,7 @@ #define AF_DEBUG(...) af_debug(__VA_ARGS__) #include "pretty-poly.h" +#include "pretty-poly-primitives.h" #include "alright-fonts.h" #include "pico_graphics.hpp" diff --git a/libraries/pico_vector/pretty-poly-primitives.h b/libraries/pico_vector/pretty-poly-primitives.h new file mode 100644 index 000000000..b6b01f1fe --- /dev/null +++ b/libraries/pico_vector/pretty-poly-primitives.h @@ -0,0 +1,181 @@ +/* + + Pretty Poly 🦜 - super-sampling polygon renderer for low resource platforms. + + Jonathan Williamson, August 2022 + Examples, source, and more: https://github.com/lowfatcode/pretty-poly + MIT License https://github.com/lowfatcode/pretty-poly/blob/main/LICENSE + + An easy way to render high quality graphics in embedded applications running + on resource constrained microcontrollers such as the Cortex M0 and up. + + - Renders polygons: concave, self-intersecting, multi contour, holes, etc. + - C11 header only library: simply copy the header file into your project + - Tile based renderer: low memory footprint, cache coherency + - Low memory usage: ~4kB of heap memory required + - High speed on low resource platforms: optionally no floating point + - Antialiasing modes: X1 (none), X4 and X16 super sampling + - Bounds clipping: all results clipped to supplied clip rectangle + - Pixel format agnostic: renders a "tile" to blend into your framebuffer + - Support for hardware interpolators on rp2040 (thanks @MichaelBell!) + + Contributor bwaaaaaarks! 🦜 + + @MichaelBell - lots of bug fixes, performance boosts, and suggestions. + @gadgetoid - integrating into the PicoVector library and testing. + +*/ + +#ifndef PPP_INCLUDE_H +#define PPP_INCLUDE_H + +#include "pretty-poly.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + PP_COORD_TYPE x, y, w, h; // coordinates + PP_COORD_TYPE s; // stroke thickness (0 == filled) + PP_COORD_TYPE r1, r2, r3, r4; // corner radii (r1 = top left then clockwise) +} ppp_rect_def; + +typedef struct { + PP_COORD_TYPE x, y; // coordinates + PP_COORD_TYPE r; // radius + int e; // edge count + PP_COORD_TYPE s; // stroke thickness (0 == filled) +} ppp_regular_def; + +typedef struct { + PP_COORD_TYPE x, y; // coordinates + PP_COORD_TYPE r; // radius + PP_COORD_TYPE s; // stroke thickness (0 == filled) +} ppp_circle_def; + +typedef struct { + PP_COORD_TYPE x, y; // coordinates + PP_COORD_TYPE r; // radius + PP_COORD_TYPE s; // stroke thickness (0 == filled) + PP_COORD_TYPE f, t; // angle from and to +} ppp_arc_def; + +pp_poly_t* ppp_rect(ppp_rect_def d); +pp_poly_t* ppp_regular(ppp_regular_def d); +pp_poly_t* ppp_circle(ppp_circle_def d); +pp_poly_t* ppp_arc(ppp_arc_def d); + +#ifdef __cplusplus +} +#endif + +#ifdef PPP_IMPLEMENTATION + +void _pp_round_rect_corner_points(pp_path_t *path, PP_COORD_TYPE cx, PP_COORD_TYPE cy, PP_COORD_TYPE r, int q) { + float quality = 5; // higher the number, lower the quality - selected by experiment + int steps = ceil(r / quality) + 2; // + 2 to include start and end + float delta = -(M_PI / 2) / steps; + float theta = (M_PI / 2) * q; // select start theta for this quadrant + for(int i = 0; i <= steps; i++) { + PP_COORD_TYPE xo = sin(theta) * r, yo = cos(theta) * r; + pp_path_add_point(path, (pp_point_t){cx + xo, cy + yo}); + theta += delta; + } +} + +void _ppp_rrect_corner(pp_path_t *path, PP_COORD_TYPE cx, PP_COORD_TYPE cy, PP_COORD_TYPE r, int q) { + float quality = 5; // higher the number, lower the quality - selected by experiment + int steps = ceil(r / quality) + 2; // + 2 to include start and end + float delta = -(M_PI / 2) / steps; + float theta = (M_PI / 2) * q; // select start theta for this quadrant + for(int i = 0; i <= steps; i++) { + PP_COORD_TYPE xo = sin(theta) * r, yo = cos(theta) * r; + pp_path_add_point(path, (pp_point_t){cx + xo, cy + yo}); + theta += delta; + } +} + +void _ppp_rrect_path(pp_path_t *path, ppp_rect_def d) { + d.r1 == 0 ? pp_path_add_point(path, (pp_point_t){d.x, d.y}) : _ppp_rrect_corner(path, d.x + d.r1, d.y + d.r1, d.r1, 3); + d.r2 == 0 ? pp_path_add_point(path, (pp_point_t){d.x + d.w, d.y}) : _ppp_rrect_corner(path, d.x + d.w - d.r2, d.y + d.r2, d.r2, 2); + d.r3 == 0 ? pp_path_add_point(path, (pp_point_t){d.x + d.w, d.y + d.h}) : _ppp_rrect_corner(path, d.x + d.w - d.r3, d.y + d.h - d.r3, d.r3, 1); + d.r4 == 0 ? pp_path_add_point(path, (pp_point_t){d.x, d.y}) : _ppp_rrect_corner(path, d.x + d.r4, d.y + d.h - d.r4, d.r4, 0); +} + +pp_poly_t* ppp_rect(ppp_rect_def d) { + pp_poly_t *poly = pp_poly_new(); + pp_path_t *path = pp_poly_add_path(poly); + if(d.r1 == 0.0f && d.r2 == 0.0f && d.r3 == 0.0f && d.r4 == 0.0f) { // non rounded rect + pp_point_t points[] = {{d.x, d.y}, {d.x + d.w, d.y}, {d.x + d.w, d.y + d.h}, {d.x, d.y + d.h}}; + pp_path_add_points(path, points, 4); + if(d.s != 0) { // stroked, not filled + d.x += d.s; d.y += d.s; d.w -= 2 * d.s; d.h -= 2 * d.s; + pp_path_t *inner = pp_poly_add_path(poly); + pp_point_t points[] = {{d.x, d.y}, {d.x + d.w, d.y}, {d.x + d.w, d.y + d.h}, {d.x, d.y + d.h}}; + pp_path_add_points(inner, points, 4); + } + }else{ // rounded rect + _ppp_rrect_path(path, d); + if(d.s != 0) { // stroked, not filled + d.x += d.s; d.y += d.s; d.w -= 2 * d.s; d.h -= 2 * d.s; + d.r1 = _pp_max(0, d.r1 - d.s); + d.r2 = _pp_max(0, d.r2 - d.s); + d.r3 = _pp_max(0, d.r3 - d.s); + d.r4 = _pp_max(0, d.r4 - d.s); + pp_path_t *inner = pp_poly_add_path(poly); + _ppp_rrect_path(inner, d); + } + } + return poly; +} + +pp_poly_t* ppp_regular(ppp_regular_def d) { + pp_poly_t *poly = pp_poly_new(); + pp_path_t *path = pp_poly_add_path(poly); + pp_path_t *inner = d.s != 0.0f ? pp_poly_add_path(poly) : NULL; + for(int i = 0; i < d.e; i++) { + float theta = ((M_PI * 2.0f) / (float)d.e) * (float)i; + pp_path_add_point(path, (pp_point_t){sin(theta) * d.r + d.x, cos(theta) * d.r + d.y}); + if(inner) { + pp_path_add_point(inner, (pp_point_t){sin(theta) * (d.r - d.s) + d.x, cos(theta) * (d.r - d.s) + d.y}); + } + } + return poly; +} + +pp_poly_t* ppp_circle(ppp_circle_def d) { + int e = _pp_max(8, d.r); // edge count + ppp_regular_def r = {d.x, d.y, d.r, e, d.s}; + return ppp_regular(r); +} + +pp_poly_t* ppp_arc(ppp_arc_def d) { + pp_poly_t *poly = pp_poly_new(); + pp_path_t *path = pp_poly_add_path(poly); + pp_path_t *inner = (pp_path_t *)(d.s == 0.0f ? NULL : calloc(1, sizeof(pp_path_t))); + + // no thickness, so add centre point to make pie shape + if(!inner) pp_path_add_point(path, (pp_point_t){d.x, d.y}); + + d.f = d.f * (M_PI / 180.0f); d.t = d.t * (M_PI / 180.0f); // to radians + int s = _pp_max(8, d.r); float astep = (d.t - d.f) / s; float a = d.f; + for(int i = 0; i <= s; i++) { + pp_path_add_point(path, (pp_point_t){sin(a) * d.r + d.x, cos(a) * d.r + d.y}); + if(inner) { + pp_path_add_point(inner, (pp_point_t){sin(d.t - (a - d.f)) * (d.r - d.s) + d.x, cos(d.t - (a - d.f)) * (d.r - d.s) + d.y}); + } + a += astep; + } + + if(inner) { // append the inner path + pp_path_add_points(path, inner->points, inner->count); + free(inner->points); free(inner); + } + + return poly; +} + +#endif // PPP_IMPLEMENTATION + +#endif // PPP_INCLUDE_H \ No newline at end of file diff --git a/libraries/pico_vector/pretty-poly.h b/libraries/pico_vector/pretty-poly.h index 9c746837a..cd2bf3094 100644 --- a/libraries/pico_vector/pretty-poly.h +++ b/libraries/pico_vector/pretty-poly.h @@ -6,8 +6,8 @@ Examples, source, and more: https://github.com/lowfatcode/pretty-poly MIT License https://github.com/lowfatcode/pretty-poly/blob/main/LICENSE - An easy way to render high quality graphics in embedded applications running - on resource constrained microcontrollers such as the Cortex M0 and up. + An easy way to render high quality graphics in embedded applications running + on resource constrained microcontrollers such as the Cortex M0 and up. - Renders polygons: concave, self-intersecting, multi contour, holes, etc. - C11 header only library: simply copy the header file into your project @@ -21,9 +21,9 @@ Contributor bwaaaaaarks! 🦜 - @MichaelBell - lots of bug fixes, performance boosts, and suggestions. + @MichaelBell - lots of bug fixes, performance boosts, and suggestions. @gadgetoid - integrating into the PicoVector library and testing. - + */ #ifndef PP_INCLUDE_H @@ -84,7 +84,7 @@ pp_point_t pp_point_transform(pp_point_t *p, pp_mat3_t *m); // rect type typedef struct { - int32_t x, y, w, h; + int32_t x, y, w, h; } pp_rect_t; bool pp_rect_empty(pp_rect_t *r); pp_rect_t pp_rect_intersection(pp_rect_t *r1, pp_rect_t *r2); @@ -112,7 +112,7 @@ void pp_path_add_path(pp_path_t *path, pp_path_t *other); pp_rect_t pp_path_bounds(const pp_path_t *c); typedef struct { - pp_path_t *paths; + pp_path_t *paths; } pp_poly_t; pp_poly_t *pp_poly_new(); void pp_poly_free(pp_poly_t *poly); @@ -120,6 +120,7 @@ pp_path_t* pp_poly_tail_path(pp_poly_t *p); pp_path_t* pp_poly_add_path(pp_poly_t *p); pp_rect_t pp_poly_bounds(pp_poly_t *p); int pp_poly_path_count(pp_poly_t *p); +void pp_poly_merge(pp_poly_t *p, pp_poly_t *m); // user settings typedef void (*pp_tile_callback_t)(const pp_tile_t *tile); @@ -186,7 +187,7 @@ void pp_mat3_mul(pp_mat3_t *m1, pp_mat3_t *m2) { r.v12 = m1->v10 * m2->v02 + m1->v11 * m2->v12 + m1->v12 * m2->v22; r.v20 = m1->v20 * m2->v00 + m1->v21 * m2->v10 + m1->v22 * m2->v20; r.v21 = m1->v20 * m2->v01 + m1->v21 * m2->v11 + m1->v22 * m2->v21; - r.v22 = m1->v20 * m2->v02 + m1->v21 * m2->v12 + m1->v22 * m2->v22; + r.v22 = m1->v20 * m2->v02 + m1->v21 * m2->v12 + m1->v22 * m2->v22; *m1 = r; } @@ -223,7 +224,7 @@ pp_rect_t pp_rect_intersection(pp_rect_t *r1, pp_rect_t *r2) { } pp_rect_t pp_rect_merge(pp_rect_t *r1, pp_rect_t *r2) { return (pp_rect_t){ - .x = _pp_min(r1->x, r2->x), + .x = _pp_min(r1->x, r2->x), .y = _pp_min(r1->y, r2->y), .w = _pp_max(r1->x + r1->w, r2->x + r2->w) - _pp_min(r1->x, r2->x), .h = _pp_max(r1->y + r1->h, r2->y + r2->h) - _pp_min(r1->y, r2->y) @@ -246,9 +247,9 @@ pp_rect_t pp_rect_transform(pp_rect_t *r, pp_mat3_t *m) { PP_COORD_TYPE maxy = _pp_max(tl.y, _pp_max(tr.y, _pp_max(bl.y, br.y))); return (pp_rect_t){ - .x = (int32_t)minx, - .y = (int32_t)miny, - .w = (int32_t)(maxx - minx), + .x = (int32_t)minx, + .y = (int32_t)miny, + .w = (int32_t)(maxx - minx), .h = (int32_t)(maxy - miny) }; } @@ -295,7 +296,7 @@ int pp_poly_path_count(pp_poly_t *poly) { return i; } -pp_path_t* pp_poly_add_path(pp_poly_t *poly) { +pp_path_t* pp_poly_add_path(pp_poly_t *poly) { pp_path_t *path = (pp_path_t *)PP_MALLOC(sizeof(pp_path_t)); memset(path, 0, sizeof(pp_path_t)); path->storage = 8; @@ -307,47 +308,63 @@ pp_path_t* pp_poly_add_path(pp_poly_t *poly) { pp_path_t *tail = pp_poly_tail_path(poly); tail->next = path; } - + return path; } +void pp_poly_merge(pp_poly_t *p, pp_poly_t *m) { + if(!p->paths) { + p->paths = m->paths; + }else{ + pp_poly_tail_path(p)->next = m->paths; + } + + m->paths = NULL; + pp_poly_free(m); +} + pp_point_t* pp_path_tail_point(pp_path_t *path) { return (path->count > 0) ? &path->points[path->count -1] : NULL; } void pp_path_add_point(pp_path_t *path, pp_point_t p) { if(path->count == path->storage) { // no storage left, double buffer size - path->points = (pp_point_t *)PP_REALLOC(path->points, sizeof(pp_point_t) * (path->storage * 2)); - path->storage *= 2; + if(path->points) { + path->storage *= 2; + path->points = (pp_point_t *)PP_REALLOC(path->points, sizeof(pp_point_t) * (path->storage)); + }else{ + path->storage = 8; + path->points = (pp_point_t *)PP_MALLOC(sizeof(pp_point_t) * (path->storage)); + } } path->points[path->count] = p; path->count++; } void pp_path_add_points(pp_path_t *path, pp_point_t *points, int count) { - if(count + path->count > path->storage) { // not enough storage, allocate - path->points = (pp_point_t *)PP_REALLOC(path->points, sizeof(pp_point_t) * (count + path->count)); - path->storage = count + path->count; + if(path->count + count > path->storage) { // not enough storage, allocate + path->storage = path->count + count; + path->points = (pp_point_t *)PP_REALLOC(path->points, sizeof(pp_point_t) * (path->storage)); } - memcpy(&path->points[count], points, sizeof(pp_point_t) * count); - path->count += count; + memcpy(&path->points[path->count], points, sizeof(pp_point_t) * count); + path->count += count; } // pp_contour_t implementation -pp_rect_t pp_path_bounds(const pp_path_t *path) { +pp_rect_t pp_path_bounds(const pp_path_t *path) { int minx = INT_MAX, maxx = -INT_MAX, miny = INT_MAX, maxy = -INT_MAX; for(int i = 0; i < path->count; i++) { minx = _pp_min(minx, path->points[i].x); miny = _pp_min(miny, path->points[i].y); - maxx = _pp_max(maxx, path->points[i].x); + maxx = _pp_max(maxx, path->points[i].x); maxy = _pp_max(maxy, path->points[i].y); } return (pp_rect_t){minx, miny, maxx - minx, maxy - miny}; } -pp_rect_t pp_polygon_bounds(pp_poly_t *p) { +pp_rect_t pp_poly_bounds(pp_poly_t *p) { pp_path_t *path = p->paths; - if(!path) return (pp_rect_t){}; + if(!path) return (pp_rect_t){}; pp_rect_t b = pp_path_bounds(path); path = path->next; while(path) { @@ -397,8 +414,8 @@ void debug_tile(const pp_tile_t *tile) { debug("[%3d]: ", y); for(int32_t x = 0; x < tile->w; x++) { debug("%02x", pp_tile_get(tile, x, y)); - } - debug("\n"); + } + debug("\n"); } debug("-----------------------\n"); } @@ -465,7 +482,7 @@ void add_line_segment_to_nodes(const pp_point_t start, const pp_point_t end) { while(e > dy) {e -= dy; x += xinc;} // clamp node x value to tile bounds - int nx = _pp_max(_pp_min(x, (PP_TILE_BUFFER_SIZE << _pp_antialias)), 0); + int nx = _pp_max(_pp_min(x, (PP_TILE_BUFFER_SIZE << _pp_antialias)), 0); //debug(" + adding node at %d, %d\n", x, y); // add node to node list nodes[y][node_counts[y]++] = nx; @@ -482,14 +499,14 @@ void build_nodes(pp_path_t *path, pp_rect_t *tb) { pp_point_t tile_origin = (pp_point_t){tb->x * aa_scale, tb->y * aa_scale}; - // start with the last point to close the loop, transform it, scale for antialiasing, and offset to tile origin + // start with the last point to close the loop, transform it, scale for antialiasing, and offset to tile origin pp_point_t last = path->points[path->count - 1]; if(_pp_transform) last = pp_point_transform(&last, _pp_transform); last.x *= aa_scale; last.y *= aa_scale; last = pp_point_sub(&last, &tile_origin); - + for(int i = 0; i < path->count; i++) { - pp_point_t next = path->points[i]; + pp_point_t next = path->points[i]; if(_pp_transform) next = pp_point_transform(&next, _pp_transform); next.x *= aa_scale; next.y *= aa_scale; next = pp_point_sub(&next, &tile_origin); @@ -527,7 +544,7 @@ pp_rect_t render_nodes(pp_rect_t *tb) { // update render bounds rb.x = _pp_min(rb.x, sx); - rb.y = _pp_min(rb.y, y); + rb.y = _pp_min(rb.y, y); minx = _pp_min(_pp_min(sx, ex), minx); maxx = _pp_max(_pp_max(sx, ex), maxx); rb.h = y - rb.y + 1; @@ -563,7 +580,7 @@ pp_rect_t render_nodes(pp_rect_t *tb) { #if PP_SCALE_TO_ALPHA == 1 for(int y = rb.y; y < rb.y + rb.h; y++) { unsigned char* row_data = &tile_buffer[y * PP_TILE_BUFFER_SIZE + rb.x]; - for(int x = rb.x; x < rb.x + rb.w; x++) { + for(int x = rb.x; x < rb.x + rb.w; x++) { *row_data = p_alpha_map[*row_data]; row_data++; } @@ -582,7 +599,7 @@ void pp_render(pp_poly_t *polygon) { if(!polygon->paths) return; // determine extreme bounds - pp_rect_t pb = pp_polygon_bounds(polygon); + pp_rect_t pb = pp_poly_bounds(polygon); if(_pp_transform) { pb = pp_rect_transform(&pb, _pp_transform); @@ -634,7 +651,7 @@ void pp_render(pp_poly_t *polygon) { if(pp_rect_empty(&tb)) { debug(" : empty after rendering, skipping\n"); continue; } - pp_tile_t tile = { + pp_tile_t tile = { .x = tb.x, .y = tb.y, .w = tb.w, .h = tb.h, .stride = PP_TILE_BUFFER_SIZE, .data = tile_buffer + rb.x + (PP_TILE_BUFFER_SIZE * rb.y) diff --git a/micropython/examples/tufty2040/vector_clock.py b/micropython/examples/tufty2040/vector_clock.py index afb626c9a..0d4591a37 100644 --- a/micropython/examples/tufty2040/vector_clock.py +++ b/micropython/examples/tufty2040/vector_clock.py @@ -1,11 +1,11 @@ import time import gc -from picographics import PicoGraphics, DISPLAY_TUFTY_2040, PEN_RGB332 -from picovector import PicoVector, Polygon, RegularPolygon, Rectangle, ANTIALIAS_X4 +from picographics import PicoGraphics, DISPLAY_TUFTY_2040, PEN_RGB565 as PEN +from picovector import PicoVector, Transform, Polygon, ANTIALIAS_X4 -display = PicoGraphics(DISPLAY_TUFTY_2040, pen_type=PEN_RGB332) +display = PicoGraphics(DISPLAY_TUFTY_2040, pen_type=PEN) vector = PicoVector(display) vector.set_antialiasing(ANTIALIAS_X4) @@ -25,15 +25,39 @@ WIDTH, HEIGHT = display.get_bounds() -hub = RegularPolygon(int(WIDTH / 2), int(HEIGHT / 2), 24, 5) +t = Transform() -face = RegularPolygon(int(WIDTH / 2), int(HEIGHT / 2), 48, int(HEIGHT / 2)) +hub = Polygon() +hub.circle(int(WIDTH / 2), int(HEIGHT / 2), 5) + +face = Polygon() +face.circle(int(WIDTH / 2), int(HEIGHT / 2), int(HEIGHT / 2)) + +tick_mark = Polygon() +tick_mark.rectangle(int(WIDTH / 2) - 3, 10, 6, int(HEIGHT / 48)) + +hour_mark = Polygon() +hour_mark.rectangle(int(WIDTH / 2) - 5, 10, 10, int(HEIGHT / 10)) + +second_hand_length = int(HEIGHT / 2) - int(HEIGHT / 8) +second_hand = Polygon() +second_hand.path((-2, -second_hand_length), (-2, int(HEIGHT / 8)), (2, int(HEIGHT / 8)), (2, -second_hand_length)) + +minute_hand_length = int(HEIGHT / 2) - int(HEIGHT / 24) +minute_hand = Polygon() +minute_hand.path((-5, -minute_hand_length), (-10, int(HEIGHT / 16)), (10, int(HEIGHT / 16)), (5, -minute_hand_length)) + +hour_hand_length = int(HEIGHT / 2) - int(HEIGHT / 8) +hour_hand = Polygon() +hour_hand.path((-5, -hour_hand_length), (-10, int(HEIGHT / 16)), (10, int(HEIGHT / 16)), (5, -hour_hand_length)) print(time.localtime()) last_second = None +vector.set_transform(None) while True: + t.reset() t_start = time.ticks_ms() year, month, day, hour, minute, second, _, _ = time.localtime() @@ -48,72 +72,80 @@ display.set_pen(BLACK) display.circle(int(WIDTH / 2), int(HEIGHT / 2), int(HEIGHT / 2)) display.set_pen(WHITE) - display.circle(int(WIDTH / 2), int(HEIGHT / 2), int(HEIGHT / 2) - 4) + #display.circle(int(WIDTH / 2), int(HEIGHT / 2), int(HEIGHT / 2) - 4) + + vector.draw(face) display.set_pen(GREY) + vector.set_transform(t) + + t.translate(0, 2) for a in range(60): - tick_mark = Rectangle(int(WIDTH / 2) - 3, 10, 6, int(HEIGHT / 48)) - vector.rotate(tick_mark, 360 / 60.0 * a, int(WIDTH / 2), int(HEIGHT / 2)) - vector.translate(tick_mark, 0, 2) - vector.draw(tick_mark) - - for a in range(12): - hour_mark = Rectangle(int(WIDTH / 2) - 5, 10, 10, int(HEIGHT / 10)) - vector.rotate(hour_mark, 360 / 12.0 * a, int(WIDTH / 2), int(HEIGHT / 2)) - vector.translate(hour_mark, 0, 2) - vector.draw(hour_mark) - - angle_second = second * 6 - second_hand_length = int(HEIGHT / 2) - int(HEIGHT / 8) - second_hand = Polygon((-2, -second_hand_length), (-2, int(HEIGHT / 8)), (2, int(HEIGHT / 8)), (2, -second_hand_length)) - vector.rotate(second_hand, angle_second, 0, 0) - vector.translate(second_hand, int(WIDTH / 2), int(HEIGHT / 2) + 5) + t.rotate(360 / 60.0, (WIDTH / 2, HEIGHT / 2)) + if a % 5 == 0: + vector.draw(hour_mark) + else: + vector.draw(tick_mark) + t.reset() angle_minute = minute * 6 angle_minute += second / 10.0 - minute_hand_length = int(HEIGHT / 2) - int(HEIGHT / 24) - minute_hand = Polygon((-5, -minute_hand_length), (-10, int(HEIGHT / 16)), (10, int(HEIGHT / 16)), (5, -minute_hand_length)) - vector.rotate(minute_hand, angle_minute, 0, 0) - vector.translate(minute_hand, int(WIDTH / 2), int(HEIGHT / 2) + 5) + t.translate(WIDTH / 2, HEIGHT / 2 + 5) + t.rotate(angle_minute, (0, 0)) + vector.draw(minute_hand) + t.reset() angle_hour = (hour % 12) * 30 angle_hour += minute / 2 - hour_hand_length = int(HEIGHT / 2) - int(HEIGHT / 8) - hour_hand = Polygon((-5, -hour_hand_length), (-10, int(HEIGHT / 16)), (10, int(HEIGHT / 16)), (5, -hour_hand_length)) - vector.rotate(hour_hand, angle_hour, 0, 0) - vector.translate(hour_hand, int(WIDTH / 2), int(HEIGHT / 2) + 5) - - display.set_pen(GREY) - - vector.draw(minute_hand) + t.translate(WIDTH / 2, HEIGHT / 2 + 5) + t.rotate(angle_hour, (0, 0)) vector.draw(hour_hand) + + t.reset() + t.translate(WIDTH / 2, HEIGHT / 2 + 5) + t.rotate(second * 6, (0, 0)) vector.draw(second_hand) display.set_pen(BLACK) + t.reset() for a in range(60): - tick_mark = Rectangle(int(WIDTH / 2) - 3, 10, 6, int(HEIGHT / 48)) - vector.rotate(tick_mark, 360 / 60.0 * a, int(WIDTH / 2), int(HEIGHT / 2)) - vector.draw(tick_mark) + t.rotate(360 / 60.0, (WIDTH / 2, HEIGHT / 2)) + if a % 5 == 0: + vector.draw(hour_mark) + else: + vector.draw(tick_mark) - for a in range(12): - hour_mark = Rectangle(int(WIDTH / 2) - 5, 10, 10, int(HEIGHT / 10)) - vector.rotate(hour_mark, 360 / 12.0 * a, int(WIDTH / 2), int(HEIGHT / 2)) - vector.draw(hour_mark) - vector.translate(minute_hand, 0, -5) - vector.translate(hour_hand, 0, -5) + t.reset() + angle_minute = minute * 6 + angle_minute += second / 10.0 + t.translate(WIDTH / 2, HEIGHT / 2) + t.rotate(angle_minute, (0, 0)) vector.draw(minute_hand) + + t.reset() + angle_hour = (hour % 12) * 30 + angle_hour += minute / 2 + t.translate(WIDTH / 2, HEIGHT / 2) + t.rotate(angle_hour, (0, 0)) vector.draw(hour_hand) display.set_pen(RED) - vector.translate(second_hand, 0, -5) + + t.reset() + t.translate(WIDTH / 2, HEIGHT / 2) + t.rotate(second * 6, (0, 0)) vector.draw(second_hand) + + vector.set_transform(None) vector.draw(hub) display.update() + mem = gc.mem_free() gc.collect() + used = gc.mem_free() - mem t_end = time.ticks_ms() - print(f"Took {t_end - t_start}ms") + print(f"Took {t_end - t_start}ms, mem free: {gc.mem_free()} {used}") diff --git a/micropython/modules/picovector/picovector.c b/micropython/modules/picovector/picovector.c index f5921ea20..856ce59dd 100644 --- a/micropython/modules/picovector/picovector.c +++ b/micropython/modules/picovector/picovector.c @@ -3,64 +3,77 @@ /* Polygon */ static MP_DEFINE_CONST_FUN_OBJ_1(POLYGON__del__obj, POLYGON__del__); + +// Transformations +//static MP_DEFINE_CONST_FUN_OBJ_KW(POLYGON_rotate_obj, 3, POLYGON_rotate); +//static MP_DEFINE_CONST_FUN_OBJ_KW(POLYGON_translate_obj, 4, POLYGON_translate); + +// Utility functions static MP_DEFINE_CONST_FUN_OBJ_1(POLYGON_centroid_obj, POLYGON_centroid); static MP_DEFINE_CONST_FUN_OBJ_1(POLYGON_bounds_obj, POLYGON_bounds); +static MP_DEFINE_CONST_FUN_OBJ_2(POLYGON_transform_obj, POLYGON_transform); + +// Primitives +static MP_DEFINE_CONST_FUN_OBJ_KW(POLYGON_rectangle_obj, 5, POLYGON_rectangle); +static MP_DEFINE_CONST_FUN_OBJ_VAR(POLYGON_path_obj, 4, POLYGON_path); +static MP_DEFINE_CONST_FUN_OBJ_KW(POLYGON_regular_obj, 5, POLYGON_regular); +static MP_DEFINE_CONST_FUN_OBJ_KW(POLYGON_circle_obj, 4, POLYGON_circle); +static MP_DEFINE_CONST_FUN_OBJ_KW(POLYGON_arc_obj, 6, POLYGON_arc); static const mp_rom_map_elem_t POLYGON_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&POLYGON__del__obj) }, + + // Transformations + //{ MP_ROM_QSTR(MP_QSTR_rotate), MP_ROM_PTR(&POLYGON_rotate_obj) }, + //{ MP_ROM_QSTR(MP_QSTR_translate), MP_ROM_PTR(&POLYGON_translate_obj) }, + + // Utility functions { MP_ROM_QSTR(MP_QSTR_centroid), MP_ROM_PTR(&POLYGON_centroid_obj) }, { MP_ROM_QSTR(MP_QSTR_bounds), MP_ROM_PTR(&POLYGON_bounds_obj) }, + { MP_ROM_QSTR(MP_QSTR_transform), MP_ROM_PTR(&POLYGON_transform_obj) }, + + // Primitives + { MP_ROM_QSTR(MP_QSTR_rectangle), MP_ROM_PTR(&POLYGON_rectangle_obj) }, + { MP_ROM_QSTR(MP_QSTR_regular), MP_ROM_PTR(&POLYGON_regular_obj) }, + { MP_ROM_QSTR(MP_QSTR_path), MP_ROM_PTR(&POLYGON_path_obj) }, + { MP_ROM_QSTR(MP_QSTR_circle), MP_ROM_PTR(&POLYGON_circle_obj) }, + { MP_ROM_QSTR(MP_QSTR_arc), MP_ROM_PTR(&POLYGON_arc_obj) }, }; static MP_DEFINE_CONST_DICT(POLYGON_locals_dict, POLYGON_locals_dict_table); -#ifdef MP_DEFINE_CONST_OBJ_TYPE MP_DEFINE_CONST_OBJ_TYPE( POLYGON_type, MP_QSTR_polygon, MP_TYPE_FLAG_NONE, make_new, POLYGON_make_new, print, POLYGON_print, - iter, PATH_getiter, - locals_dict, (mp_obj_dict_t*)&POLYGON_locals_dict -); -MP_DEFINE_CONST_OBJ_TYPE( - REGULAR_POLYGON_type, - MP_QSTR_regular_polygon, - MP_TYPE_FLAG_NONE, - make_new, REGULAR_POLYGON_make_new, + iter, POLYGON_getiter, locals_dict, (mp_obj_dict_t*)&POLYGON_locals_dict ); + +/* Transform */ + +static MP_DEFINE_CONST_FUN_OBJ_3(TRANSFORM_rotate_obj, TRANSFORM_rotate); +static MP_DEFINE_CONST_FUN_OBJ_3(TRANSFORM_translate_obj, TRANSFORM_translate); +static MP_DEFINE_CONST_FUN_OBJ_1(TRANSFORM_reset_obj, TRANSFORM_reset); + +static const mp_rom_map_elem_t TRANSFORM_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_rotate), MP_ROM_PTR(&TRANSFORM_rotate_obj) }, + { MP_ROM_QSTR(MP_QSTR_translate), MP_ROM_PTR(&TRANSFORM_translate_obj) }, + { MP_ROM_QSTR(MP_QSTR_reset), MP_ROM_PTR(&TRANSFORM_reset_obj) }, +}; + +static MP_DEFINE_CONST_DICT(TRANSFORM_locals_dict, TRANSFORM_locals_dict_table); + MP_DEFINE_CONST_OBJ_TYPE( - RECTANGLE_type, - MP_QSTR_rectangle, + TRANSFORM_type, + MP_QSTR_Transform, MP_TYPE_FLAG_NONE, - make_new, RECTANGLE_make_new, - locals_dict, (mp_obj_dict_t*)&POLYGON_locals_dict + make_new, TRANSFORM_make_new, + locals_dict, (mp_obj_dict_t*)&TRANSFORM_locals_dict ); -#else -const mp_obj_type_t POLYGON_type = { - { &mp_type_type }, - .name = MP_QSTR_polygon, - .make_new = POLYGON_make_new, - .print = POLYGON_print, - .iter = PATH_getiter, - .locals_dict = (mp_obj_dict_t*)&POLYGON_locals_dict, -}; -const mp_obj_type_t REGULAR_POLYGON_type = { - { &mp_type_type }, - .name = MP_QSTR_regular_polygon, - .make_new = REGULAR_POLYGON_make_new, - .locals_dict = (mp_obj_dict_t*)&POLYGON_locals_dict, -}; -const mp_obj_type_t RECTANGLE_type = { - { &mp_type_type }, - .name = MP_QSTR_rectangle, - .make_new = RECTANGLE_make_new, - .locals_dict = (mp_obj_dict_t*)&POLYGON_locals_dict, -}; -#endif /* PicoVector */ @@ -68,25 +81,22 @@ static MP_DEFINE_CONST_FUN_OBJ_KW(VECTOR_text_obj, 4, VECTOR_text); static MP_DEFINE_CONST_FUN_OBJ_3(VECTOR_set_font_obj, VECTOR_set_font); static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_font_size_obj, VECTOR_set_font_size); static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_antialiasing_obj, VECTOR_set_antialiasing); +static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_transform_obj, VECTOR_set_transform); -static MP_DEFINE_CONST_FUN_OBJ_KW(VECTOR_draw_obj, 2, VECTOR_draw); -static MP_DEFINE_CONST_FUN_OBJ_KW(VECTOR_rotate_obj, 3, VECTOR_rotate); -static MP_DEFINE_CONST_FUN_OBJ_KW(VECTOR_translate_obj, 4, VECTOR_translate); +static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_draw_obj, VECTOR_draw); static const mp_rom_map_elem_t VECTOR_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_set_font), MP_ROM_PTR(&VECTOR_set_font_obj) }, { MP_ROM_QSTR(MP_QSTR_set_font_size), MP_ROM_PTR(&VECTOR_set_font_size_obj) }, { MP_ROM_QSTR(MP_QSTR_set_antialiasing), MP_ROM_PTR(&VECTOR_set_antialiasing_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_transform), MP_ROM_PTR(&VECTOR_set_transform_obj) }, { MP_ROM_QSTR(MP_QSTR_text), MP_ROM_PTR(&VECTOR_text_obj) }, { MP_ROM_QSTR(MP_QSTR_draw), MP_ROM_PTR(&VECTOR_draw_obj) }, - { MP_ROM_QSTR(MP_QSTR_rotate), MP_ROM_PTR(&VECTOR_rotate_obj) }, - { MP_ROM_QSTR(MP_QSTR_translate), MP_ROM_PTR(&VECTOR_translate_obj) }, }; static MP_DEFINE_CONST_DICT(VECTOR_locals_dict, VECTOR_locals_dict_table); -#ifdef MP_DEFINE_CONST_OBJ_TYPE MP_DEFINE_CONST_OBJ_TYPE( VECTOR_type, MP_QSTR_picovector, @@ -94,14 +104,6 @@ MP_DEFINE_CONST_OBJ_TYPE( make_new, VECTOR_make_new, locals_dict, (mp_obj_dict_t*)&VECTOR_locals_dict ); -#else -const mp_obj_type_t VECTOR_type = { - { &mp_type_type }, - .name = MP_QSTR_picovector, - .make_new = VECTOR_make_new, - .locals_dict = (mp_obj_dict_t*)&VECTOR_locals_dict, -}; -#endif /* Module */ @@ -109,8 +111,7 @@ static const mp_map_elem_t VECTOR_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_picovector) }, { MP_OBJ_NEW_QSTR(MP_QSTR_PicoVector), (mp_obj_t)&VECTOR_type }, { MP_OBJ_NEW_QSTR(MP_QSTR_Polygon), (mp_obj_t)&POLYGON_type }, - { MP_OBJ_NEW_QSTR(MP_QSTR_RegularPolygon), (mp_obj_t)®ULAR_POLYGON_type }, - { MP_OBJ_NEW_QSTR(MP_QSTR_Rectangle), (mp_obj_t)&RECTANGLE_type }, + { MP_OBJ_NEW_QSTR(MP_QSTR_Transform), (mp_obj_t)&TRANSFORM_type }, { MP_ROM_QSTR(MP_QSTR_ANTIALIAS_NONE), MP_ROM_INT(0) }, { MP_ROM_QSTR(MP_QSTR_ANTIALIAS_X4), MP_ROM_INT(1) }, { MP_ROM_QSTR(MP_QSTR_ANTIALIAS_X16), MP_ROM_INT(2) }, @@ -125,8 +126,4 @@ const mp_obj_module_t VECTOR_user_cmodule = { .globals = (mp_obj_dict_t*)&mp_module_VECTOR_globals, }; -#if MICROPY_VERSION <= 70144 -MP_REGISTER_MODULE(MP_QSTR_picovector, VECTOR_user_cmodule, MODULE_PICOVECTOR_ENABLED); -#else -MP_REGISTER_MODULE(MP_QSTR_picovector, VECTOR_user_cmodule); -#endif \ No newline at end of file +MP_REGISTER_MODULE(MP_QSTR_picovector, VECTOR_user_cmodule); \ No newline at end of file diff --git a/micropython/modules/picovector/picovector.cpp b/micropython/modules/picovector/picovector.cpp index 8da08e4ff..8098e0c41 100644 --- a/micropython/modules/picovector/picovector.cpp +++ b/micropython/modules/picovector/picovector.cpp @@ -25,10 +25,15 @@ typedef struct _VECTOR_obj_t { PicoVector *vector; } _VECTOR_obj_t; -typedef struct _PATH_obj_t { +typedef struct _TRANSFORM_obj_t { mp_obj_base_t base; - pp_path_t *path; -} _PATH_obj_t; + pp_mat3_t transform; +} _TRANSFORM_obj_t; + +typedef struct _POLY_obj_t { + mp_obj_base_t base; + pp_poly_t *poly; +} _POLY_obj_t; void __printf_debug_flush() { for(auto i = 0u; i < 10; i++) { @@ -54,20 +59,22 @@ void af_debug(const char *fmt, ...) { void *af_malloc(size_t size) { //mp_printf(&mp_plat_print, "af_malloc %lu\n", size); //__printf_debug_flush(); - void *addr = m_tracked_calloc(sizeof(uint8_t), size); + //void *addr = m_tracked_calloc(sizeof(uint8_t), size); + void *addr = m_malloc(size); //mp_printf(&mp_plat_print, "addr %lu\n", addr); //__printf_debug_flush(); return addr; } void *af_realloc(void *p, size_t size) { - return NULL; + return m_realloc(p, size); } void af_free(void *p) { //mp_printf(&mp_plat_print, "af_free\n"); //__printf_debug_flush(); - m_tracked_free(p); + //m_tracked_free(p); + m_free(p); } void* fileio_open(const char *filename) { @@ -157,123 +164,228 @@ static const std::string_view mp_obj_to_string_r(const mp_obj_t &obj) { /* POLYGON */ -mp_obj_t RECTANGLE_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_x, ARG_y, ARG_w, ARG_h }; +static void _pp_poly_merge(pp_poly_t *dst, pp_poly_t *src) { + pp_path_t *src_path = src->paths; + + while(src_path) { + pp_path_t *path = pp_poly_add_path(dst); + pp_path_add_points(path, src_path->points, src_path->count); + src_path = src_path->next; + } +} + +mp_obj_t POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + _POLY_obj_t *self = mp_obj_malloc_with_finaliser(_POLY_obj_t, &POLYGON_type); + self->poly = pp_poly_new(); + return self; +} + +mp_obj_t POLYGON__del__(mp_obj_t self_in) { + _POLY_obj_t *self = MP_OBJ_TO_PTR2(self_in, _POLY_obj_t); + pp_poly_free(self->poly); + return mp_const_none; +} + +mp_obj_t POLYGON_path(size_t n_args, const mp_obj_t *all_args) { + _POLY_obj_t *self = MP_OBJ_TO_PTR2(all_args[0], _POLY_obj_t); + + size_t num_points = n_args - 1; + const mp_obj_t *points = all_args + 1; + + pp_path_t *path = pp_poly_add_path(self->poly); + + if(num_points < 3) mp_raise_ValueError("Polygon: At least 3 points required."); + + for(auto i = 0u; i < num_points; i++) { + mp_obj_t c_obj = points[i]; + + if(!mp_obj_is_exact_type(c_obj, &mp_type_tuple)) mp_raise_ValueError("Not a tuple"); + + mp_obj_tuple_t *t_point = MP_OBJ_TO_PTR2(c_obj, mp_obj_tuple_t); + + if(t_point->len != 2) mp_raise_ValueError("Tuple must have X, Y"); + + pp_path_add_point(path, { + (picovector_point_type)mp_picovector_get_point_type(t_point->items[0]), + (picovector_point_type)mp_picovector_get_point_type(t_point->items[1]), + }); + } + + return mp_const_none; +} + +mp_obj_t POLYGON_rectangle(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_x, ARG_y, ARG_w, ARG_h, ARG_corners, ARG_stroke }; static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_x, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_y, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_w, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_h, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_corners, MP_ARG_OBJ, { .u_obj = mp_const_none }}, + { MP_QSTR_stroke, MP_ARG_OBJ, { .u_obj = mp_const_none }}, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - _PATH_obj_t *self = mp_obj_malloc_with_finaliser(_PATH_obj_t, &POLYGON_type); - self->path = (pp_path_t *)PP_MALLOC(sizeof(pp_path_t)); - self->path->storage = 4; - self->path->points = (pp_point_t *)PP_MALLOC(sizeof(pp_point_t) * self->path->storage); + _POLY_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _POLY_obj_t); picovector_point_type x = mp_picovector_get_point_type(args[ARG_x].u_obj); picovector_point_type y = mp_picovector_get_point_type(args[ARG_y].u_obj); picovector_point_type w = mp_picovector_get_point_type(args[ARG_w].u_obj); picovector_point_type h = mp_picovector_get_point_type(args[ARG_h].u_obj); + picovector_point_type s = args[ARG_stroke].u_obj == mp_const_none ? 0 : mp_picovector_get_point_type(args[ARG_stroke].u_obj); + + picovector_point_type r1 = 0; + picovector_point_type r2 = 0; + picovector_point_type r3 = 0; + picovector_point_type r4 = 0; + + if(mp_obj_is_exact_type(args[ARG_corners].u_obj, &mp_type_tuple)){ + mp_obj_tuple_t *t_corners = MP_OBJ_TO_PTR2(args[ARG_corners].u_obj, mp_obj_tuple_t); - pp_path_add_point(self->path, {picovector_point_type(x), picovector_point_type(y)}); - pp_path_add_point(self->path, {picovector_point_type(x + w), picovector_point_type(y)}); - pp_path_add_point(self->path, {picovector_point_type(x + w), picovector_point_type(y + h)}); - pp_path_add_point(self->path, {picovector_point_type(x), picovector_point_type(y + h)}); + if(t_corners->len != 4) mp_raise_ValueError("Corners must have r1, r2, r3, r4"); + + r1 = mp_picovector_get_point_type(t_corners->items[0]); + r2 = mp_picovector_get_point_type(t_corners->items[1]); + r3 = mp_picovector_get_point_type(t_corners->items[2]); + r4 = mp_picovector_get_point_type(t_corners->items[3]); + } + + pp_poly_t *rect = ppp_rect({ + x, y, w, h, + s, + r1, r2, r3, r4 + }); + _pp_poly_merge(self->poly, rect); + pp_poly_free(rect); return self; } -mp_obj_t REGULAR_POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_x, ARG_y, ARG_sides, ARG_radius, ARG_rotation }; +mp_obj_t POLYGON_regular(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_x, ARG_y, ARG_sides, ARG_radius, ARG_stroke }; static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_x, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_y, MP_ARG_REQUIRED | MP_ARG_OBJ }, - { MP_QSTR_sides, MP_ARG_REQUIRED | MP_ARG_INT }, { MP_QSTR_radius, MP_ARG_REQUIRED | MP_ARG_OBJ }, - { MP_QSTR_rotation, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_sides, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_stroke, MP_ARG_OBJ, { .u_obj = mp_const_none }}, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - _PATH_obj_t *self = mp_obj_malloc_with_finaliser(_PATH_obj_t, &POLYGON_type); - - Point origin(args[ARG_x].u_int, args[ARG_y].u_int); - unsigned int sides = args[ARG_sides].u_int; - float radius = mp_obj_get_float(args[ARG_radius].u_obj); - float rotation = 0.0f; - if (args[ARG_rotation].u_obj != mp_const_none) { - rotation = mp_obj_get_float(args[ARG_rotation].u_obj); - rotation *= (M_PI / 180.0f); - } - picovector_point_type o_x = mp_picovector_get_point_type(args[ARG_x].u_obj); - picovector_point_type o_y = mp_picovector_get_point_type(args[ARG_y].u_obj); - - float angle = (360.0f / sides) * (M_PI / 180.0f); + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + _POLY_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _POLY_obj_t); - self->path = (pp_path_t *)PP_MALLOC(sizeof(pp_path_t)); - self->path->storage = sides; - self->path->points = (pp_point_t *)PP_MALLOC(sizeof(pp_point_t) * self->path->storage); - for(auto s = 0u; s < sides; s++) { - float current_angle = angle * s + rotation; - pp_path_add_point(self->path, { - (picovector_point_type)(cos(current_angle) * radius) + o_x, - (picovector_point_type)(sin(current_angle) * radius) + o_y - }); - } + picovector_point_type x = mp_picovector_get_point_type(args[ARG_x].u_obj); + picovector_point_type y = mp_picovector_get_point_type(args[ARG_y].u_obj); + picovector_point_type r = mp_picovector_get_point_type(args[ARG_radius].u_obj); + int e = args[ARG_sides].u_int; + picovector_point_type s = args[ARG_stroke].u_obj == mp_const_none ? 0 : mp_picovector_get_point_type(args[ARG_stroke].u_obj); + + pp_poly_t *regular = ppp_regular({ + x, y, + r, + e, + s + }); + _pp_poly_merge(self->poly, regular); + pp_poly_free(regular); return self; } -mp_obj_t POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - _PATH_obj_t *self = mp_obj_malloc_with_finaliser(_PATH_obj_t, &POLYGON_type); +mp_obj_t POLYGON_circle(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_x, ARG_y, ARG_radius, ARG_stroke }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_x, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_y, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_radius, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_stroke, MP_ARG_OBJ, { .u_obj = mp_const_none }}, + }; - size_t num_points = n_args; - const mp_obj_t *points = all_args; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - self->path = (pp_path_t *)PP_MALLOC(sizeof(pp_path_t)); - self->path->storage = num_points; - self->path->points = (pp_point_t *)PP_MALLOC(sizeof(pp_point_t) * self->path->storage); + _POLY_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _POLY_obj_t); - if(num_points < 3) mp_raise_ValueError("Polygon: At least 3 points required."); + picovector_point_type x = mp_picovector_get_point_type(args[ARG_x].u_obj); + picovector_point_type y = mp_picovector_get_point_type(args[ARG_y].u_obj); + picovector_point_type r = mp_picovector_get_point_type(args[ARG_radius].u_obj); + picovector_point_type s = args[ARG_stroke].u_obj == mp_const_none ? 0 : mp_picovector_get_point_type(args[ARG_stroke].u_obj); - for(auto i = 0u; i < num_points; i++) { - mp_obj_t c_obj = points[i]; + pp_poly_t *circle = ppp_circle({ + x, y, + r, + s + }); + _pp_poly_merge(self->poly, circle); + pp_poly_free(circle); - if(!mp_obj_is_exact_type(c_obj, &mp_type_tuple)) mp_raise_ValueError("Not a tuple"); + return self; +} - mp_obj_tuple_t *t_point = MP_OBJ_TO_PTR2(c_obj, mp_obj_tuple_t); +mp_obj_t POLYGON_arc(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_x, ARG_y, ARG_radius, ARG_from, ARG_to, ARG_stroke }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_x, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_y, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_radius, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_from, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_to, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_stroke, MP_ARG_OBJ, { .u_obj = mp_const_none }}, + }; - if(t_point->len != 2) mp_raise_ValueError("Tuple must have X, Y"); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - pp_path_add_point(self->path, { - (picovector_point_type)mp_picovector_get_point_type(t_point->items[0]), - (picovector_point_type)mp_picovector_get_point_type(t_point->items[1]), - }); - } + _POLY_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _POLY_obj_t); + + picovector_point_type x = mp_picovector_get_point_type(args[ARG_x].u_obj); + picovector_point_type y = mp_picovector_get_point_type(args[ARG_y].u_obj); + picovector_point_type r = mp_picovector_get_point_type(args[ARG_radius].u_obj); + picovector_point_type f = mp_picovector_get_point_type(args[ARG_from].u_obj); + picovector_point_type t = mp_picovector_get_point_type(args[ARG_to].u_obj); + picovector_point_type s = args[ARG_stroke].u_obj == mp_const_none ? 0 : mp_picovector_get_point_type(args[ARG_stroke].u_obj); + + pp_poly_t *arc = ppp_arc({ + x, y, + r, + s, + f, + t + }); + _pp_poly_merge(self->poly, arc); + pp_poly_free(arc); return self; } +// Utility functions + mp_obj_t POLYGON_centroid(mp_obj_t self_in) { - _PATH_obj_t *self = MP_OBJ_TO_PTR2(self_in, _PATH_obj_t); + _POLY_obj_t *self = MP_OBJ_TO_PTR2(self_in, _POLY_obj_t); PP_COORD_TYPE sum_x = (PP_COORD_TYPE)0; PP_COORD_TYPE sum_y = (PP_COORD_TYPE)0; - for(auto i = 0; i < self->path->count; i++) { - sum_x += self->path->points[i].x; - sum_y += self->path->points[i].y; + // TODO: Maybe include in pretty-poly? + // Might need to handle multiple paths + pp_path_t *path = self->poly->paths; + + for(auto i = 0; i < path->count; i++) { + sum_x += path->points[i].x; + sum_y += path->points[i].y; } - sum_x /= (float)self->path->count; - sum_y /= (float)self->path->count; + sum_x /= (float)path->count; + sum_y /= (float)path->count; mp_obj_t tuple[2]; tuple[0] = mp_picovector_set_point_type((int)(sum_x)); @@ -283,9 +395,9 @@ mp_obj_t POLYGON_centroid(mp_obj_t self_in) { } mp_obj_t POLYGON_bounds(mp_obj_t self_in) { - _PATH_obj_t *self = MP_OBJ_TO_PTR2(self_in, _PATH_obj_t); + _POLY_obj_t *self = MP_OBJ_TO_PTR2(self_in, _POLY_obj_t); - pp_rect_t bounds = pp_path_bounds(self->path); + pp_rect_t bounds = pp_poly_bounds(self->poly); mp_obj_t tuple[4]; tuple[0] = mp_picovector_set_point_type((int)(bounds.x)); @@ -298,63 +410,101 @@ mp_obj_t POLYGON_bounds(mp_obj_t self_in) { void POLYGON_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; - _PATH_obj_t *self = MP_OBJ_TO_PTR2(self_in, _PATH_obj_t); - - pp_rect_t bounds = pp_path_bounds(self->path); - - mp_print_str(print, "Polygon(points = "); - mp_obj_print_helper(print, mp_picovector_set_point_type(self->path->count), PRINT_REPR); - mp_print_str(print, ", bounds = "); - mp_obj_print_helper(print, mp_picovector_set_point_type(bounds.x), PRINT_REPR); - mp_print_str(print, ", "); - mp_obj_print_helper(print, mp_picovector_set_point_type(bounds.y), PRINT_REPR); - mp_print_str(print, ", "); - mp_obj_print_helper(print, mp_picovector_set_point_type(bounds.w), PRINT_REPR); - mp_print_str(print, ", "); - mp_obj_print_helper(print, mp_picovector_set_point_type(bounds.h), PRINT_REPR); - mp_print_str(print, ")"); -} + _POLY_obj_t *self = MP_OBJ_TO_PTR2(self_in, _POLY_obj_t); + (void)self; -mp_obj_t POLYGON__del__(mp_obj_t self_in) { - _PATH_obj_t *self = MP_OBJ_TO_PTR2(self_in, _PATH_obj_t); - PP_FREE(self->path->points); - PP_FREE(self->path); - // TODO: Do we actually need to free anything here, if it's on GC heap it should get collected - return mp_const_none; + // TODO: Make print better + mp_print_str(print, "Polygon();"); } typedef struct _mp_obj_polygon_it_t { mp_obj_base_t base; mp_fun_1_t iternext; mp_obj_t polygon; - int cur; + pp_path_t *cur; } mp_obj_polygon_it_t; -static mp_obj_t py_path_it_iternext(mp_obj_t self_in) { +static mp_obj_t POLYGON_it_iternext(mp_obj_t self_in) { mp_obj_polygon_it_t *self = MP_OBJ_TO_PTR2(self_in, mp_obj_polygon_it_t); - _PATH_obj_t *path = MP_OBJ_TO_PTR2(self->polygon, _PATH_obj_t); + //_POLY_obj_t *poly = MP_OBJ_TO_PTR2(self->polygon, _POLY_obj_t); //mp_printf(&mp_plat_print, "points: %d, current: %d\n", polygon->contour.count, self->cur); - if(self->cur >= path->path->count) return MP_OBJ_STOP_ITERATION; + if(!self->cur) return MP_OBJ_STOP_ITERATION; - mp_obj_t tuple[2]; - tuple[0] = mp_picovector_set_point_type((int)(path->path->points[self->cur].x)); - tuple[1] = mp_picovector_set_point_type((int)(path->path->points[self->cur].y)); + mp_obj_t tuple[self->cur->count]; + for (auto i = 0; i < self->cur->count; i++) { + mp_obj_t t_point[2] = { + mp_picovector_set_point_type((int)(self->cur->points[i].x)), + mp_picovector_set_point_type((int)(self->cur->points[i].y)) + }; + tuple[i] = mp_obj_new_tuple(2, t_point); + } - self->cur++; - return mp_obj_new_tuple(2, tuple); + self->cur = self->cur->next; + return mp_obj_new_tuple(self->cur->count, tuple); } -mp_obj_t PATH_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { +mp_obj_t POLYGON_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { mp_obj_polygon_it_t *o = (mp_obj_polygon_it_t *)iter_buf; o->base.type = &mp_type_polymorph_iter; - o->iternext = py_path_it_iternext; + o->iternext = POLYGON_it_iternext; o->polygon = o_in; - o->cur = 0; + o->cur = MP_OBJ_TO_PTR2(o_in, _POLY_obj_t)->poly->paths; return MP_OBJ_FROM_PTR(o); } +/* TRANSFORM */ + +mp_obj_t TRANSFORM_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + _TRANSFORM_obj_t *self = m_new_obj(_TRANSFORM_obj_t); + self->base.type = &TRANSFORM_type; + + self->transform = pp_mat3_identity(); + + return self; +} + +mp_obj_t TRANSFORM_rotate(mp_obj_t self_in, mp_obj_t angle_in, mp_obj_t origin_in) { + _TRANSFORM_obj_t *transform = MP_OBJ_TO_PTR2(self_in, _TRANSFORM_obj_t); + + float angle = mp_obj_get_float(angle_in); + + if(mp_obj_is_exact_type(origin_in, &mp_type_tuple)) { + mp_obj_tuple_t *t_origin = MP_OBJ_TO_PTR2(origin_in, mp_obj_tuple_t); + + if(t_origin->len != 2) mp_raise_ValueError("Origin Tuple must have X, Y"); + + picovector_point_type x = mp_picovector_get_point_type(t_origin->items[0]); + picovector_point_type y = mp_picovector_get_point_type(t_origin->items[1]); + + pp_mat3_translate(&transform->transform, x, y); + pp_mat3_rotate(&transform->transform, angle); + pp_mat3_translate(&transform->transform, -x, -y); + } else { + pp_mat3_rotate(&transform->transform, angle); + } + + return mp_const_none; +} + +mp_obj_t TRANSFORM_translate(mp_obj_t self_in, mp_obj_t x_in, mp_obj_t y_in) { + _TRANSFORM_obj_t *transform = MP_OBJ_TO_PTR2(self_in, _TRANSFORM_obj_t); + + picovector_point_type o_x = mp_picovector_get_point_type(x_in); + picovector_point_type o_y = mp_picovector_get_point_type(y_in); + + pp_mat3_translate(&transform->transform, o_x, o_y); + + return mp_const_none; +} + +mp_obj_t TRANSFORM_reset(mp_obj_t self_in) { + _TRANSFORM_obj_t *transform = MP_OBJ_TO_PTR2(self_in, _TRANSFORM_obj_t); + transform->transform = pp_mat3_identity(); + return mp_const_none; +} + /* VECTOR */ mp_obj_t VECTOR_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { @@ -383,6 +533,24 @@ mp_obj_t VECTOR_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, return self; } +mp_obj_t VECTOR_set_transform(mp_obj_t self_in, mp_obj_t transform_in) { + _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); + (void)self; + + if(transform_in == mp_const_none) { + pp_mat3_t* old = pp_transform(NULL); + (void)old; // TODO: Return old transform? + } else if MP_OBJ_IS_TYPE(transform_in, &TRANSFORM_type) { + _TRANSFORM_obj_t *transform = (_TRANSFORM_obj_t *)MP_OBJ_TO_PTR(transform_in); + pp_mat3_t* old = pp_transform(&transform->transform); + (void)old; + } else { + // TODO: ValueError? + } + + return mp_const_none; +} + mp_obj_t VECTOR_set_font(mp_obj_t self_in, mp_obj_t font, mp_obj_t size) { _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); (void)self; @@ -465,94 +633,15 @@ mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) return mp_const_none; } -mp_obj_t VECTOR_rotate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_self, ARG_polygon, ARG_angle, ARG_origin_x, ARG_origin_y }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, - { MP_QSTR_polygon, MP_ARG_REQUIRED | MP_ARG_OBJ }, - { MP_QSTR_angle, MP_ARG_REQUIRED | MP_ARG_OBJ }, - { MP_QSTR_origin_x, MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_origin_y, MP_ARG_INT, {.u_int = 0} } - }; - - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _VECTOR_obj_t); - - if(!MP_OBJ_IS_TYPE(args[ARG_polygon].u_obj, &POLYGON_type)) mp_raise_TypeError("rotate: polygon required"); - - _PATH_obj_t *poly = MP_OBJ_TO_PTR2(args[ARG_polygon].u_obj, _PATH_obj_t); - - pp_point_t origin = {(PP_COORD_TYPE)args[ARG_origin_x].u_int, (PP_COORD_TYPE)args[ARG_origin_y].u_int}; - - float angle = mp_obj_get_float(args[ARG_angle].u_obj); - - self->vector->rotate(poly->path, origin, angle); - - return mp_const_none; -} - -mp_obj_t VECTOR_translate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_self, ARG_polygon, ARG_x, ARG_y }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, - { MP_QSTR_polygon, MP_ARG_REQUIRED | MP_ARG_OBJ }, - { MP_QSTR_x, MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_y, MP_ARG_INT, {.u_int = 0} } - }; - - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _VECTOR_obj_t); - - if(!MP_OBJ_IS_TYPE(args[ARG_polygon].u_obj, &POLYGON_type)) mp_raise_TypeError("rotate: polygon required"); - - _PATH_obj_t *poly = MP_OBJ_TO_PTR2(args[ARG_polygon].u_obj, _PATH_obj_t); - - pp_point_t translate = {(PP_COORD_TYPE)args[ARG_x].u_int, (PP_COORD_TYPE)args[ARG_y].u_int}; - - self->vector->translate(poly->path, translate); - - return mp_const_none; -} - -mp_obj_t VECTOR_draw(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - - size_t num_polygons = n_args - 1; - const mp_obj_t *polygons = pos_args + 1; - - _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(pos_args[0], _VECTOR_obj_t); - - if(num_polygons == 1) { - mp_obj_t poly_obj = polygons[0]; - - if(!MP_OBJ_IS_TYPE(poly_obj, &POLYGON_type)) mp_raise_TypeError("draw: Polygon required."); - - _PATH_obj_t *poly = MP_OBJ_TO_PTR2(poly_obj, _PATH_obj_t); - - self->vector->draw(poly->path); - - return mp_const_none; - } - - - pp_poly_t *group = pp_poly_new(); - - for(auto i = 0u; i < num_polygons; i++) { - pp_path_t *path = pp_poly_add_path(group); - mp_obj_t poly_obj = polygons[i]; - - if(!MP_OBJ_IS_TYPE(poly_obj, &POLYGON_type)) mp_raise_TypeError("draw: Polygon required."); +mp_obj_t VECTOR_draw(mp_obj_t self_in, mp_obj_t poly_in) { + _VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t); + (void)self; - _PATH_obj_t *poly = MP_OBJ_TO_PTR2(poly_obj, _PATH_obj_t); - pp_path_add_points(path, poly->path->points, poly->path->count); - } + if(!MP_OBJ_IS_TYPE(poly_in, &POLYGON_type)) mp_raise_TypeError("draw: Polygon required."); - self->vector->draw(group); + _POLY_obj_t *poly = MP_OBJ_TO_PTR2(poly_in, _POLY_obj_t); - pp_poly_free(group); + pp_render(poly->poly); return mp_const_none; } diff --git a/micropython/modules/picovector/picovector.h b/micropython/modules/picovector/picovector.h index 547c59a1e..160b79d77 100644 --- a/micropython/modules/picovector/picovector.h +++ b/micropython/modules/picovector/picovector.h @@ -3,26 +3,41 @@ extern const mp_obj_type_t VECTOR_type; extern const mp_obj_type_t POLYGON_type; -extern const mp_obj_type_t REGULAR_POLYGON_type; -extern const mp_obj_type_t RECTANGLE_type; +extern const mp_obj_type_t TRANSFORM_type; + +/* Polygon */ extern mp_obj_t POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); -extern mp_obj_t REGULAR_POLYGON_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); -extern mp_obj_t RECTANGLE_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); +extern mp_obj_t POLYGON_path(size_t n_args, const mp_obj_t *all_args); +extern mp_obj_t POLYGON_regular(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t POLYGON_rectangle(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t POLYGON_circle(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t POLYGON_arc(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); + extern void POLYGON_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind); extern mp_obj_t POLYGON_centroid(mp_obj_t self_in); extern mp_obj_t POLYGON_bounds(mp_obj_t self_in); -extern mp_obj_t PATH_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf); - +extern mp_obj_t POLYGON_transform(mp_obj_t self_in, mp_obj_t transform_in); +extern mp_obj_t POLYGON_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf); extern mp_obj_t POLYGON__del__(mp_obj_t self_in); +/* Transform */ + +extern mp_obj_t TRANSFORM_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); +extern mp_obj_t TRANSFORM_rotate(mp_obj_t self_in, mp_obj_t angle_in, mp_obj_t origin_in); +extern mp_obj_t TRANSFORM_translate(mp_obj_t self_in, mp_obj_t x_in, mp_obj_t y_in); +extern mp_obj_t TRANSFORM_reset(mp_obj_t self_in); + +/* Vector */ + extern mp_obj_t VECTOR_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); extern mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); extern mp_obj_t VECTOR_set_font(mp_obj_t self_in, mp_obj_t font, mp_obj_t size); extern mp_obj_t VECTOR_set_font_size(mp_obj_t self_in, mp_obj_t size); extern mp_obj_t VECTOR_set_antialiasing(mp_obj_t self_in, mp_obj_t aa); +extern mp_obj_t VECTOR_set_transform(mp_obj_t self_in, mp_obj_t transform_in); -extern mp_obj_t VECTOR_draw(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t VECTOR_draw(mp_obj_t self_in, mp_obj_t poly_in); extern mp_obj_t VECTOR_rotate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); extern mp_obj_t VECTOR_translate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); \ No newline at end of file