-
Notifications
You must be signed in to change notification settings - Fork 474
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Cosmic Unicorn: Fast, numpy effect examples.
- Loading branch information
Showing
5 changed files
with
561 additions
and
0 deletions.
There are no files selected for viewing
126 changes: 126 additions & 0 deletions
126
micropython/examples/cosmic_unicorn/numpy/fire_effect.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
Oops, something went wrong.