#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()