/// <summary>
        /// Performs a VPT in a sphere medium with specific settings starting at center and returns the summary of the path traced.
        /// </summary>
        public static PathSummary GetVPTSampleInSphereOffcenter(MediumSettings settings, GRandom rnd)
        {
            float3 x          = float3(0, 0, 0);
            float3 w          = float3(0, 0, 1);
            float3 X          = x;
            float3 W          = w;
            int    N          = 0;
            float  accum      = 0;
            float  importance = 1;

            // First flight from center
            float t = settings.Sigma < 0.00001 ? 10000000 : -log(max(0.000000001f, 1 - rnd.random())) / settings.Sigma;

            if (t >= 1.0f) // leaves the sphere before scattering
            {
                return(new PathSummary
                {
                    N = N,
                    x = w,
                    w = w,
                    X = x, // can not used but in any case...
                    W = W
                });
            }
            x += w * t; // move to first scatter offcenter

            while (true)
            {
                importance *= settings.Phi;
                accum      += importance;

                if (rnd.random() < importance / accum) // replace the representative by this one
                {
                    X = x;
                    W = w;
                }

                w = SamplePhase(w, settings.G, rnd);

                N++;

                float d = DistanceToBoundary(x, w);

                t = settings.Sigma < 0.00001 ? 10000000 : -log(max(0.000000001f, 1 - rnd.random())) / settings.Sigma;

                if (t >= d || float.IsNaN(t) || float.IsInfinity(t))
                {
                    x += w * d;
                    return(new PathSummary
                    {
                        N = N,
                        x = x,
                        w = w,
                        X = X,
                        W = W
                    });
                }
                x += w * t;
            }
        }
        /// <summary>
        /// Performs a VPT in a sphere medium with specific settings starting at center and returns the summary of the path traced.
        /// </summary>
        static PathSummary GetVPTSampleInSphere(MediumSettings settings)
        {
            float3 x          = float3(0, 0, 0);
            float3 w          = float3(0, 0, 1);
            float3 X          = x;
            float3 W          = w;
            int    N          = 0;
            float  accum      = 0;
            float  importance = 1;

            while (true)
            {
                importance *= settings.Phi;
                accum      += importance;

                if (random() < importance / accum) // replace the representative by this one
                {
                    X = x;
                    W = w;
                }

                w = SamplePhase(w, settings.G);

                N++;

                float d = DistanceToBoundary(x, w);

                float t = settings.Sigma < 0.00001 ? 10000000 : -log(max(0.000000001f, 1 - random())) / settings.Sigma;

                if (t >= d || float.IsNaN(t) || float.IsInfinity(t))
                {
                    x += w * d;
                    return(new PathSummary
                    {
                        N = N,
                        x = x,
                        w = w,
                        X = X,
                        W = W
                    });
                }
                x += w * t;
            }
        }
        static void GenerateDatasetForTabulationMethod()
        {
            /*
             * This method generates data samples and builds a table using
             * the idea in Mueller et al 2016.
             */
            float[] table = new float[BINS_G * BINS_SA * BINS_R * BINS_THETA];
            float[,,] oneTimeScatteringAlbedo   = new float[BINS_G, BINS_SA, BINS_R];
            float[,,] multiTimeScatteringAlbedo = new float[BINS_G, BINS_SA, BINS_R];

            int MAX_N = 200;
            int P     = 1 << 18; // one quarter million photons per setting.

            Console.WriteLine("Process started.");
            Stopwatch stopwatch = new Stopwatch();

            stopwatch.Start();

            int TOTAL_NUMBER_OF_SETTINGS = BINS_G * BINS_R;
            int solvedSettings           = 0;

            Parallel.For(0, BINS_G, binG =>
            {
                GRandom rnd = new GRandom(binG);

                float g = (binG + rnd.random()) * 2.0f * 0.99f / BINS_G - 0.99f;
                for (int binR = 0; binR < BINS_R; binR++)
                {
                    float r = pow(2.0f, binR);

                    Console.WriteLine("Solved {0}%.", solvedSettings * 100.0f / TOTAL_NUMBER_OF_SETTINGS);

                    if (solvedSettings >= 1)
                    {
                        Console.WriteLine("ETA {0} ", stopwatch.Elapsed * (TOTAL_NUMBER_OF_SETTINGS / (float)solvedSettings - 1));
                    }

                    MediumSettings settings = new MediumSettings();
                    settings.Sigma          = r;
                    settings.Phi            = 1;
                    settings.G = g;

                    long[,] Hl = new long[BINS_THETA, MAX_N + 1];

                    for (int sample = 0; sample < P; sample++)
                    {
                        var summary = Scattering.GetVPTSampleInSphereOffcenter(settings, rnd);

                        int thetaBin = (int)min(BINS_THETA - 1, BINS_THETA * (summary.x.z * 0.5f + 0.5f));
                        int nBin     = (int)min(MAX_N, summary.N);
                        Hl[thetaBin, nBin]++;
                    }

                    for (int binSA = 0; binSA < BINS_SA; binSA++)
                    {
                        float phi = binSA == BINS_SA - 1 ? 1.0f : 1 - exp(binSA * log(0.001f) / (BINS_SA - 1));

                        for (int binTheta = 0; binTheta < BINS_THETA; binTheta++)
                        {
                            int pos = STRIDE_G * binG + STRIDE_SA * binSA + STRIDE_R * binR + binTheta;
                            for (int n = 2; n <= MAX_N; n++)
                            {
                                table[pos] += pow(phi, n) * Hl[binTheta, n];
                                multiTimeScatteringAlbedo[binG, binSA, binR] += pow(phi, n) * Hl[binTheta, n];
                            }

                            oneTimeScatteringAlbedo[binG, binSA, binR] += phi * Hl[binTheta, 1];
                        }

                        // Sum and normalize table slice to create an empirical cdf
                        for (int binTheta = 0; binTheta < BINS_THETA; binTheta++)
                        {
                            int pos = STRIDE_G * binG + STRIDE_SA * binSA + STRIDE_R * binR + binTheta;

                            if (binTheta > 0)
                            {
                                table[pos] += table[pos - 1];
                            }
                        }
                    }

                    solvedSettings++;
                }
            });

            Console.WriteLine("Finished generation in {0}", stopwatch.Elapsed);

            Console.WriteLine("Saving table");

            BinaryWriter bw = new BinaryWriter(new FileStream("stf.bin", FileMode.Create));

            for (int binG = 0; binG < BINS_G; binG++)
            {
                for (int binSA = 0; binSA < BINS_SA; binSA++)
                {
                    for (int binR = 0; binR < BINS_R; binR++)
                    {
                        bw.Write(oneTimeScatteringAlbedo[binG, binSA, binR] / P);
                    }
                }
            }

            for (int binG = 0; binG < BINS_G; binG++)
            {
                for (int binSA = 0; binSA < BINS_SA; binSA++)
                {
                    for (int binR = 0; binR < BINS_R; binR++)
                    {
                        bw.Write(multiTimeScatteringAlbedo[binG, binSA, binR] / P);
                    }
                }
            }

            int index = 0;

            for (int binG = 0; binG < BINS_G; binG++)
            {
                for (int binSA = 0; binSA < BINS_SA; binSA++)
                {
                    for (int binR = 0; binR < BINS_R; binR++)
                    {
                        for (int binTheta = 0; binTheta < BINS_THETA; binTheta++)
                        {
                            bw.Write(table[index++] / P);
                        }
                    }
                }
            }

            bw.Close();

            Console.WriteLine("Done.");
        }