Want more features on Pastebin? Sign Up, it's FREE!
Guest

HelloRayRewriteMultithread

By: a guest on Jan 9th, 2013  |  syntax: Python  |  size: 13.16 KB  |  views: 119  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. #! /usr/bin/python
  2.  
  3. # a rewrite of my pygame port of
  4. # youtube.com/watch?v=N8elxpSu9pw
  5. # which is by Bisqwit
  6. #
  7. # (multithreaded version)
  8. #
  9. # theinternetftw
  10.  
  11. import pygame, math, random, sys
  12. from multiprocessing import Pool
  13.  
  14. def vMul(a,b):  return [ a[0]*b[0], a[1]*b[1], a[2]*b[2] ]
  15. def vMulD(a,b): return [ a[0]*b,    a[1]*b,    a[2]*b    ]
  16. def vAdd(a,b):  return [ a[0]+b[0], a[1]+b[1], a[2]+b[2] ]
  17. def vAddD(a,b): return [ a[0]+b,    a[1]+b,    a[2]+b    ]
  18. def vSub(a,b):  return [ a[0]-b[0], a[1]-b[1], a[2]-b[2] ]
  19. def vSubD(a,b): return [ a[0]-b,    a[1]-b,    a[2]-b    ]
  20. def vNeg(a):    return [-a[0],     -a[1],     -a[2]      ]
  21. def vPow(a,b):  return [ a[0]**b,   a[1]**b,   a[2]**b   ]
  22.  
  23. def vDot(a,b):  return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]
  24. def vSqr(a):    return vDot(a,a)
  25. def vLen(a):    return math.sqrt(vSqr(a))
  26. def vNorm(a):   return vMulD(a, 1.0/vLen(a))
  27.  
  28. def vMirrorAround(a, axis):
  29.     n = vNorm(axis)
  30.     v = vDot(a,n)
  31.     return vSub(vMulD(n,v+v), a)
  32.  
  33. # for color vectors (rgb)
  34. def vLuma(a): return a[0]*0.299 + a[1]*0.587 + a[2]*0.114
  35. def vClamp(a):
  36.     for i in range(3):
  37.         if   a[i] < 0.0: a[i] = 0.0
  38.         elif a[i] > 1.0: a[i] = 1.0
  39.     return a
  40.  
  41. def vClampWithDesaturation(a):
  42.     # if the color represented by this triplet
  43.     # is too bright or too dim, decrease the saturation
  44.     # as much as required, while keeping the luma unmodified
  45.     l = vLuma(a)
  46.     if   l > 1.0: return [1.0, 1.0, 1.0]
  47.     elif l < 0.0: return [0.0, 0.0, 0.0]
  48.     # if any component is over the bounds,
  49.     # calculate how much the saturation must
  50.     # be reduced to achieve an in-bounds value.
  51.     # Since the luma was verified to be in 0..1,
  52.     # a maximum reduction of saturation to 0% will
  53.     # always produce an in-bounds value, but usually
  54.     # such a drastic reduction is not necessary.
  55.     # Because we're only doing relative modifications,
  56.     # we don't need the original saturation level of the
  57.     # pixel.
  58.     sat = 1.0
  59.     for c in a:
  60.         if   c > 1.0: sat = min(sat, (l-1.0) / (l-c))
  61.         elif c < 0.0: sat = min(sat,  l      / (l-c))
  62.     if sat != 1.0:
  63.         a = vAddD(vMulD(vSubD(a,l), sat),l)
  64.         a = vClamp(a)
  65.     return a
  66.  
  67. def vGetRotMatrix(ang):
  68.     cx,cy,cz = math.cos(ang[0]),math.cos(ang[1]),math.cos(ang[2])
  69.     sx,sy,sz = math.sin(ang[0]),math.sin(ang[1]),math.sin(ang[2])
  70.     sxsz,cxsz = sx*sz,cx*sz
  71.     cxcz,sxcz = cx*cz,sx*cz
  72.     return [ [cy*cz,          cy*sz,          -sy  ],
  73.              [sxcz*sy - cxsz, sxsz*sy + cxcz, sx*cy],
  74.              [cxcz*sy + sxsz, cxsz*sy - sxcz, cx*cy] ]
  75.  
  76. def mTransform(m, vec):
  77.     return [ vDot(m[0], vec), vDot(m[1], vec), vDot(m[2], vec) ]
  78.  
  79. class Plane:
  80.     def __init__(self, norm, off):
  81.         self.normal = norm
  82.         self.offset = off
  83. # declare six planes, each looks
  84. # towards the origin and is 30 units away
  85. planes = [ Plane( [0.0,0.0,-1.0], -30),
  86.            Plane( [0.0,1.0,0.0],  -30),
  87.            Plane( [0.0,-1.0,0.0], -30),
  88.            Plane( [1.0,0.0,0.0],  -30),
  89.            Plane( [0.0,0.0,1.0],  -30),
  90.            Plane( [-1.0,0.0,0.0], -30) ]
  91.  
  92. class Sphere:
  93.     def __init__(self, c, r):
  94.         self.center = c
  95.         self.radius = r
  96. # declare a few spheres
  97. spheres = [ Sphere( [0.0,0.0,0.0],    7.0),
  98.             Sphere( [19.4, -19.4, 0], 2.1),
  99.             Sphere( [-19.4, 19.4, 0], 2.1),
  100.             Sphere( [13.1, 5.1, 0.0], 1.1),
  101.             Sphere( [-5.1, -13.1, 0], 1.1),
  102.             Sphere( [-30.0,-30.0,15.0], 11.0),
  103.             Sphere( [15.0, -30.0,30.0], 6.0),
  104.             Sphere( [30.0, 15.0, -30.0],6.0) ]
  105.  
  106. class LightSource:
  107.     def __init__(self, w, c):
  108.         self.where = w
  109.         self.color = c
  110. #declare lightsources, each w/ a loc and a rgb color
  111. lights = [ LightSource( [-28.0,-14.0,  4.0],  [ .4,.51, .9] ),
  112.            LightSource( [-29.0,-29.0,-29.0],  [.95, .1, .1] ),
  113.            LightSource( [ 14.0, 29.0,-14.0],  [ .8, .8, .8] ),
  114.            LightSource( [ 29.0, 29.0, 29.0],  [1.0,1.0,1.0] ),
  115.            LightSource( [ 28.0,  0.0, 29.0],  [ .5, .6, .1] ) ]
  116.  
  117. numPlanes = len(planes)
  118. numSpheres = len(spheres)
  119. numLights = len(lights)
  120. MAXTRACE = 6
  121.  
  122. def rayFindObstacle(eye, dir, hitDist):
  123.     #try to intersect ray w/ each object, see which gives the closest hit
  124.     hitType = -1
  125.     hitIndex = -1
  126.     hitLoc = [0.0,0.0,0.0]
  127.     hitNormal = [0.0,0.0,0.0]
  128.     for i in range(numSpheres):
  129.         v = vSub(eye, spheres[i].center)
  130.         r = spheres[i].radius
  131.         dv = vDot(dir,v)
  132.         d2 = vSqr(dir)
  133.         sq = dv*dv - d2*(vSqr(v) - r*r)
  134.         #does the ray coincide w/ the sphere?
  135.         if (sq < 1e-6):
  136.             continue
  137.         #if so, where?
  138.         sqt = math.sqrt(sq)
  139.         dist = min(-dv-sqt, -dv+sqt) / d2
  140.         if dist < 1e-6 or dist >= hitDist:
  141.             continue
  142.         hitType = 1
  143.         hitIndex = i
  144.         hitDist = dist
  145.         hitLoc = vAdd(eye, vMulD(dir,hitDist))
  146.         hitNormal = vMulD(vSub(hitLoc,spheres[i].center), 1/r)
  147.     for i in range(numPlanes):
  148.         dv = -vDot(planes[i].normal,dir)
  149.         if dv > -1e-6:
  150.             continue
  151.         d2 = vDot(planes[i].normal,eye)
  152.         dist = (d2 + planes[i].offset) / dv
  153.         if dist < 1e-6 or dist >= hitDist:
  154.             continue
  155.         hitType = 0
  156.         hitIndex = i
  157.         hitDist = dist
  158.         hitLoc = vAdd(eye, vMulD(dir,hitDist))
  159.         hitNormal = vNeg(planes[i].normal)
  160.     return hitType, hitIndex, hitDist, hitLoc, hitNormal
  161.  
  162. random.seed(1) #not doing this lends an interesting speckled pattern to the lighting,
  163.                #as each proc that's started up (max of 4 atm) has different
  164.                #values for the arealight vectors (i think the work is done as threads
  165.                #on those procs, so the vectors are *not* changed w/ each call to
  166.                #apply_sync)
  167. numArealightVectors = 20
  168. arealightVectors = []
  169. for i in range(numArealightVectors):
  170.     temp = []
  171.     for i in range(3):
  172.         temp.append(2.0*(random.random() - 0.5))
  173.     arealightVectors.append( [temp[0],temp[1],temp[2]] )
  174.  
  175. def rayTrace(eye, dir, k):
  176.     hitDist = 1e6
  177.     hitType, hitIndex, hitDist, hitLoc, hitNormal = rayFindObstacle(eye,dir,hitDist)
  178.  
  179.     if hitType != -1:
  180.  
  181.         # Found an obstacle. Next, find out how it is illuminated.
  182.         # Shoot a ray to each lightsource, and determine if there
  183.         # is an obstacle behind it. This is called "diffuse light".
  184.         # To smooth out the infinitely sharp shadows caused by
  185.         # infinitely small point-lightsources, assume the lightsource
  186.         # is actually a cloud of small lightsources around its center.
  187.         diffuseLight = [0.0,0.0,0.0]
  188.         specularLight = [0.0,0.0,0.0]
  189.         pigment = [1.0, .98, .94] #default pigment
  190.         for i in range(numLights):
  191.             for j in range(numArealightVectors):
  192.                 v = vSub(vAdd(lights[i].where,arealightVectors[j]),hitLoc)
  193.                 lightDist = vLen(v)
  194.                 v = vNorm(v)
  195.                 diffuseEffect = vDot(hitNormal,v) / (numArealightVectors*1.0)
  196.                 attenuation = (1 + ((lightDist/34.0)**2.0))
  197.                 diffuseEffect /= attenuation
  198.                 if diffuseEffect > 1e-3:
  199.                     shadowDist = lightDist - 1e-4
  200.                     t,hi,hd,hl,hn = rayFindObstacle(vAdd(hitLoc,vMulD(v,1e-4)),v,shadowDist)
  201.                     if t == -1: #no obstacle occluding the light
  202.                         diffuseLight = vAdd(diffuseLight,vMulD(lights[i].color,diffuseEffect))
  203.  
  204.         if k > 1:
  205.             # add specular light/reflection, unless recursion depth is maxed
  206.             v = vNeg(dir)
  207.             v = vMirrorAround(v, hitNormal)
  208.             specularLight = rayTrace(vAdd(hitLoc, vMulD(v,1e-4)),v,k-1)
  209.  
  210.         if hitType == 0: #plane
  211.             diffuseLight = vMulD(diffuseLight,0.9)
  212.             specularLight = vMulD(specularLight,0.5)
  213.             # color the different walls differently
  214.             idx = hitIndex % 3
  215.             if   idx == 0: pigment = [0.9,0.7,0.6]
  216.             elif idx == 1: pigment = [0.6,0.7,0.7]
  217.             elif idx == 2: pigment = [0.5,0.8,0.3]
  218.  
  219.         elif hitType == 1: #sphere
  220.             diffuseLight = vMulD(diffuseLight,1.0)
  221.             specularLight = vMulD(specularLight, 0.34)
  222.        
  223.         return vMul(vAdd(diffuseLight,specularLight),pigment)
  224.  
  225.     #didn't hit anything, return black
  226.     return [0.0,0.0,0.0]
  227.  
  228. def getPix(x, y, w, h, zoom, camlookmatrix, campos, MAXTRACE):
  229.  
  230.     camray = [ x/float(w) - 0.5,
  231.                y/float(h) - 0.5,
  232.                zoom ]
  233.     camray[0] *= 4.0/3 # Aspect Ratio Correction
  234.     camray = vNorm(camray)
  235.     camray = mTransform(camlookmatrix, camray)
  236.     campix = rayTrace(campos, camray, MAXTRACE)
  237.  
  238.     campix = vMulD(campix, 0.5) #Adjust brightness
  239.    
  240.     return campix
  241.  
  242. def main():
  243.  
  244.     pygame.display.init()
  245.     screenw, screenh = 640, 480
  246.     w, h = int(sys.argv[1]),int(sys.argv[2])
  247.     screen = pygame.display.set_mode((screenw, screenh), pygame.DOUBLEBUF)
  248.     frame = pygame.Surface((w,h))
  249.        
  250.     camangle =      [  0.0,  0.0,  0.0]
  251.     camangledelta = [-.005,-.011,-.017]
  252.     camlook =       [  0.0,  0.0,  0.0]
  253.     camlookdelta =  [-.001, .005, .004]
  254.  
  255.     zoom = 46.0
  256.     zoomdelta = 0.99
  257.  
  258.     contrast = 32
  259.     contrast_offset = -0.17
  260.  
  261.     frames = []
  262.  
  263.     #numFrames = 9300
  264.     numFrames = int(sys.argv[3])
  265.  
  266.     MAXTRACE = int(sys.argv[4])
  267.    
  268.     pool = Pool(processes=4)
  269.  
  270.     for frameNo in range(numFrames):
  271.         # Put camera between central sphere and walls
  272.         campos = [0.0,0.0,16.0]
  273.         camrotatematrix = vGetRotMatrix(camangle)
  274.         campos = mTransform(camrotatematrix, campos)
  275.         camlookmatrix = vGetRotMatrix(camlook)
  276.  
  277.         # Determine contrast ratio for this frame's pixels
  278.         thisframe_min = 100
  279.         thisframe_max = -100
  280.  
  281.         pixels = pygame.PixelArray(frame)
  282.                
  283.         for y in range(h):
  284.        
  285.             results = []
  286.  
  287.             for x in range(w):
  288.                 result = pool.apply_async(getPix, [x, y, w, h, zoom, camlookmatrix, campos, MAXTRACE])
  289.                 results.append(result)
  290.  
  291.             for x in range(w):
  292.            
  293.                 campix = results[x].get()
  294.                
  295.                 # update frame luma info for automatic contrast adjuster
  296.                 lum = vLuma(campix)
  297.                 if lum < thisframe_min: thisframe_min = lum
  298.                 if lum > thisframe_max: thisframe_max = lum
  299.                
  300.                 # exagerate the colors to bring contrast better forth
  301.                 campix = vMulD(vAddD(campix,contrast_offset),contrast)
  302.  
  303.                 # Clamp, and compensate for display gamma (for dithering)
  304.                 # ...But don't actually dither. Because that shit is bananas.
  305.                 # Maybe later, I can look up the EGA palette, etc.
  306.                 campix = vClampWithDesaturation(campix)
  307.  
  308.                 # Draw pixel (use pygame)
  309.                 r = int(campix[0] * 255)
  310.                 g = int(campix[1] * 255)
  311.                 b = int(campix[2] * 255)
  312.  
  313.                 pixels[x][y] = pygame.Color(r,g,b)
  314.  
  315.                 #del pixels?
  316.                 pygame.transform.scale(frame, (screenw,screenh), screen)
  317.                 pygame.display.flip()
  318.  
  319.                 for e in pygame.event.get():
  320.                     if e.type == pygame.QUIT:
  321.                         pygame.quit()
  322.                         sys.exit()
  323.  
  324.         frames.append(frame.copy())
  325.  
  326.         print 'frame ',frameNo
  327.         sys.stdout.flush()
  328.  
  329.         # Tweak coordinates/camera params for next frame
  330.         much = 1.0
  331.  
  332.         # In the beginning, do some camera action (play with zoom)
  333.         if zoom <= 1.1:
  334.             zoom = 1.1
  335.         else:
  336.             if zoom > 40:
  337.                 if zoomdelta > 0.95:
  338.                     zoomdelta -= 0.001
  339.             elif zoom < 3:
  340.                 if zoomdelta < 0.99:
  341.                     zoomdelta += 0.001
  342.             zoom *= zoomdelta
  343.             much = 1.1 / ((zoom/1.1)**3.0)
  344.  
  345.         # Update rotation angle
  346.         camlook =  vAdd(camlook,  vMulD(camlookdelta,much))
  347.         camangle = vAdd(camangle, vMulD(camangledelta,much))
  348.  
  349.         # dynamically adjust the contrast based on the contents of the
  350.         # last frame
  351.  
  352.         middle = (thisframe_min + thisframe_max) * 0.5
  353.         span = (thisframe_max - thisframe_min)
  354.         thisframe_min = middle - span*0.60 # Avoid dark tones
  355.         thisframe_max = middle + span*0.37 # Emphasize bright tones
  356.  
  357.         new_contrast_offset = -thisframe_min
  358.         new_contrast = 1 / (thisframe_max - thisframe_min)
  359.  
  360.         # Avoid abrupt changes, though
  361.         l = 0.85
  362.         if frameNo == 0: l = 0.7
  363.         contrast_offset = (contrast_offset*l + new_contrast_offset*(1.0-l))
  364.         contrast        = (contrast       *l + new_contrast       *(1.0-l))
  365.                
  366.         sys.exit()
  367.     while True:
  368.         for f in frames:
  369.             pygame.transform.scale(f, (screenw,screenh), screen)
  370.             pygame.display.flip()
  371.             pygame.time.wait(33)
  372.             for e in pygame.event.get():
  373.                 if e.type == pygame.QUIT:
  374.                     pygame.quit()
  375.                     sys.exit()
  376.            
  377. if __name__ == '__main__':
  378.     main()
clone this paste RAW Paste Data