diff --git a/micropython/examples/galactic_unicorn/numpy/eighties_super_computer.py b/micropython/examples/galactic_unicorn/numpy/eighties_super_computer.py new file mode 100644 index 00000000..65c06433 --- /dev/null +++ b/micropython/examples/galactic_unicorn/numpy/eighties_super_computer.py @@ -0,0 +1,88 @@ +import gc +import time +import random +from galactic import GalacticUnicorn +from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN, PEN_P8 +from ulab import numpy + +""" +A random, computer effect. +Experiment with the damping, number of spawns and intensity to change the effect. +""" + +# MAXIMUM OVERKILL +# machine.freq(250_000_000) + +gu = GalacticUnicorn() +gu.set_brightness(0.5) +graphics = PicoGraphics(DISPLAY_GALACTIC_UNICORN, pen_type=PEN_P8) + + +DAMPING_FACTOR = 0.95 +NUMBER_OF_LIGHTS = 10 +INTENSITY = 20 + +# Fill palette with a yellow +r, g, b = (230, 150, 0) +PALETTE_ENTRIES = 255 +for x in range(PALETTE_ENTRIES): + _ = graphics.create_pen(r * x // PALETTE_ENTRIES, g * x // PALETTE_ENTRIES, b) + + +def update(): + computer[:] *= DAMPING_FACTOR + + # Spawn random drops + for _ in range(NUMBER_OF_LIGHTS): + x = random.randint(0, width - 1) + y = random.randint(0, height - 1) + computer[y][x] = random.randint(0, INTENSITY) + + +def draw(): + # Copy the effect to the framebuffer + memoryview(graphics)[:] = numpy.ndarray(numpy.clip(computer, 0, 1) * (PALETTE_ENTRIES - 1), dtype=numpy.uint8).tobytes() + gu.update(graphics) + + +width = GalacticUnicorn.WIDTH +height = GalacticUnicorn.HEIGHT +computer = numpy.zeros((height, width)) + +t_count = 0 +t_total = 0 + + +while True: + if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP): + gu.adjust_brightness(+0.01) + + if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN): + gu.adjust_brightness(-0.01) + + tstart = time.ticks_ms() + gc.collect() + update() + draw() + tfinish = time.ticks_ms() + + total = tfinish - tstart + t_total += total + t_count += 1 + + if t_count == 60: + per_frame_avg = t_total / t_count + print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS") + t_count = 0 + t_total = 0 + + # pause for a moment (important or the USB serial device will fail) + # try to pace at 60fps or 30fps + if total > 1000 / 30: + time.sleep(0.0001) + elif total > 1000 / 60: + t = 1000 / 30 - total + time.sleep(t / 1000) + else: + t = 1000 / 60 - total + time.sleep(t / 1000) diff --git a/micropython/examples/galactic_unicorn/numpy/fire_effect.py b/micropython/examples/galactic_unicorn/numpy/fire_effect.py new file mode 100644 index 00000000..8e1c5db0 --- /dev/null +++ b/micropython/examples/galactic_unicorn/numpy/fire_effect.py @@ -0,0 +1,128 @@ +import time +import gc +import random +from galactic import GalacticUnicorn +from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN, PEN_P8 +from ulab import numpy + +""" +Classic fire effect. +Play with the number of spawns, heat, damping factor and colour palette to tweak it. + +This is vertical because my brain would not let me rotate it. +""" + +# MAXIMUM OVERKILL +# machine.freq(250_000_000) + +gu = GalacticUnicorn() +gu.set_brightness(0.5) +graphics = PicoGraphics(DISPLAY_GALACTIC_UNICORN, pen_type=PEN_P8) + +# Number of random fire spawns +FIRE_SPAWNS = 5 + +# Fire damping +DAMPING_FACTOR = 0.98 + +# TURN UP THE HEEEAAT +HEAT = 3.0 + +# Create the fire palette +""" +# Raging Gas Inferno +graphics.create_pen(0, 0, 0) +graphics.create_pen(0, 0, 0) +graphics.create_pen(20, 20, 20) +graphics.create_pen(50, 10, 0) +graphics.create_pen(180, 30, 0) +graphics.create_pen(220, 160, 0) +graphics.create_pen(255, 255, 180) +graphics.create_pen(255, 255, 220) +graphics.create_pen(90, 90, 255) +graphics.create_pen(255, 0, 255) +""" +# Original Colours +graphics.create_pen(0, 0, 0) +graphics.create_pen(20, 20, 20) +graphics.create_pen(180, 30, 0) +graphics.create_pen(220, 160, 0) +graphics.create_pen(255, 255, 180) + +PALETTE_SIZE = 5 # Should match the number of colours defined above + + +def update(): + # Clear the bottom two rows (off screen) + heat[height - 1][:] = 0.0 + heat[height - 2][:] = 0.0 + + # Add random fire spawns + for c in range(FIRE_SPAWNS): + x = random.randint(0, width - 4) + 2 + heat[height - 1][x - 1:x + 1] = HEAT / 2.0 + heat[height - 2][x - 1:x + 1] = HEAT + + # Propagate the fire upwards + a = numpy.roll(heat, -1, axis=0) # y + 1, x + b = numpy.roll(heat, -2, axis=0) # y + 2, x + c = numpy.roll(heat, -1, axis=0) # y + 1 + d = numpy.roll(c, 1, axis=1) # y + 1, x + 1 + e = numpy.roll(c, -1, axis=1) # y + 1, x - 1 + + # Average over 5 adjacent pixels and apply damping + heat[:] += a + b + d + e + heat[:] *= DAMPING_FACTOR / 5.0 + + +def draw(): + # Copy the fire effect to the framebuffer + # Clips the fire to 0.0 to 1.0 + # Multiplies it by the number of palette entries (-1) to turn it into a palette index + # Converts to uint8_t datatype to match the framebuffer + memoryview(graphics)[:] = numpy.ndarray(numpy.clip(heat[0:11, 0:53], 0, 1) * (PALETTE_SIZE - 1), dtype=numpy.uint8).tobytes() + gu.update(graphics) + + +width = GalacticUnicorn.WIDTH +height = GalacticUnicorn.HEIGHT + 4 +heat = numpy.zeros((height, width)) + +t_count = 0 +t_total = 0 + +while True: + gc.collect() + + if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP): + gu.adjust_brightness(+0.01) + + if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN): + gu.adjust_brightness(-0.01) + + tstart = time.ticks_ms() + gc.collect() + update() + draw() + tfinish = time.ticks_ms() + + total = tfinish - tstart + t_total += total + t_count += 1 + + if t_count == 60: + per_frame_avg = t_total / t_count + print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS") + t_count = 0 + t_total = 0 + + # pause for a moment (important or the USB serial device will fail) + # try to pace at 60fps or 30fps + if total > 1000 / 30: + time.sleep(0.0001) + elif total > 1000 / 60: + t = 1000 / 30 - total + time.sleep(t / 1000) + else: + t = 1000 / 60 - total + time.sleep(t / 1000) diff --git a/micropython/examples/galactic_unicorn/numpy/lava_lamp.py b/micropython/examples/galactic_unicorn/numpy/lava_lamp.py new file mode 100644 index 00000000..5489fdb8 --- /dev/null +++ b/micropython/examples/galactic_unicorn/numpy/lava_lamp.py @@ -0,0 +1,120 @@ +import gc +import time +import math +import random +from galactic import GalacticUnicorn +from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN, PEN_P8 +from ulab import numpy + +""" +A lava lamp effect, created by blurred, moving particles. + +Oh boy, this one's real lively! Stand your Galactic Unicorn on its side for best effect. +""" + +# MAXIMUM OVERKILL +# machine.freq(250_000_000) + +gu = GalacticUnicorn() +graphics = PicoGraphics(DISPLAY_GALACTIC_UNICORN, pen_type=PEN_P8) +gu.set_brightness(0.5) + +width = GalacticUnicorn.WIDTH +height = GalacticUnicorn.HEIGHT +lava = numpy.zeros((height, width)) + + +class Blob(): + def __init__(self): + self.x = float(random.randint(0, width - 1)) + self.y = float(random.randint(0, height - 1)) + self.r = (float(random.randint(0, 40)) / 5.0) + self.dx = (float(random.randint(0, 20)) / 80.0) + self.dy = (float(random.randint(0, 20)) / 80.0) - 0.15 + + def move(self): + self.x += self.dx + self.y += self.dy + + if self.x < 0.0 or self.x >= float(width): + self.x = max(0.0, self.x) + self.x = min(float(width - 1), self.x) + self.dx = -self.dx + + if self.y < 0.0 or self.y >= float(height): + self.y = max(0.0, self.y) + self.y = min(float(height - 1), self.y) + self.dy = -self.dy + + +blobs = [Blob() for _ in range(10)] + + +# Fill palette with a steep falloff from bright red to dark blue +PAL_COLS = 9 +for x in range(PAL_COLS): + graphics.create_pen_hsv(0.5 + math.log(x + 1, PAL_COLS + 1) / 2.0, 1.0, math.log(x + 1, PAL_COLS + 1)) + + +def update(): + # Update the blobs and draw them into the effect + for blob in blobs: + blob.move() + lava[int(blob.y)][int(blob.x)] = blob.r + + # Propogate the blobs outwards + a = numpy.roll(lava, 1, axis=0) + b = numpy.roll(lava, -1, axis=0) + d = numpy.roll(lava, 1, axis=1) + e = numpy.roll(lava, -1, axis=1) + + # Average over 5 adjacent pixels and apply damping + lava[:] += a + b + d + e + lava[:] *= 0.97 / 5.0 + + +def draw(): + # Copy the lava effect to the framebuffer + # Clips to 0.0 - 1.0 + # Multiplies by palette entries (-1) to turn it into a palette index + # Converts to uint8_t datatype to match the framebuffer + memoryview(graphics)[:] = numpy.ndarray(numpy.clip(lava, 0.0, 1.0) * (PAL_COLS - 1), dtype=numpy.uint8).tobytes() + gu.update(graphics) + + +t_count = 0 +t_total = 0 + +while True: + if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP): + gu.adjust_brightness(+0.01) + + if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN): + gu.adjust_brightness(-0.01) + + tstart = time.ticks_ms() + gc.collect() + update() + draw() + tfinish = time.ticks_ms() + + total = tfinish - tstart + t_total += total + t_count += 1 + + if t_count == 60: + per_frame_avg = t_total / t_count + print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS") + t_count = 0 + t_total = 0 + + # pause for a moment (important or the USB serial device will fail) + # try to pace at 60fps or 30fps + if total > 1000 / 30: + time.sleep(0.0001) + elif total > 1000 / 60: + t = 1000 / 30 - total + time.sleep(t / 1000) + else: + t = 1000 / 60 - total + time.sleep(t / 1000) diff --git a/micropython/examples/galactic_unicorn/numpy/the_matrix.py b/micropython/examples/galactic_unicorn/numpy/the_matrix.py new file mode 100644 index 00000000..9c290a86 --- /dev/null +++ b/micropython/examples/galactic_unicorn/numpy/the_matrix.py @@ -0,0 +1,88 @@ +import gc +import time +import random +from galactic import GalacticUnicorn +from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN, PEN_P8 +from ulab import numpy + +""" +HELLO NEO. +""" + +# MAXIMUM OVERKILL +# machine.freq(250_000_000) + +gu = GalacticUnicorn() +gu.set_brightness(1.0) +graphics = PicoGraphics(DISPLAY_GALACTIC_UNICORN, pen_type=PEN_P8) + + +# Fill half the palette with GREEEN +for g in range(128): + _ = graphics.create_pen(0, g, 0) + +# And half with bright green for white sparkles +for g in range(128): + _ = graphics.create_pen(128, 128 + g, 128) + + +def update(): + the_matrix[:] *= 0.65 + + x = random.randint(0, width // 2) + y = random.randint(0, height - 1) + the_matrix[y][x] = random.randint(128, 255) / 255.0 + + # Propagate downwards + old = numpy.ndarray(the_matrix) * 0.5 + the_matrix[:] = numpy.roll(the_matrix, 1, axis=1) + the_matrix[:] += old + + +def draw(): + # Copy the effect to the framebuffer + memoryview(graphics)[:] = numpy.ndarray(numpy.clip(the_matrix, 0, 1) * 254, dtype=numpy.uint8).tobytes() + gu.update(graphics) + + +width = GalacticUnicorn.WIDTH +height = GalacticUnicorn.HEIGHT +the_matrix = numpy.zeros((height, width)) + +t_count = 0 +t_total = 0 + + +while True: + if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP): + gu.adjust_brightness(+0.01) + + if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN): + gu.adjust_brightness(-0.01) + + tstart = time.ticks_ms() + gc.collect() + update() + draw() + tfinish = time.ticks_ms() + + total = tfinish - tstart + t_total += total + t_count += 1 + + if t_count == 60: + per_frame_avg = t_total / t_count + print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS") + t_count = 0 + t_total = 0 + + # pause for a moment (important or the USB serial device will fail) + # try to pace at 60fps or 30fps + if total > 1000 / 30: + time.sleep(0.0001) + elif total > 1000 / 60: + t = 1000 / 30 - total + time.sleep(t / 1000) + else: + t = 1000 / 60 - total + time.sleep(t / 1000) diff --git a/micropython/examples/galactic_unicorn/numpy/this_is_fine.py b/micropython/examples/galactic_unicorn/numpy/this_is_fine.py new file mode 100644 index 00000000..16ae9e17 --- /dev/null +++ b/micropython/examples/galactic_unicorn/numpy/this_is_fine.py @@ -0,0 +1,134 @@ +import time +import gc +import random +from galactic import GalacticUnicorn +from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN, PEN_P8 +from ulab import numpy + +""" +Classic fire effect. +Play with the number of spawns, heat, damping factor and colour palette to tweak it. + +This is vertical because my brain would not let me rotate it. +""" + +# MAXIMUM OVERKILL +# machine.freq(250_000_000) + +gu = GalacticUnicorn() +gu.set_brightness(0.5) +graphics = PicoGraphics(DISPLAY_GALACTIC_UNICORN, pen_type=PEN_P8) + +# Number of random fire spawns +FIRE_SPAWNS = 5 + +# Fire damping +DAMPING_FACTOR = 0.98 + +# TURN UP THE HEEEAAT +HEAT = 3.0 + +# Create the fire palette +""" +# Raging Gas Inferno +graphics.create_pen(0, 0, 0) +graphics.create_pen(0, 0, 0) +graphics.create_pen(20, 20, 20) +graphics.create_pen(50, 10, 0) +graphics.create_pen(180, 30, 0) +graphics.create_pen(220, 160, 0) +graphics.create_pen(255, 255, 180) +graphics.create_pen(255, 255, 220) +graphics.create_pen(90, 90, 255) +graphics.create_pen(255, 0, 255) +""" +# Original Colours +graphics.create_pen(0, 0, 0) +graphics.create_pen(20, 20, 20) +graphics.create_pen(180, 30, 0) +graphics.create_pen(220, 160, 0) +graphics.create_pen(255, 255, 180) + +PALETTE_SIZE = 5 # Should match the number of colours defined above + + +def update(): + # Clear the bottom two rows (off screen) + heat[height - 1][:] = 0.0 + heat[height - 2][:] = 0.0 + + # Add random fire spawns + for c in range(FIRE_SPAWNS): + x = random.randint(0, width - 4) + 2 + heat[height - 1][x - 1:x + 1] = HEAT / 2.0 + heat[height - 2][x - 1:x + 1] = HEAT + + # Propagate the fire upwards + a = numpy.roll(heat, -1, axis=0) # y + 1, x + b = numpy.roll(heat, -2, axis=0) # y + 2, x + c = numpy.roll(heat, -1, axis=0) # y + 1 + d = numpy.roll(c, 1, axis=1) # y + 1, x + 1 + e = numpy.roll(c, -1, axis=1) # y + 1, x - 1 + + # Average over 5 adjacent pixels and apply damping + heat[:] += a + b + d + e + heat[:] *= DAMPING_FACTOR / 5.0 + + +def draw(): + # Copy the fire effect to the framebuffer + # Clips the fire to 0.0 to 1.0 + # Multiplies it by the number of palette entries (-1) to turn it into a palette index + # Converts to uint8_t datatype to match the framebuffer + memoryview(graphics)[:] = numpy.ndarray(numpy.clip(heat[0:11, 0:53], 0, 1) * (PALETTE_SIZE - 1), dtype=numpy.uint8).tobytes() + + # Draw text over the top + graphics.set_pen(0) + graphics.text("This", 1, 2, 1, 1) + graphics.text("is", 22, 2, 1, 1) + graphics.text("fine", 33, 2, 1, 1) + gu.update(graphics) + + +width = GalacticUnicorn.WIDTH +height = GalacticUnicorn.HEIGHT + 4 +heat = numpy.zeros((height, width)) + +t_count = 0 +t_total = 0 + +while True: + gc.collect() + + if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP): + gu.adjust_brightness(+0.01) + + if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN): + gu.adjust_brightness(-0.01) + + tstart = time.ticks_ms() + gc.collect() + update() + draw() + tfinish = time.ticks_ms() + + total = tfinish - tstart + t_total += total + t_count += 1 + + if t_count == 60: + per_frame_avg = t_total / t_count + print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS") + t_count = 0 + t_total = 0 + + # pause for a moment (important or the USB serial device will fail) + # try to pace at 60fps or 30fps + if total > 1000 / 30: + time.sleep(0.0001) + elif total > 1000 / 60: + t = 1000 / 30 - total + time.sleep(t / 1000) + else: + t = 1000 / 60 - total + time.sleep(t / 1000) diff --git a/micropython/examples/galactic_unicorn/numpy/trippy.py b/micropython/examples/galactic_unicorn/numpy/trippy.py new file mode 100644 index 00000000..126b5e50 --- /dev/null +++ b/micropython/examples/galactic_unicorn/numpy/trippy.py @@ -0,0 +1,97 @@ +import gc +import time +import random +from galactic import GalacticUnicorn +from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN, PEN_P8 +from ulab import numpy + +""" +A random, trippy effect. +Experiment with the damping, number of spawns, intensity and offset to change the effect. +""" + +# MAXIMUM OVERKILL +# machine.freq(250_000_000) + +gu = GalacticUnicorn() +gu.set_brightness(0.5) +graphics = PicoGraphics(DISPLAY_GALACTIC_UNICORN, pen_type=PEN_P8) + + +DAMPING_FACTOR = 0.95 +NUMBER_OF_DROPS = 5 +INTENSITY = 10 +OFFSET = 0.0 # Try 0.5 + +# Fill palette with a rainbow sweep +PALETTE_ENTRIES = 255 +for x in range(PALETTE_ENTRIES): + _ = graphics.create_pen_hsv(float(x) / PALETTE_ENTRIES + OFFSET, 1.0, 1.0) + + +def update(): + trippy[:] *= DAMPING_FACTOR + + # Spawn random drops + for _ in range(NUMBER_OF_DROPS): + x = random.randint(0, width - 1) + y = random.randint(0, height - 1) + trippy[y][x] = random.randint(0, INTENSITY) + + a = numpy.roll(trippy, 1, axis=0) + b = numpy.roll(trippy, -1, axis=0) + d = numpy.roll(trippy, 1, axis=1) + e = numpy.roll(trippy, -1, axis=1) + + # Average over 5 adjacent pixels and apply damping + trippy[:] += a + b + d + e + trippy[:] /= 5.0 + + +def draw(): + # Copy the effect to the framebuffer + memoryview(graphics)[:] = numpy.ndarray(numpy.clip(trippy, 0, 1) * (PALETTE_ENTRIES - 1), dtype=numpy.uint8).tobytes() + gu.update(graphics) + + +width = GalacticUnicorn.WIDTH +height = GalacticUnicorn.HEIGHT +trippy = numpy.zeros((height, width)) + +t_count = 0 +t_total = 0 + + +while True: + if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP): + gu.adjust_brightness(+0.01) + + if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN): + gu.adjust_brightness(-0.01) + + tstart = time.ticks_ms() + gc.collect() + update() + draw() + tfinish = time.ticks_ms() + + total = tfinish - tstart + t_total += total + t_count += 1 + + if t_count == 60: + per_frame_avg = t_total / t_count + print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS") + t_count = 0 + t_total = 0 + + # pause for a moment (important or the USB serial device will fail) + # try to pace at 60fps or 30fps + if total > 1000 / 30: + time.sleep(0.0001) + elif total > 1000 / 60: + t = 1000 / 30 - total + time.sleep(t / 1000) + else: + t = 1000 / 60 - total + time.sleep(t / 1000)