SHARE
TWEET

Untitled

a guest Aug 7th, 2011 274 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import sys, time
  2. from math import sqrt
  3. from math import sin
  4. from math import cos
  5. from math import tan
  6. from math import pi
  7. from math import floor
  8.  
  9. # use PIL if not using java
  10. if 'java' in sys.platform:
  11.     from java.util import Random
  12.     randomObject = Random()
  13.     random = randomObject.nextFloat
  14. else:
  15.     from random import random
  16.  
  17. def DOT(a, b):
  18.     ax,ay,az = a.val
  19.     bx,by,bz = b.val
  20.     return ax*bx + ay*by + az*bz
  21.  
  22. class Image:
  23.     def __init__(self, width, height):
  24.         self.size = (width, height)
  25.         self.grid = []
  26.         for i in xrange(0, width*height):
  27.             self.grid.append((0,0,0))
  28.     def put(self, pos, color):
  29.         x = pos[0]
  30.         y = pos[1]
  31.         self.grid[x + y*self.size[0]] = color
  32.  
  33.     def update(self):
  34.         from PIL import Image as pilImage
  35.         im = pilImage.new("RGB", self.size, "white")
  36.         for x in xrange(0, self.size[0]):
  37.             for y in xrange(0, self.size[1]):
  38.                 color = self.grid[x + y*self.size[1]]
  39. #                print("(%i, %i) - %f %f %f" % (x, y, color[0], color[1], color[2]))
  40.                 r = int(255*color[0])
  41.                 g = int(255*color[1])
  42.                 b = int(255*color[2])
  43.                 im.putpixel((x,y), (r,g,b))
  44.         im.show()
  45.  
  46. class Tuple3(object):
  47.     # make Tuple3 immutable
  48.     def __setattr__(self, *args):
  49.         raise TypeError("Tuple3 immutable")
  50.     __delattr__ = __setattr__
  51.  
  52. #    def __mul__(self, b):
  53. #        ax,ay,az = self.val
  54. #        bx,by,bz = b.val
  55. #        return ax*bx + ay*by + az*bz
  56.     def __init__(self, x, y, z):
  57.         super(Tuple3, self).__setattr__('val', (x,y,z))
  58.  
  59.     def x(self):
  60.         return self.val[0]
  61.     def y(self):
  62.         return self.val[1]
  63.     def z(self):
  64.         return self.val[2]
  65.  
  66. class Vector3(Tuple3):
  67.     def __init__(self, x, y, z):
  68.         Tuple3.__init__(self, x,y,z)
  69.     def length(self):
  70.         x,y,z = self.val
  71.         return sqrt(x*x + y*y + z*z)
  72.     def normalize(self):
  73.         x,y,z = self.val
  74.         inv_l = 1.0 / self.length()
  75.         return Vector3(x*inv_l, y*inv_l, z*inv_l)
  76.     def cross(self, b):
  77.         a = self
  78.         a1,a2,a3 = a.val
  79.         b1,b2,b3 = b.val
  80.         return Vector3(a2*b3 - a3*b2, a3*b1 - a1*b3, a1*b2 - a2*b1)
  81.     def __mul__(self, s):
  82.         x,y,z = self.val
  83.         return Vector3(s*x,s*y,s*z)
  84.     def __sub__(self, b):
  85.         ax,ay,az = self.val
  86.         bx,by,bz = b.val
  87.         return Vector3(ax-bx,ay-by,az-bz)
  88.     def __str__(self):
  89.         x,y,z = self.val
  90.         return "<%f,%f,%f>" % (x,y,z)
  91.  
  92. class Point3(Tuple3):
  93.     def __init__(self, x, y, z):
  94.         Tuple3.__init__(self, x,y,z)
  95.     def __sub__(self, b):
  96.         ax,ay,az = self.val
  97.         bx,by,bz = b.val
  98.         return Vector3(ax-bx,ay-by,az-bz)
  99.     def __add__(self, vec):
  100.         ax,ay,az = self.val
  101.         vx,vy,vz = vec.val
  102.         return Point3(ax+vx, ay+vy, az+vz)
  103.     def __str__(self):
  104.         return "(%f,%f,%f)" % self.val
  105.  
  106. class Color(Tuple3):
  107.     def __init__(self, x, y, z):
  108.         Tuple3.__init__(self, x,y,z)
  109.     def tuple(self):
  110.         return self.val
  111.     def toneMapTuple(self, gamma=1.0):
  112.         x,y,z = self.val
  113.         return (x/(1.0+x), y/(1.0+y), z/(1.0+z))
  114.     def scale(self, s):
  115.         x,y,z = self.val
  116.         return Color(s*x,s*y,s*z)
  117.     def modulate(self, b):
  118.         ax,ay,az = self.val
  119.         bx,by,bz = b.val
  120.         return Color(ax*bx, ay*by, az*bz)
  121.     def __add__(self, b):
  122.         ax,ay,az = self.val
  123.         bx,by,bz = b.val
  124.         return Color(ax+bx, ay+by, az+bz)
  125.  
  126.  
  127.     def __str__(self):
  128.         return "(%f,%f,%f)" % self.val
  129.  
  130. class Ray(object):
  131.     # make Ray immutable
  132.     def __setattr__(self, *args):
  133.         raise TypeError("Ray immutable")
  134.     __delattr__ = __setattr__
  135.  
  136.     def __init__(self, ray_orig, ray_dir, color, t=sys.maxint, hit=None, prim=None, scratch=None, depth=0):
  137.         super(Ray, self).__setattr__('orig', ray_orig)
  138.         super(Ray, self).__setattr__('dir', ray_dir)
  139.         super(Ray, self).__setattr__('color', color)
  140.         super(Ray, self).__setattr__('t', t)
  141.         super(Ray, self).__setattr__('hit', hit)
  142.         super(Ray, self).__setattr__('primitive', prim)
  143.         super(Ray, self).__setattr__('scratch', scratch)
  144.         super(Ray, self).__setattr__('depth', depth)
  145. #    def updateT(self, time, primitive, scr):
  146. #        ray = Ray(self.orig, self.dir, self.color, t=time, prim=primitive, scratch=scr)
  147. #        return ray
  148.     def updateT(self, t, primitive, scr):
  149.         ray = Ray(self.orig, self.dir, self.color, t, primitive, scr)
  150.         return ray
  151.     def surfOffset(self):
  152.         # move ray over to surface and offset a bit
  153.         new_orig = self.orig + self.dir*self.t + self.hit.hitNormal(self)*.000001
  154.         return Ray(new_orig, self.dir, self.color, self.t, self.hit, self.primitive, self.scratch, self.depth+1)
  155.     def reflect(self, normal):
  156.         refl_dir = self.dir - normal * (2*DOT(self.dir, normal))
  157.         ray = Ray(self.orig, refl_dir, self.color)
  158.         return ray
  159.  
  160. class Material:
  161.     def __init__(self):
  162.         pass
  163.     def shade(self):
  164.         raise NotImplementedError("Material shade is virtual and must be overridden.")
  165.  
  166. class CheckerMat3D(Material):
  167.     def __init__(self, color1, color2, scale_x=1, scale_y=1, scale_z=1):
  168.         self.color1 = color1
  169.         self.color2 = color2
  170.     def shade(self, ray):
  171.         pos = ray.hit.hitPosition(ray)
  172.         x,y,z = pos.val
  173.         fx,fy,fz = floor(x), floor(y), floor(z)
  174.         if (fx + fy + fz) % 2 == 0:
  175.             return self.color1
  176.         else:
  177.             return self.color2
  178.  
  179. class MetalMat(Material):
  180.     def shade(self, ray):
  181.         normal = ray.hit.hitNormal(ray)
  182.         ray = ray.surfOffset()
  183.         r_ray = ray.reflect(normal)
  184.         ray = None # to avoid using it for now
  185.  
  186.  
  187. #        print("start")
  188. #        print(ray.orig)
  189. #        print(r_ray.orig)
  190. #        print("end")
  191.  
  192.         if r_ray.depth > 5:
  193.             return r_ray.hit.error_mat.shade(r_ray)
  194.  
  195.         for primitive in scene.geometry:
  196.             r_ray = primitive.intersect(r_ray)
  197.  
  198.             if r_ray.hit is not None:
  199.                 color = r_ray.hit.material.shade(r_ray)
  200.                 return color
  201.         else:
  202.             return Color(0,0,0)
  203.  
  204. class DiffuseMat(Material):
  205.     def __init__(self, color):
  206.         self.color = color
  207.     def shade(self, ray):
  208.         ray = ray.surfOffset()
  209.  
  210.         total_color = Color(0,0,0)
  211.         for light in scene.lights:
  212.             for plight in light.samples():
  213.                 light_dir = plight.position - ray.orig
  214.                 light_dist = light_dir.length()
  215.                 light_dir = light_dir.normalize()
  216.                 light_ray = Ray(ray.orig, light_dir, Color(0,0,0))
  217.  
  218.                 for primitive in scene.geometry:
  219.                     light_ray = primitive.intersect(light_ray)
  220.  
  221.                 # not occluded (in shadow)
  222.                 if light_ray.t > light_dist:
  223. #                if ray.hit is None:
  224.                     light_contrib = plight.color.scale(DOT(light_dir, ray.hit.hitNormal(ray)) * plight.intensity)
  225.                     filter = light_contrib.modulate(self.color)
  226.                     total_color = total_color + filter
  227. #                    color = r_ray.hit.material.shade(r_ray)
  228.         return total_color
  229.  
  230. class Light:
  231.     def __init__(self):
  232.         pass
  233.     def generateRays(self):
  234.         raise NotImplementedError("Light generateRays is virtual and must be overridden.")
  235.  
  236. class PointLight(Light):
  237.     def __init__(self, color, intensity, position):
  238.         self.color = color
  239.         self.intensity = intensity
  240.         self.position = position
  241.     def generateRays(self, num_rays):
  242.         rays = []
  243.         for i in xrange(num_rays):
  244.             # generate a random point on a sphere
  245.             # taken from http://demonstrations.wolfram.com/RandomPointsOnASphere/
  246.             u = 2*random() - 1
  247.             theta = 2*pi*random()
  248.             x = cos(theta)*sqrt(1-(u*u))
  249.             y = sin(theta)*sqrt(1-(u*u))
  250.             z = u
  251.             dir = Vector3(x,y,z)
  252.             rays.append(Ray(self.position, dir.normalize(), self.color))
  253.  
  254.         return rays
  255.     def samples(self):
  256.         return [self]
  257.  
  258. class Primitive:
  259.     def __init__(self):
  260.         self.error_mat = CheckerMat3D(Color(1,0,1), Color(1,1,0))
  261.         self.material = CheckerMat3D(Color(1,0,1), Color(0,1,1))
  262.     def setMaterial(self, mat):
  263.         self.material = mat
  264.     def intersect(self, ray):
  265.         raise NotImplementedError("Primitive intersect is virtual and must be overridden.")
  266.     def hitNormal(self, ray):
  267.         raise NotImplementedError("Primitive hitNormal is virtual and must be overridden.")
  268.     def hitPosition(self, ray):
  269.         return ray.orig + ray.dir*ray.t
  270.  
  271. class Plane(Primitive):
  272.     def __init__(self, normal, d):
  273.         Primitive.__init__(self)
  274.         self.normal = normal.normalize()
  275.         self.d = d
  276.     def intersect(self, ray):
  277.         t = -(DOT(self.normal, ray.orig) + self.d) / (DOT(self.normal, ray.dir))
  278.         if t > 0 and t < ray.t:
  279.             ray = ray.updateT(t, self, t)
  280.         return ray
  281.     def hitNormal(self, ray):
  282.         return self.normal
  283.  
  284. class Sphere(Primitive):
  285.     def __init__(self, center, radius):
  286.         Primitive.__init__(self)
  287.         self.center = center
  288.         self.radius = radius
  289.         self.radiusSq = radius*radius
  290.     def intersect(self, ray):
  291.         CO = self.center - ray.orig
  292.         L2_co = DOT(CO, CO)
  293.         t_ca = DOT(CO, ray.dir)
  294.         if L2_co < self.radiusSq:
  295.             t2_hc = self.radiusSq - L2_co + t_ca*t_ca
  296.             root = t_ca + sqrt(t2_hc)
  297.         else:
  298.             if t_ca < 0:
  299.                 return ray # miss
  300.             else:
  301.                 t2_hc = self.radiusSq - L2_co + t_ca*t_ca
  302.                 if t2_hc < 0:
  303.                     return ray # miss
  304.                 else:
  305.                     root = t_ca - sqrt(t2_hc)
  306.         if root > 0 and root < ray.t:
  307.             ray = ray.updateT(root, self, root)
  308.         return ray
  309.  
  310.     def hitNormal(self, ray):
  311.         return (self.hitPosition(ray) - self.center).normalize()
  312.  
  313. class Box(Primitive):
  314.     def __init__(self, min, max):
  315.         Primitive.__init__(self)
  316.         self.bounds = (min, max)
  317.     def intersect(self, ray):
  318.         # taken from "An Efficient and Robust Ray-Box Intersection Algorithm" by Williams et al
  319.         bounds = self.bounds
  320.         normal = Vector3(0,0,1)
  321.         if ray.dir.x() >= 0:
  322.             tmin = (bounds[0].x() - ray.orig.x()) / ray.dir.x()
  323.             tmax = (bounds[1].x() - ray.orig.x()) / ray.dir.x()
  324.         else:
  325.             tmin = (bounds[1].x() - ray.orig.x()) / ray.dir.x()
  326.             tmax = (bounds[0].x() - ray.orig.x()) / ray.dir.x()
  327.  
  328.         if (ray.dir.y() >= 0):
  329.             tymin = (bounds[0].y() - ray.orig.y()) / ray.dir.y()
  330.             tymax = (bounds[1].y() - ray.orig.y()) / ray.dir.y()
  331.         else:
  332.             tymin = (bounds[1].y() - ray.orig.y()) / ray.dir.y()
  333.             tymax = (bounds[0].y() - ray.orig.y()) / ray.dir.y()
  334.  
  335.         if (tmin > tymax or tymin > tmax):
  336.             return ray
  337.  
  338.         if (tymin > tmin):
  339.             tmin = tymin
  340.             normal = Vector3(0,1,0)
  341.         if (tymax < tmax):
  342.             tmax = tymax
  343.             normal = Vector3(1,0,0)
  344.         if (ray.dir.z() >= 0):
  345.             tzmin = (bounds[0].z() - ray.orig.z()) / ray.dir.z()
  346.             tzmax = (bounds[1].z() - ray.orig.z()) / ray.dir.z()
  347.         else:
  348.             tzmin = (bounds[1].z() - ray.orig.z()) / ray.dir.z()
  349.             tzmax = (bounds[0].z() - ray.orig.z()) / ray.dir.z()
  350.  
  351.         if (tmin > tzmax or tzmin > tmax):
  352.             return ray
  353.         if (tzmin > tmin):
  354.             tmin = tzmin
  355.             normal = Vector3(0,0,1)
  356.         if (tzmax < tmax):
  357.             tmax = tzmax
  358.             normal = Vector3(0,0,1)
  359.  
  360.         if tmin > 0 and tmin < ray.t:
  361.             ray = ray.updateT(tmin, self, (tmin, normal))
  362.  
  363.         return ray
  364.  
  365.     def hitNormal(self, ray):
  366.         return ray.scratch[1]
  367.         return Vector3(1,1,1).normalize()
  368.  
  369. class Camera:
  370.     def __init__(self):
  371.         pass
  372.  
  373. class PinholeCamera(Camera):
  374.     def __init__(self, eye, lookat, up):
  375.         self.eye = eye
  376.         self.lookat = lookat
  377.         self.fov = 45
  378.         self.lookdir = (self.lookat - self.eye).normalize()
  379.         self.right = self.lookdir.cross(up).normalize()
  380.  
  381.         # recalculate eye to be orthogonal to look direction
  382.         self.up = self.right.cross(self.lookdir)
  383.  
  384.     def preprocess(self, dims):
  385.         width, height = dims
  386.         aspect = float(width) / float(height)
  387.         fov_length = 2*tan(self.fov*.5)
  388.         self.u = self.right * fov_length * aspect
  389.         self.v = self.up * fov_length
  390.  
  391.     def computeRay(self, i, j):
  392.         screen = self.eye + self.lookdir + self.u*i + self.v*j
  393.         ray_dir = screen - self.eye
  394.         return Ray(self.eye, ray_dir.normalize(), Tuple3(0,0,0))
  395.        
  396. class PhotonMap:
  397.     def __init__(self):
  398.         self.photons = []
  399.         pass
  400.     def storePhoton(self, photon):
  401.         self.photons.append(photon)
  402.  
  403. class Scene:
  404.     def __init__(self):
  405.         self.lights = []
  406.         self.geometry = []
  407.         self.image = Image(512,512)
  408. #        self.image = Image(256,256)
  409.         self.photon_map = PhotonMap()
  410.     def addLight(self, light):
  411.         self.lights.append(light)
  412.     def addGeometry(self, geometry):
  413.         self.geometry.append(geometry)
  414.     def setCamera(self, camera):
  415.         self.camera = camera
  416.     def render(self):
  417.         # do some preprocessing
  418.         self.camera.preprocess(self.image.size)
  419.  
  420. #        self.image.show()
  421. #        draw = ImageDraw.Draw(self.image)
  422. #        draw.line((0, 0) + self.image.size, fill=128)
  423. #        draw.line((0, self.image.size[1], self.image.size[0], 0), fill=128)
  424. #        del draw
  425. #        self.image.show()
  426.  
  427.         # start the clock
  428.         start = time.time()
  429.  
  430.         # send out photon rays
  431.  
  432.                    
  433.         # render from screen
  434.         print("Rendering image")
  435.         width = self.image.size[0]
  436.         height = self.image.size[1]
  437.         dx = 2.0 / width
  438.         dy = 2.0 / height
  439.         hw = width / 2.0
  440.         hh = height / 2.0
  441.         aspect = float(width) / float(height)
  442.         total_pixels = float(width*height)
  443.         for i in xrange(0,width):
  444.             for j in xrange(0,height):
  445.                 percent_done = 100*(i*height+j)/total_pixels
  446.                 sys.stdout.write('\r working: %.0f%%' % percent_done)
  447.                 sys.stdout.flush()
  448.                 ray = self.camera.computeRay(i*dx-1,j*dy-1)
  449.  
  450.                 for primitive in self.geometry:
  451.                     ray = primitive.intersect(ray)
  452.  
  453.                 if ray.hit is not None:
  454.                     color = ray.hit.material.shade(ray)
  455.                     color = color.toneMapTuple()
  456.                     self.image.put((i,self.image.size[1]-j-1), color)
  457.  
  458.         elapsed = int(time.time() - start)
  459.         hours = elapsed / 360
  460.         elapsed -= 360*hours
  461.         minutes = elapsed / 60
  462.         elapsed -= 60*minutes
  463.         seconds = elapsed
  464.         print("")
  465.         print("Done in %ih %im %is" % (hours, minutes, seconds))
  466.  
  467.         # display on screen
  468.         self.image.update()
  469.  
  470.  
  471.  
  472. # create the scene
  473. scene = Scene()
  474.  
  475. scene.addLight(PointLight(Color(1,1,1), 100, Point3(0,10,0)))
  476.  
  477. plane1 = Plane(Vector3(0,1,0), 0.001)
  478. plane1.setMaterial(DiffuseMat(Color(1,1,0)))
  479. scene.addGeometry(plane1)
  480.  
  481. sphere1 = Sphere(Point3(0,2,0), 2)
  482. sphere1.setMaterial(MetalMat())
  483. scene.addGeometry(sphere1)
  484.  
  485. sphere2 = Sphere(Point3(-3,2,3), 2)
  486. sphere2.setMaterial(CheckerMat3D(Color(1,0,0), Color(0,0,1)))
  487. scene.addGeometry(sphere2)
  488.  
  489. scene.setCamera(PinholeCamera(Point3(8,8,8),
  490.                               Point3(0,0,0),
  491.                               Vector3(0,1,0)))
  492. #cProfile.run('scene.render()', 'profile')
  493.  
  494. scene.render()
RAW Paste Data
Top