• API
• FAQ
• Tools
• Trends
• Archive
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)
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)
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
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
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):
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:
194.
195.         for primitive in scene.geometry:
196.             r_ray = primitive.intersect(r_ray)
197.
198.             if r_ray.hit is not None:
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
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
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):
286.         Primitive.__init__(self)
287.         self.center = center
290.     def intersect(self, ray):
291.         CO = self.center - ray.orig
292.         L2_co = DOT(CO, CO)
293.         t_ca = DOT(CO, ray.dir)
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()
411.         self.lights.append(light)
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:
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.
476.
477. plane1 = Plane(Vector3(0,1,0), 0.001)
478. plane1.setMaterial(DiffuseMat(Color(1,1,0)))
480.
481. sphere1 = Sphere(Point3(0,2,0), 2)
482. sphere1.setMaterial(MetalMat())