// Credit goes to Kragrathea. public static UnsafeBitmap BumpToNormalMap(UnsafeBitmap source, Single strength) { strength = Mathf.Clamp(strength, 0.0F, 10.0F); UnsafeBitmap result = new UnsafeBitmap(new Bitmap(source.Bitmap.Width, source.Bitmap.Height, PixelFormat.Format32bppArgb)); source.LockBitmap(); result.LockBitmap(); for (Int32 by = 0; by < result.Bitmap.Height; by++) { for (Int32 bx = 0; bx < result.Bitmap.Width; bx++) { Int32 x = bx == 0 ? result.Bitmap.Width : bx; Single xLeft = ((Color)source.GetPixel(x - 1, by)).grayscale * strength; x = bx == (result.Bitmap.Width - 1) ? 0 : bx; Single xRight = ((Color)source.GetPixel(x + 1, by)).grayscale * strength; Int32 y = by == 0 ? result.Bitmap.Height : by; Single yUp = ((Color)source.GetPixel(bx, y - 1)).grayscale * strength; y = by == (result.Bitmap.Height - 1) ? 0 : by; Single yDown = ((Color)source.GetPixel(bx, y + 1)).grayscale * strength; Single xDelta = ((xLeft - xRight) + 1) * 0.5f; Single yDelta = ((yUp - yDown) + 1) * 0.5f; result.SetPixel(bx, by, new Color(yDelta, yDelta, yDelta, xDelta)); } } source.UnlockBitmap(); result.UnlockBitmap(); return(result); }
/// <summary> /// Extracts the average color from a bitmap file /// </summary> public static Color GetAverageColor(Bitmap bm) { Byte avgB; Byte avgG; Byte avgR; using (UnsafeBitmap bitmap = new UnsafeBitmap(bm)) { bitmap.LockBitmap(); Int64[] totals = { 0, 0, 0 }; Int64 width = bm.Width; Int64 height = bm.Height; for (Int32 y = 0; y < height; y++) { for (Int32 x = 0; x < width; x++) { System.Drawing.Color c = bitmap.GetPixel(x, y); totals[0] += c.B; totals[1] += c.G; totals[2] += c.R; } } avgB = (Byte)(totals[0] / (width * height)); avgG = (Byte)(totals[1] / (width * height)); avgR = (Byte)(totals[2] / (width * height)); bitmap.UnlockBitmap(); } return(new Color32(avgR, avgG, avgB, 255)); }
/// <summary> /// Generates a PQS Setup + the Scaled Space Maps needed /// </summary> /// <returns></returns> // ReSharper disable once InconsistentNaming private static void GeneratePQS(ref ConfigNode node, String name, String folder, Planet planet, Color planetColor, out Color average, out List <Color> biomes) { // Log Console.WriteLine("Preparing to load PQS data"); // Create the node ConfigNode pqs = new ConfigNode("PQS"); // TODO: Material Settings? // Create a node for the mods ConfigNode mods = new ConfigNode("Mods"); pqs.AddConfigNode(mods); // Load the PQSDatabase and select a setup ConfigNode pqsDatabase = Utility.Load("pqs"); List <PQSPreset> data = pqsDatabase.nodes.Select(n => Parser.CreateObjectFromConfigNode <PQSPreset>(n)) .ToList(); data = data.Where(d => ((planet.radius * 100) > d.MinRadius) && ((planet.radius * 100) < d.MaxRadius)) .ToList(); PQSPreset setup = data[Random.Next(0, data.Count)]; // Setup the interpreter Interpreter interpreter = new Interpreter() .SetVariable("planet", planet, typeof(Planet)) .SetVariable("pqsVersion", setup, typeof(PQSPreset)) .SetVariable("Random", Random, typeof(Random)) .SetVariable("Seed", Seed, typeof(Int32)) .SetVariable("Color", Utility.GenerateColor(), typeof(Color)) .Reference(typeof(Parser)) .Reference(typeof(Generator)) .Reference(typeof(Utility)) .Reference(typeof(Utils)); // Transfer the mod nodes and evaluate expressions foreach (ConfigNode modNode in setup.Mods.nodes) { mods.AddConfigNode(Utility.Eval(modNode, interpreter)); } // Create a new PQSObject PQS pqsVersion = new PQS(planet.radius * 100); List <PQSMod> patchedmods = new List <PQSMod>(); // Log Console.WriteLine($"Created PQS Object for {name}"); // Get all loaded types List <Type> types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).ToList(); // Load mods from Config foreach (ConfigNode mod in mods.nodes) { // get the mod type if (types.Count(t => t.Name == mod.name) == 0) { continue; } Type loaderType = types.FirstOrDefault(t => t.Name == mod.name); String testName = mod.name != "LandControl" ? "PQSMod_" + mod.name : "PQSLandControl"; Type modType = types.FirstOrDefault(t => t.Name == testName); if ((loaderType == null) || (modType == null)) { continue; } // Do any PQS mods already exist on this PQS matching this mod? IEnumerable <PQSMod> existingmods = pqsVersion.mods.Where(m => m.GetType() == modType); // Create the loader Object loader = Activator.CreateInstance(loaderType); // Reflection, because C# being silly... :/ MethodInfo createNew = loaderType.GetMethod("Create", new[] { typeof(PQS) }); MethodInfo create = loaderType.GetMethod("Create", new[] { modType }); IList <PQSMod> pqsmods = existingmods as IList <PQSMod> ?? existingmods.ToList(); if (pqsmods.Any()) { // Attempt to find a PQS mod we can edit that we have not edited before PQSMod existingMod = pqsmods.FirstOrDefault(m => !patchedmods.Contains(m) && (!mod.HasValue("name") || (mod.HasValue("index") ? (pqsmods.ToList().IndexOf(m) == Int32.Parse(mod.GetValue("index"))) && (m.name == mod.GetValue("name")) : m.name == mod.GetValue("name")))); if (existingMod != null) { create.Invoke(loader, new Object[] { existingMod }); Parser.LoadObjectFromConfigurationNode(loader, mod); patchedmods.Add(existingMod); } else { createNew.Invoke(loader, new Object[] { pqsVersion }); Parser.LoadObjectFromConfigurationNode(loader, mod); } } else { createNew.Invoke(loader, new Object[] { pqsVersion }); Parser.LoadObjectFromConfigurationNode(loader, mod); } // Log Console.WriteLine($"Created a new instance of {loaderType.Name} on {name}"); } // Size Int32 width = pqsVersion.radius >= 600000 ? 4096 : pqsVersion.radius <= 100000 ? 1024 : 2048; // Biome colors int numBiomes = Random.Next(2, 10); biomes = new List <Color>(numBiomes); for (int i = 0; i < numBiomes; i++) { biomes.Add(Utility.GenerateColor()); } // Export ScaledSpace Maps using (UnsafeBitmap diffuse = new UnsafeBitmap(width, width / 2)) { using (UnsafeBitmap height = new UnsafeBitmap(width, width / 2)) { using (UnsafeBitmap biomeMap = new UnsafeBitmap(width, width / 2)) { Console.WriteLine("Exporting Scaled Space maps from the PQS. This could take a while..."); // Iterate over the PQS pqsVersion.SetupSphere(); diffuse.LockBitmap(); height.LockBitmap(); biomeMap.LockBitmap(); for (Int32 i = 0; i < width; i++) { for (Int32 j = 0; j < (width / 2); j++) { // Create a VertexBuildData VertexBuildData builddata = new VertexBuildData { directionFromCenter = Quaternion.CreateFromAngleAxis((360d / width) * i, Vector3.Up) * Quaternion .CreateFromAngleAxis(90d - ((180d / (width / 2.0)) * j), Vector3.Right) * Vector3.Forward, vertHeight = pqsVersion.radius }; // Build the maps pqsVersion.OnVertexBuildHeight(builddata); pqsVersion.OnVertexBuild(builddata); builddata.vertColor.a = 1f; Single h = Mathf.Clamp01((Single)((builddata.vertHeight - pqsVersion.radius) * (1d / pqsVersion.radiusMax))); Single h1 = Mathf.Clamp01((Single)((builddata.vertHeight - pqsVersion.radius) * (1d / (pqsVersion.radiusMax != 0 ? pqsVersion.radiusMax : planet.radius)))); diffuse.SetPixel(i, j, builddata.vertColor); height.SetPixel(i, j, new Color(h, h, h)); biomeMap.SetPixel(i, j, biomes[(int)(h1 * (numBiomes - 1))]); } } diffuse.UnlockBitmap(); height.UnlockBitmap(); biomeMap.UnlockBitmap(); // Save the textures Directory.CreateDirectory(Directory.GetCurrentDirectory() + "/systems/" + folder + "/PluginData/"); using (UnsafeBitmap normals = Utility.BumpToNormalMap(height, 9)) { // TODO: Implement something to make strength dynamic diffuse.Bitmap .Save(Directory.GetCurrentDirectory() + "/systems/" + folder + "/PluginData/" + name + "_Texture.png", ImageFormat.Png); height.Bitmap .Save(Directory.GetCurrentDirectory() + "/systems/" + folder + "/PluginData/" + name + "_Height.png", ImageFormat.Png); // In case you need it :) normals.Bitmap .Save(Directory.GetCurrentDirectory() + "/systems/" + folder + "/PluginData/" + name + "_Normals.png", ImageFormat.Png); biomeMap.Bitmap .Save(Directory.GetCurrentDirectory() + "/systems/" + folder + "/PluginData/" + name + "_Biomes.png", ImageFormat.Png); } } } // Log Console.WriteLine($"Saved maps to {Directory.GetCurrentDirectory() + "/systems/" + folder + "/PluginData/"}"); // Finish node.AddConfigNode(pqs); // Colors average = Utility.GetAverageColor(diffuse.Bitmap); } }