public void Writing_pixels_to_a_canvas()
        {
            var canvas = new MutableCanvas(10, 20);

            canvas.SetPixel(2, 3, Colors.Red);
            canvas.GetPixel(2, 3).Should().Be(Colors.Red);
        }
        protected override Canvas Render(CameraRenderOptions options)
        {
            var canvas = new MutableCanvas(CanvasWidth, CanvasHeight);

            var sphere = new Sphere();

            var          rayOrigin = new Point(0, 0, -5);
            const double wallZ     = 10;
            const double wallSize  = 7.0;

            double       pixelSize    = wallSize / canvas.Width;
            const double halfWallSize = wallSize / 2;
            double       totalPixels  = canvas.Width * canvas.Height;

            // For each row of pixels in the canvas...
            for (int y = 0; y < canvas.Height; y++)
            {
                // Compute the world y coordinate (top = +half, bottom = -half).
                double worldY = halfWallSize - (pixelSize * y);

                // For each pixel in the row...
                for (int x = 0; x < canvas.Width; x++)
                {
                    // See if we should stop.
                    if (options.CancellationToken.IsCancellationRequested)
                    {
                        return(canvas.ToImmutable());
                    }

                    // Compute the world x coordinate (left = -half, right = +half).
                    double worldX = -halfWallSize + (pixelSize * x);

                    // Describe the point on the wall that the ray will target.
                    var position = new Point(worldX, worldY, wallZ);

                    // Cast the ray into the scene to see what it hits.
                    var ray           = new Ray(rayOrigin, (position - rayOrigin).Normalize());
                    var intersections = sphere.Intersect(ray);

                    if (intersections.Hit != null)
                    {
                        canvas.SetPixel(x, y, Colors.Red);
                    }
                }

                // Report the progress.
                double pixelsRendered  = (y * CanvasWidth) + CanvasWidth;
                int    percentComplete = (int)Math.Round((pixelsRendered / totalPixels) * 100.0);
                options.Progress?.Report(new RenderProgressStep(percentComplete, y, canvas.GetRow(y)));
            }

            return(canvas.ToImmutable());
        }
        protected override Canvas Render(CameraRenderOptions options)
        {
            var canvas = new MutableCanvas(CanvasWidth, CanvasHeight);

            var sphere        = new Sphere(material: new Material(new Color(1, 0.2, 1)));
            var lightPosition = new Point(-10, 10, -10);
            var lightColor    = Colors.White;
            var light         = new PointLight(lightPosition, lightColor);

            var          rayOrigin = new Point(0, 0, -5);
            const int    wallZ     = 10;
            const double wallSize  = 7.0;

            double       pixelSize    = wallSize / canvas.Width;
            const double halfWallSize = wallSize / 2;
            double       totalPixels  = canvas.Width * canvas.Height;

            // For each row of pixels in the canvas...
            for (int y = 0; y < canvas.Height; y++)
            {
                // Compute the world y coordinate (top = +half, bottom = -half).
                double worldY = halfWallSize - (pixelSize * y);

                // For each pixel in the row...
                for (int x = 0; x < canvas.Width; x++)
                {
                    // See if we should stop.
                    if (options.CancellationToken.IsCancellationRequested)
                    {
                        return(canvas.ToImmutable());
                    }

                    // Compute the world x coordinate (left = -half, right = +half).
                    double worldX = -halfWallSize + (pixelSize * x);

                    // Describe the point on the wall that the ray will target.
                    var position = new Point(worldX, worldY, wallZ);

                    // Cast the ray into the scene to see what it hits.
                    var ray = new Ray(rayOrigin, (position - rayOrigin).Normalize());
                    IntersectionList intersections = sphere.Intersect(ray);
                    Intersection?    hit           = intersections.Hit;

                    if (hit != null)
                    {
                        Point  point  = ray.PositionAt(hit.T);
                        Vector normal = hit.Shape.NormalAt(point);
                        Vector eye    = ray.Direction.Negate();
                        Color  color  = hit.Shape.Material.CalculateLighting(hit.Shape, light, point, eye, normal, false);
                        canvas.SetPixel(x, y, color);
                    }
                }

                // Report the progress.
                double pixelsRendered  = (y * CanvasWidth) + CanvasWidth;
                int    percentComplete = (int)Math.Round((pixelsRendered / totalPixels) * 100.0);
                options.Progress?.Report(new RenderProgressStep(percentComplete, y, canvas.GetRow(y)));
            }

            return(canvas.ToImmutable());
        }