Here's a perspective test I wrote, that I thought others might find
enjoyable. It illustrates a single-point perspective (a 'vanishing
point'), and shows how a room with a floor, ceiling, door, and avatar
changes as the vanishing point changes.
import pygame, os, sys, urllib
SCREENSIZE = (1024, 768)
BACKCOLOR = (150, 150, 150)
INVISIBLE = ( 0, 0, 0, 0)
PERSPECTIVECOLOR = ( 0, 0, 0, 128)
PERSPECTIVEWIDTH = 2
WALLCOLOR = (255, 128, 0, 220)
WINDOWCOLOR = ( 0, 64, 200, 100)
DOORCOLOR = (100, 64, 50, 200)
CEILINGCOLOR = (255, 255, 255, 255)
FLOORCOLOR = (121, 74, 81, 255)
HORIZONCOLOR = ( 0, 128, 0, 200)
VPCOLOR = (255, 0, 0, 255)
WALLDEPTH = .35
VANISHINGPOINT = (SCREENSIZE[0]/2, SCREENSIZE[1]/2)
bg = None
temp = None
girl = None
def CalculatePoint(depth, point):
xa, ya = VANISHINGPOINT
xb, yb = point
xbb = xa + depth * (xb-xa)
ybb = ya + depth * (yb-ya)
return int(xbb), int(ybb)
def Intersection(a,b,c,d):
Ax,Ay = a; Bx,By = b; Cx,Cy = c; Dx,Dy = d
u = float((Dx-Cx)*(Ay-Cy)-(Dy-Cy)*(Ax-Cx)) /
float((Dy-Cy)*(Bx-Ax)-(Dx-Cx)*(By-Ay))
x = Ax + u*(Bx-Ax)
y = Ay + u*(By-Ay)
return int(x),int(y)
def DrawWall():
w, h = SCREENSIZE
topleft = CalculatePoint(WALLDEPTH, (0, 0))
topright = CalculatePoint(WALLDEPTH, (w, 0))
bottomleft = CalculatePoint(WALLDEPTH, (0, h))
bottomright = CalculatePoint(WALLDEPTH, (w, h))
pos = topleft
wallwidth = int(topright[0]-topleft[0])
wallheight = int(bottomleft[1]-topleft[1])
temp.fill(WALLCOLOR)
bg.blit(temp, topleft, [0, 0, wallwidth, wallheight])
pygame.draw.lines(bg, PERSPECTIVECOLOR, 1, [topleft, topright,
bottomright, bottomleft], PERSPECTIVEWIDTH)
def DrawOrthogonals():
pygame.draw.line(bg, PERSPECTIVECOLOR, (0, 0), VANISHINGPOINT,
PERSPECTIVEWIDTH)
pygame.draw.line(bg, PERSPECTIVECOLOR, (SCREENSIZE[0], 0),
VANISHINGPOINT, PERSPECTIVEWIDTH)
pygame.draw.line(bg, PERSPECTIVECOLOR, (0, SCREENSIZE[1]),
VANISHINGPOINT, PERSPECTIVEWIDTH)
pygame.draw.line(bg, PERSPECTIVECOLOR, SCREENSIZE, VANISHINGPOINT,
PERSPECTIVEWIDTH)
pygame.draw.line(bg, HORIZONCOLOR, (0, VANISHINGPOINT[1]),
(SCREENSIZE[0], VANISHINGPOINT[1]), PERSPECTIVEWIDTH)
def DrawDoor():
w, h = SCREENSIZE
topleft = CalculatePoint(.9, (0, .25*h))
topright = CalculatePoint(.7, (0, .25*h))
bottomleft = CalculatePoint(.9, (0, h))
bottomright = CalculatePoint(.7, (0, h))
pos = topleft
windowwidth = int(topright[0]-topleft[0])
windowheight = int(bottomleft[1]-topleft[1])
lst = [topleft, topright, bottomright, bottomleft, topleft]
temp.fill(INVISIBLE)
pygame.draw.polygon(temp, DOORCOLOR, lst)
bg.blit(temp, [0, 0])
pygame.draw.circle(bg, DOORCOLOR, topleft, 5)
pygame.draw.circle(bg, DOORCOLOR, topright, 5)
pygame.draw.circle(bg, DOORCOLOR, bottomright, 5)
pygame.draw.circle(bg, DOORCOLOR, bottomleft, 5)
def DrawCeiling():
w, h = SCREENSIZE
vx, vy = VANISHINGPOINT
numtiles = 8
for i in range(0, SCREENSIZE[0]+1, SCREENSIZE[0]/numtiles):
pygame.draw.line(bg, CEILINGCOLOR, (i, 0), VANISHINGPOINT, 1)
# Calculate horizontal lines
topright = CalculatePoint(WALLDEPTH, (w, 0))
for i in range(0, SCREENSIZE[0]+1, SCREENSIZE[0]/numtiles):
try:
x, y = Intersection((0, 0), topright, (i, 0), VANISHINGPOINT)
left = Intersection([0, y], [w, y], (0, 0), VANISHINGPOINT)
right = Intersection([0, y], [w, y], (w, 0), VANISHINGPOINT)
pygame.draw.line(bg, CEILINGCOLOR, left, right, 1)
except:
pass
#pygame.draw.line(bg, CEILINGCOLOR, (0, 0), topright, 1)
def DrawFloor():
w, h = SCREENSIZE
factor = 4
for i in range(-w*factor, w*2*factor, 100):
pygame.draw.line(bg, FLOORCOLOR, (i, h), VANISHINGPOINT, 1)
def DrawGirl():
w, h = SCREENSIZE
data = [(.8, (75, h)), (.9, (w/2+50, h)), (WALLDEPTH+.05, (150, h))]
data.sort()
for depth, dest in data:
pt = CalculatePoint(depth, dest)
resizedgirl = pygame.transform.rotozoom(girl, 0, depth)
bg.blit(resizedgirl, [pt[0]-resizedgirl.get_width()/2,
pt[1]-resizedgirl.get_height()])
pygame.draw.circle(bg, (0, 0, 255), pt, 5)
def Text(s, _font = []):
if len(_font) == 0:
font = pygame.font.Font(None, 24)
_font.append(font)
font = _font[0]
shadow = font.render(s, 1, (0, 0, 0))
color = font.render(s, 1, (255, 255, 0))
w, h = color.get_size()
pic = pygame.Surface((w, h), pygame.SRCALPHA, 32).convert_alpha()
pic.fill(INVISIBLE)
pic.blit(shadow, [1, 1])
pic.blit(color, [0, 0])
return pic
def Draw():
bg.fill(BACKCOLOR)
DrawCeiling()
DrawFloor()
DrawOrthogonals()
DrawDoor()
DrawWall()
DrawGirl()
pygame.draw.circle(bg, VPCOLOR, VANISHINGPOINT, 5)
def main():
global bg, temp, girl, VANISHINGPOINT, WALLDEPTH
pygame.init()
pygame.key.set_repeat(500, 30)
bg = pygame.display.set_mode(SCREENSIZE, 0, 32)
temp = pygame.Surface(SCREENSIZE, pygame.SRCALPHA, 32).convert_alpha()
filename = "posette.png"
if os.path.exists(filename):
girl = pygame.image.load(filename).convert_alpha()
else:
try:
url = "http://www.kamilche.org/webimages/posette.png"
urllib.urlretrieve(url, filename)
girl = pygame.image.load(filename).convert_alpha()
except:
girl = pygame.Surface((150, 300), pygame.SRCALPHA,
32).convert_alpha()
girl.fill((255, 0, 0, 128))
quit = 0
info1 = Text("Click to move vanishing point, arrow keys to move wall.")
caption = ""
while not quit:
Draw()
info2 = Text(caption)
bg.blit(info1, [5, 5])
bg.blit(info2, [5, info1.get_height()+5])
pygame.display.flip()
pygame.event.pump()
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit = 1
break
elif (event.type == pygame.MOUSEBUTTONDOWN and event.button
== 1) or (event.type == pygame.MOUSEMOTION and event.buttons[0] == 1):
# Move vanishing point
VANISHINGPOINT = event.pos
caption = 'Vanishing point = %s' % str(VANISHINGPOINT)
elif event.type == pygame.KEYDOWN:
# Move back wall
if event.key in [pygame.K_LEFT, pygame.K_UP]:
WALLDEPTH = max(0.01, WALLDEPTH - .05)
elif event.key in [pygame.K_RIGHT, pygame.K_DOWN]:
WALLDEPTH = min(1.0, WALLDEPTH + .05)
pygame.quit()
main()