I'm trying to model a 3d array of LEDs on a raspberry pi (model 2B) in a 10x10x10 grid. I simply want them to turn on and off based on a pattern generation algorithm.
I've written some basic code in pi3d to model 1000 spheres, held in an array. It cycles through the array and switched each led on or off by changing the sphere's colour to blue or black.
The core part of the code is as follows:
spheres = [[[pi3d.Sphere(x=x-5,y=y-5,z=z-5,radius=0.1) for x in range(dim)] for y in range(dim)] for z in range(dim)]
i = 0
while DISPLAY.loop_running():
k = mykeys.read()
if k == 27:
mykeys.close()
ISPLAY.destroy()
break
CAM.update(mymouse)
for x in range (dim):
for y in range(dim):
for z in range(dim):
colour=0.1
if(((x-dim/2.0) * (x-dim/2.0)) + ((y-dim/2.0) * (y-dim/2.0)) + ((z-dim/2.0) * (z-dim/2.0)) <= i * dim):
colour = 1.0
spheres[x][y][z].set_material((0.0,0.0,colour))
spheres[x][y][z].draw()
i=i+0.1
if i > 4:
i=0
This works fine, but gives me about 5 fps. Changing the spheres to cubes improved this very slightly, but I'd really like an order of magnitude performance improvement, at the least. I know there are a few efficiency gains I could make in the maths, but I experienced similar performance turning them on and off randomly, so I'm not focusing on that for now.
I though perhaps that this was just asking too much of a raspberry pi, but then played the minecraft game that comes bundled with it and found it to have greater complexity whilst rendering smoothly.
I'm wondering if there is another approach, or perhaps even another language, that I could use to give me the kind of performance I'm looking for.
I know very little of 3d programming so any suggestions or tutorials anyone can point me at would potentially be useful.
The problem is that there is python code doing the matrix multiplication for each pi3d.Shape one at a time. Although this is done using numpy and is as fast as possible it's still slow.
You can make all of your spheres into one pi3d.MergeShape which will then require only one draw() per frame and will be very fast... But
Your Sphere objects use the default values of 12 sides x 12 slices giving 288 faces and 864 vertices so your MergeShape will have 864,000 vertices which is probably starting to slow the GPU down.
the bundled shaders just use one material RGB value for the whole Shape, you want to specify a different colour for each sphere which would need a hacked shader (easy to do if you're used to hacking shaders) where you specify the RGB value in the texture coordinate fields of the buffer array.
Your code doesn't show what shader you are using, the default will be mat_light which will give a smooth 3D effect for each sphere but if you could manage with points (see the demo SpriteBalls) then you could have thousands of spheres running fast... But you would still need to modify the shader to vary the diffuse colour for each vertex.
Or you could make a texture half blue, half black and tweak the texture coordinates of the various spheres each frame. Assuming you've merged all the spheres into one shape this will be very quick (though will involve an ungainly numpy formula to reproduce the effect of your x,y,z nested loops)
Over the next few days I will try to devise a demo showing how to do these options and add it to https://github.com/pi3d/pi3d_demos
EDIT I just remembered the Starfield.py demo which uses variable colour 'billboard' points. This can render many thousand points every frame but it has all kinds of complications that obscure the relatively simple structure, as I mention above I will make a simpler version to demo your 10x10x10 array with colour change using Euclidean distance from the centre.
2nd EDIT Here is a billboard or sprite version using pi3d_demos/shaders/star_point
import pi3d
import numpy as np
DIM = 10
half_d = DIM/2.0
arr_len = DIM ** 3
disp = pi3d.Display.create()
shader = pi3d.Shader('shaders/star_point')
cam = pi3d.Camera()
spheres = pi3d.Points(camera=cam, point_size=400.0, z=15.0,
vertices=[[x - half_d, y - half_d, z - half_d] for x in range(DIM) for y in range(DIM) for z in range(DIM)],
normals=np.zeros((arr_len, 3)), tex_coords=np.full((arr_len, 2), 1.0))
spheres.set_shader(shader)
arr_buf = spheres.buf[0].array_buffer # shortcut to numpy array shape (1000,8) [[vx,vy,vz,nx,ny,nz,u,v]]
# the star_point shader uses nx,ny,nz as RGB values, only the B value is being
# changed here i.e. arr_buff[:,5]
i = 0
while disp.loop_running():
spheres.draw()
ix = np.where(np.sum((arr_buf[:,:3] - [half_d, half_d, half_d]) ** 2, axis=1) <= i * DIM)[0]
arr_buf[:,5] = 0.1 # set all to midnight blue first
arr_buf[ix,5] = 1.0 # set ones within (i * DIM) ** 0.5 to blue
spheres.re_init() # have to update buffer
i += 0.1
if i > 4.0:
i = 0.0
and here is a version using MergeShape then tweaking the uv coordinates
import pi3d
import numpy as np
DIM = 10
half_d = DIM/2.0
arr_len = DIM ** 3
disp = pi3d.Display.create()
shader = pi3d.Shader('uv_light')
cam = pi3d.Camera()
tex_array = np.zeros((16,16,3), dtype=np.uint8)
tex_array[:8,:8] = [0, 0, 25] # top left midnight blue
tex_array[8:, 8:] = [0, 0, 255] # bottom right bright blue
tex = pi3d.Texture(tex_array, mipmap=False)
spheres = pi3d.MergeShape(camera=cam, z=15.0)
spheres.merge([[pi3d.Sphere(radius=0.1, sides=6, slices=6), x - half_d, y - half_d, z - half_d, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0]
for x in range(DIM) for y in range(DIM) for z in range(DIM)])
spheres.set_draw_details(shader, [tex])
arr_buf = spheres.buf[0].array_buffer # shortcut to numpy array shape (1000,8) [[vx,vy,vz,nx,ny,nz,u,v]]
arr_buf[:,6:8] *= 0.5 # scale uv to just use top left part of texture
base_tex_c = arr_buf[:,6:8].copy()
i = 0
while disp.loop_running():
spheres.draw()
ix = np.where(np.sum((arr_buf[:,:3] - [half_d, half_d, half_d]) ** 2, axis=1) <= i * DIM)[0]
arr_buf[:,6:8] = base_tex_c # set uv to base (top left)
arr_buf[ix,6:8] += 0.5 # set index ix to bottome right
spheres.re_init() # have to update buffer
i += 0.1
if i > 4.0:
i = 0.0
I found that the size of the array buffer became too large with the default Sphere so reduced it to a 6x6 version. Hope this helps someone at some stage.