public void Execute(int roughnessIndex)
            {
                // Create the fitter
                NelderMead fitter = new NelderMead(3);
                IBRDF      brdf   = LTCAreaLight.GetBRDFInterface(lightingModel);

                // Compute all the missing LTCData (0 of the first line is already done)
                for (int thetaIndex = 1; thetaIndex < tableResolution; thetaIndex++)
                {
                    Fit(roughnessIndex, thetaIndex, fitter, brdf);
                }
            }
        static public void ExecuteFittingJob(BRDFGenerator brdfGenerator, bool parallel)
        {
            // When dispatching the table on the two dimensions (X, Y) a set of constrains apply:
            // - Every element (Xi, Yi) has a dependency on the previous one on the same column.
            // - The first element of a column has a dependency on the first element of the previous column.
            // - The element (0,0) doesn't have a dependency on any element.
            // To be able to dispatch this as a job, we need to compute the first line linearly and then dispatch every column starting from the second element.

            using (var ltcData = new NativeArray <LTCData>(brdfGenerator.tableResolution * brdfGenerator.tableResolution, Allocator.TempJob))
            {
                // Create the fitter
                NelderMead fitter = new NelderMead(3);

                Debug.Log("Running fitting job on the " + brdfGenerator.type.Name + " BRDF.");

                // Fill the first line
                for (int roughnessIndex = brdfGenerator.tableResolution - 1; roughnessIndex >= 0; roughnessIndex--)
                {
                    FitInitial(brdfGenerator, fitter, ltcData, roughnessIndex, 0);
                }

                BRDFGeneratorJob brdfJob = new BRDFGeneratorJob
                {
                    ltcData         = ltcData,
                    tableResolution = brdfGenerator.tableResolution,
                    sampleCount     = brdfGenerator.sampleCount,
                    lightingModel   = brdfGenerator.brdf.GetLightingModel(),
                    parametrization = brdfGenerator.parametrization,
                };

                if (parallel)
                {
                    // Create, run the job and wait for its completion.
                    JobHandle fittingJob = brdfJob.Schedule(brdfGenerator.tableResolution, 1);
                    fittingJob.Complete();
                }
                else
                {
                    for (int i = 0; i < brdfGenerator.tableResolution; ++i)
                    {
                        brdfJob.Execute(i);
                    }
                }

                Debug.Log("Fitting done. Exporting the file");

                // Export the table to disk
                string   BRDFName       = brdfGenerator.type.Name;
                FileInfo CSharpFileName = new FileInfo(Path.Combine(brdfGenerator.outputDir, "LtcData." + BRDFName + ".cs"));
                ExportToCSharp(ltcData, brdfGenerator.tableResolution, brdfGenerator.parametrization, CSharpFileName, BRDFName);
            }
        }
            public void Fit(int roughnessIndex, int thetaIndex, NelderMead fitter, IBRDF brdf)
            {
                // Compute the roughness and cosTheta for this sample
                float roughness, cosTheta;

                GetRoughnessAndAngle(roughnessIndex, thetaIndex, tableResolution, parametrization, out roughness, out cosTheta);

                // Compute the matching view vector
                Vector3 tsView = new Vector3(Mathf.Sqrt(1 - cosTheta * cosTheta), 0, cosTheta);

                // Compute BRDF's magnitude and average direction
                LTCData currentLTCData;

                LTCDataUtilities.Initialize(out currentLTCData);
                LTCDataUtilities.ComputeAverageTerms(brdf, ref tsView, roughness, sampleCount, ref currentLTCData);

                // Otherwise use average direction as Z vector
                int     previousLTCDataIndex = (thetaIndex - 1) * tableResolution + roughnessIndex;
                LTCData previousLTC          = ltcData[previousLTCDataIndex];

                currentLTCData.m11 = previousLTC.m11;
                currentLTCData.m22 = previousLTC.m22;
                currentLTCData.m13 = previousLTC.m13;

                LTCDataUtilities.Update(ref currentLTCData);

                // Find best-fit LTC lobe (scale, alphax, alphay)
                if (currentLTCData.magnitude > 1e-6)
                {
                    double[] startFit  = LTCDataUtilities.GetFittingParms(in currentLTCData);
                    double[] resultFit = new double[startFit.Length];

                    int localSampleCount = sampleCount;
                    currentLTCData.error = (float)fitter.FindFit(resultFit, startFit, (double)k_FitExploreDelta, (double)k_Tolerance, k_MaxIterations, (double[] parameters) =>
                    {
                        LTCDataUtilities.SetFittingParms(ref currentLTCData, parameters, false);
                        return(ComputeError(currentLTCData, brdf, localSampleCount, ref tsView, roughness));
                    });
                    currentLTCData.iterationsCount = fitter.m_lastIterationsCount;

                    // Update LTC with final best fitting values
                    LTCDataUtilities.SetFittingParms(ref currentLTCData, resultFit, false);
                }

                // Store new valid result
                int currentLTCDataIndex = thetaIndex * tableResolution + roughnessIndex;

                ltcData[currentLTCDataIndex] = currentLTCData;
            }
        static public void FitInitial(BRDFGenerator brdfGenerator, NelderMead fitter, NativeArray <LTCData> ltcData, int roughnessIndex, int thetaIndex)
        {
            // Compute the roughness and cosTheta for this sample
            float roughness, cosTheta;

            GetRoughnessAndAngle(roughnessIndex, thetaIndex, brdfGenerator.tableResolution, brdfGenerator.parametrization, out roughness, out cosTheta);

            // Compute the matching view vector
            Vector3 tsView = new Vector3(Mathf.Sqrt(1 - cosTheta * cosTheta), 0, cosTheta);

            // Compute BRDF's magnitude and average direction
            LTCData currentLTCData;

            LTCDataUtilities.Initialize(out currentLTCData);
            LTCDataUtilities.ComputeAverageTerms(brdfGenerator.brdf, ref tsView, roughness, brdfGenerator.sampleCount, ref currentLTCData);

            // if theta == 0 the lobe is rotationally symmetric and aligned with Z = (0 0 1)
            currentLTCData.X.x = 1;
            currentLTCData.X.y = 0;
            currentLTCData.X.z = 0;

            currentLTCData.Y.x = 0;
            currentLTCData.Y.y = 1;
            currentLTCData.Y.z = 0;

            currentLTCData.Z.x = 0;
            currentLTCData.Z.y = 0;
            currentLTCData.Z.z = 1;

            if (roughnessIndex == (brdfGenerator.tableResolution - 1))
            {
                // roughness = 1 or no available result
                currentLTCData.m11 = 1.0f;
                currentLTCData.m22 = 1.0f;
            }
            else
            {
                // init with roughness of previous fit
                LTCData previousLTC = ltcData[roughnessIndex + 1];
                currentLTCData.m11 = previousLTC.m11;
                currentLTCData.m22 = previousLTC.m22;
            }
            currentLTCData.m13 = 0;

            LTCDataUtilities.Update(ref currentLTCData);

            // Find best-fit LTC lobe (scale, alphax, alphay)
            if (currentLTCData.magnitude > 1e-6)
            {
                double[] startFit  = LTCDataUtilities.GetFittingParms(in currentLTCData);
                double[] resultFit = new double[startFit.Length];

                currentLTCData.error = (float)fitter.FindFit(resultFit, startFit, k_FitExploreDelta, k_Tolerance, k_MaxIterations, (double[] parameters) =>
                {
                    LTCDataUtilities.SetFittingParms(ref currentLTCData, parameters, true);
                    return(ComputeError(currentLTCData, brdfGenerator.brdf, brdfGenerator.sampleCount, ref tsView, roughness));
                });
                currentLTCData.iterationsCount = fitter.m_lastIterationsCount;

                // Update LTC with final best fitting values
                LTCDataUtilities.SetFittingParms(ref currentLTCData, resultFit, true);
            }

            // Store new valid result
            ltcData[roughnessIndex] = currentLTCData;
        }