Last active
December 6, 2022 00:28
-
-
Save samneggs/028934cb1b0decb929b05b223a5a70ff to your computer and use it in GitHub Desktop.
OBJ file viewer for Pi Pico in MicroPython
This file contains hidden or 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
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