2020-05-02 05:21:35 -04:00
|
|
|
|
|
|
|
#include "shared-module/vectorio/__init__.h"
|
|
|
|
#include "shared-bindings/vectorio/Polygon.h"
|
|
|
|
#include "shared-module/displayio/area.h"
|
|
|
|
|
|
|
|
#include "py/runtime.h"
|
2020-05-13 00:55:18 -04:00
|
|
|
#include "py/gc.h"
|
|
|
|
|
2020-05-02 05:21:35 -04:00
|
|
|
#include "stdlib.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
|
|
|
|
#define VECTORIO_POLYGON_DEBUG(...) (void)0
|
|
|
|
// #define VECTORIO_POLYGON_DEBUG(...) mp_printf(&mp_plat_print __VA_OPT__(,) __VA_ARGS__)
|
|
|
|
|
|
|
|
|
2020-05-13 00:55:18 -04:00
|
|
|
// Converts a list of points tuples to a flat list of ints for speedier internal use.
|
|
|
|
// Also validates the points.
|
|
|
|
static void _clobber_points_list(vectorio_polygon_t *self, mp_obj_t points_tuple_list) {
|
|
|
|
size_t len = 0;
|
|
|
|
mp_obj_t *items;
|
|
|
|
mp_obj_list_get(points_tuple_list, &len, &items);
|
2020-07-19 15:27:35 -04:00
|
|
|
VECTORIO_POLYGON_DEBUG(" self.len: %d, len: %d, ", self->len, len);
|
2020-05-13 00:55:18 -04:00
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
if (len < 3) {
|
2020-05-13 00:55:18 -04:00
|
|
|
mp_raise_TypeError_varg(translate("Polygon needs at least 3 points"));
|
|
|
|
}
|
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
if (self->len < 2 * len) {
|
|
|
|
if (self->points_list != NULL) {
|
2020-07-19 15:27:35 -04:00
|
|
|
VECTORIO_POLYGON_DEBUG("free(%d), ", sizeof(self->points_list));
|
2021-03-15 09:57:36 -04:00
|
|
|
gc_free(self->points_list);
|
2020-05-13 00:55:18 -04:00
|
|
|
}
|
2021-03-15 09:57:36 -04:00
|
|
|
self->points_list = gc_alloc(2 * len * sizeof(int), false, false);
|
2020-07-19 15:27:35 -04:00
|
|
|
VECTORIO_POLYGON_DEBUG("alloc(%p, %d)", self->points_list, 2 * len * sizeof(int));
|
2020-05-13 00:55:18 -04:00
|
|
|
}
|
2021-03-15 09:57:36 -04:00
|
|
|
self->len = 2 * len;
|
2020-05-13 00:55:18 -04:00
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
for (size_t i = 0; i < len; ++i) {
|
2020-05-13 00:55:18 -04:00
|
|
|
size_t tuple_len = 0;
|
|
|
|
mp_obj_t *tuple_items;
|
|
|
|
mp_obj_tuple_get(items[i], &tuple_len, &tuple_items);
|
|
|
|
|
|
|
|
if (tuple_len != 2) {
|
|
|
|
mp_raise_ValueError_varg(translate("%q must be a tuple of length 2"), MP_QSTR_point);
|
|
|
|
}
|
2021-03-15 09:57:36 -04:00
|
|
|
if (!mp_obj_get_int_maybe(tuple_items[ 0 ], &self->points_list[2 * i ])
|
|
|
|
|| !mp_obj_get_int_maybe(tuple_items[ 1 ], &self->points_list[2 * i + 1])
|
|
|
|
) {
|
2020-05-13 00:55:18 -04:00
|
|
|
self->len = 0;
|
2021-03-15 09:57:36 -04:00
|
|
|
gc_free(self->points_list);
|
2020-05-13 00:55:18 -04:00
|
|
|
self->points_list = NULL;
|
|
|
|
mp_raise_ValueError_varg(translate("unsupported %q type"), MP_QSTR_point);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-05-02 05:21:35 -04:00
|
|
|
void common_hal_vectorio_polygon_construct(vectorio_polygon_t *self, mp_obj_t points_list) {
|
2020-07-19 15:27:35 -04:00
|
|
|
VECTORIO_POLYGON_DEBUG("%p polygon_construct: ", self);
|
2020-05-13 00:55:18 -04:00
|
|
|
self->points_list = NULL;
|
|
|
|
self->len = 0;
|
2020-05-02 05:21:35 -04:00
|
|
|
self->on_dirty.obj = NULL;
|
2021-03-15 09:57:36 -04:00
|
|
|
_clobber_points_list(self, points_list);
|
2020-07-19 15:27:35 -04:00
|
|
|
VECTORIO_POLYGON_DEBUG("\n");
|
2020-05-02 05:21:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
mp_obj_t common_hal_vectorio_polygon_get_points(vectorio_polygon_t *self) {
|
2020-07-19 15:27:35 -04:00
|
|
|
VECTORIO_POLYGON_DEBUG("%p common_hal_vectorio_polygon_get_points {len: %d, points_list: %p}\n", self, self->len, self->points_list);
|
2021-03-15 09:57:36 -04:00
|
|
|
mp_obj_t list = mp_obj_new_list(self->len / 2, NULL);
|
2020-07-19 15:27:35 -04:00
|
|
|
|
|
|
|
for (size_t i = 0; i < self->len; i += 2) {
|
2021-03-15 09:57:36 -04:00
|
|
|
mp_obj_t tuple[] = { mp_obj_new_int(self->points_list[i]), mp_obj_new_int(self->points_list[i + 1]) };
|
2020-07-19 15:27:35 -04:00
|
|
|
mp_obj_list_append(
|
|
|
|
list,
|
|
|
|
mp_obj_new_tuple(2, tuple)
|
2021-03-15 09:57:36 -04:00
|
|
|
);
|
2020-07-19 15:27:35 -04:00
|
|
|
}
|
|
|
|
return list;
|
2020-05-02 05:21:35 -04:00
|
|
|
}
|
|
|
|
void common_hal_vectorio_polygon_set_points(vectorio_polygon_t *self, mp_obj_t points_list) {
|
2020-07-19 15:27:35 -04:00
|
|
|
VECTORIO_POLYGON_DEBUG("%p common_hal_vectorio_polygon_set_points: ", self);
|
2021-03-15 09:57:36 -04:00
|
|
|
_clobber_points_list(self, points_list);
|
2020-05-02 05:21:35 -04:00
|
|
|
if (self->on_dirty.obj != NULL) {
|
|
|
|
self->on_dirty.event(self->on_dirty.obj);
|
|
|
|
}
|
2020-07-19 15:27:35 -04:00
|
|
|
VECTORIO_POLYGON_DEBUG("\n");
|
2020-05-02 05:21:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void common_hal_vectorio_polygon_set_on_dirty(vectorio_polygon_t *self, vectorio_event_t notification) {
|
2021-03-15 09:57:36 -04:00
|
|
|
if (self->on_dirty.obj != NULL) {
|
2020-05-02 05:21:35 -04:00
|
|
|
mp_raise_TypeError(translate("polygon can only be registered in one parent"));
|
|
|
|
}
|
|
|
|
self->on_dirty = notification;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void common_hal_vectorio_polygon_get_area(void *polygon, displayio_area_t *area) {
|
|
|
|
vectorio_polygon_t *self = polygon;
|
|
|
|
|
|
|
|
area->x1 = SHRT_MAX;
|
|
|
|
area->y1 = SHRT_MAX;
|
|
|
|
area->x2 = SHRT_MIN;
|
|
|
|
area->y2 = SHRT_MIN;
|
2021-03-15 09:57:36 -04:00
|
|
|
for (size_t i = 0; i < self->len; ++i) {
|
2020-05-13 00:55:18 -04:00
|
|
|
int x = self->points_list[i];
|
2020-05-02 05:21:35 -04:00
|
|
|
++i;
|
2020-05-13 00:55:18 -04:00
|
|
|
int y = self->points_list[i];
|
2021-03-15 09:57:36 -04:00
|
|
|
if (x <= area->x1) {
|
|
|
|
area->x1 = x - 1;
|
|
|
|
}
|
|
|
|
if (y <= area->y1) {
|
|
|
|
area->y1 = y - 1;
|
|
|
|
}
|
|
|
|
if (x >= area->x2) {
|
|
|
|
area->x2 = x + 1;
|
|
|
|
}
|
|
|
|
if (y >= area->y2) {
|
|
|
|
area->y2 = y + 1;
|
|
|
|
}
|
2020-05-02 05:21:35 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// <0 if the point is to the left of the line vector
|
|
|
|
// 0 if the point is on the line
|
|
|
|
// >0 if the point is to the right of the line vector
|
2021-03-15 09:57:36 -04:00
|
|
|
__attribute__((always_inline)) static inline int line_side(mp_int_t x1, mp_int_t y1, mp_int_t x2, mp_int_t y2, int16_t px, int16_t py) {
|
2020-05-02 05:21:35 -04:00
|
|
|
return (px - x1) * (y2 - y1)
|
2021-03-15 09:57:36 -04:00
|
|
|
- (py - y1) * (x2 - x1);
|
2020-05-02 05:21:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t common_hal_vectorio_polygon_get_pixel(void *obj, int16_t x, int16_t y) {
|
|
|
|
VECTORIO_POLYGON_DEBUG("%p polygon get_pixel %d, %d\n", obj, x, y);
|
|
|
|
vectorio_polygon_t *self = obj;
|
|
|
|
|
2020-05-13 00:55:18 -04:00
|
|
|
if (self->len == 0) {
|
2020-05-02 05:21:35 -04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int winding_number = 0;
|
2020-05-13 00:55:18 -04:00
|
|
|
int x1 = self->points_list[0];
|
|
|
|
int y1 = self->points_list[1];
|
2021-03-15 09:57:36 -04:00
|
|
|
for (size_t i = 2; i <= self->len + 1; ++i) {
|
2020-05-02 05:21:35 -04:00
|
|
|
VECTORIO_POLYGON_DEBUG(" {(%3d, %3d),", x1, y1);
|
2020-05-13 00:55:18 -04:00
|
|
|
int x2 = self->points_list[i % self->len];
|
2020-05-02 05:21:35 -04:00
|
|
|
++i;
|
2020-05-13 00:55:18 -04:00
|
|
|
int y2 = self->points_list[i % self->len];
|
2020-05-02 05:21:35 -04:00
|
|
|
VECTORIO_POLYGON_DEBUG(" (%3d, %3d)}\n", x2, y2);
|
2021-03-15 09:57:36 -04:00
|
|
|
if (y1 <= y) {
|
|
|
|
if (y2 > y && line_side(x1, y1, x2, y2, x, y) < 0) {
|
use top-left heuristic for vectorio.Polygon
this flips the bottom-right style to top-left which is at least
kind of normal. A 2x2 square at (0,0) would be defined like
(0,0), (3,0), (3,3), (0,3)
Which seems kind of surprising but at least less bonkers than
that square being defined at (1,1), which is the current behavior.
2020-07-29 01:58:28 -04:00
|
|
|
// Wind up, point is to the left of the edge vector
|
2020-05-02 05:21:35 -04:00
|
|
|
++winding_number;
|
|
|
|
VECTORIO_POLYGON_DEBUG(" wind:%2d winding_number:%2d\n", 1, winding_number);
|
|
|
|
}
|
2021-03-15 09:57:36 -04:00
|
|
|
} else if (y2 <= y && line_side(x1, y1, x2, y2, x, y) > 0) {
|
use top-left heuristic for vectorio.Polygon
this flips the bottom-right style to top-left which is at least
kind of normal. A 2x2 square at (0,0) would be defined like
(0,0), (3,0), (3,3), (0,3)
Which seems kind of surprising but at least less bonkers than
that square being defined at (1,1), which is the current behavior.
2020-07-29 01:58:28 -04:00
|
|
|
// Wind down, point is to the right of the edge vector
|
2020-05-02 05:21:35 -04:00
|
|
|
--winding_number;
|
2020-05-30 05:44:13 -04:00
|
|
|
VECTORIO_POLYGON_DEBUG(" wind:%2d winding_number:%2d\n", -1, winding_number);
|
2020-05-02 05:21:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
x1 = x2;
|
|
|
|
y1 = y2;
|
|
|
|
}
|
|
|
|
return winding_number == 0 ? 0 : 1;
|
|
|
|
}
|