public void Precomputing_the_reflection_vector()
        {
            var shape = new Plane();
            var ray   = new Ray(new Point(0, 1, -1), new Vector(0, -Math.Sqrt(2) / 2, Math.Sqrt(2) / 2));
            var hit   = new Intersection(Math.Sqrt(2), shape);
            var state = IntersectionState.Create(hit, ray, new IntersectionList(hit));

            state.Reflection.Should().Be(new Vector(0, Math.Sqrt(2) / 2, Math.Sqrt(2) / 2));
        }
        public void The_hit_should_be_preserved_when_calculating_the_intersection_state()
        {
            var hit           = new Intersection(1, _triangle, 0.45, 0.25);
            var ray           = new Ray(new Point(-0.2, 0.3, -2), Vector.UnitZ);
            var intersections = new IntersectionList(hit);
            var state         = IntersectionState.Create(hit, ray, intersections);

            state.Normal.Should().Be(new Vector(-0.5547, 0.83205, 0));
        }
        public void Precomputing_the_hit_when_an_intersection_occurs_on_the_outside()
        {
            var ray   = new Ray(new Point(0, 0, -5), new Vector(0, 0, 1));
            var shape = new Sphere();
            var hit   = new Intersection(4, shape);
            var state = IntersectionState.Create(hit, ray, new IntersectionList(hit));

            state.IsInside.Should().BeFalse();
        }
        public void Precomputing_the_hit_should_offset_the_point()
        {
            var ray   = new Ray(new Point(0, 0, -5), Vector.UnitZ);
            var shape = new Sphere(transform: Matrix4x4.CreateTranslation(0, 0, 1));
            var hit   = new Intersection(5, shape);
            var state = IntersectionState.Create(hit, ray, new IntersectionList(hit));

            state.OverPoint.Z.Should().BeLessThan(-NumberExtensions.Epsilon / 2);
            state.Point.Z.Should().BeGreaterThan(state.OverPoint.Z);
        }
        public void ShadeHit_should_shade_an_intersection()
        {
            var   world        = World.CreateDefaultWorld();
            var   ray          = new Ray(new Point(0, 0, -5), Vector.UnitZ);
            Shape shape        = world.Shapes[0];
            var   intersection = new Intersection(4, shape);
            var   state        = IntersectionState.Create(intersection, ray, new IntersectionList(intersection));
            Color color        = world.ShadeHit(state);

            color.Should().Be(new Color(0.38066, 0.47583, 0.2855));
        }
        public void ShadeHit_should_shade_an_intersection_from_the_inside()
        {
            var   world        = World.CreateDefaultWorld().ChangeLights(new PointLight(new Point(0, 0.25, 0), Colors.White));
            var   ray          = new Ray(new Point(0, 0, 0), Vector.UnitZ);
            Shape shape        = world.Shapes[1];
            var   intersection = new Intersection(0.5, shape);
            var   state        = IntersectionState.Create(intersection, ray, new IntersectionList(intersection));
            Color color        = world.ShadeHit(state);

            color.Should().Be(new Color(0.90498, 0.90498, 0.90498));
        }
        public void RefractedColor_should_return_the_refracted_color_at_the_maximum_recursive_depth()
        {
            var   world         = World.CreateDefaultWorld();
            var   shape         = world.Shapes[0].ChangeMaterial(m => m.WithTransparency(1.0).WithRefractiveIndex(1.5));
            var   ray           = new Ray(new Point(0, 0, -5), Vector.UnitZ);
            var   intersections = new IntersectionList((4, shape), (6, shape));
            var   state         = IntersectionState.Create(intersections[0], ray, intersections);
            Color color         = world.RefractedColor(state, 0);

            color.Should().Be(Colors.Black);
        }
        public void RefractedColor_should_return_black_for_the_refracted_color_with_an_opaque_surface()
        {
            var   world         = World.CreateDefaultWorld();
            var   shape         = world.Shapes[0];
            var   ray           = new Ray(new Point(0, 0, -5), Vector.UnitZ);
            var   intersections = new IntersectionList((4, shape), (6, shape));
            var   state         = IntersectionState.Create(intersections[0], ray, intersections);
            Color color         = world.RefractedColor(state, 5);

            color.Should().Be(Colors.Black);
        }
        public void ReflectedColor_should_return_black_for_the_reflected_color_of_a_non_reflective_surface()
        {
            var world = World.CreateDefaultWorld();
            var ray   = new Ray(Point.Zero, Vector.UnitZ);
            var shape = world.Shapes[1].ChangeMaterial(m => m.WithAmbient(1));

            var   intersection = new Intersection(1, shape);
            var   state        = IntersectionState.Create(intersection, ray, new IntersectionList(intersection));
            Color color        = world.ReflectedColor(state);

            color.Should().Be(Colors.Black);
        }
        public void Precomputing_the_state_of_an_intersection()
        {
            var ray   = new Ray(new Point(0, 0, -5), new Vector(0, 0, 1));
            var shape = new Sphere();
            var hit   = new Intersection(4, shape);
            var state = IntersectionState.Create(hit, ray, new IntersectionList(hit));

            state.T.Should().Be(hit.T);
            state.Shape.Should().Be(hit.Shape);
            state.Point.Should().Be(new Point(0, 0, -1));
            state.Eye.Should().Be(new Vector(0, 0, -1));
            state.Normal.Should().Be(new Vector(0, 0, -1));
        }
        public void RefractedColor_should_return_the_refracted_color_with_a_refracted_ray()
        {
            var world = World.CreateDefaultWorld();
            var a     = world.Shapes[0].ChangeMaterial(m => m.WithAmbient(1).WithPattern(new PatternTests.TestPattern()));
            var b     = world.Shapes[1].ChangeMaterial(m => m.WithTransparency(1).WithRefractiveIndex(1.5));

            var   ray           = new Ray(new Point(0, 0, 0.1), Vector.UnitY);
            var   intersections = new IntersectionList((-0.9899, a), (-0.4899, b), (0.4899, b), (0.9899, a));
            var   state         = IntersectionState.Create(intersections[2], ray, intersections);
            Color color         = world.RefractedColor(state, 5);

            color.Should().Be(new Color(0, 0.99887, 0.04722));
        }
        public void RefractedColor_should_return_black_for_the_refracted_color_under_total_internal_reflection()
        {
            var world         = World.CreateDefaultWorld();
            var shape         = world.Shapes[0].ChangeMaterial(m => m.WithTransparency(1.0).WithRefractiveIndex(1.5));
            var ray           = new Ray(new Point(0, 0, Math.Sqrt(2) / 2), Vector.UnitY);
            var intersections = new IntersectionList((-Math.Sqrt(2) / 2, shape), (Math.Sqrt(2) / 2, shape));

            // Note this time we're inside the sphere, so we need to look at intersections[1], not 0.
            var   state = IntersectionState.Create(intersections[1], ray, intersections);
            Color color = world.RefractedColor(state, 5);

            color.Should().Be(Colors.Black);
        }
        public void Precomputing_the_hit_when_an_intersection_occurs_on_the_inside()
        {
            var ray   = new Ray(new Point(0, 0, 0), new Vector(0, 0, 1));
            var shape = new Sphere();
            var hit   = new Intersection(1, shape);
            var state = IntersectionState.Create(hit, ray, new IntersectionList(hit));

            state.Point.Should().Be(new Point(0, 0, 1));
            state.Eye.Should().Be(new Vector(0, 0, -1));
            state.IsInside.Should().BeTrue();

            // Normal would have been (0, 0, 1) but is inverted!
            state.Normal.Should().Be(new Vector(0, 0, -1));
        }
        public void ShadeHit_should_calculate_the_reflected_color_for_a_reflective_material()
        {
            var world = World.CreateDefaultWorld();
            var floor = new Plane("Floor", Matrix4x4.CreateTranslation(0, -1, 0), new Material(reflective: 0.5));

            world = world.AddShapes(floor);

            var ray          = new Ray(new Point(0, 0, -3), new Vector(0, -Math.Sqrt(2) / 2, Math.Sqrt(2) / 2));
            var intersection = new Intersection(Math.Sqrt(2), floor);
            var state        = IntersectionState.Create(intersection, ray, new IntersectionList(intersection));

            var color = world.ShadeHit(state);

            color.Should().Be(new Color(0.87675, 0.92434, 0.82917));
        }
        public void ReflectedColor_should_only_allow_a_maximum_recursion_depth()
        {
            var world = World.CreateDefaultWorld();
            var floor = new Plane("Floor", Matrix4x4.CreateTranslation(0, -1, 0), new Material(reflective: 0.5));

            world = world.AddShapes(floor);

            var ray          = new Ray(new Point(0, 0, -3), new Vector(0, -Math.Sqrt(2) / 2, Math.Sqrt(2) / 2));
            var intersection = new Intersection(Math.Sqrt(2), floor);
            var state        = IntersectionState.Create(intersection, ray, new IntersectionList(intersection));

            Color color = world.ReflectedColor(state, 0);

            color.Should().Be(Colors.Black);
        }
        public void ShadeHit_should_shade_an_intersection_in_shadow()
        {
            var sphere1 = new Sphere();
            var sphere2 = new Sphere(transform: Matrix4x4.CreateTranslation(0, 0, 10));
            var world   = World.CreateDefaultWorld()
                          .ChangeLights(new PointLight(new Point(0, 0, -10), Colors.White))
                          .AddShapes(sphere1, sphere2);

            var ray          = new Ray(new Point(0, 0, 5), Vector.UnitZ);
            var intersection = new Intersection(4, sphere2);
            var state        = IntersectionState.Create(intersection, ray, new IntersectionList(intersection));

            Color color = world.ShadeHit(state);

            color.Should().Be(new Color(0.1, 0.1, 0.1));
        }
        public void ShadeHit_should_calculate_the_shade_color_with_a_reflective_and_transparent_material()
        {
            var floor = new Plane(
                "Floor",
                Matrix4x4.CreateTranslation(0, -1, 0),
                new Material(reflective: 0.5, transparency: 0.5, refractiveIndex: 1.5));

            var ball = new Sphere(
                "Ball",
                Matrix4x4.CreateTranslation(0, -3.5, -0.5),
                new Material(Colors.Red, ambient: 0.5));
            var world = World.CreateDefaultWorld().AddShapes(floor, ball);

            var   ray           = new Ray(new Point(0, 0, -3), new Vector(0, -Math.Sqrt(2) / 2, Math.Sqrt(2) / 2));
            var   intersections = new IntersectionList((Math.Sqrt(2), floor));
            var   state         = IntersectionState.Create(intersections[0], ray, intersections);
            Color color         = world.ShadeHit(state, 5);

            color.Should().Be(new Color(0.93391, 0.69643, 0.69243));
        }