#tiny-tracer-no-vector-class. 2x speedup. #original pastebin: http://pastebin.com/F8f5GHJZ #original thread: http://www.reddit.com/r/tinycode/comments/169ri9/ray_tracer_in_140_sloc_of_python_with_picture/ from math import sqrt, pow, pi import Image, sys 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 sqrt(vSqr(a)) def vNorm(a): return vMulD(a, 1.0/vLen(a)) class Sphere( object ): def __init__(self, center, radius, color): self.c = center self.r = radius self.col = color def intersection(self, l): q = vDot(l.d, vSub(l.o, self.c))**2 - vSqr(vSub(l.o, self.c)) + self.r**2 if q < 0: return Intersection( [0,0,0], -1, [0,0,0], self) else: d = -vDot(l.d, vSub(l.o, self.c)) d1 = d - sqrt(q) d2 = d + sqrt(q) if 0 < d1 and ( d1 < d2 or d2 < 0): return Intersection(vAdd(l.o,vMulD(l.d,d1)), d1, self.normal(vAdd(l.o,vMulD(l.d,d1))), self) elif 0 < d2 and ( d2 < d1 or d1 < 0): return Intersection(vAdd(l.o,vMulD(l.d,d2)), d2, self.normal(vAdd(l.o,vMulD(l.d,d2))), self) else: return Intersection( [0,0,0], -1, [0,0,0], self) def normal(self, b): return vNorm(vSub(b,self.c)) class Plane( object ): def __init__(self, point, normal, color): self.n = normal self.p = point self.col = color def intersection(self, l): d = vDot(l.d, self.n) if d == 0: return Intersection( [0,0,0], -1, vector[0,0,0], self) else: d = vDot(vSub(self.p,l.o),self.n) / d return Intersection(vAdd(l.o,vMulD(l.d,d)), d, self.n, self) class Ray( object ): def __init__(self, origin, direction): self.o = origin self.d = direction class Intersection( object ): def __init__(self, point, distance, normal, obj): self.p = point self.d = distance self.n = normal self.obj = obj def testRay(ray, objects, ignore=None): intersect = Intersection( [0,0,0], -1, [0,0,0], None) for obj in objects: if obj is not ignore: currentIntersect = obj.intersection(ray) if currentIntersect.d > 0 and intersect.d < 0: intersect = currentIntersect elif 0 < currentIntersect.d < intersect.d: intersect = currentIntersect return intersect def trace(ray, objects, light, maxRecur): if maxRecur < 0: return [0,0,0] intersect = testRay(ray, objects) if intersect.d == -1: col = [AMBIENT,AMBIENT,AMBIENT] elif vDot(intersect.n, vSub(light,intersect.p)) < 0: col = vMulD(intersect.obj.col, AMBIENT) else: lightRay = Ray(intersect.p, vNorm(vSub(light,intersect.p))) if testRay(lightRay, objects, intersect.obj).d == -1: lightIntensity = 1000.0/(4*pi*vLen(vSub(light,intersect.p))**2) col = vMulD(intersect.obj.col, max(vDot(vNorm(intersect.n),vMulD(vNorm(vSub(light,intersect.p)),lightIntensity)), AMBIENT)) else: col = vMulD(intersect.obj.col, AMBIENT) return col def gammaCorrection(color,factor): return (int(pow(color[0]/255.0,factor)*255), int(pow(color[1]/255.0,factor)*255), int(pow(color[2]/255.0,factor)*255)) AMBIENT = 0.1 GAMMA_CORRECTION = 1/2.2 MAX_RECURSION = 10 #these are left global so we don't have to pass them to apply_async each pixel objs = [ Sphere([-2, 0,-10], 2, [0,255,0]), Sphere([ 2, 0,-10], 3.5, [255,0,0]), Sphere([ 0,-4,-10], 3, [0,0,255]), Plane([0,0,-12], [0,0,1], [255,255,255]) ] lightSource = [0,10,0] cameraPos = [0,0,20] def main(): img = Image.new("RGB",(500,500)) for x in range(500): results = [] for y in range(500): ray = Ray(cameraPos, vNorm(vSub([x/50.0-5, y/50.0-5, 0], cameraPos))) col = trace(ray, objs, lightSource, MAX_RECURSION) img.putpixel((x,499-y),gammaCorrection(col,GAMMA_CORRECTION)) print x sys.stdout.flush() img.save("trace.bmp","BMP") if __name__ == '__main__': main()