Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gravity #15

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions cubehelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ def color_to_int(color):
b = int(b * 256.0 - 0.5)
return (r, g, b)

# WARN: rounding behaviour isn't perfect, eg
# >>> cubehelper.color_to_float((0,0,0))
# (0.001953125, 0.001953125, 0.001953125)
def color_to_float(color):
if isinstance(color, numbers.Integral):
r = color >> 16
Expand All @@ -111,3 +114,86 @@ def color_to_float(color):
g = (g + 0.5) / 256.0
b = (b + 0.5) / 256.0
return (r, g, b)

# in 3d space any floating coord lies between 8 pixels (or exactly on/between)
# return a list of the surrounding 8 pixels with the color scaled proportionately
# ie list of 8x ((x,y,z), (r,g,b))
# Due to the rounding issues in color_to_float we end up with values where
# we'd expect 0 (which would allow us to clean up black pixels)
def interpolated_pixels_from_point(xyz, color):
(x, y, z) = xyz

x0 = int(x)
y0 = int(y)
z0 = int(z)

x1 = x0 + 1
y1 = y0 + 1
z1 = z0 + 1

# weightings on each axis
x1w = x - x0
y1w = y - y0
z1w = z - z0

x0w = 1 - x1w
y0w = 1 - y1w
z0w = 1 - z1w

# weighting for each of the pixels
c000w = x0w * y0w * z0w
c001w = x0w * y0w * z1w
c010w = x0w * y1w * z0w
c011w = x0w * y1w * z1w
c100w = x1w * y0w * z0w
c101w = x1w * y0w * z1w
c110w = x1w * y1w * z0w
c111w = x1w * y1w * z1w

black = (0,0,0)

c000 = mix_color(black, color, c000w)
c001 = mix_color(black, color, c001w)
c010 = mix_color(black, color, c010w)
c011 = mix_color(black, color, c011w)
c100 = mix_color(black, color, c100w)
c101 = mix_color(black, color, c101w)
c110 = mix_color(black, color, c110w)
c111 = mix_color(black, color, c111w)

return [
((x0, y0, z0), c000),
((x0, y0, z1), c001),
((x0, y1, z0), c010),
((x0, y1, z1), c011),
((x1, y0, z0), c100),
((x1, y0, z1), c101),
((x1, y1, z0), c110),
((x1, y1, z1), c111)
]

# This doesn't check for negative coords
def restrict_pixels_to_cube_bounds(pixels, cube_size):
return [
pixel for pixel in pixels if (
pixel[0][0] < cube_size and
pixel[0][1] < cube_size and
pixel[0][2] < cube_size )
]

def restrict_pixels_to_non_black(pixels):
threshold = 0.002 # this is mostly to work round the rounding issue above
return [
pixel for pixel in pixels if (
pixel[1][0] > threshold and
pixel[1][1] > threshold and
pixel[1][2] > threshold )
]

# provide xyz as float coords, returns a list of between one and 8 pixels
def sanitized_interpolated(xyz, color, cube_size):
return restrict_pixels_to_non_black(
restrict_pixels_to_cube_bounds(
interpolated_pixels_from_point(xyz, color),
cube_size
))
69 changes: 69 additions & 0 deletions patterns/gravity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Fork of rain - pastel "snow flakes" slowly falling (interpolated)
# Copyright (C) Paul Brook <[email protected]>
# Released under the terms of the GNU General Public License version 3

import random
import math
import cubehelper

BLACK = (0,0,0)
WHITE = (255,255,255)

FRAME_RATE = 25
FRAME_TIME = 1.0 / FRAME_RATE
SPAWN_PROBABILITY = 5.0 / FRAME_RATE

GRAVITY = 10.0
COEFFICIENT_OF_RESTITUTION = 0.9

class Drop(object):
def __init__(self, cube, x, y):
self.cube = cube
self.x = x
self.y = y
self.z = -10
def reset(self):
self.z = self.cube.size
self.speed = 0; #random.uniform(1, 2)
self.color = cubehelper.mix_color(cubehelper.random_color(), WHITE, 0.4)
def tick(self):
self.speed += GRAVITY * FRAME_TIME
self.z -= (self.speed) * FRAME_TIME
# apply bounce
# make sure there's still enough energy left not to sit on bottom
# if there's not gravity will suck them down to be cleaned up
if self.z < 0 and self.speed > GRAVITY:
self.speed = -self.speed * COEFFICIENT_OF_RESTITUTION

position_float = (self.x,self.y,self.z)
pixels = cubehelper.sanitized_interpolated(position_float, self.color, self.cube.size)
self.cube.set_pixels(pixels)

class Pattern(object):
def init(self):
self.double_buffer = True
self.drops = []
self.unused = []
for x in range(0, self.cube.size):
for y in range(0, self.cube.size):
self.unused.append(Drop(self.cube, x, y))
return FRAME_TIME
def spawn(self):
try:
d = self.unused.pop(random.randrange(len(self.unused)))
d.reset()
self.drops.append(d)
except ValueError:
pass
def tick(self):
if random.random() < SPAWN_PROBABILITY:
self.spawn()
drops = self.drops;
self.drops = []
self.cube.clear()
for d in drops:
d.tick()
if d.z < -5:
self.unused.append(d)
else:
self.drops.append(d)
57 changes: 57 additions & 0 deletions patterns/snow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Fork of rain - pastel "snow flakes" slowly falling (interpolated)
# Copyright (C) Paul Brook <[email protected]>
# Released under the terms of the GNU General Public License version 3

import random
import math
import cubehelper

BLACK = (0,0,0)
WHITE = (255,255,255)

FRAME_RATE = 25
FRAME_TIME = 1.0 / FRAME_RATE
SPAWN_PROBABILITY = 5.0 / FRAME_RATE

class Drop(object):
def __init__(self, cube, x, y):
self.cube = cube
self.x = x
self.y = y
self.z = -1
def reset(self):
self.z = self.cube.size
self.speed = random.uniform(1, 2)
self.color = cubehelper.mix_color(cubehelper.random_color(), WHITE, 0.7)
def tick(self):
self.z -= self.speed * FRAME_TIME
if self.z >= 0:
position_float = (self.x,self.y,self.z)
pixels = cubehelper.sanitized_interpolated(position_float, self.color, self.cube.size)
self.cube.set_pixels(pixels)

class Pattern(object):
def init(self):
self.double_buffer = True
self.drops = []
self.unused = []
for x in range(0, self.cube.size):
for y in range(0, self.cube.size):
self.unused.append(Drop(self.cube, x, y))
return FRAME_TIME
def spawn(self):
d = self.unused.pop(random.randrange(len(self.unused)))
d.reset()
self.drops.append(d)
def tick(self):
if random.random() < SPAWN_PROBABILITY:
self.spawn()
drops = self.drops;
self.drops = []
self.cube.clear()
for d in drops:
d.tick()
if d.z < 0:
self.unused.append(d)
else:
self.drops.append(d)
9 changes: 9 additions & 0 deletions serialcube.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,15 @@ def set_pixel(self, xyz, rgb):
self.select_board(board)
self.do_cmd(offset, r, g, b)

# This doesn't do anything smart if the same pixel is specified multiple
# times
def set_pixels(self, pixels):
# Potential optimisation: sort the pixels by y
# (minimises board switches)
for pixel in pixels:
(xyz, rgb) = pixel
self.set_pixel(xyz, rgb)

def render(self):
self.bus_reset()
self._flush_data()