def is_shadowed(world, light_position, point): """ >>> w = default_world() >>> p = point(0,10,0) >>> is_shadowed(w, p) False >>> w = default_world() >>> p = point(10,-10,10) >>> is_shadowed(w, p) True >>> w = default_world() >>> p = point(-20,20,-20) >>> is_shadowed(w, p) False >>> w = default_world() >>> p = point(-2,2,-2) >>> is_shadowed(w, p) False """ v = light_position - point distance = magnitude(v) direction = normalize(v) r = shapes.ray(point, direction) intersections = intersect_world(world, r) intersections = [ i for i in intersections if i.object.material.casts_shadow ] h = shapes.hit(intersections) return h is not None and h.t < distance
def get_eccentricities(self, start, end): """"Returns: an array of the eccentricities of the contained geometry Notes: eccentricity is a number representing how close a ray is to intersecting the object, 0.0 is the edge of the object, more positive numbers indicate we're further from intersection, more negative numbers mean the intersection is less avoidable """ ray = shapes.ray(start, end-start) return [ s.eccentricity(ray) for s in self.collides ]
def reflected_color(world, comps, remaining=5): """ >>> w = default_world() >>> r = ray(point(0,0,0), vector(0,0,1)) >>> shape = w.contains[1] >>> shape.material.ambient = 1 >>> i = intersection(1, shape) >>> xs = intersections(i) >>> comps = prepare_computations(i, r, xs) >>> c = reflected_color(w, comps) >>> np.isclose(c, color(0,0,0)) array([ True, True, True]) >>> w = default_world() >>> shape = plane() >>> shape.material.reflective = 0.5 >>> shape.transform = translation(0, -1, 0) >>> w.contains.append(shape) >>> r = ray(point(0,0,-3), vector(0,-np.sqrt(2)/2, np.sqrt(2)/2)) >>> i = intersection(np.sqrt(2), shape) >>> xs = intersections(i) >>> comps = prepare_computations(i, r, xs) >>> c = reflected_color(w, comps) >>> w.contains = w.contains[:-1] >>> len(w.contains) == 2 True >>> np.isclose(c, color(0.19033077, 0.23791346, 0.14274808)) array([ True, True, True]) >>> w = default_world() >>> shape = plane() >>> shape.material.reflective = 0.5 >>> shape.transform = translation(0, -1, 0) >>> w.contains.append(shape) >>> r = ray(point(0,0,-3), vector(0,-np.sqrt(2)/2, np.sqrt(2)/2)) >>> i = intersection(np.sqrt(2), shape) >>> xs = intersections(i) >>> comps = prepare_computations(i, r, xs) >>> c = reflected_color(w, comps, 0) >>> w.contains = w.contains[:-1] >>> len(w.contains) == 2 True >>> np.isclose(c, color(0,0,0)) array([ True, True, True]) """ if remaining == 0 or comps.object.material.reflective == 0: return color(0, 0, 0) reflect_ray = shapes.ray(comps.over_point, comps.reflectv) c = color_at(world, reflect_ray, remaining - 1) return c * comps.object.material.reflective
def is_blocked(self, start, end): """Returns: boolean value indicating whether is any geometry between start and end Notes: More highly optimized than standard collide """ #be careful with length, ray will normalize its direction direct = end - start direct_mag2 = direct.mag_squared() ray = shapes.ray(start, direct) for x in self.collides: coll = x.collide(ray) if coll and coll.t**2 < direct_mag2: return True return False
def render_ray(self, xfactor, yfactor): "Renders a single ray at the fraction of the screen specified" #generate ray for this pixel d = ( self.look + self.right * xfactor * self.tanfov + self.up * -yfactor * self.tanfov ) r = shapes.ray(self.pos, d) intersect = self.scene.collide( r ) #see if we managed to hit anything if not intersect: self.intersect_info = () return vector3() color = intersect.surf.m.color( self.scene, intersect, self.pos ) self.intersect_info = intersect.surf.m.info return color
def ray_for_pixel(cam, px, py): """ >>> c = camera(201, 101, np.pi/2) >>> r = ray_for_pixel(c, 100, 50) >>> r.origin.compare(point(0,0,0)) True >>> r.direction.compare(vector(0,0,-1)) True >>> c = camera(201, 101, np.pi/2) >>> r = ray_for_pixel(c, 0, 0) >>> r.origin.compare(point(0,0,0)) True >>> r.direction.compare(vector(0.66519, 0.33259, -0.66851)) True >>> c = camera(201, 101, np.pi/2) >>> c.transform = matrix_multiply(rotation_y(np.pi/4), translation(0,-2,5)) >>> r = ray_for_pixel(c, 100, 50) >>> r.origin.compare(point(0,2,-5)) True >>> r.direction.compare(vector(np.sqrt(2)/2, 0, -np.sqrt(2)/2)) True """ xoffset = (px + 0.5) * cam.pixel_size yoffset = (py + 0.5) * cam.pixel_size world_x = cam.half_width - xoffset world_y = cam.half_height - yoffset pixel = inverse(cam.transform) * point(world_x, world_y, -1) origin = inverse(cam.transform) * point(0, 0, 0) direction = normalize(pixel - origin) return shapes.ray(origin, direction)
def test_one(self): p = vector3() d = vector3(0.0, 0.0, -1.0) self.scene.collide( shapes.ray(p, d) ) return
def get_augmented_eccentricities(self, start, end): """Notes: see documentation for get_eccentricities adds the relevant shape to a tuple with the eccentricity """ ray = shapes.ray(start,end-start) return [ ( s.eccentricity(ray), s ) for s in self.collides ]
def refracted_color(world, comps, remaining=5): """ >>> w = default_world() >>> shape = w.contains[0] >>> r = ray(point(0,0,-5), vector(0,0,1)) >>> xs = intersections(intersection(4,shape), intersection(6,shape)) >>> comps = prepare_computations(xs[0], r, xs) >>> c = refracted_color(w, comps, 5) >>> c == color(0,0,0) array([ True, True, True]) >>> w = default_world() >>> shape = w.contains[0] >>> shape.material.transparency = 1.0 >>> shape.material.refractive_index = 1.5 >>> r = ray(point(0,0,-5), vector(0,0,1)) >>> xs = intersections(intersection(4,shape), intersection(6,shape)) >>> comps = prepare_computations(xs[0], r, xs) >>> c = refracted_color(w, comps, 0) >>> c == color(0,0,0) array([ True, True, True]) >>> w = default_world() >>> shape = w.contains[0] >>> shape.material.transparency = 1.0 >>> shape.material.refractive_index = 1.5 >>> r = ray(point(0,0,np.sqrt(2)/2), vector(0,1,0)) >>> xs = intersections(intersection(-np.sqrt(2),shape), intersection(np.sqrt(2),shape)) >>> comps = prepare_computations(xs[1], r, xs) >>> c = refracted_color(w, comps, 5) >>> c == color(0,0,0) array([ True, True, True]) >>> w = default_world() >>> A = w.contains[0] >>> A.material.ambient = 1.0 >>> A.material.pattern = test_pattern() >>> B = w.contains[1] >>> B.material.transparency = 1.0 >>> B.material.refractive_index = 1.5 >>> r = ray(point(0,0,0.1), vector(0,1,0)) >>> xs = intersections(intersection(-0.9899, A), intersection(-0.4899, B), intersection(0.4899, B), intersection(0.9899, A)) >>> comps = prepare_computations(xs[2], r, xs) >>> c = refracted_color(w, comps, 5) >>> c array([0. , 0.99888367, 0.04721668]) Below is how the above test is written in the book np.isclose(c, color(0, 0.99888, 0.04725)) array([ True, True, True]) """ if comps.object.material.transparency == 0 or remaining == 0: return color(0, 0, 0) n_ratio = comps.n1 / comps.n2 cos_i = dot(comps.eyev, comps.normalv) sin2_t = n_ratio**2 * (1 - cos_i**2) if sin2_t > 1.0: return color(0, 0, 0) cos_t = np.sqrt(1.0 - sin2_t) direction = comps.normalv * (n_ratio * cos_i - cos_t) - \ comps.eyev * n_ratio refracted_ray = shapes.ray(comps.under_point, direction) c = color_at(world, refracted_ray, remaining - 1) * \ comps.object.material.transparency return c
def color(self, scene, collision, camera): """Arguments: scene containing all geometry and lighting, collision object containing information about the impact on this surface """ if not collision: return (0.0, 0.0, 0.0) #info used to constuct final stored info struct #info struct can be check to see if pixels are on a boundary #to use supersampling anti-aliasing only when necessary num_lights = 0 refl_info = None #base color is the emissive color = self.emis #add ambient, no falloff or anything color += self.ambient.component_mult( self.ambi ) #need to pull out a little to avoid colliding #with surface that this point is on surface_norm = collision.surf.normal( collision.point ) collision.point += surface_norm * 0.0001 to_camera = (camera - collision.point).norm() #other light components must be done independently for each light for light in scene.lights: #check the angles because that's the cheapest #nothing's going to happen if we're on the wrong side of surface to_light = (light.p - collision.point).norm() dp = to_light.dp(surface_norm) if dp < 0.0: continue #check to see if this light is visible from current point #back out if have collision betweeen source and light shade = light.get_shade( scene, collision.point ) if shade < 0.01: continue #num_lights is used in the info struct that deals #with anti-aliasing, if the light doesn't affect this ignore it if light.alias_contributor: num_lights += 1 #add diffuse, falloff on angle from light and distance diff_factor = max( 0.0, to_light.dp(surface_norm) ) * shade color += light.diff.component_mult(self.diff) * diff_factor #specular factor will be computed based on dot product #between normal and vector halfway between #vector to light and vector to camera #this means the closers the object is to directly reflecting #the light into the camera the higher the specular factor #which is exactly what we want ideal_reflect = (to_camera + to_light).norm() spec_factor = max( 0.0, ideal_reflect.dp(surface_norm) ) spec_factor = (spec_factor ** self.shiny) * shade color += light.spec.component_mult(self.spec) * spec_factor if self.refl > 0.0: #reflect ray by subtracting twice the ray projected onto #the normal, this essentially reverses its direction relative #to the normal while leaving the rest intact #which is what we want ray_onto_norm = surface_norm * collision.ray.d.dp(surface_norm) reflected = collision.ray.d - (ray_onto_norm * 2.0) reflect_ray = shapes.ray( collision.point, reflected ) ref_collide = scene.collide( reflect_ray ) ref_color = ref_collide.surf.m.color(scene, ref_collide, camera ) ref_color = ref_color / 255.0 refl_info = ref_collide.surf.m.info color *= (1.0-self.refl) color += ref_color * self.refl # set our info struct so it can be read if desired self.info = (collision.surf, num_lights, refl_info) color.cut_max(1.0) color = color * 255.0 return color