diff --git a/micropython/examples/cosmic_unicorn/numpy/fire_effect.py b/micropython/examples/cosmic_unicorn/numpy/fire_effect.py new file mode 100644 index 000000000..bf3804847 --- /dev/null +++ b/micropython/examples/cosmic_unicorn/numpy/fire_effect.py @@ -0,0 +1,126 @@ +import time +import gc +import random +from cosmic import CosmicUnicorn +from picographics import PicoGraphics, DISPLAY_COSMIC_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. +""" + +# MAXIMUM OVERKILL +# machine.freq(250_000_000) + +gu = CosmicUnicorn() +gu.set_brightness(0.5) +graphics = PicoGraphics(DISPLAY_COSMIC_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:32, 0:32], 0, 1) * (PALETTE_SIZE - 1), dtype=numpy.uint8).tobytes() + gu.update(graphics) + + +width = CosmicUnicorn.WIDTH +height = CosmicUnicorn.HEIGHT + 4 +heat = numpy.zeros((height, width)) + +t_count = 0 +t_total = 0 + +while True: + gc.collect() + + if gu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): + gu.adjust_brightness(+0.01) + + if gu.is_pressed(CosmicUnicorn.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/cosmic_unicorn/numpy/lava_lamp.py b/micropython/examples/cosmic_unicorn/numpy/lava_lamp.py new file mode 100644 index 000000000..3e5b4665f --- /dev/null +++ b/micropython/examples/cosmic_unicorn/numpy/lava_lamp.py @@ -0,0 +1,118 @@ +import gc +import time +import math +import random +from cosmic import CosmicUnicorn +from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN, PEN_P8 +from ulab import numpy + +""" +A lava lamp effect, created by blurred, moving particles. +""" + +# MAXIMUM OVERKILL +# machine.freq(250_000_000) + +gu = CosmicUnicorn() +graphics = PicoGraphics(DISPLAY_COSMIC_UNICORN, pen_type=PEN_P8) +gu.set_brightness(0.5) + +width = CosmicUnicorn.WIDTH +height = CosmicUnicorn.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)) / 10.0) + 5.0 + self.dx = (float(random.randint(0, 2)) / 20.0) - 0.05 + self.dy = (float(random.randint(0, 2)) / 20.0) - 0.025 # positive bias + + 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(width - 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(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): + gu.adjust_brightness(+0.01) + + if gu.is_pressed(CosmicUnicorn.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/cosmic_unicorn/numpy/the_matrix.py b/micropython/examples/cosmic_unicorn/numpy/the_matrix.py new file mode 100644 index 000000000..bf41e71d2 --- /dev/null +++ b/micropython/examples/cosmic_unicorn/numpy/the_matrix.py @@ -0,0 +1,89 @@ +import gc +import time +import random +from cosmic import CosmicUnicorn +from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN, PEN_P8 +from ulab import numpy + +""" +HELLO NEO. +""" + +# MAXIMUM OVERKILL +# machine.freq(250_000_000) + +gu = CosmicUnicorn() +gu.set_brightness(1.0) +graphics = PicoGraphics(DISPLAY_COSMIC_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(): + trippy[:] *= 0.65 + + for _ in range(2): + x = random.randint(0, width - 1) + y = random.randint(0, height // 2) + trippy[y][x] = random.randint(128, 255) / 255.0 + + # Propagate downwards + old = numpy.ndarray(trippy) * 0.5 + trippy[:] = numpy.roll(trippy, 1, axis=0) + trippy[:] += old + + +def draw(): + # Copy the effect to the framebuffer + memoryview(graphics)[:] = numpy.ndarray(numpy.clip(trippy, 0, 1) * 254, dtype=numpy.uint8).tobytes() + gu.update(graphics) + + +width = CosmicUnicorn.WIDTH +height = CosmicUnicorn.HEIGHT +trippy = numpy.zeros((height, width)) + +t_count = 0 +t_total = 0 + + +while True: + if gu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): + gu.adjust_brightness(+0.01) + + if gu.is_pressed(CosmicUnicorn.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/cosmic_unicorn/numpy/this_is_fine.py b/micropython/examples/cosmic_unicorn/numpy/this_is_fine.py new file mode 100644 index 000000000..2a367151c --- /dev/null +++ b/micropython/examples/cosmic_unicorn/numpy/this_is_fine.py @@ -0,0 +1,131 @@ +import time +import gc +import random +from cosmic import CosmicUnicorn +from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN, PEN_P8 +from ulab import numpy + +""" +THIS IS FINE! +""" + +# MAXIMUM OVERKILL +# machine.freq(250_000_000) + +gu = CosmicUnicorn() +gu.set_brightness(0.5) +graphics = PicoGraphics(DISPLAY_COSMIC_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:32, 0:32], 0, 1) * (PALETTE_SIZE - 1), dtype=numpy.uint8).tobytes() + + # Draw text over the top + graphics.set_pen(0) + graphics.text("This", 6, 4, 1, 1) + graphics.text("is", 11, 12, 1, 1) + graphics.text("fine", 6, 20, 1, 1) + gu.update(graphics) + + +width = CosmicUnicorn.WIDTH +height = CosmicUnicorn.HEIGHT + 4 +heat = numpy.zeros((height, width)) + +t_count = 0 +t_total = 0 + +while True: + gc.collect() + + if gu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): + gu.adjust_brightness(+0.01) + + if gu.is_pressed(CosmicUnicorn.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/cosmic_unicorn/numpy/trippy.py b/micropython/examples/cosmic_unicorn/numpy/trippy.py new file mode 100644 index 000000000..1f1ff102f --- /dev/null +++ b/micropython/examples/cosmic_unicorn/numpy/trippy.py @@ -0,0 +1,97 @@ +import gc +import time +import random +from cosmic import CosmicUnicorn +from picographics import PicoGraphics, DISPLAY_COSMIC_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 = CosmicUnicorn() +gu.set_brightness(0.5) +graphics = PicoGraphics(DISPLAY_COSMIC_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 = CosmicUnicorn.WIDTH +height = CosmicUnicorn.HEIGHT +trippy = numpy.zeros((height, width)) + +t_count = 0 +t_total = 0 + + +while True: + if gu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): + gu.adjust_brightness(+0.01) + + if gu.is_pressed(CosmicUnicorn.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)