/// <summary>
        /// Forces the generation of any shaders that make use of generated noise header files. Gathers all
        /// the NoiseShaderGenerators and generates shaders based on the ".noisehlsltemplate" file
        /// provided by that particular NoiseShaderGenerator implementation
        /// </summary>
        public static void GenerateShaders()
        {
            System.Globalization.CultureInfo prevCultureInfo = System.Threading.Thread.CurrentThread.CurrentCulture;
            System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;

            GatherGenerators();

            IFractalType[] fractalTypes = s_fractalTypes;
            INoiseType[]   noiseTypes   = s_noiseTypes;
            Dictionary <Type, INoiseShaderGenerator> generators = s_generators;

            StringBuilder shaderSB = new StringBuilder();
            StringBuilder passesSB = new StringBuilder();

            foreach (KeyValuePair <Type, INoiseShaderGenerator> pair in generators)
            {
                shaderSB.Clear();
                passesSB.Clear();

                string shaderTemplateStr = null;

                INoiseShaderGenerator     generator     = pair.Value;
                ShaderGeneratorDescriptor generatorDesc = generator.GetDescription();

                if (!File.Exists(generatorDesc.templatePath))
                {
                    Debug.LogError("Could not find specified template file for noise shader generator: " + generator);
                    continue;
                }

                // load contents of shader template
                using (StreamReader sr = new StreamReader(generatorDesc.templatePath))
                {
                    shaderTemplateStr = sr.ReadToEnd();
                }

                // find the pass template using regex matching
                Match passTemplateMatch = Regex.Match(shaderTemplateStr, Strings.k_regexPassTemplate1);

                if (!passTemplateMatch.Success)
                {
                    Debug.LogError($"Could not find pass template in {generatorDesc.templatePath}. Skipping noise shader generation for this generator type!!");
                    continue;
                }

                string passTemplateStr = passTemplateMatch.Value;

                // generate shaders for each fractal type
                foreach (IFractalType fractal in fractalTypes)
                {
                    FractalTypeDescriptor fractalDesc = fractal.GetDescription();
                    string fullShaderCategory         = GetShaderName(generatorDesc, fractalDesc);

                    shaderSB.Append(NoiseLib.Strings.k_warningHeader);
                    shaderSB.Append(shaderTemplateStr);
                    shaderSB.Replace(NoiseLib.Strings.k_tagShaderCategory, $"\"{fullShaderCategory}\"");

                    // add passes for each noise type
                    foreach (INoiseType noise in noiseTypes)
                    {
                        GeneratedShaderInfo info = new GeneratedShaderInfo(fractal, noise);

                        // add to passes string builer
                        passesSB.Append(passTemplateStr);
                        passesSB.AppendLine();
                        passesSB.Replace(NoiseLib.Strings.k_tagIncludes, string.Format("#include \"{0}\"", info.generatedIncludePath));

                        info.ReplaceTags(passesSB);
                    }

                    // replace template with generated passes
                    string newContents = Regex.Replace(shaderSB.ToString(), Strings.k_regexPassTemplate2, passesSB.ToString());

                    newContents = newContents.Replace(NoiseLib.Strings.k_tagFractalName, fractalDesc.name);

                    // load shader contents from disk if it exists
                    string fileName = string.Format("{0}{1}.shader", generatorDesc.name, fractalDesc.name);
                    string filePath = string.Format("{0}/{1}", generatorDesc.outputDir, fileName);

                    if (!Directory.Exists(generatorDesc.outputDir))
                    {
                        Directory.CreateDirectory(generatorDesc.outputDir);
                    }

                    string currentContents = null;

                    FileInfo fi = new FileInfo(filePath);

                    if (File.Exists(filePath))
                    {
                        using (StreamReader sr = new StreamReader(filePath))
                        {
                            currentContents = sr.ReadToEnd();
                            currentContents = NormalizeLineEndings(currentContents);
                        }
                    }

                    // do some code cleanup
                    newContents = Regex.Replace(newContents, NoiseLib.Strings.k_regexDupCommas, ", ");
                    newContents = Regex.Replace(newContents, NoiseLib.Strings.k_emptyArgsRight, " )");
                    newContents = Regex.Replace(newContents, NoiseLib.Strings.k_emptyArgsLeft, "( ");

                    newContents = NormalizeLineEndings(newContents);

                    // only write to file if it is not read-only, ie. if it is one of the generated
                    // shader files that we ship with the TerrainTools package
                    if (!fi.IsReadOnly)
                    {
                        if (currentContents == null || currentContents.CompareTo(newContents) != 0)
                        {
                            try
                            {
                                using (StreamWriter sw = new StreamWriter(filePath))
                                {
                                    sw.Write(newContents);
                                }
                            }
                            catch (Exception)
                            {
                                // restore previous cultureinfo
                                System.Threading.Thread.CurrentThread.CurrentCulture = prevCultureInfo;
                            }
                        }
                    }

                    shaderSB.Clear();
                    passesSB.Clear();
                }
            }

            // restore previous cultureinfo
            System.Threading.Thread.CurrentThread.CurrentCulture = prevCultureInfo;

            // UnityEditor.AssetDatabase.Refresh();
        }
        /// <summary>
        /// Forces generation of the NoiseType and FractalType variant HLSL header files
        /// </summary>
        public static void GenerateHeaderFiles()
        {
            System.Globalization.CultureInfo prevCultureInfo = System.Threading.Thread.CurrentThread.CurrentCulture;
            System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;

            GatherNoiseTypes();
            GatherFractalTypes();

            INoiseType[]   noiseTypes   = s_noiseTypes;
            IFractalType[] fractalTypes = s_fractalTypes;

            string[] fractalContents = LoadFractalSource(fractalTypes);
            string[] noiseContents   = LoadNoiseSource(noiseTypes);

            for (int f = 0; f < fractalTypes.Length; ++f)
            {
                string fractalStr = fractalContents[f];

                // dont generate for this fractal type if the source could not be found
                if (fractalStr == null)
                {
                    continue;
                }

                IFractalType fractal = fractalTypes[f];

                for (int n = 0; n < noiseTypes.Length; ++n)
                {
                    string noiseStr = noiseContents[n];

                    // dont generate for this noise type if the source could not be found
                    if (noiseStr == null)
                    {
                        continue;
                    }

                    INoiseType          noise = noiseTypes[n];
                    GeneratedShaderInfo info  = new GeneratedShaderInfo(fractal, noise);

                    StringBuilder sb = new StringBuilder();

                    sb.Append(Strings.k_warningHeader);                             // add the DO NOT EDIT warning
                    sb.Append(fractalStr);                                          // add the fractal template

                    info.ReplaceTags(sb);

                    string newContents = sb.ToString();

                    // do some code cleanup
                    newContents = Regex.Replace(newContents, Strings.k_regexDupCommas, ", ");
                    newContents = Regex.Replace(newContents, Strings.k_emptyArgsRight, " )");
                    newContents = Regex.Replace(newContents, Strings.k_emptyArgsLeft, "( ");

                    newContents = NormalizeLineEndings(newContents);

                    string outputDir = info.outputDir;

                    // TODO(wyatt): need to verify this is actually a directory and not a file
                    if (!Directory.Exists(outputDir))
                    {
                        Directory.CreateDirectory(outputDir);
                    }

                    string oldContents = null;

                    FileInfo fi = new FileInfo(info.generatedIncludePath);

                    if (File.Exists(info.generatedIncludePath))
                    {
                        using (StreamReader sr = new StreamReader(info.generatedIncludePath))
                        {
                            oldContents = sr.ReadToEnd();
                            oldContents = NormalizeLineEndings(oldContents);
                        }
                    }

                    if (!fi.IsReadOnly)
                    {
                        if (oldContents == null || newContents.CompareTo(oldContents) != 0)
                        {
                            try
                            {
                                using (StreamWriter sw = new StreamWriter(info.generatedIncludePath))
                                {
                                    sw.Write(newContents);
                                }
                            }
                            catch (Exception)
                            {
                                // restore previous cultureinfo
                                System.Threading.Thread.CurrentThread.CurrentCulture = prevCultureInfo;
                            }
                        }
                    }
                }
            }

            // restore previous cultureinfo
            System.Threading.Thread.CurrentThread.CurrentCulture = prevCultureInfo;

            // UnityEditor.AssetDatabase.Refresh();
        }