Skip to content

Instantly share code, notes, and snippets.

@samneggs
Last active December 6, 2022 00:28
Show Gist options
  • Save samneggs/028934cb1b0decb929b05b223a5a70ff to your computer and use it in GitHub Desktop.
Save samneggs/028934cb1b0decb929b05b223a5a70ff to your computer and use it in GitHub Desktop.
OBJ file viewer for Pi Pico in MicroPython
import gc9a01
from machine import Pin, SPI, PWM, WDT, mem32
import framebuf
from time import sleep_ms, sleep_us, ticks_diff, ticks_us, sleep
from micropython import const
import array
from usys import exit
import gc
from math import sin,cos,pi,radians
from uctypes import addressof
MAXSCREEN_X = const(200)
MAXSCREEN_Y = const(190)
MAX_TRIANGLES = const(500)
MAX_POINTS = const(300)
SCALE = const(14) #14
TEXTURE_HEIGHT = const(47)
TEXTURE_WIDTH = const(90)
BLUE = const(0x1f00)
DRK_BLUE = const(0x0800)
BLACK = const(0)
WHITE = const(0xffff)
GREEN = const(0xe00A)
BROWN = const(0xe091)
RED = const(0x07e0)
YELLOW=const(0x00fe)
ORANGE=const(0x00fc)
MAGENTA=const(0x1FF8)
WIRE_COLOR = const(0x0) # 0=no lines
joyRight = Pin(17,Pin.IN)
joyDown = Pin(18,Pin.IN)
joySel = Pin(19,Pin.IN)
joyLeft = Pin(20,Pin.IN)
joyUp = Pin(21,Pin.IN)
machine.freq(270_000_000)
class Offset():
def __init__(self):
self.x = 0
self.y = 0
offset = Offset()
NUM_TRIANGLES = 0
NUM_POINTS = 0
xcoord=array.array('i', ())
ycoord=array.array('i', ())
zcoord=array.array('i', ())
tri_a=array.array('i', ())
tri_b=array.array('i', ())
tri_c=array.array('i', ())
edgebuf_l=array.array('h', 0 for _ in range(MAXSCREEN_Y))
edgebuf_r=array.array('h', 0 for _ in range(MAXSCREEN_Y))
isin=array.array('i',range(0,361))
icos=array.array('i',range(0,361))
# blit_image_file from Stewart Watkiss
# http://www.penguintutor.com/programming/picodisplayanimations
def blit_image_file(buf,filename,width,height,cw,ch): # file width, file height, char width, char height
with open (filename, "rb") as file:
file_position = 0
char_position = 0
ecount = 0
current_byte = file.read(4) # header
while file_position < (width * height * 2):
current_byte = file.read(1)
# if eof
if len(current_byte) == 0:
break
# copy to buffer
buf[char_position] = ord(current_byte)
char_position += 1
file_position += 1
if char_position == (cw * ch* 2):
char_position = 0
file.close()
def read_obj(filename):
global NUM_POINTS,NUM_TRIANGLES,rot_x,rot_y,rot_z
with open (filename, "rt") as file:
text = file.readline()
vertices = 0
faces = 0
while text[1:5] != '#end':
if text[0] == 'v':
text_list = text[1:].split('\t') # remove 'v' and convert to list
xcoord.append(int( float(text_list[0]) * 1.0))#25
ycoord.append(int( float(text_list[2]) * 1.0)+39)#5
zcoord.append(int( float(text_list[4]) * 1.0))
vertices += 1
if text[0] == 'f':
text_list = text[1:].split('\t')
tri_a.append(int(text_list[0])-1)
tri_b.append(int(text_list[1])-1)
tri_c.append(int(text_list[2])-1)
faces += 1
text = file.readline()
file.close()
NUM_POINTS = vertices
NUM_TRIANGLES = faces
rot_x=array.array('i', 0 for _ in range(vertices))
rot_y=array.array('i', 0 for _ in range(vertices))
rot_z=array.array('i', 0 for _ in range(vertices))
def buttons():
if not joyUp.value():
offset.y-=1
if not joyDown.value():
offset.y+=1
if not joyRight.value():
offset.x+=1
if not joyLeft.value():
offset.x-=1
if not joySel.value():
#offset.x = 0
#offset.y = 0
print(offset.x,offset.y)
@micropython.viper
def draw_line( x1:int, y1:int, x2:int, y2:int , z1:int,z2:int,color:int):
width=MAXSCREEN_X
dest=ptr16(screen)
ebufl=ptr16(edgebuf_l)
ebufr=ptr16(edgebuf_r)
color_addr=ptr16(color_l2)
max_screen = MAXSCREEN_X * MAXSCREEN_Y
zspan=z1-z2
yLonger=False
shortLen=y2-y1
longLen=x2-x1
shortLen_abs = shortLen
if shortLen_abs < 0:
shortLen_abs = -1 * shortLen_abs
longLen_abs = longLen
if longLen_abs < 0:
longLen_abs = -1 * longLen_abs
if shortLen_abs > longLen_abs:
swap=shortLen
shortLen=longLen
longLen=swap
yLonger=True
if (longLen==0):
decInc=0
zdecInc=0
else:
decInc = (shortLen << 16) // longLen
zdecInc = (zspan << 16) // longLen
if z1==z2: # hack...fix
if z1==47:
z2=0
else:
z2=47
if (yLonger):
if (longLen>0):
longLen+=y1
j=0x8000+(x1<<16)
j2=0x8000+(z2<<16)
while y1<=longLen:
x2=j>>16
i = y1 * width + x2
if i < 0 or i> max_screen or x2>=MAXSCREEN_X or x2 < 0:
return
if color:
dest[i]=color
if x2<ebufl[y1]:
ebufl[y1] = x2
if x2>ebufr[y1]:
ebufr[y1] = x2
#zbuf[i]=j2>>16
j2+=zdecInc
j+=decInc
y1+=1
return
longLen+=y1
j=0x8000+(x1<<16)
j2=0x8000+(z2<<16)
while y1>=longLen:
x2=j>>16
i = y1 * width + x2
if i < 0 or i> max_screen or x2>=MAXSCREEN_X or x2 < 0:
return
if color:
dest[i]=color
if x2<ebufl[y1]:
ebufl[y1] = x2
if x2>ebufr[y1]:
ebufr[y1] = x2
#zbuf[i]=j2>>16
j2-=zdecInc
j-=decInc
y1-=1
return
if (longLen>0):
longLen+=x1
j=0x8000+(y1<<16)
j2=0x8000+(z2<<16)
while x1<=longLen:
y2=j>>16
i = y2 * width + x1
if i < 0 or i> max_screen or x1>=MAXSCREEN_X or x1 < 0:
return
if color:
dest[i]=color
if x1<ebufl[y2]:
ebufl[y2] = x1
if x1>ebufr[y2]:
ebufr[y2] = x1
#zbuf[i]=j2>>16
j2+=zdecInc
j+=decInc
x1+=1
return
longLen+=x1
j=0x8000+(y1<<16)
j2=0x8000+(z2<<16)
while x1>=longLen:
y2=j>>16
i = y2 * width + x1
if i < 0 or i > max_screen or x1>=MAXSCREEN_X or x1 < 0:
return
if color:
dest[i]=color
if x1<ebufl[y2]:
ebufl[y2] = x1
if x1>ebufr[y2]:
ebufr[y2] = x1
#zbuf[i]=j2>>16
j2-=zdecInc
j-=decInc
x1-=1
@micropython.viper
def texture_background():
screen_addr=ptr16(screen)
texture_addr=ptr16(wood_buff)
for y in range(MAXSCREEN_Y):
for x in range(MAXSCREEN_X):
i=y*MAXSCREEN_X+x
screen_addr[i]=texture_addr[TEXTURE_WIDTH*(y%(TEXTURE_HEIGHT-1)) + (x%(TEXTURE_WIDTH-1))]
@micropython.asm_thumb
def background_asm(r0,r1,r2,r3): # r0=screen, r1=texture, r2=height, r3=width
label(HEIGHT_LOOP)
mov(r4,r3) # r4 = x
label(WIDTH_LOOP)
mov(r5,r2) # r5 = texture_y <- screen_y
label(Y_TEST)
cmp(r5,47) # screen y less than texture y
blt(Y_OK)
sub(r5,47)
b(Y_TEST)
label(Y_OK)
mov(r7,r4) # r7 = texture_x <- screen_x
label(X_TEST)
cmp(r7,90) # screen x less than texture x
blt(X_OK)
sub(r7,90)
b(X_TEST)
label(X_OK)
mov(r6,90)
mul(r5,r6) # y * width
add(r5,r5,r7) # y * width + x
add(r5,r5,r5) # double for 2 bytes per pixel
add(r5,r5,r1) # source[y * width + x]
ldrh(r6, [r5, 0]) # r6 = texture
mov(r7,r2) # r2 = y
mov(r5,r3)
mul(r7,r5) # y * width
add(r7,r7,r4) # y * width + x
add(r7,r7,r7) # double for 2 bytes per pixel
add(r7,r7,r0) # dest[y * width + x]
strh(r6, [r7, 0])
sub(r4, 1) # sub width by 1
bgt(WIDTH_LOOP)
sub(r2, 1) # sub height by 1
bge(HEIGHT_LOOP)
label(EXIT)
mov(r0,r3)
@micropython.viper
def fill_triangle(solid_color:int,z1:int,z2:int,z3:int):
texture=ptr16(wood_buff)
ebufl=ptr16(edgebuf_l)
ebufr=ptr16(edgebuf_r)
screen_addr=ptr16(screen)
color=ptr16(color_l2)
#z = z1-z2
#if z==0:
# z=z1-z3
#if z==0:
# z=z2-z3
#if z<0:
# z=-1*z
#step = 1+ (z * TEXTURE_WIDTH)
for y in range(MAXSCREEN_Y): # scan top to bottom of screen
if ebufl[y]<MAXSCREEN_X and ebufl[y]>0: # is triangle on this row?
t= ebufl[y]
for x in range(ebufl[y],ebufr[y]): # from left edge to right edge
i=y*MAXSCREEN_X+x
#u=(t) % TEXTURE_WIDTH
#v=(y) % TEXTURE_HEIGHT
#t_color=(v*TEXTURE_WIDTH+t) % (TEXTURE_WIDTH * TEXTURE_HEIGHT)
#screen_addr[i] = texture[t_color]
#t+=step
screen_addr[i] = color[solid_color] # solid_color
ebufl[y] = MAXSCREEN_X # reset left edge
ebufr[y] = 0 # reset right edge
EDGEBUF_L_CTL = const(0) # 0
EDGEBUF_R_CTL = const(4) # 1
ZBUFFER_CTL = const(8) # 2
SCREEN_CTL = const(12) # 3
COLOR_CTL = const(16) # 4
SOLID_CLR_CTL = const(20) # 5
@micropython.asm_thumb
def fill_triangle_asm(r0):
mov(r1,0) # r1 = y
label(LOOP_Y)
ldr(r7,[r0,EDGEBUF_L_CTL]) # ebufl[] address
add(r2,r1,r1) # double for ldrh
add(r2,r2,r7) # r2 = ebufl[y] address
ldrh(r3,[r2,0]) # r3 = ebufl[y] value
cmp(r3,MAXSCREEN_X) # ebufl[y]<MAXSCREEN_X
bge(NO_TRIANGLE)
cmp(r3,1) # ebufl[y]>0
blt(NO_TRIANGLE)
mov(r4,r3) # r4 = x
label(LOOP_X)
mov(r5,MAXSCREEN_X)
mul(r5,r1) # y*MAXSCREEN_X
add(r5,r5,r4) # y*MAXSCREEN_X+x
add(r5,r5,r5) # r5=i double for strh
ldr(r6,[r0,SCREEN_CTL]) # screen_addr address
add(r5,r5,r6) # screen_addr[i] address
ldr(r6,[r0,SOLID_CLR_CTL]) # solid_color
add(r6,r6,r6) # double for ldrh
ldr(r7,[r0,COLOR_CTL]) # color[] address
add(r7,r7,r6) # color[solid_color] address
ldrh(r7,[r7,0]) # color[solid_color] value
strh(r7,[r5,0]) # screen_addr[i] = color[solid_color]
ldr(r7,[r0,EDGEBUF_R_CTL]) #
add(r6,r1,r1) # y+y for ldrh
add(r7,r7,r6) # ebufr[y] address
ldrh(r6,[r7,0]) # ebufr[y] value
add(r4,r4,1)
cmp(r4,r6) # x < ebufr[y]
blt(LOOP_X)
label(NO_TRIANGLE)
ldr(r7,[r0,EDGEBUF_R_CTL]) # ebufr[] address
add(r6,r1,r1) # y+y for ldrh
add(r7,r7,r6) # ebufr[y] address
mov(r6,0)
strh(r6,[r7,0]) # ebufr[y] = 0
mov(r7,MAXSCREEN_X)
strh(r7,[r2,0]) # ebufl[y] = MAXSCREEN_X
add(r1,r1,1)
cmp(r1,MAXSCREEN_Y)
blt(LOOP_Y)
label(EXIT)
@micropython.viper
def rotate(num_points:int,deg:int,offset_x:int,offset_y:int):
x_addr=ptr32(rot_x)
y_addr=ptr32(rot_y)
z_addr=ptr32(rot_z)
xc=ptr32(xcoord)
yc=ptr32(ycoord)
zc=ptr32(zcoord)
sin_addr=ptr32(isin)
cos_addr=ptr32(icos)
s=5
for i in range(num_points):
x_addr[i] = int((s*(xc[i]-offset_x))*cos_addr[deg]+(s*(yc[i]-offset_y))*sin_addr[deg])>>(SCALE+2)
y_addr[i] = int((s*(xc[i]-offset_x))*sin_addr[deg]-(s*(yc[i]-offset_y))*cos_addr[deg])>>(SCALE+2)
z_addr[i] = zc[i]*1
@micropython.viper
def draw_rotate(num_triangles:int,xoffset:int,yoffset:int,zoffset:int):
x=ptr32(rot_x)
y=ptr32(rot_y)
z=ptr32(rot_z)
a=ptr32(tri_a)
b=ptr32(tri_b)
c=ptr32(tri_c)
for j in range(num_triangles): # num_triangles
i=int(j)
ta=a[i]
tb=b[i]
tc=c[i]
x1=x[ta]+z[ta]+xoffset
y1=y[ta]-z[ta]+yoffset
x2=x[tb]+z[tb]+xoffset
y2=y[tb]-z[tb]+yoffset
x3=x[tc]+z[tc]+xoffset
y3=y[tc]-z[tc]+yoffset
z1=z[ta]+zoffset
z2=z[tb]+zoffset
z3=z[tc]+zoffset
area = x1 * y2 - y1 * x2 + x2 * y3 - y2 * x3 + x3 * y1 - y3 * x1
if area>0: # hide back faces
continue
draw_line(x1,y1,x2,y2,z1,z2,WIRE_COLOR)
draw_line(x2,y2,x3,y3,z2,z3,WIRE_COLOR)
draw_line(x3,y3,x1,y1,z3,z1,WIRE_COLOR)
p1=(y2-y1)*(z3-z1)-(y3-y1)*(z2-z1)
p2=(z2-z1)*(x3-x1)-(x2-x1)*(z3-z1)
p3=(x2-x1)*(y3-y1)-(x3-x1)*(y2-y1)
p=(p1+p2+p3+700)>>4
if p>0 and not WIRE_COLOR:
fill_triangle(p,z1,z2,z3)
#fill_triangle_ctl[SOLID_CLR_CTL//4] = p
#fill_triangle_asm(fill_triangle_ctl)
def fade(input_color1, input_color2):
color1=input_color1<<8 | input_color1>>8 # byte swap to normal RBG565
red1 =color1>>11& 0b11111 # extract red #13
green1=color1>>6 & 0b11111 # extract green
blue1 =color1 & 0b11111 # extract blue
color2=input_color2<<8 | input_color2>>8 # byte swap
red2 =color2>>11& 0b11111 # extract red
green2=color2>>6 & 0b11111 # extract green
blue2 =color2 & 0b11111 # extract blue
inc_red =(red2- red1)/31 # find increment step
inc_green=(green2-green1)/31
inc_blue =(blue2- blue1)/31
for i in range(0,32):
red3 =red1 +int(i*inc_red) # build colors by steps
green3=green1+int(i*inc_green)
blue3 =blue1 +int(i*inc_blue)
color3=red3<<11 | green3<<6 | blue3 # combine RGB
color_l2.append((color3 & 0x00FF)<<8 | (color3>>8)) # byte swap to LCD RGB565
def init_isin(): # integer sin lookup table
for i in range(0,361):
isin[i]=int(sin(radians(i))*(1<<SCALE))
def init_icos(): # integer cos lookup table
for i in range(0,361):
icos[i]=int(cos(radians(i))*(1<<SCALE))
@micropython.asm_thumb
def clear_zbuf(r0,r1): # r0=address, r1= # of words
label(LOOP)
mov(r2,0xff)
strb(r2, [r0, 0]) #r2
add(r0, 1) # add 1 to address (next word)
sub(r1, 1) # dec number of words
bgt(LOOP) # branch if not done
if __name__=='__main__':
spi = SPI(1, baudrate=63_000_000, sck=Pin(10), mosi=Pin(11))
tft = gc9a01.GC9A01(
spi,
240,
240,
reset=Pin(12, Pin.OUT),
cs=Pin(9, Pin.OUT),
dc=Pin(8, Pin.OUT),
backlight=Pin(13, Pin.OUT),
rotation=0)
tft.init()
tft.rotation(0)
tft.fill(gc9a01.BLACK)
sleep(0.5)
machine.mem32[0x40008048] = 1<<11 # enable peri_ctrl clock
display_buffer=bytearray(MAXSCREEN_X * MAXSCREEN_Y * 2)
screen=framebuf.FrameBuffer(display_buffer, MAXSCREEN_X , MAXSCREEN_Y, framebuf.RGB565)
wood_buff=bytearray(TEXTURE_WIDTH*TEXTURE_HEIGHT*2)
blit_image_file(wood_buff,"wood.bin",90,47,90,47)
color_l2 = array.array('H', [])
solid_colors = array.array('H', [BLUE,ORANGE,RED,YELLOW,WHITE,GREEN])
fade(BLACK,DRK_BLUE)
fade(DRK_BLUE,WHITE)
fade(YELLOW,ORANGE)
fade(ORANGE,RED)
fade(RED,BLACK)
fill_triangle_ctl=array.array('I',(addressof(edgebuf_l),addressof(edgebuf_r),0,addressof(screen),addressof(color_l2),
0,0,0,0,0))
init_isin()
init_icos()
read_obj('sphere.txt') # toe.txt
print('points: ',NUM_POINTS)
print('triangles: ',NUM_TRIANGLES)
gc.collect()
print('memory free:',gc.mem_free())
first=True
fps = 0
while(1):
for deg in range(0,360,3):
gticks=ticks_us()
buttons() # 88uS
rotate(NUM_POINTS,deg,offset.x,offset.y) # 252uS
#gticks=ticks_us()
draw_rotate(NUM_TRIANGLES,87,105,0) # 32167uS
screen.text(str(fps)+' FPS',80,180,0)
tft.blit_buffer(screen, 20, 20, MAXSCREEN_X, MAXSCREEN_Y) #6157uS
background_asm(screen,wood_buff,MAXSCREEN_Y,MAXSCREEN_X) #5278uS
#screen.fill(0)
fps=1_000_000//ticks_diff(ticks_us(), gticks)
if first or 0:
print('fps: ',1_000_000//ticks_diff(ticks_us(), gticks),end='\r')
first = False
#exit()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment