Skip to content

Instantly share code, notes, and snippets.

@samneggs
Created July 30, 2022 22:52
Show Gist options
  • Save samneggs/f777d76a6f079ca3894bb87f0846de3c to your computer and use it in GitHub Desktop.
Save samneggs/f777d76a6f079ca3894bb87f0846de3c to your computer and use it in GitHub Desktop.
Affine Transformation and Perlin Noise Generated Sky
from machine import Pin, SPI
import gc9a01
import framebuf, array
from time import sleep, ticks_diff, ticks_us
from math import sin,cos,radians
from micropython import const
from uctypes import addressof
import gc
from usys import exit
from random import randint
# OneLoneCoder.com - Programming Pseudo 3D planes, aka MODE7
# @Javidx9
SCALE = const(12)
SCALE2 = const(12)
fWorldX = 120<<SCALE #1000.0
fWorldY = 120<<SCALE #1000.0
fWorldA = 90
fNear = int(0.005*(1<<SCALE)) #.005
fFar = 15<<SCALE #0.03
fFoVHalf = 3.14159 / 4.0
FOV = 45
GROUND = 0
nMapSize = 1024
ScreenHeight = const(240)
ScreenWidth = const(240)
TEXTURE_WIDTH = const(32) #90
TEXTURE_HEIGHT = const(32) #47
NUM_TEXTURES = const(5)
BLACK = const(0x0000)
BLUE = const(0x001F)
RED = const(0xF800)
GREEN = const(0x07E0)
CYAN = const(0x07FF)
MAGENTA = const(0xF81F)
YELLOW = const(0xFFE0)
WHITE = const(0xFFFF)
BROWN = const(0x4000)
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)
isin=array.array('i',range(0,360))
icos=array.array('i',range(0,360))
colors = array.array('H', (BLACK,BLUE,RED,GREEN,CYAN,MAGENTA,YELLOW,WHITE,BROWN))
def init_isin(): # integer sin lookup table
for i in range(0,360):
isin[i]=int(sin(radians(i))*(1<<SCALE))
def init_icos(): # integer cos lookup table
for i in range(0,360):
icos[i]=int(cos(radians(i))*(1<<SCALE))
def grid():
for y in range(0,45,15):
texture.line(0,y,TEXTURE_WIDTH,y,0xff00)
for x in range(0,TEXTURE_WIDTH,15):
texture.line(x,0,x,TEXTURE_HEIGHT,0xff)
# blit_image_file from Stewart Watkiss
# http://www.penguintutor.com/programming/picodisplayanimations
def blit_image_file(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
texture_buffer[char_position] = ord(current_byte) #and LCD.red
char_position += 1
file_position += 1
file.close()
return
@micropython.viper
def calc_screen(fWorldX:int,fWorldY:int,fFar:int,fNear:int,fWorldA:int,FOV:int,GROUND:int):
d=ptr16(screen_buffer)
s=ptr16(texture_buffer)
sine = ptr32(isin)
cosine = ptr32(icos)
# Create Frustum corner points
fFarX1 = fWorldX + ((cosine[fWorldA-FOV] * fFar)>>SCALE)
fFarY1 = fWorldY + ((sine[fWorldA-FOV] * fFar)>>SCALE)
fNearX1 = fWorldX + ((cosine[fWorldA-FOV] * fNear)>>SCALE)
fNearY1 = fWorldY + ((sine[fWorldA-FOV] * fNear)>>SCALE)
fFarX2 = fWorldX + ((cosine[fWorldA+FOV] * fFar)>>SCALE)
fFarY2 = fWorldY + ((sine[fWorldA+FOV] * fFar)>>SCALE)
fNearX2 = fWorldX + ((cosine[fWorldA+FOV] * fNear)>>SCALE)
fNearY2 = fWorldY + ((sine[fWorldA+FOV] * fNear)>>SCALE)
#print(fFarX1,fFarY1,fNearX1,fNearY1,fFarX2,fFarY2,fNearX2,fNearY2)
# Starting with furthest away line and work towards the camera point
y=0
while y < (ScreenHeight-1) >> 1:
# Take a sample point for depth linearly related to rows down screen
fSampleDepth = (y<<SCALE) // (ScreenHeight>>1)
# Use sample point in non-linear (1/x) way to enable perspective
# and grab start and end points for lines across the screen
fStartX = ((fFarX1 - fNearX1)<<SCALE) // (fSampleDepth) + fNearX1
fStartY = ((fFarY1 - fNearY1)<<SCALE) // (fSampleDepth) + fNearY1
fEndX = ((fFarX2 - fNearX2)<<SCALE) // (fSampleDepth) + fNearX2
fEndY = ((fFarY2 - fNearY2)<<SCALE) // (fSampleDepth) + fNearY2
# Linearly interpolate lines across the screen
x=0
span_x = fEndX - fStartX
span_y = fEndY - fStartY
while x < ScreenWidth:
#fSampleWidth = (x<<SCALE) // ScreenWidth
fSampleWidth = (x<<SCALE)>>8
fSampleX = (((span_x * fSampleWidth)>>SCALE) + fStartX)>>SCALE
fSampleY = (((span_y * fSampleWidth)>>SCALE) + fStartY)>>SCALE
# Wrap sample coordinates to give "infinite" periodicity on maps
#fSampleX = fSampleX % TEXTURE_WIDTH
#fSampleY = fSampleY % TEXTURE_HEIGHT
fSampleX = fSampleX & TEXTURE_WIDTH-1
fSampleY = fSampleY & TEXTURE_HEIGHT-1
# Sample symbol and colour from map sprite, and draw the
# pixel to the screen
s_addr=fSampleY*TEXTURE_WIDTH+fSampleX+(32*32*GROUND) # gound
d_addr=(y + (ScreenHeight//2))*ScreenHeight+x
d[d_addr]=s[s_addr]
s_addr=fSampleY*TEXTURE_WIDTH+fSampleX+(32*32*3) # sky
d_addr=((ScreenHeight//2)-y)*ScreenHeight+x
d[d_addr]=s[s_addr]
x+=1
y+=1
# Based on Javidx9 C code
# OneLoneCoder.com - What Is Perlin Noise? Video: https://youtu.be/6-0UaeJBumA
@micropython.viper
def PerlinNoise2D(nWidth:int,nHeight:int,Seed_arry,nOctaves:int,fBias:int, Output_arry,s:int):
seed_addr=ptr16(Seed_arry)
output_addr=ptr16(Output_arry)
color_addr=ptr16(color_l2)
for x in range(nWidth):
for y in range(nHeight):
fNoise = 0
fScaleAcc = 0
fScale = s
for o in range(nOctaves):
nPitch = nWidth >> o
nSampleX1 = (x // nPitch) * nPitch
nSampleY1 = (y // nPitch) * nPitch
nSampleX2 = (nSampleX1 + nPitch) % nWidth
nSampleY2 = (nSampleY1 + nPitch) % nWidth
fBlendX = ((x - nSampleX1)<<SCALE) // nPitch
fBlendY = ((y - nSampleY1)<<SCALE) // nPitch
fSampleT = ((((1<<SCALE) - fBlendX) * seed_addr[nSampleY1 * nWidth + nSampleX1])>>SCALE) + ((fBlendX * seed_addr[nSampleY1 * nWidth + nSampleX2])>>SCALE)
fSampleB = ((((1<<SCALE) - fBlendX) * seed_addr[nSampleY2 * nWidth + nSampleX1])>>SCALE) + ((fBlendX * seed_addr[nSampleY2 * nWidth + nSampleX2])>>SCALE)
fScaleAcc += fScale
fNoise += ((((fBlendY * (fSampleB - fSampleT))>>SCALE) + fSampleT) // fScale)
fScale = fScale // fBias
output_addr[32*32*3+y * nWidth + x] = color_addr[(fNoise // fScaleAcc)]
def fade(input_color1, input_color2):
#color1=input_color1<<8 | input_color1>>8 # byte swap to normal RBG565
color1=input_color1
color2=input_color2
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[i]=((color3 & 0x00FF)<<8 | (color3>>8)) # byte swap to LCD RGB565
@micropython.viper
def init_seed(width:int,height:int,seed_arry):
seed_addr=ptr16(seed_arry)
for i in range(width*height):
seed_addr[i] = int(randint(0,0xffff))
@micropython.viper
def buttons(x:int,y:int,a:int):
global fWorldX,fWorldY,fWorldA,SCALE, GROUND
sine = ptr32(isin)
cosine = ptr32(icos)
c=ptr16(colors)
if not joyUp.value():
fWorldX = x + 3*cosine[a]
fWorldY = y + 3*sine[a]
if not joySel.value():
fade(c[int(randint(0,8))],c[int(randint(0,8))])
init_seed(TEXTURE_WIDTH,TEXTURE_HEIGHT,seed_buffer)
PerlinNoise2D(TEXTURE_WIDTH,TEXTURE_HEIGHT,seed_buffer,4,1, texture_buffer,50) # 6,1,50
GROUND = randint(0,2)
if not joyRight.value():
a = a + 5
if a > 310:
a = a - 360+90
fWorldA = a
if not joyLeft.value():
a = a - 5
if a < 45:
a = a + 360-90
fWorldA = a
spi = SPI(1, baudrate=80_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(BLACK)
sleep(0.5)
screen_buffer = bytearray(ScreenHeight*ScreenWidth*2)
texture_buffer = bytearray(TEXTURE_HEIGHT*TEXTURE_WIDTH*2*NUM_TEXTURES)
seed_buffer = bytearray(TEXTURE_HEIGHT*TEXTURE_WIDTH*2)
texture = framebuf.FrameBuffer(texture_buffer,TEXTURE_WIDTH,TEXTURE_HEIGHT, framebuf.RGB565)
color_l2 = array.array('H', range(0,33))
fade(BLUE,WHITE) # blue,white
init_isin()
init_icos()
init_seed(TEXTURE_WIDTH,TEXTURE_HEIGHT,seed_buffer)
blit_image_file("floor0.bin",TEXTURE_WIDTH,TEXTURE_HEIGHT*NUM_TEXTURES,TEXTURE_WIDTH,TEXTURE_HEIGHT)
PerlinNoise2D(TEXTURE_WIDTH,TEXTURE_HEIGHT,seed_buffer,4,1, texture_buffer,50) # 6,1,50
gc.collect()
print(gc.mem_free())
while(1):
for i in range(45,315,5):
gticks=ticks_us()
buttons(fWorldX,fWorldY,fWorldA)
#fWorldY+=1<<SCALE
#fWorldY+=.1
#fWorldA=i
calc_screen(fWorldX,fWorldY,fFar,fNear,fWorldA,FOV,GROUND)
tft.blit_buffer(screen_buffer, 0, 0, ScreenWidth,ScreenHeight)
fps=1_000_000//(ticks_diff(ticks_us(),gticks))
#rprint(fps)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment