Skip to content

Instantly share code, notes, and snippets.

@todbot
Created July 21, 2022 23:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save todbot/5cf76ca7344e9196326014989172fee1 to your computer and use it in GitHub Desktop.
Save todbot/5cf76ca7344e9196326014989172fee1 to your computer and use it in GitHub Desktop.
Demo rotation of a vectorio polygon
# vectorio_rotate_example.py
# 21 Jul 2022 - @todbot / Tod Kurt
# Demo rotation of a vectorio polygon
import time
import board
import displayio
import vectorio
import math
#import ulab.numpy as np
display = board.DISPLAY # assume we have built-in display
# 6-pointed star created with Figma & PathToPoints: https://todbot.github.io/PathToPoints/
path0_w = 43.301
path0_h = 50.000
path0 = [(22.001,1.003),(22.398,2.963),(22.795,4.924),(23.192,6.880),(23.590,8.848),(23.985,10.794),(24.383,12.759),(24.779,14.715),(25.176,16.674),(25.574,18.637),(26.816,19.147),(28.706,18.513),(30.594,17.880),(32.492,17.243),(34.399,16.604),(36.292,15.968),(38.183,15.334),(40.093,14.694),(41.983,14.060),(43.472,13.658),(41.972,14.983),(40.475,16.305),(38.978,17.628),(37.475,18.955),(35.975,20.280),(34.479,21.602),(32.980,22.926),(31.482,24.249),(29.987,25.570),(30.518,26.899),(32.014,28.220),(33.517,29.549),(35.012,30.869),(36.513,32.195),(38.017,33.524),(39.510,34.842),(41.006,36.164),(42.505,37.488),(43.208,38.352),(41.310,37.715),(39.408,37.077),(37.515,36.442),(35.617,35.805),(33.719,35.168),(31.823,34.532),(29.924,33.895),(28.035,33.262),(26.124,32.621),(25.434,34.057),(25.036,36.019),(24.640,37.972),(24.241,39.942),(23.845,41.897),(23.447,43.859),(23.050,45.821),(22.653,47.777),(22.255,49.743),(21.858,50.297),(21.463,48.349),(21.063,46.378),(20.667,44.424),(20.268,42.452),(19.873,40.504),(19.473,38.529),(19.078,36.579),(18.680,34.618),(18.282,32.655),(16.517,33.076),(14.606,33.718),(12.721,34.350),(10.814,34.990),(8.933,35.621),(7.028,36.260),(5.135,36.895),(3.245,37.529),(1.337,38.169),(1.071,37.863),(2.560,36.547),(4.067,35.216),(5.558,33.899),(7.062,32.570),(8.560,31.247),(10.061,29.921),(11.551,28.605),(13.052,27.279),(14.449,25.955),(12.945,24.627),(11.447,23.303),(9.945,21.976),(8.448,20.654),(6.948,19.329),(5.458,18.013),(3.957,16.687),(2.453,15.358),(0.957,14.037),(1.483,13.880),(3.376,14.515),(5.264,15.149),(7.168,15.787),(9.064,16.423),(10.969,17.062),(12.855,17.695),(14.756,18.333),(16.646,18.967),(18.312,19.199),(18.710,17.236),(19.106,15.283),(19.504,13.317),(19.900,11.361),(20.298,9.400),(20.694,7.446),(21.092,5.479),(21.490,3.515),(21.888,1.554),]
shape0_w = 35 # desired screen size of path
shape0_h = shape0_w * (path0_h/path0_w) # maintain aspect ratio
shape0_color = 0xff00ff # preferred color
def rotate_points(pts, a):
"""Rotate a list of points pts by angle a around origin"""
sa, ca = math.sin(a), math.cos(a) # do this computation only once
return [ (p[0]*ca - p[1]*sa, p[1]*ca + p[0]*sa) for p in pts ] # p[0]=x, p[1]=y
def recenter_points(pts, cx, cy):
"""Center points around new origin c"""
return [(p[0] - cx, p[1] - cy) for p in pts]
def rescale_points(pts, orig_w, orig_h, new_w, new_h):
"""Rescale point path to new w,h"""
return [ (new_w * p[0] / orig_w, new_h * p[1] / orig_h ) for p in pts]
def int_points(pts):
"""Convert array of flooat points to int, for vectorio"""
return [(int(p[0]),int(p[1])) for p in pts]
# rescale to desired size
path0 = rescale_points(path0, path0_w, path0_h, shape0_w, shape0_h)
# recenter points around midpoint of path so rotation works as expected
path0 = recenter_points(path0, cx=shape0_w/2, cy=shape0_h/2)
maingroup = displayio.Group()
display.show(maingroup) # put main group on display, everything goes in maingroup
# create vectorio shape, put in a group, put that on maingroup
pal = displayio.Palette(1)
pal[0] = shape0_color
shape0 = vectorio.Polygon(pixel_shader=pal, points=int_points(path0))
shapeg = displayio.Group()
shapeg.append(shape0)
maingroup.append(shapeg)
x, y, theta = 80, 80, 0 # starting position for our shape
xvel, yvel, theta_vel = 1.8, 1.0, 0.04 # starting velocities
last_debug_time = 0
while True:
elapsed_time = time.monotonic()
# rotate about shape origin, based off original float points
shape0.points = int_points( rotate_points(path0, theta) )
elapsed_time = time.monotonic() - elapsed_time
if time.monotonic() - last_debug_time > 0.5:
last_debug_time = time.monotonic()
print("elapsed millis %d" % (elapsed_time * 1000))
# update position
x, y, theta = x + xvel, y + yvel, theta + theta_vel
shapeg.x = int(x)
shapeg.y = int(y)
# bounce on screen edge hit
if x < shape0_w//2 or x > (display.width - shape0_w//2):
xvel = -xvel
if y < shape0_h//2 or y > (display.height - shape0_h//2):
yvel = -yvel
time.sleep(0.05)
@todbot
Copy link
Author

todbot commented Jul 21, 2022

Example video:

vectorio_rotate_example.mp4

@tlyu
Copy link

tlyu commented Jul 22, 2022

@tlyu
Copy link

tlyu commented Jul 22, 2022

Summary (on a Clue):

  • np_rotate_points 1ms
  • np_rotate_points plus np_int_points total 21ms
  • rotate_points 9ms
  • rotate_points plus int_points total 16ms

Creating lists of tuples is expensive! The ulab.numpy times seem to have a fairly high variance. Maybe it's some garbage collection stuff?

@todbot
Copy link
Author

todbot commented Jul 22, 2022

Thank you so much for this! For some reason I had convinced myself that np.dot() didn't return the correct data structure when given a list of points.

But wow I wish vectorio took floats so we could avoid the int conversion. Maybe I will make a PR for that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment