Exemple #1
0
    def testHit(self):
        sphere = Sphere()

        ray1 = Ray(origin=Point(0, 0, 2), dir=-VEC_Z)
        intersection1 = sphere.ray_intersection(ray1)
        assert intersection1
        assert HitRecord(
            world_point=Point(0.0, 0.0, 1.0),
            normal=Normal(0.0, 0.0, 1.0),
            surface_point=Vec2d(0.0, 0.0),
            t=1.0,
            ray=ray1,
            material=sphere.material,
        ).is_close(intersection1)

        ray2 = Ray(origin=Point(3, 0, 0), dir=-VEC_X)
        intersection2 = sphere.ray_intersection(ray2)
        assert intersection2
        assert HitRecord(
            world_point=Point(1.0, 0.0, 0.0),
            normal=Normal(1.0, 0.0, 0.0),
            surface_point=Vec2d(0.0, 0.5),
            t=2.0,
            ray=ray2,
            material=sphere.material,
        ).is_close(intersection2)

        assert not sphere.ray_intersection(
            Ray(origin=Point(0, 10, 2), dir=-VEC_Z))
Exemple #2
0
    def testTransformation(self):
        sphere = Sphere(transformation=translation(Vec(10.0, 0.0, 0.0)))

        ray1 = Ray(origin=Point(10, 0, 2), dir=-VEC_Z)
        intersection1 = sphere.ray_intersection(ray1)
        assert intersection1
        assert HitRecord(
            world_point=Point(10.0, 0.0, 1.0),
            normal=Normal(0.0, 0.0, 1.0),
            surface_point=Vec2d(0.0, 0.0),
            t=1.0,
            ray=ray1,
            material=sphere.material,
        ).is_close(intersection1)

        ray2 = Ray(origin=Point(13, 0, 0), dir=-VEC_X)
        intersection2 = sphere.ray_intersection(ray2)
        assert intersection2
        assert HitRecord(
            world_point=Point(11.0, 0.0, 0.0),
            normal=Normal(1.0, 0.0, 0.0),
            surface_point=Vec2d(0.0, 0.5),
            t=2.0,
            ray=ray2,
            material=sphere.material,
        ).is_close(intersection2)

        # Check if the sphere failed to move by trying to hit the untransformed shape
        assert not sphere.ray_intersection(
            Ray(origin=Point(0, 0, 2), dir=-VEC_Z))

        # Check if the *inverse* transformation was wrongly applied
        assert not sphere.ray_intersection(
            Ray(origin=Point(-10, 0, 0), dir=-VEC_Z))
Exemple #3
0
    def test_vec_point_multiplication(self):
        m = Transformation(
            m=[
                [1.0, 2.0, 3.0, 4.0],
                [5.0, 6.0, 7.0, 8.0],
                [9.0, 9.0, 8.0, 7.0],
                [0.0, 0.0, 0.0, 1.0],
            ],
            invm=[
                [-3.75, 2.75, -1, 0],
                [5.75, -4.75, 2.0, 1.0],
                [-2.25, 2.25, -1.0, -2.0],
                [0.0, 0.0, 0.0, 1.0],
            ],
        )
        assert m.is_consistent()

        expected_v = Vec(14.0, 38.0, 51.0)
        assert expected_v.is_close(m * Vec(1.0, 2.0, 3.0))

        expected_p = Point(18.0, 46.0, 58.0)
        assert expected_p.is_close(m * Point(1.0, 2.0, 3.0))

        expected_n = Normal(-8.75, 7.75, -3.0)
        assert expected_n.is_close(m * Normal(3.0, 2.0, 4.0))
Exemple #4
0
    def testTransformation(self):
        plane = Plane(transformation=rotation_y(angle_deg=90.0))

        ray1 = Ray(origin=Point(1, 0, 0), dir=-VEC_X)
        intersection1 = plane.ray_intersection(ray1)
        assert intersection1
        assert HitRecord(
            world_point=Point(0.0, 0.0, 0.0),
            normal=Normal(1.0, 0.0, 0.0),
            surface_point=Vec2d(0.0, 0.0),
            t=1.0,
            ray=ray1,
            material=plane.material,
        ).is_close(intersection1)

        ray2 = Ray(origin=Point(0, 0, 1), dir=VEC_Z)
        intersection2 = plane.ray_intersection(ray2)
        assert not intersection2

        ray3 = Ray(origin=Point(0, 0, 1), dir=VEC_X)
        intersection3 = plane.ray_intersection(ray3)
        assert not intersection3

        ray4 = Ray(origin=Point(0, 0, 1), dir=VEC_Y)
        intersection4 = plane.ray_intersection(ray4)
        assert not intersection4
Exemple #5
0
    def ray_intersection(self, ray: Ray) -> Union[HitRecord, None]:
        """Checks if a ray intersects the plane

        Return a `HitRecord`, or `None` if no intersection was found.
        """
        inv_ray = ray.transform(self.transformation.inverse())
        if abs(inv_ray.dir.z) < 1e-5:
            return None

        t = -inv_ray.origin.z / inv_ray.dir.z

        if (t <= inv_ray.tmin) or (t >= inv_ray.tmax):
            return None

        hit_point = inv_ray.at(t)

        return HitRecord(
            world_point=self.transformation * hit_point,
            normal=self.transformation *
            Normal(0.0, 0.0, 1.0 if inv_ray.dir.z < 0.0 else -1.0),
            surface_point=Vec2d(hit_point.x - floor(hit_point.x),
                                hit_point.y - floor(hit_point.y)),
            t=t,
            ray=ray,
            material=self.material,
        )
Exemple #6
0
    def scatter_ray(self, pcg: PCG, incoming_dir: Vec,
                    interaction_point: Point, normal: Normal, depth: int):
        # There is no need to use the PCG here, as the reflected direction is always completely deterministic
        # for a perfect mirror

        ray_dir = Vec(incoming_dir.x, incoming_dir.y,
                      incoming_dir.z).normalize()
        normal = normal.to_vec().normalize()
        dot_prod = normal.dot(ray_dir)

        return Ray(
            origin=interaction_point,
            dir=ray_dir - normal * 2 * dot_prod,
            tmin=1e-5,
            tmax=inf,
            depth=depth,
        )
Exemple #7
0
    def testNormals(self):
        sphere = Sphere(transformation=scaling(Vec(2.0, 1.0, 1.0)))

        ray = Ray(origin=Point(1.0, 1.0, 0.0), dir=Vec(-1.0, -1.0))
        intersection = sphere.ray_intersection(ray)
        # We normalize "intersection.normal", as we are not interested in its length
        assert intersection.normal.normalize().is_close(
            Normal(1.0, 4.0, 0.0).normalize())
Exemple #8
0
    def testNormalDirection(self):
        # Scaling a sphere by -1 keeps the sphere the same but reverses its
        # reference frame
        sphere = Sphere(transformation=scaling(Vec(-1.0, -1.0, -1.0)))

        ray = Ray(origin=Point(0.0, 2.0, 0.0), dir=-VEC_Y)
        intersection = sphere.ray_intersection(ray)
        # We normalize "intersection.normal", as we are not interested in its length
        assert intersection.normal.normalize().is_close(
            Normal(0.0, 1.0, 0.0).normalize())
Exemple #9
0
def _sphere_normal(point: Point, ray_dir: Vec) -> Normal:
    """Compute the normal of a unit sphere

    The normal is computed for `point` (a point on the surface of the
    sphere), and it is chosen so that it is always in the opposite
    direction with respect to `ray_dir`.

    """
    result = Normal(point.x, point.y, point.z)
    return result if (point.to_vec().dot(ray_dir) < 0.0) else -result
    def testInnerHit(self):
        sphere = Sphere()

        ray = Ray(origin=Point(0, 0, 0), dir=VEC_X)
        intersection = sphere.ray_intersection(ray)
        assert intersection
        assert HitRecord(
            world_point=Point(1.0, 0.0, 0.0),
            normal=Normal(-1.0, 0.0, 0.0),
            surface_point=Vec2d(0.0, 0.5),
            t=1.0,
            ray=ray,
        ).is_close(intersection)
Exemple #11
0
    def __mul__(self, other):
        if isinstance(other, Vec):
            row0, row1, row2, row3 = self.m
            return Vec(
                x=other.x * row0[0] + other.y * row0[1] + other.z * row0[2],
                y=other.x * row1[0] + other.y * row1[1] + other.z * row1[2],
                z=other.x * row2[0] + other.y * row2[1] + other.z * row2[2])
        elif isinstance(other, Point):
            row0, row1, row2, row3 = self.m
            p = Point(x=other.x * row0[0] + other.y * row0[1] +
                      other.z * row0[2] + row0[3],
                      y=other.x * row1[0] + other.y * row1[1] +
                      other.z * row1[2] + row1[3],
                      z=other.x * row2[0] + other.y * row2[1] +
                      other.z * row2[2] + row2[3])
            w = other.x * row3[0] + other.y * row3[1] + other.z * row3[
                2] + row3[3]

            if w == 1.0:
                return p
            else:
                return Point(p.x / w, p.y / w, p.z / w)
        elif isinstance(other, Normal):
            row0, row1, row2, _ = self.invm
            return Normal(
                x=other.x * row0[0] + other.y * row1[0] + other.z * row2[0],
                y=other.x * row0[1] + other.y * row1[1] + other.z * row2[1],
                z=other.x * row0[2] + other.y * row1[2] + other.z * row2[2])
        elif isinstance(other, Transformation):
            result_m = _matr_prod(self.m, other.m)
            result_invm = _matr_prod(
                other.invm, self.invm)  # Reverse order! (A B)^-1 = B^-1 A^-1
            return Transformation(m=result_m, invm=result_invm)
        else:
            raise TypeError(
                f"Invalid type {type(other)} multiplied to a Transformation object"
            )