Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #! /usr/bin/python
- # a rewrite of my pygame port of
- # youtube.com/watch?v=N8elxpSu9pw
- # which is by Bisqwit
- #
- # (multithreaded version)
- #
- # theinternetftw
- import pygame, math, random, sys
- from multiprocessing import Pool
- def vMul(a,b): return [ a[0]*b[0], a[1]*b[1], a[2]*b[2] ]
- def vMulD(a,b): return [ a[0]*b, a[1]*b, a[2]*b ]
- def vAdd(a,b): return [ a[0]+b[0], a[1]+b[1], a[2]+b[2] ]
- def vAddD(a,b): return [ a[0]+b, a[1]+b, a[2]+b ]
- def vSub(a,b): return [ a[0]-b[0], a[1]-b[1], a[2]-b[2] ]
- def vSubD(a,b): return [ a[0]-b, a[1]-b, a[2]-b ]
- def vNeg(a): return [-a[0], -a[1], -a[2] ]
- def vPow(a,b): return [ a[0]**b, a[1]**b, a[2]**b ]
- def vDot(a,b): return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]
- def vSqr(a): return vDot(a,a)
- def vLen(a): return math.sqrt(vSqr(a))
- def vNorm(a): return vMulD(a, 1.0/vLen(a))
- def vMirrorAround(a, axis):
- n = vNorm(axis)
- v = vDot(a,n)
- return vSub(vMulD(n,v+v), a)
- # for color vectors (rgb)
- def vLuma(a): return a[0]*0.299 + a[1]*0.587 + a[2]*0.114
- def vClamp(a):
- for i in range(3):
- if a[i] < 0.0: a[i] = 0.0
- elif a[i] > 1.0: a[i] = 1.0
- return a
- def vClampWithDesaturation(a):
- # if the color represented by this triplet
- # is too bright or too dim, decrease the saturation
- # as much as required, while keeping the luma unmodified
- l = vLuma(a)
- if l > 1.0: return [1.0, 1.0, 1.0]
- elif l < 0.0: return [0.0, 0.0, 0.0]
- # if any component is over the bounds,
- # calculate how much the saturation must
- # be reduced to achieve an in-bounds value.
- # Since the luma was verified to be in 0..1,
- # a maximum reduction of saturation to 0% will
- # always produce an in-bounds value, but usually
- # such a drastic reduction is not necessary.
- # Because we're only doing relative modifications,
- # we don't need the original saturation level of the
- # pixel.
- sat = 1.0
- for c in a:
- if c > 1.0: sat = min(sat, (l-1.0) / (l-c))
- elif c < 0.0: sat = min(sat, l / (l-c))
- if sat != 1.0:
- a = vAddD(vMulD(vSubD(a,l), sat),l)
- a = vClamp(a)
- return a
- def vGetRotMatrix(ang):
- cx,cy,cz = math.cos(ang[0]),math.cos(ang[1]),math.cos(ang[2])
- sx,sy,sz = math.sin(ang[0]),math.sin(ang[1]),math.sin(ang[2])
- sxsz,cxsz = sx*sz,cx*sz
- cxcz,sxcz = cx*cz,sx*cz
- return [ [cy*cz, cy*sz, -sy ],
- [sxcz*sy - cxsz, sxsz*sy + cxcz, sx*cy],
- [cxcz*sy + sxsz, cxsz*sy - sxcz, cx*cy] ]
- def mTransform(m, vec):
- return [ vDot(m[0], vec), vDot(m[1], vec), vDot(m[2], vec) ]
- class Plane:
- def __init__(self, norm, off):
- self.normal = norm
- self.offset = off
- # declare six planes, each looks
- # towards the origin and is 30 units away
- planes = [ Plane( [0.0,0.0,-1.0], -30),
- Plane( [0.0,1.0,0.0], -30),
- Plane( [0.0,-1.0,0.0], -30),
- Plane( [1.0,0.0,0.0], -30),
- Plane( [0.0,0.0,1.0], -30),
- Plane( [-1.0,0.0,0.0], -30) ]
- class Sphere:
- def __init__(self, c, r):
- self.center = c
- self.radius = r
- # declare a few spheres
- spheres = [ Sphere( [0.0,0.0,0.0], 7.0),
- Sphere( [19.4, -19.4, 0], 2.1),
- Sphere( [-19.4, 19.4, 0], 2.1),
- Sphere( [13.1, 5.1, 0.0], 1.1),
- Sphere( [-5.1, -13.1, 0], 1.1),
- Sphere( [-30.0,-30.0,15.0], 11.0),
- Sphere( [15.0, -30.0,30.0], 6.0),
- Sphere( [30.0, 15.0, -30.0],6.0) ]
- class LightSource:
- def __init__(self, w, c):
- self.where = w
- self.color = c
- #declare lightsources, each w/ a loc and a rgb color
- lights = [ LightSource( [-28.0,-14.0, 4.0], [ .4,.51, .9] ),
- LightSource( [-29.0,-29.0,-29.0], [.95, .1, .1] ),
- LightSource( [ 14.0, 29.0,-14.0], [ .8, .8, .8] ),
- LightSource( [ 29.0, 29.0, 29.0], [1.0,1.0,1.0] ),
- LightSource( [ 28.0, 0.0, 29.0], [ .5, .6, .1] ) ]
- numPlanes = len(planes)
- numSpheres = len(spheres)
- numLights = len(lights)
- MAXTRACE = 6
- def rayFindObstacle(eye, dir, hitDist):
- #try to intersect ray w/ each object, see which gives the closest hit
- hitType = -1
- hitIndex = -1
- hitLoc = [0.0,0.0,0.0]
- hitNormal = [0.0,0.0,0.0]
- for i in range(numSpheres):
- v = vSub(eye, spheres[i].center)
- r = spheres[i].radius
- dv = vDot(dir,v)
- d2 = vSqr(dir)
- sq = dv*dv - d2*(vSqr(v) - r*r)
- #does the ray coincide w/ the sphere?
- if (sq < 1e-6):
- continue
- #if so, where?
- sqt = math.sqrt(sq)
- dist = min(-dv-sqt, -dv+sqt) / d2
- if dist < 1e-6 or dist >= hitDist:
- continue
- hitType = 1
- hitIndex = i
- hitDist = dist
- hitLoc = vAdd(eye, vMulD(dir,hitDist))
- hitNormal = vMulD(vSub(hitLoc,spheres[i].center), 1/r)
- for i in range(numPlanes):
- dv = -vDot(planes[i].normal,dir)
- if dv > -1e-6:
- continue
- d2 = vDot(planes[i].normal,eye)
- dist = (d2 + planes[i].offset) / dv
- if dist < 1e-6 or dist >= hitDist:
- continue
- hitType = 0
- hitIndex = i
- hitDist = dist
- hitLoc = vAdd(eye, vMulD(dir,hitDist))
- hitNormal = vNeg(planes[i].normal)
- return hitType, hitIndex, hitDist, hitLoc, hitNormal
- random.seed(1) #not doing this lends an interesting speckled pattern to the lighting,
- #as each proc that's started up (max of 4 atm) has different
- #values for the arealight vectors (i think the work is done as threads
- #on those procs, so the vectors are *not* changed w/ each call to
- #apply_sync)
- numArealightVectors = 20
- arealightVectors = []
- for i in range(numArealightVectors):
- temp = []
- for i in range(3):
- temp.append(2.0*(random.random() - 0.5))
- arealightVectors.append( [temp[0],temp[1],temp[2]] )
- def rayTrace(eye, dir, k):
- hitDist = 1e6
- hitType, hitIndex, hitDist, hitLoc, hitNormal = rayFindObstacle(eye,dir,hitDist)
- if hitType != -1:
- # Found an obstacle. Next, find out how it is illuminated.
- # Shoot a ray to each lightsource, and determine if there
- # is an obstacle behind it. This is called "diffuse light".
- # To smooth out the infinitely sharp shadows caused by
- # infinitely small point-lightsources, assume the lightsource
- # is actually a cloud of small lightsources around its center.
- diffuseLight = [0.0,0.0,0.0]
- specularLight = [0.0,0.0,0.0]
- pigment = [1.0, .98, .94] #default pigment
- for i in range(numLights):
- for j in range(numArealightVectors):
- v = vSub(vAdd(lights[i].where,arealightVectors[j]),hitLoc)
- lightDist = vLen(v)
- v = vNorm(v)
- diffuseEffect = vDot(hitNormal,v) / (numArealightVectors*1.0)
- attenuation = (1 + ((lightDist/34.0)**2.0))
- diffuseEffect /= attenuation
- if diffuseEffect > 1e-3:
- shadowDist = lightDist - 1e-4
- t,hi,hd,hl,hn = rayFindObstacle(vAdd(hitLoc,vMulD(v,1e-4)),v,shadowDist)
- if t == -1: #no obstacle occluding the light
- diffuseLight = vAdd(diffuseLight,vMulD(lights[i].color,diffuseEffect))
- if k > 1:
- # add specular light/reflection, unless recursion depth is maxed
- v = vNeg(dir)
- v = vMirrorAround(v, hitNormal)
- specularLight = rayTrace(vAdd(hitLoc, vMulD(v,1e-4)),v,k-1)
- if hitType == 0: #plane
- diffuseLight = vMulD(diffuseLight,0.9)
- specularLight = vMulD(specularLight,0.5)
- # color the different walls differently
- idx = hitIndex % 3
- if idx == 0: pigment = [0.9,0.7,0.6]
- elif idx == 1: pigment = [0.6,0.7,0.7]
- elif idx == 2: pigment = [0.5,0.8,0.3]
- elif hitType == 1: #sphere
- diffuseLight = vMulD(diffuseLight,1.0)
- specularLight = vMulD(specularLight, 0.34)
- return vMul(vAdd(diffuseLight,specularLight),pigment)
- #didn't hit anything, return black
- return [0.0,0.0,0.0]
- def getPix(x, y, w, h, zoom, camlookmatrix, campos, MAXTRACE):
- camray = [ x/float(w) - 0.5,
- y/float(h) - 0.5,
- zoom ]
- camray[0] *= 4.0/3 # Aspect Ratio Correction
- camray = vNorm(camray)
- camray = mTransform(camlookmatrix, camray)
- campix = rayTrace(campos, camray, MAXTRACE)
- campix = vMulD(campix, 0.5) #Adjust brightness
- return campix
- def main():
- pygame.display.init()
- screenw, screenh = 640, 480
- w, h = int(sys.argv[1]),int(sys.argv[2])
- screen = pygame.display.set_mode((screenw, screenh), pygame.DOUBLEBUF)
- frame = pygame.Surface((w,h))
- camangle = [ 0.0, 0.0, 0.0]
- camangledelta = [-.005,-.011,-.017]
- camlook = [ 0.0, 0.0, 0.0]
- camlookdelta = [-.001, .005, .004]
- zoom = 46.0
- zoomdelta = 0.99
- contrast = 32
- contrast_offset = -0.17
- frames = []
- #numFrames = 9300
- numFrames = int(sys.argv[3])
- MAXTRACE = int(sys.argv[4])
- pool = Pool(processes=4)
- for frameNo in range(numFrames):
- # Put camera between central sphere and walls
- campos = [0.0,0.0,16.0]
- camrotatematrix = vGetRotMatrix(camangle)
- campos = mTransform(camrotatematrix, campos)
- camlookmatrix = vGetRotMatrix(camlook)
- # Determine contrast ratio for this frame's pixels
- thisframe_min = 100
- thisframe_max = -100
- pixels = pygame.PixelArray(frame)
- for y in range(h):
- results = []
- for x in range(w):
- result = pool.apply_async(getPix, [x, y, w, h, zoom, camlookmatrix, campos, MAXTRACE])
- results.append(result)
- for x in range(w):
- campix = results[x].get()
- # update frame luma info for automatic contrast adjuster
- lum = vLuma(campix)
- if lum < thisframe_min: thisframe_min = lum
- if lum > thisframe_max: thisframe_max = lum
- # exagerate the colors to bring contrast better forth
- campix = vMulD(vAddD(campix,contrast_offset),contrast)
- # Clamp, and compensate for display gamma (for dithering)
- # ...But don't actually dither. Because that shit is bananas.
- # Maybe later, I can look up the EGA palette, etc.
- campix = vClampWithDesaturation(campix)
- # Draw pixel (use pygame)
- r = int(campix[0] * 255)
- g = int(campix[1] * 255)
- b = int(campix[2] * 255)
- pixels[x][y] = pygame.Color(r,g,b)
- #del pixels?
- pygame.transform.scale(frame, (screenw,screenh), screen)
- pygame.display.flip()
- for e in pygame.event.get():
- if e.type == pygame.QUIT:
- pygame.quit()
- sys.exit()
- frames.append(frame.copy())
- print 'frame ',frameNo
- sys.stdout.flush()
- # Tweak coordinates/camera params for next frame
- much = 1.0
- # In the beginning, do some camera action (play with zoom)
- if zoom <= 1.1:
- zoom = 1.1
- else:
- if zoom > 40:
- if zoomdelta > 0.95:
- zoomdelta -= 0.001
- elif zoom < 3:
- if zoomdelta < 0.99:
- zoomdelta += 0.001
- zoom *= zoomdelta
- much = 1.1 / ((zoom/1.1)**3.0)
- # Update rotation angle
- camlook = vAdd(camlook, vMulD(camlookdelta,much))
- camangle = vAdd(camangle, vMulD(camangledelta,much))
- # dynamically adjust the contrast based on the contents of the
- # last frame
- middle = (thisframe_min + thisframe_max) * 0.5
- span = (thisframe_max - thisframe_min)
- thisframe_min = middle - span*0.60 # Avoid dark tones
- thisframe_max = middle + span*0.37 # Emphasize bright tones
- new_contrast_offset = -thisframe_min
- new_contrast = 1 / (thisframe_max - thisframe_min)
- # Avoid abrupt changes, though
- l = 0.85
- if frameNo == 0: l = 0.7
- contrast_offset = (contrast_offset*l + new_contrast_offset*(1.0-l))
- contrast = (contrast *l + new_contrast *(1.0-l))
- sys.exit()
- while True:
- for f in frames:
- pygame.transform.scale(f, (screenw,screenh), screen)
- pygame.display.flip()
- pygame.time.wait(33)
- for e in pygame.event.get():
- if e.type == pygame.QUIT:
- pygame.quit()
- sys.exit()
- if __name__ == '__main__':
- main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement