Created
July 30, 2022 22:52
-
-
Save samneggs/f777d76a6f079ca3894bb87f0846de3c to your computer and use it in GitHub Desktop.
Affine Transformation and Perlin Noise Generated Sky
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
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