def __init__(self, center, radius, color=Color()): self._pp = Plane(Vector(), Vector(), Color()) self._pn = Plane(Vector(), Vector(), Color()) Sphere.__init__(self, center, radius, color) self.set_reflectivity(0.2) self.set_orientation(Vector()) self.set_cosine(1.0)
def __init__(self, center = Vector(0.0,0.0,0.0), radius = 1.0, color = Color(), orientation = Vector(0.0,1.0,0.0)): Sphere.__init__(self, center, radius, color) self.set_orientation(orientation) self.set_reflectivity(0.9,0.1)
def __init__(self, normal = Vector(0.0,1.0,0.0), origin = Vector(0.0,0.0,0.0), orientation = Vector(1.0,0.0,0.0), c1 = Color(0.01,0.01,0.01), c2 = Color(0.99,0.99,0.99)): """Initializes plane and plane colors.""" Plane.__init__(self, normal, origin) self.set_orientation(orientation) self.set_colors(c1,c2)
def __init__(self, r=1.0, normal=Vector(0.0, 1.0, 0.0), origin=Vector(0.0, 0.0, 0.0), orientation=Vector(1.0, 0.0, 0.0), c1=Color(0.01, 0.01, 0.01), c2=Color(0.99, 0.99, 0.99)): """Initializes plane and plane colors.""" CheckPlane.__init__(self, normal, origin, orientation, c1, c2) self.origin = origin self.set_orientation(orientation) self.r = r self.R = r**2.0
def __init__(self, origin=Vector(0.0, 1.0, 0.0), focus=Vector(0.0, 0.0, 1.0), width=2.0, height=2.0, up=Vector(0.0, 1.0, 0.0)): """Instantiates new object.""" self._init = False self.set_position(origin) self.set_focus(focus) self.set_orientation(up) self.set_window(width, height) self.set_ppu() self.set_position_delta(None) self._init = True
def run(): for r in rays: i.reset() for b in bodies: d = b.intersection(r) if d > 0.0: i.hit(b, d) i.register_hit(r) if i._body == None: print "interface didn't register a hit" else: if i._distance < 0.0: print "interface registered hit at negative distance" if i._distance > 0.1: print "interface didn't catch closest hit" if i._body.p() != 'bodystub': print "interface didn't catch body of correct type" if not i._normal == Vector(0.0, 0.0, 1.0): print "interface caught wrong surface normal." if not i._color == Color(0.5, 0.5, 0.5): print "interface caught wrong surface color."
from pytrace import World, Camera, Tracer from py3D import Color, Vector from py3D import Plane, CheckPlane, Sphere win_w = 6.4 win_h = 4.0 ppu = 25 passes = 1 filename = 'room-{0}x{1}.png'.format(int(win_w * ppu), int(win_h * ppu)) ball = Sphere(Vector(0.0, 0.0, 0.0), 1.0, Color(0.1, 0.1, 0.1)) ball.set_reflectivity(0.6) floor = CheckPlane().set_reflectivity(0.0) wall_color = Color(0.1, 0.1, 0.1) n_wall = Plane(Vector(0.0, 0.0, -1.0), Vector(0.0, 0.0, 6.0), wall_color.dup()) s_wall = Plane(Vector(0.0, 0.0, 1.0), Vector(0.0, 0.0, -6.0), wall_color.dup()) w_wall = Plane(Vector(-1.0, 0.0, 0.0), Vector(6.0, 0.0, 0.0), wall_color.dup()) e_wall = Plane(Vector(1.0, 0.0, 0.0), Vector(-6.0, 0.0, 0.0), wall_color.dup()) ceiling = Plane(Vector(0.0, -1.0, 0.0), Vector(0.0, 6.0, 0.0), wall_color.dup()) bodies = [n_wall, s_wall, w_wall, e_wall, ceiling] for b in bodies: b.set_reflectivity(0.6) bodies.append(ball) bodies.append(floor) world = World(bodies).set_base_brightness(0.9)
class World: """This class holds body/camera/view information and performs raytrace.""" _bodies = [] _sky = Color() _interface = Interface() _light = Vector().norm() _base_brightness = 0.2 def __init__(self, bodies=[], sky=Sky()): self._bodies = bodies self._sky = sky self._light = sky.get_light() interface = Interface() self._base_brightness = 0.2 def add_body(self, body): self._bodies.append(body) return self def set_sky(self, sky): self._sky = sky return self def get_sky(self, ray): return self._sky.get_color(ray) def get_light(self): return self._light.dup() def set_base_brightness(self, b): self._base_brightness = b return self def trace(self, ray, last_hit=None): """Finds first interaction of a ray within the world.""" self._interface.reset() for bodies in self._bodies: # check for intersection of ray with body distance = bodies.intersection(ray) if distance < 0.0: continue # move on if we're interacting with our starting point if bodies == last_hit and distance < bounds.too_close: continue # if we've got a hit, register it if it's closer self._interface.hit(bodies, distance) # end for # register the rest of our interface self._interface.register_hit(ray) return self._interface def shade(self, interface): """Detects amount of illumination at point.""" ray = Ray(interface.poi, self.get_light()) lambertian = self._light.dot(interface.normal) if lambertian < 0.0: return lambertian for bodies in self._bodies: distance = bodies.intersection(ray) if distance < 0.0: continue if bodies != interface.body or distance > bounds.too_small: lambertian = -1.0 break return lambertian def highlight(self, lambertian, ray, interface): if lambertian < 0.0: return interface.color.dim(self._base_brightness) lambertian = max(lambertian, self._base_brightness) ray.reflect(i.poi, i.normal) highlight = max(0.0, ray.d.dot(self.get_light())) highlight **= i.exp return i.color.dim(L).gamma(1 - highlight) def sample(self, ray, index=1.0, depth=0, last_hit=None): """Recursively traces ray within world.""" # check trace depth boundary if depth == bounds.max_depth: return Color(0.001, 0.001, 0.001) # get a trace interface i = self.trace(ray, last_hit) # detect hitting the sky if i.body == None: return self.get_sky(ray) # shade point L = self.shade(i) # add specular highlight if matte surface if i.matte: return self.highlight(L, ray, i) # if not matte, we do specular reflection else: if L < self._base_brightness: L = self._base_brightness cos_i = abs(ray.d.dot(i.normal)) R = i.body.reflectivity(i.poi) D = 1 - R Ps = R + D * ((1 - cos_i)**i.exp) Pt = 1.0 - Ps color = i.color.dim(L).dim(Pt) return color + self.sample(ray.reflect(i.poi, i.normal), index, depth + 1, i.body).dim(Ps)
def __init__(self, light=Vector(0.0, 1.0, 0.0), color=Color(0.2, 0.2, 0.9)): Sky.__init__(self, light) self.color = color self._exp = _default_exponent
def run(): d = 0.0001 zero = Vector(0.0,0.0,0.0) up = Vector(0.0,1.0,0.0) left = Vector(1.0,0.0,0.0) forward = Vector(0.0,0.0,1.0) # test __init__() method (and implicitly: length() method) u = Vector(3.0,4.0,1.0) dx,dy,dz = u.x - 3.0, u.y - 4.0, u.z - 1.0 if dx > d or dy > d or dz > d: print "Constructor: bad coordinates" if abs( u.length() - (26.0 ** 0.5) ) > d: print "Constructor: bad length" del u # test __sub__() method u = Vector(1.5, 2.5, 3.5) v = Vector(3.5, 2.5, 1.5) w = u - v x = Vector( -2.0, 0.0, 2.0 ) if abs((w - x).length()) > d: print "__sub__ method: incorrect coordinates" if abs( w.length() - (8 ** 0.5) ) > d: print "__sub__ method: incorrect length." del u,v,w,x # test __eq__() method u = Vector() v = u.dup() if not u == v: print "__eq__() method: not evaluating true correctly" v.trans(1.0,1.0,1.0) if u == v: print "__eq__() method: not evaluating false correctly" del u,v # test dup() method u = Vector(1.0,2.0,3.0) v = u.dup() if (u - v).length() > d: print "dup() method: bad coordinates" v.x = 4.0 if (u - v).length() < 1.0: print "dup() method: not a new instance" del u,v # test copy() method u = Vector(1.0,2.0,3.0) v = Vector() v.copy(u) if (u - v).length() > d: print "copy() method: bad coordinates" if abs(u.length() - v.length()) > d: print "copy() method: bad length" del u,v # test add() method u = Vector(-1.0,1.0,3.0) v = Vector(-2.0,2.0,3.0) w = Vector() for s in [-2.0, -1.0, 0.0, 1.0, 2.0]: w.copy(u) w.add(v, s) x = Vector(u.x + s * v.x, u.y + s * v.y, u.z + s * v.z) if (w - x).length() > d: print "add() method: bad coordinates with scalar", s if abs(w.length() - x.length()) > d: print "add() method: bad length with scalar", s del x del s,u,v,w # test scale() method u = Vector(1.0,2.0,3.0) for s in [-2.0, -1.0, 0.0, 1.0, 2.0]: v = Vector( s * 1.0, s * 2.0, s * 3.0 ) w = u.dup().scale(s) if (w - v).length() > d: print "scale() method: bad coordinates with scalar", s if abs(v.length() - w.length()) > d: print "scale() method: bad length with scalar", s del v del w del s,u # test trans() method u = Vector(1.0,2.0,3.0) v = Vector(-1.0,5.0,0.0) u.trans(-2.0,3.0,-3.0) if (u - v).length() > d: print "trans() method: bad coordinates" if abs(u.length() - v.length()) > d: print "trans() method: bad length" del u,v # test norm() method u = Vector(-3.0,0.0,4.0) v = u.dup().norm() if abs(v.length() - 1.0) > d: print "norm() method: didn't normalize to length 1.0" if (v.scale(u.length()) - u).length() > d: print "norm() method: didn't maintain direction" del u,v # test dot() method for v in [up, left,forward]: if abs(v.dot(zero)) > d: print "dot() method: handles zero vector poorly" for v in [left,forward]: if abs(up.dot(v)) > d: print "dot() method: handles orthogonal axis vectors poorly" if abs(left.dot(forward)) > d: print "dot() method: handles orthogonal axis vectors poorly" u = Vector(2.0,2.0,2.0).norm() v = Vector(1.0,1.0,-2.0).norm() if abs(u.dot(v)) > d: print "dot() method: handles orthogonal vectors poorly" del u,v u = Vector(3.0,-2.0,-5.0) v = Vector(7.0,-1.0,2.0) if abs(u.dot(v) - 13.0) > d: print "dot() method: bad non-orthogonal dot products" del u,v # test cross() method if (left.cross(up) - forward) > d: print "cross() method: fails on axis vectors" u = Vector(1.0,2.0,3.0) v = Vector(-1.0,3.0,2.0) w = u.cross(v) if abs(u.dot(w)) > d or abs(v.dot(w)) > d: print "cross() method: result not perpendicular" exit() del u,v,w # test delta() method u = Vector(-3.0,-2.0,-1.0) for translations in range(3): for delt in [0.001, 0.01, 0.1, 1.0, 10.0]: v = u.dup().delta(delt) if abs( (v - u).length() - delt ) > delt: print "delta() method: wrong delta distance" u.trans(1.0,1.0,1.0) del u,v
def run(): sky = Sky(Vector(1.0,0.0,0.0)) world = World([],sky) cam = Camera(Vector(), Vector(1.0,0.0,0.0)) cam.set_ppu(20) Tracer(world, cam).draw(1).write()
def __init__(self, light=Vector(0.0, 1.0, 0.0)): Sky.__init__(self, light)
def normal(self, point): return Vector(0.0, 0.0, 1.0)
def normal(self, point): return Vector(0.0, 0.0, 1.0) def reflectivity(self, point): return 0.5 def intersection(self, ray): d = (ray.o - self.center).length() if d < 1.0: return d else: return -1.0 ray_origins = [ Vector(-3.0, -2.0, -1.0), Vector(-2.0, -1.0, 0.0), Vector(-1.0, 0.0, 1.0), Vector(0.0, 1.0, 2.0), Vector(1.0, 2.0, 3.0) ] ray_directions = [ Vector(-3.0, -2.0, -1.0).norm(), Vector(-2.0, -1.0, 0.0).norm(), Vector(-1.0, 0.0, 1.0).norm(), Vector(0.0, 1.0, 2.0).norm(), Vector(1.0, 2.0, 3.0).norm() ] rays = [] bodies = [] for origins in ray_origins:
def run(): small = 0.00001 zero = Vector(0.0,0.0,0.0) up = Vector(0.0,1.0,0.0) ray_origins = [ Vector(-3.0,-2.0,-1.0), Vector(-2.0,-1.0,0.0), Vector(-1.0,0.0,1.0), Vector(0.0,1.0,2.0), Vector(1.0,2.0,3.0)] ray_directions = [ Vector(-3.0,-2.0,-1.0).norm(), Vector(-2.0,-1.0,0.0).norm(), Vector(-1.0,0.0,1.0).norm(), Vector(0.0,1.0,2.0).norm(), Vector(1.0,2.0,3.0).norm()] # test initialization for o in ray_origins: for d in ray_directions: ray = Ray(o,d) if not ray.o == o: print "__init__() method: set origin incorrectly" if not ray.d == d: print "__init__() method: set direction incorrectly" del ray # test ray equality bad_ray = Ray(ray_origins[-1], ray_directions[-1]) for o in ray_origins: for d in ray_directions: ray = Ray(o.dup(),d.dup()) good_ray = Ray(o.dup(),d.dup()) if not ray == good_ray: print "__eq__() method: evaluates true wrong" if ray == bad_ray: print "__eq__() method: evaluates false wrong" bad_ray.d = d.dup() bad_ray.o = o.dup() # test set_origin() method ray = Ray(zero.dup(), up.dup()) for o in ray_origins: ray.set_origin(o) if not ray.o == o: print "set_origin() method: set bad origin" del ray # test set_direction() method ray = Ray(zero.dup(),up.dup()) for d in ray_directions: ray.set_direction(d.dup()) if not ray.d == d: print "set_direction() method: set bad direction" # test follow() method distances = [-3,-2,-1,0,1,2,3] for t in distances: for o in ray_origins: for d in ray_directions: p = Ray(o.dup(),d.dup()).follow(t) if not p == o.dup().add(d, t): print "follow() method: didn't follow right" # test reflect() method for poi in ray_origins: ray = Ray(zero.dup(),poi.dup()) for normal in ray_directions: bounce = ray.dup() bounce.reflect(poi.dup(), normal.dup()) o_sine = ray.d.dot(normal) r_sine = bounce.d.dot(normal) if abs( o_sine + r_sine ) > small: print "reflect() method: exit not at same angle to normal" print " poi: ", o.p() print " normal:", d.p() print " rayout:", bounce.d.p() print " o_sine:", o_sine print " r_sine:", r_sine
class TruncSphere(Sphere): center = Vector() radius = 0.0 R = 0.0 color = [0.01, 0.01, 0.01] def p(self): """Returns the name of the type of body this is.""" return 'TrunkSphere' def normal(self, point): """Returns normal vector of body at given point.""" s = point - self.center if abs(s.dot(s) - self.R) < bounds.small: return s.norm() else: if s.dot(self._orientation) > 0.0: sign = 1 else: sign = -1 return self._orientation.dup().scale(sign) def set_color(self, c): self.color = c self._pp.set_color(c) self._pn.set_color(c) return self def set_reflectivity(self, r): r = max(0.0, min(1.0, r)) self._r = r self._pp.set_reflectivity(r) self._pn.set_reflectivity(r) return self def set_orientation(self, v): self._orientation = v.norm() self._pp.set_normal(v.dup()) self._pn.set_normal(v.dup().scale(-1.0)) return self def set_cosine(self, c): self._cosine = c cp = self.center.dup().add(self._orientation, c * self.radius) cn = self.center.dup().add(self._orientation, -1 * c * self.radius) self._pp.set_position(cp) self._pn.set_position(cn) return self def __init__(self, center, radius, color=Color()): self._pp = Plane(Vector(), Vector(), Color()) self._pn = Plane(Vector(), Vector(), Color()) Sphere.__init__(self, center, radius, color) self.set_reflectivity(0.2) self.set_orientation(Vector()) self.set_cosine(1.0) def intersection(self, ray): """Returns distance from ray to closest intersection with sphere.""" S = ray.o - self.center SD = S.dot(ray.d) SS = S.dot(S) # no hit if sphere is really far away if SS > bounds.too_far**2: return -1.0 radical = SD**2 + self.R - SS # negative radical implies no solutions if radical < 0.0: return -1.0 radical **= 0.5 hits = [-1 * SD - radical, -1 * SD + radical] if hits[0] < bounds.too_close: if hits[1] < bounds.too_small: return -1.0 pp = self._pp.intersection(ray) pn = self._pn.intersection(ray) if pp < pn: hitp = [pp, pn] else: hitp = [pn, pp] # if two plane hits before sphere hits, we miss if hitp[1] < hits[0]: return -1.0 # if two sphere hits before plane hits, we miss if hits[1] < hitp[0]: return -1.0 # if the second thing hit is forward, that's our distance hit = max(hits[0], hitp[0]) if hit > 0: return hit # otherwise it's the third hit (if positive) hit = min(hits[1], hitp[1]) if hit > 0: return hit # otherwise, we didn't hit anything else: return -1.0
from py3D import Vector, Ray, Sphere, hmSphere, Plane, CheckPlane, Color, Sky from pytrace import World, Image, rand from time import time filename = 'sample-09.png' width = 400 height = width s = hmSphere(Vector(0.0, 1.0, 0.0), 1.0, Color(0.001, 0.99, 0.25), Vector(10.0, 1.0, 0.0)) p = CheckPlane() w = World() w.add_body(s) w.add_body(p) w.set_sky(Sky(Vector(1.0, 10.0, 1.0), Color(0.2, 0.2, 0.8))) camera = Vector(0.0, 0.8, 16.0) c_dir = Vector(0.0, 0.0, -1.0).norm() c_up = Vector(0.0, 1.0, 0.0).norm() c_origin = Vector(-1.5, 2.3, 0.0) - camera c_width = 3.0 c_height = 3.0 c_x = c_dir.cross(c_up).scale(c_width / width) c_y = c_up.dup().scale(-1 * c_height / height) ray = Ray() image = Image(width, height) color = Color() loops_per_pixel = 16 t_0 = time()
def run(): d = 0.0001 zero = Vector(0.0, 0.0, 0.0) up = Vector(0.0, 1.0, 0.0) left = Vector(1.0, 0.0, 0.0) forward = Vector(0.0, 0.0, 1.0) # test __init__() method (and implicitly: length() method) u = Vector(3.0, 4.0, 1.0) dx, dy, dz = u.x - 3.0, u.y - 4.0, u.z - 1.0 if dx > d or dy > d or dz > d: print "Constructor: bad coordinates" if abs(u.length() - (26.0**0.5)) > d: print "Constructor: bad length" del u # test __sub__() method u = Vector(1.5, 2.5, 3.5) v = Vector(3.5, 2.5, 1.5) w = u - v x = Vector(-2.0, 0.0, 2.0) if abs((w - x).length()) > d: print "__sub__ method: incorrect coordinates" if abs(w.length() - (8**0.5)) > d: print "__sub__ method: incorrect length." del u, v, w, x # test __eq__() method u = Vector() v = u.dup() if not u == v: print "__eq__() method: not evaluating true correctly" v.trans(1.0, 1.0, 1.0) if u == v: print "__eq__() method: not evaluating false correctly" del u, v # test dup() method u = Vector(1.0, 2.0, 3.0) v = u.dup() if (u - v).length() > d: print "dup() method: bad coordinates" v.x = 4.0 if (u - v).length() < 1.0: print "dup() method: not a new instance" del u, v # test copy() method u = Vector(1.0, 2.0, 3.0) v = Vector() v.copy(u) if (u - v).length() > d: print "copy() method: bad coordinates" if abs(u.length() - v.length()) > d: print "copy() method: bad length" del u, v # test add() method u = Vector(-1.0, 1.0, 3.0) v = Vector(-2.0, 2.0, 3.0) w = Vector() for s in [-2.0, -1.0, 0.0, 1.0, 2.0]: w.copy(u) w.add(v, s) x = Vector(u.x + s * v.x, u.y + s * v.y, u.z + s * v.z) if (w - x).length() > d: print "add() method: bad coordinates with scalar", s if abs(w.length() - x.length()) > d: print "add() method: bad length with scalar", s del x del s, u, v, w # test scale() method u = Vector(1.0, 2.0, 3.0) for s in [-2.0, -1.0, 0.0, 1.0, 2.0]: v = Vector(s * 1.0, s * 2.0, s * 3.0) w = u.dup().scale(s) if (w - v).length() > d: print "scale() method: bad coordinates with scalar", s if abs(v.length() - w.length()) > d: print "scale() method: bad length with scalar", s del v del w del s, u # test trans() method u = Vector(1.0, 2.0, 3.0) v = Vector(-1.0, 5.0, 0.0) u.trans(-2.0, 3.0, -3.0) if (u - v).length() > d: print "trans() method: bad coordinates" if abs(u.length() - v.length()) > d: print "trans() method: bad length" del u, v # test norm() method u = Vector(-3.0, 0.0, 4.0) v = u.dup().norm() if abs(v.length() - 1.0) > d: print "norm() method: didn't normalize to length 1.0" if (v.scale(u.length()) - u).length() > d: print "norm() method: didn't maintain direction" del u, v # test dot() method for v in [up, left, forward]: if abs(v.dot(zero)) > d: print "dot() method: handles zero vector poorly" for v in [left, forward]: if abs(up.dot(v)) > d: print "dot() method: handles orthogonal axis vectors poorly" if abs(left.dot(forward)) > d: print "dot() method: handles orthogonal axis vectors poorly" u = Vector(2.0, 2.0, 2.0).norm() v = Vector(1.0, 1.0, -2.0).norm() if abs(u.dot(v)) > d: print "dot() method: handles orthogonal vectors poorly" del u, v u = Vector(3.0, -2.0, -5.0) v = Vector(7.0, -1.0, 2.0) if abs(u.dot(v) - 13.0) > d: print "dot() method: bad non-orthogonal dot products" del u, v # test cross() method if (left.cross(up) - forward) > d: print "cross() method: fails on axis vectors" u = Vector(1.0, 2.0, 3.0) v = Vector(-1.0, 3.0, 2.0) w = u.cross(v) if abs(u.dot(w)) > d or abs(v.dot(w)) > d: print "cross() method: result not perpendicular" exit() del u, v, w # test delta() method u = Vector(-3.0, -2.0, -1.0) for translations in range(3): for delt in [0.001, 0.01, 0.1, 1.0, 10.0]: v = u.dup().delta(delt) if abs((v - u).length() - delt) > delt: print "delta() method: wrong delta distance" u.trans(1.0, 1.0, 1.0) del u, v
class Plane(Body): _normal = Vector(0.0, 1.0, 0.0) _origin = Vector() _color = [0.05, 0.05, 0.05] def p(self): """Returns the name of the type of body this is.""" return 'Plane' def set_position(self, p): self._origin = p return self def set_color(self, c): self._color = c return self def get_color(self, point): """Returns color of body at given point.""" return self._color.dup() def set_normal(self, n): self._normal = n.norm() return self def normal(self, point): """Returns normal vector of body at given point.""" return self._normal.dup() def set_reflectivity(self, r): self._r = r return self def reflectivity(self, point): """Returns percentage of brightness due to specular reflection.""" return self._r def __init__(self, normal, origin, color=Color(0.001, 0.001, 0.001)): Body.__init__(self) self.set_normal(normal) self.set_position(origin) self.set_color(color) self.set_reflectivity(0.2) # intersection of a ray with a plane is pretty easy: # * first, find the projection of ray direction onto the normal # * this is the portion of the velocity on the shortest path to plane # * divide the length of the shortest vector from plane to ray origin # by the length of the projection vector above # * get this vector by projecting ray origin minus plane origin onto # the plane normal # * now you have the distance along the ray to the point of intersection! # * this distance might be negative. Take this into account. def intersection(self, ray): """Returns positive distance to target (or negative if no hit).""" d_proj = self._normal.dot(ray.d) if abs(d_proj) < bounds.too_small: return -1.0 s_proj = (self._origin - ray.o).dot(self._normal) if d_proj * s_proj < 0.0: # ray going away from plane return -1.0 else: return s_proj / d_proj
def __init__(self, light=Vector(0.0, 1.0, 0.0)): self.light = light.norm()