def test_point(self): """ Test if a point with given distance along the ray is calculated correctly. """ r = Ray((0, 0, 0), (1, 0, 0)) nt.assert_almost_equal(r.point(0), r.position) nt.assert_almost_equal(r.point(1), r.position + r.direction) nt.assert_almost_equal(r.point(2), (2, 0, 0))
def test_hypothesis(self, arg): """ Test the ray's invariants with 'random' input from hypothesis """ p, d = arg norm_d = np.linalg.norm(d) hy.assume(norm_d > 1e-8) r = Ray(p, d) nt.assert_almost_equal(r.position, p) nt.assert_almost_equal(r.direction * norm_d, d) nt.assert_almost_equal(np.linalg.norm(r.direction), 1) point = r.point(4) nt.assert_almost_equal(np.linalg.norm(r.position - point), 4)
def test_intersect(self): """ Test if the ray-sphere intersection works as expected. """ s = Sphere() nt.assert_almost_equal(s.intersect(Ray()), 1) r = Ray(position=(-10, 0, 0)) nt.assert_almost_equal(s.intersect(r), 9) r = Ray(position=(0, -5, 0), direction=(0, 1, 0)) nt.assert_almost_equal(s.intersect(r), 4) r = Ray(position=(10, 0, 0), direction=(1, 0, 0)) nt.assert_almost_equal(s.intersect(r), np.inf) r = Ray(position=(0, 0, 10), direction=(0, 0, -1)) nt.assert_almost_equal(s.intersect(r), 9)
def test_string(self): """ Test if the string representation of the ray is correct. Because testing against a concrete string is tough if numpy changes how they print arrays - we will just test if the call succedes. """ str(Ray())
def setUp(self): self.r1 = Ray() self.s1 = Sphere(position=(-2, 0, 0)) # intersect at np.inf self.s2 = Sphere(position=( 2, 0, 0)) # intersect at 1 self.p1 = Plane(position=(3, 0, 0)) # intersect at np.inf self.p2 = Plane(position=(.5, 0, 0), normal=(1, 0, 0)) # at 0.5 self.scn = Scene(self.s1, self.s2, self.p1, self.p2)
def ray(self, pixel, resolutions, rand=True): """ Given the pixel and the camera resolution, returns a ray that originates at the camera position and passes through the pixel. If rand is set true, a little random offset (smaller than the distance between two pixels is added to the pixel position. This will together with multiple samples per pixel mitigate aliasing. Parameters ---------- pixel : numpy.ndarray_like of shape (2, ) (x, y) coordinates of the pixel in the image. Numpy style: aka (0, 0) is the upper left hand corner and the x values are iterating downwards while y is iterating horizontally. x must be in the intervall of [0, dimension[0]] and y must be in [0, dimension[1]] The pixel [0,0] is the upper lefthand corner and the pixel [res_x, rex_y] is the lower righthand corner. resolutions : numpy.ndarray_like of shape (2, ) the resolution of the camera in x and y. rand : boolean When False, every ray passes through the exact center of the pixel. When True a random offset smaller than the distance between two pixels is added the the pixel center. The ray then passes through the perturbed pixel center. Returns ------- ray : Ray with the position being the camera position and direction being a vector that starts at the position and passes through the (potentiall offsetted) given pixel Examples -------- >>> camera = PerspectiveCamera() >>> camera.ray((50, 50), (100, 100), False) Ray(position=[0, 0, 0], direction=[0, 0, 1]) """ pixel_x, pixel_y = pixel res_x, res_y = resolutions max_dim = np.maximum(res_x, res_y) # image plane coordinates (x, y) of the pixel # x and y are in [-1, 1] x = (res_x - 2 * pixel_x) / max_dim y = (res_y - 2 * pixel_y) / max_dim if rand: # add a small perturbation to the pixel coordinate delta_x, delta_y = 1 / res_x, 1 / res_y sample_x, sample_y = np.random.uniform(-1, 1, size=(2, )) x += sample_x * delta_x y += sample_y * delta_y # Numpy Style - x is vertical - y is horizontal world_coord = self._image_plane_center + x * self._up + y * self._righthand return Ray(self._position, world_coord - self._position)
def test_intersect(self): """ Test if the ray-plane intersection works as expected. """ r = Ray() nt.assert_almost_equal(Plane().intersect(r), np.inf) p = Plane(position=(0.5, 0, 0), normal=(1, 0, 0)) nt.assert_almost_equal(p.intersect(r), 0.5)
def test_default_construction(self): """ Test if the ray is constructed with the expected default parameters. """ r = Ray() nt.assert_almost_equal(r.position, (0, 0, 0)) nt.assert_almost_equal(r.direction, (1, 0, 0)) nt.assert_almost_equal(r.position, r._position) nt.assert_almost_equal(r.direction, r._direction)
def test_intersection(self): """ Test if a scene performs the ray-object intersection with all contained renderables correctly. """ (d, o) = self.scn.intersect(self.r1) nt.assert_almost_equal(d, 0.5) self.assertTrue(o is self.p2) r2 = Ray(direction=(0, 1, 0)) # does not intersect scene (d, o) = self.scn.intersect(r2) nt.assert_almost_equal(d, np.inf) self.assertIsNone(o)
def _trace(self, ray, path_length): """ Trace a given ray one step through the scene. ray : padvinder.ray.Ray the ray defining the direction and starting position to continue in path_length : number the number of intersections this path already had Returns ------- color : numpy.ndarray_like of shape (3, ) returns the light color flowing along the path """ if path_length >= self.path_length: return np.zeros(3, ) (t, obj) = self.scn.intersect(ray) if obj is None: return self.background_color point = ray.point(t) normal = obj.normal(point) # to avoid numerical inaccuracies and the intersection point # ending up within an object point += 1e-4 * normal out_dir = obj.material.outgoing_direction(normal, ray.direction) color = self._trace(Ray(point, out_dir), path_length + 1) # path tracing defined 'forward' direction # color accumulation, however, is backwards in <-> out return obj.material(normal, color, -out_dir, -ray.direction)
def test_input(self): """ Test if the input values are checked correctly. Values that do not validate the checks are omitted because they are covered by the remaining tests. """ with self.assertRaises(ValueError): Ray(position=np.array((np.nan, 0, 0))) with self.assertRaises(ValueError): Ray(position=np.array((np.inf, 0, 0))) with self.assertRaises(ValueError): Ray(position=np.array((np.inf, 0, 0))) with self.assertRaises(ValueError): Ray(direction=np.array((np.nan, 0, 0))) with self.assertRaises(ValueError): Ray(direction=np.array((np.inf, 0, 0))) with self.assertRaises(ValueError): Ray(direction=np.array((-np.inf, 0, 0)))