// this function generates a copy of the original planet map and adds a number of rows to the top and bottom of the map (to prevent pole pinching) float[,] PrepareHeightMap(PG_Planet pgPlanet) { // compute height of the new map var height = PG_Planet.c_height + m_numPolePaddingRows * 2; // allocate the new map var buffer = new float[height, PG_Planet.c_width]; // get the maximum height for the top row var maximumHeight = 0.0f; for (var x = 0; x < PG_Planet.c_width; x++) { if (pgPlanet.m_color[0, x] == m_topPaddingColor) { if (pgPlanet.m_height[0, x] > maximumHeight) { maximumHeight = pgPlanet.m_height[0, x]; } } } if (m_topPaddingColor == pgPlanet.m_waterColor) { maximumHeight = 0.0f; } // set the height of the padded rows for (var i = 0; i < m_numPolePaddingRows; i++) { var y = m_numPolePaddingRows - i - 1; var t = (float)(i + 1) / (float)m_numPolePaddingRows; t *= 2.0f; for (var x = 0; x < PG_Planet.c_width; x++) { var originalHeight = pgPlanet.m_height[0, x]; buffer[y, x] = Mathf.Lerp(originalHeight, maximumHeight, t); } } // get the maximum height for the bottom row maximumHeight = 0.0f; for (var x = 0; x < PG_Planet.c_width; x++) { if (pgPlanet.m_color[PG_Planet.c_height - 1, x] == m_bottomPaddingColor) { if (pgPlanet.m_height[0, x] > maximumHeight) { maximumHeight = pgPlanet.m_height[0, x]; } } } if (m_bottomPaddingColor == pgPlanet.m_waterColor) { maximumHeight = 0.0f; } // set the height of the padded rows for (var i = 0; i < m_numPolePaddingRows; i++) { var y = m_numPolePaddingRows + i + PG_Planet.c_height; var t = (float)(i + 1) / (float)m_numPolePaddingRows; t *= 2.0f; for (var x = 0; x < PG_Planet.c_width; x++) { var originalHeight = pgPlanet.m_height[PG_Planet.c_height - 1, x]; buffer[y, x] = Mathf.Lerp(originalHeight, maximumHeight, t); } } // copy the original map into the are in between the padded rows for (var y = 0; y < PG_Planet.c_height; y++) { for (var x = 0; x < PG_Planet.c_width; x++) { buffer[y + m_numPolePaddingRows, x] = pgPlanet.m_height[y, x]; } } // all done return(buffer); }
// this function generates a copy of the original planet map and adds a number of rows to the top and bottom of the map (to prevent pole pinching) Color[,] PrepareColorMap(PG_Planet pgPlanet) { // compute height of the new map var height = PG_Planet.c_height + m_numPolePaddingRows * 2; // allocate the new map var buffer = new Color[height, PG_Planet.c_width]; // find the most used color on the top row var tally = new Dictionary <Color, int>(); for (var x = 0; x < PG_Planet.c_width; x++) { var color = pgPlanet.m_color[0, x]; if (!tally.ContainsKey(color)) { tally.Add(color, 0); } tally[color]++; } var mostUsedColor = Color.black; var mostUsedCount = 0; foreach (var key in tally.Keys) { if (tally[key] > mostUsedCount) { mostUsedCount = tally[key]; mostUsedColor = key; } } m_topPaddingColor = mostUsedColor; // add north pole color for (var i = 0; i < m_numPolePaddingRows; i++) { var y = m_numPolePaddingRows - i - 1; for (var x = 0; x < PG_Planet.c_width; x++) { buffer[y, x] = m_topPaddingColor; } } // find the most used color on the bottom row tally = new Dictionary <Color, int>(); for (var x = 0; x < PG_Planet.c_width; x++) { var color = pgPlanet.m_color[PG_Planet.c_height - 1, x]; if (!tally.ContainsKey(color)) { tally.Add(color, 0); } tally[color]++; } mostUsedColor = Color.black; mostUsedCount = 0; foreach (var key in tally.Keys) { if (tally[key] > mostUsedCount) { mostUsedCount = tally[key]; mostUsedColor = key; } } m_bottomPaddingColor = mostUsedColor; // add south pole color for (var i = 0; i < m_numPolePaddingRows; i++) { var y = m_numPolePaddingRows + i + PG_Planet.c_height; for (var x = 0; x < PG_Planet.c_width; x++) { buffer[y, x] = m_bottomPaddingColor; } } // copy the original map into the are in between the padded rows for (var y = 0; y < PG_Planet.c_height; y++) { for (var x = 0; x < PG_Planet.c_width; x++) { buffer[y + m_numPolePaddingRows, x] = pgPlanet.m_color[y, x]; } } // all done return(buffer); }
// call this to generate the planet texture maps bool GeneratePlanetTextureMaps(PG_Planet pgPlanet, string filename) { // vars for the progress bar var currentStep = 1; var totalSteps = 8; // update the progress bar EditorUtility.DisplayProgressBar("Planet " + (pgPlanet.m_id + 1), "Preparing color map...", (float)currentStep++ / totalSteps); // prepare the color map var preparedColorMap = PrepareColorMap(pgPlanet); if (m_debugMode) { EditorUtility.DisplayProgressBar("Planet " + (pgPlanet.m_id + 1), "Saving prepared color map...", 0.0f); PG_Tools.SaveAsPNG(preparedColorMap, Application.dataPath + "/Exported/Debug - Prepared Color Map.png"); } // update the progress bar EditorUtility.DisplayProgressBar("Planet " + (pgPlanet.m_id + 1), "Preparing height map...", (float)currentStep++ / totalSteps); // prepare the height map var preparedHeightMap = PrepareHeightMap(pgPlanet); if (m_debugMode) { EditorUtility.DisplayProgressBar("Planet " + (pgPlanet.m_id + 1), "Saving prepared height map...", 0.0f); PG_Tools.SaveAsPNG(preparedHeightMap, Application.dataPath + "/Exported/Debug - Prepared Height Map.png"); } float minimumDifference = 0.0f; float maximumDifference = 0.0f; byte[] differenceBuffer = null; if (pgPlanet.m_surfaceId != 1) { // scale to power of two EditorUtility.DisplayProgressBar("Planet " + (pgPlanet.m_id + 1), "Scaling to power of two...", (float)currentStep++ / totalSteps); var bicubicScale = new PG_BicubicScaleElevation(); var elevation = bicubicScale.Process(preparedHeightMap, m_textureMapWidth, m_textureMapHeight); if (m_debugMode) { EditorUtility.DisplayProgressBar("Planet " + (pgPlanet.m_id + 1), "Saving bicubic scale map...", 0.0f); var contourMap = new PG_ContourMap(); var tempBuffer = contourMap.Process(elevation, pgPlanet.m_waterElevation); PG_Tools.SaveAsEXR(tempBuffer, Application.dataPath + "/Exported/Debug - Bicubic Scale.exr"); } // craters pass EditorUtility.DisplayProgressBar("Planet " + (pgPlanet.m_id + 1), "Creating craters...", (float)currentStep++ / totalSteps); if (pgPlanet.m_atmosphericDensityId == 0) { var craters = new PG_Craters(); elevation = craters.Process(elevation, pgPlanet.m_id, m_craterGain, pgPlanet.m_waterElevation); if (m_debugMode) { EditorUtility.DisplayProgressBar("Planet " + (pgPlanet.m_id + 1), "Saving craters map...", 0.0f); var contourMap = new PG_ContourMap(); var tempBuffer = contourMap.Process(elevation, pgPlanet.m_waterElevation); PG_Tools.SaveAsEXR(tempBuffer, Application.dataPath + "/Exported/Debug - Craters.exr"); } } // at this point we want to save the current elevation buffer to use when calculating the difference map later var baseElevationBuffer = elevation; // mountains pass EditorUtility.DisplayProgressBar("Planet " + (pgPlanet.m_id + 1), "Creating mountains...", (float)currentStep++ / totalSteps); if (pgPlanet.m_atmosphericDensityId != 0) { var mountains = new PG_Mountains(); elevation = mountains.Process(elevation, pgPlanet.m_id, m_octaves, m_mountainScale, m_mountainLacunarity, m_mountainPersistence, m_mountainGain, pgPlanet.m_waterElevation); if (m_debugMode) { EditorUtility.DisplayProgressBar("Planet " + (pgPlanet.m_id + 1), "Saving mountains map...", 0.0f); var contourMap = new PG_ContourMap(); var tempBuffer = contourMap.Process(elevation, pgPlanet.m_waterElevation); PG_Tools.SaveAsEXR(tempBuffer, Application.dataPath + "/Exported/Debug - Mountains.exr"); } } // hydraulic erosion pass var minimumElevation = pgPlanet.m_waterElevation - (pgPlanet.m_waterElevation / 16.0f); EditorUtility.DisplayProgressBar("Planet " + (pgPlanet.m_id + 1), "Hydraulic erosion pass...", (float)currentStep++ / totalSteps); if (m_doHydraulicErosionPass) { if (pgPlanet.m_atmosphericDensityId != 0) { var hydraulicErosion = new PG_HydraulicErosion(); var gravityConstant = m_gravityConstant * pgPlanet.m_gravity; var rainWaterAmount = m_rainWaterAmount * (float)pgPlanet.m_atmosphericDensityId / 3.0f; elevation = hydraulicErosion.Process(elevation, minimumElevation, m_xyScaleToMeters, m_zScaleToMeters, rainWaterAmount, m_sedimentCapacity, gravityConstant, m_frictionConstant, m_evaporationConstant, m_depositionConstant, m_dissolvingConstant, m_stepDeltaTime, m_finalBlurRadius); if (elevation == null) { return(false); } if (m_debugMode) { EditorUtility.DisplayProgressBar("Planet " + (pgPlanet.m_id + 1), "Saving hydraulic erosion map...", 0.0f); var contourMap = new PG_ContourMap(); var tempBuffer = contourMap.Process(elevation, pgPlanet.m_waterElevation); PG_Tools.SaveAsEXR(tempBuffer, Application.dataPath + "/Exported/Debug - Hydraulic Erosion.exr"); } } } if (m_debugMode) { // generate and save the albedo map EditorUtility.DisplayProgressBar("Planet " + (pgPlanet.m_id + 1), "Saving albedo map...", 0.0f); var albedoMap = new PG_AlbedoMap(); var albedoBuffer = albedoMap.Process(elevation, preparedColorMap, pgPlanet.m_waterElevation, pgPlanet.m_waterColor, pgPlanet.m_groundColor); PG_Tools.SaveAsPNG(albedoBuffer, Application.dataPath + "/Exported/Debug - Albedo Map.png"); // generate and save the normal map EditorUtility.DisplayProgressBar("Planet " + (pgPlanet.m_id + 1), "Saving normal map...", 0.0f); var normalMap = new PG_NormalMap(); var normalsBuffer = normalMap.Process(elevation, 256.0f, pgPlanet.m_waterElevation, 1); PG_Tools.SaveAsPNG(normalsBuffer, Application.dataPath + "/Exported/Debug - Normal Map.png"); // generate and save the specular map EditorUtility.DisplayProgressBar("Planet " + (pgPlanet.m_id + 1), "Saving specular map...", 0.0f); var waterSpecularColor = new Color(1.0f, 1.0f, 1.0f); var specularMap = new PG_SpecularMap(); var specularBuffer = specularMap.Process(elevation, albedoBuffer, pgPlanet.m_waterElevation, waterSpecularColor, 0.75f, 1); PG_Tools.SaveAsPNG(specularBuffer, Application.dataPath + "/Exported/Debug - Specular Map.png", true); // generate and save the water mask map EditorUtility.DisplayProgressBar("Planet " + (pgPlanet.m_id + 1), "Saving water mask map...", 0.0f); var waterMaskMap = new PG_WaterMaskMap(); var waterMaskBuffer = waterMaskMap.Process(elevation, pgPlanet.m_waterElevation, 1); PG_Tools.SaveAsPNG(waterMaskBuffer, Application.dataPath + "/Exported/Debug - Water Mask Map.png", true); // generate and save the elevation map (for the terrain grid) EditorUtility.DisplayProgressBar("Planet " + (pgPlanet.m_id + 1), "Saving elevation map...", 0.0f); PG_Tools.SaveAsEXR(elevation, Application.dataPath + "/Exported/Debug - Elevation Map.exr"); } // figure out what our minimum and maximum deltas are EditorUtility.DisplayProgressBar("Planet " + (pgPlanet.m_id + 1), "Computing deltas...", (float)currentStep++ / totalSteps); minimumDifference = 0; maximumDifference = 0; for (var y = 0; y < m_textureMapHeight; y++) { for (var x = 0; x < m_textureMapWidth; x++) { var difference = elevation[y, x] - baseElevationBuffer[y, x]; if (difference < minimumDifference) { minimumDifference = difference; } if (difference > maximumDifference) { maximumDifference = difference; } } } // rescale float deltas to 0 to 255 var elevationScale = 255.0f / (maximumDifference - minimumDifference); differenceBuffer = new byte[m_textureMapWidth * m_textureMapHeight]; for (var y = 0; y < m_textureMapHeight; y++) { for (var x = 0; x < m_textureMapWidth; x++) { var difference = (byte)Mathf.RoundToInt((elevation[y, x] - baseElevationBuffer[y, x] - minimumDifference) * elevationScale); differenceBuffer[y * m_textureMapWidth + x] = difference; elevation[y, x] = difference / 255.0f; } } if (m_debugMode) { EditorUtility.DisplayProgressBar("Planet " + (pgPlanet.m_id + 1), "Saving difference buffer...", 0.0f); PG_Tools.SaveAsPNG(elevation, Application.dataPath + "/Exported/" + "Debug - Difference Buffer.png"); AssetDatabase.Refresh(); } } // save the map! EditorUtility.DisplayProgressBar("Planet " + (pgPlanet.m_id + 1), "Compressing and saving the planet data...", (float)currentStep++ / totalSteps); SavePlanetMap(filename, pgPlanet, preparedHeightMap, preparedColorMap, minimumDifference, maximumDifference, differenceBuffer); return(true); }
void SavePlanetMap(string filename, PG_Planet pgPlanet, float[,] preparedHeightMap, Color[,] preparedColorMap, float minimumDifference, float maximumDifference, byte[] differenceBuffer) { // save map to compressed bytes file with a header using (var fileStream = new FileStream(filename, FileMode.Create)) { using (var gZipStream = new GZipStream(fileStream, CompressionMode.Compress, false)) { var binaryWriter = new BinaryWriter(gZipStream); // version number binaryWriter.Write(c_versionNumber); // misc data binaryWriter.Write(pgPlanet.m_minimumElevation); binaryWriter.Write(pgPlanet.m_waterElevation); binaryWriter.Write(pgPlanet.m_snowElevation); binaryWriter.Write(pgPlanet.m_waterColor.r); binaryWriter.Write(pgPlanet.m_waterColor.g); binaryWriter.Write(pgPlanet.m_waterColor.b); binaryWriter.Write(pgPlanet.m_groundColor.r); binaryWriter.Write(pgPlanet.m_groundColor.g); binaryWriter.Write(pgPlanet.m_groundColor.b); binaryWriter.Write(pgPlanet.m_snowColor.r); binaryWriter.Write(pgPlanet.m_snowColor.g); binaryWriter.Write(pgPlanet.m_snowColor.b); // prepared map width and height var preparedMapWidth = preparedHeightMap.GetLength(1); var preparedMapHeight = preparedHeightMap.GetLength(0); binaryWriter.Write(preparedMapWidth); binaryWriter.Write(preparedMapHeight); // prepared height map for (var y = 0; y < preparedMapHeight; y++) { for (var x = 0; x < preparedMapWidth; x++) { binaryWriter.Write(preparedHeightMap[y, x]); } } // prepared color map for (var y = 0; y < preparedMapHeight; y++) { for (var x = 0; x < preparedMapWidth; x++) { binaryWriter.Write(preparedColorMap[y, x].r); binaryWriter.Write(preparedColorMap[y, x].g); binaryWriter.Write(preparedColorMap[y, x].b); } } // difference buffer if (differenceBuffer != null) { // minimum and maximum difference binaryWriter.Write(minimumDifference); binaryWriter.Write(maximumDifference); // difference buffer binaryWriter.Write(differenceBuffer); } } } }
// generate maps and show them on the planet game object void MakeSomeMagic() { // show progress bar EditorUtility.DisplayProgressBar("Planet Generator", "Initializing...", 0.0f); // load the game data var textAsset = Resources.Load(m_gameDataFileName) as TextAsset; // convert it from the json string to our game data class var gameData = JsonUtility.FromJson <GameData>(textAsset.text); // initialize the game data gameData.Initialize(); // initialize static components of planet generator PG_AlbedoMap.Initialize(); PG_Craters.Initialize(); // calculate the texture map scale (and it must be an even number) m_textureMapScaleX = Mathf.FloorToInt((float)m_textureMapWidth / (float)PG_Planet.c_width); m_textureMapScaleY = Mathf.FloorToInt((float)m_textureMapHeight / (float)(PG_Planet.c_height + m_numPolePaddingRows * 2)); if (m_textureMapScaleX < 2) { m_textureMapScaleX = 2; } else if ((m_textureMapScaleX & 1) == 1) { m_textureMapScaleX--; } if (m_textureMapScaleY < 2) { m_textureMapScaleY = 2; } else if ((m_textureMapScaleY & 1) == 1) { m_textureMapScaleY--; } // do some magic PG_Planet pgPlanet; for (var id = 0; id < c_numPlanets; id++) { if (m_debugMode) { id = m_debugPlanetID; } pgPlanet = new PG_Planet(gameData, m_planetImagesPath, id); if (pgPlanet.m_mapIsValid) { var filename = Application.dataPath + "/" + m_resourcesPath + "/Planets/" + pgPlanet.m_id + ".bytes"; if (m_debugMode || !File.Exists(filename)) { if (!GeneratePlanetTextureMaps(pgPlanet, filename)) { break; } } } if (m_debugMode) { break; } } // show progress bar EditorUtility.ClearProgressBar(); }