diff --git a/TileBakeLibrary/CityJSONParsing/CityJSON.cs b/TileBakeLibrary/CityJSONParsing/CityJSON.cs index 8c165d7..e60cfd9 100644 --- a/TileBakeLibrary/CityJSONParsing/CityJSON.cs +++ b/TileBakeLibrary/CityJSONParsing/CityJSON.cs @@ -27,6 +27,8 @@ namespace Netherlands3D.CityJSON { public class CityJSON { + public string sourceFilePath = ""; + public JSONNode cityJsonNode; public Vector3Double[] vertices; private List textureVertices; @@ -43,7 +45,7 @@ public class CityJSON public CityJSON(string filepath, bool applyTransformScale = true, bool applyTransformOffset = true) { - //string jsonString = File.ReadAllText(filepath); + sourceFilePath = filepath; cityJsonNode = JSON.StreamParse(filepath); if (cityJsonNode == null || cityJsonNode["CityObjects"] == null) @@ -53,7 +55,6 @@ public CityJSON(string filepath, bool applyTransformScale = true, bool applyTran } //Get vertices - Console.Write("\r reading vertices"); textureVertices = new List(); Textures = new List(); @@ -70,7 +71,7 @@ public CityJSON(string filepath, bool applyTransformScale = true, bool applyTran cityJsonNode["transform"]["translate"][2].AsDouble ) : new Vector3Double(0, 0, 0); - //now load all the vertices with the scaler and offset applied + //Load all the vertices with the scaler and offset applied vertices = new Vector3Double[cityJsonNode["vertices"].Count]; int counter = 0; foreach (JSONNode node in cityJsonNode["vertices"]) @@ -82,7 +83,7 @@ public CityJSON(string filepath, bool applyTransformScale = true, bool applyTran ); vertices[counter++] = vertCoordinates; } - //get textureVertices + //Get texture vertices foreach (JSONNode node in cityJsonNode["appearance"]["vertices-texture"]) { textureVertices.Add(new Vector2(node[0].AsFloat, node[1].AsFloat)); diff --git a/TileBakeLibrary/CityJSONParsing/SimpleJSON.cs b/TileBakeLibrary/CityJSONParsing/SimpleJSON.cs index 06abf6a..d25059e 100644 --- a/TileBakeLibrary/CityJSONParsing/SimpleJSON.cs +++ b/TileBakeLibrary/CityJSONParsing/SimpleJSON.cs @@ -781,7 +781,7 @@ public static JSONNode StreamParse(string fileName) { i = 0; string percentage = ((int)(100 * streamReader.BaseStream.Position / bytelength)).ToString(); - Console.Write($"\rreading file ... { percentage}%"); + Console.Write($"\rReading {Path.GetFileName(fileName)} ... { percentage}%"); } switch (str) { diff --git a/TileBakeLibrary/CityJSONToTileConverter.cs b/TileBakeLibrary/CityJSONToTileConverter.cs index 30b4b16..e4f8a66 100644 --- a/TileBakeLibrary/CityJSONToTileConverter.cs +++ b/TileBakeLibrary/CityJSONToTileConverter.cs @@ -15,8 +15,6 @@ * implied. See the License for the specific language governing * permissions and limitations under the License. */ -#define DEBUG - using System; using System.Collections.Generic; using System.IO; @@ -38,21 +36,13 @@ public class CityJSONToTileConverter { private string sourcePath = ""; private string outputPath = ""; - private string identifier = ""; private string removeFromID = ""; - - private bool createOBJFiles = false; private bool brotliCompress = false; - private bool replaceExistingIDs = true; - - private bool addToExistingTiles = false; - private bool exportUVCoordinates = false; private float lod = 0; - private string filterType = ""; public string TilingMethod = "OVERLAP"; //OVERLAP, TILED @@ -61,12 +51,16 @@ public class CityJSONToTileConverter private List allSubObjects = new List(); private List tiles = new List(); + private CityJSON cityJson; private CityObjectFilter[] cityObjectFilters; private bool clipSpikes = false; private float spikeCeiling = 0; private float spikeFloor = 0; + private int filecounter = 0; + private int totalFiles = 0; + /// /// Sets the normal angle threshold for vertex+normal combinations to be considered the same /// @@ -77,6 +71,13 @@ public void SetVertexMergeAngleThreshold(float mergeVerticesBelowNormalAngle) VertexNormalCombination.normalAngleComparisonThreshold = mergeVerticesBelowNormalAngle; } + /// + /// Set vertex max floor and height to clip off spikes. + /// Verts below floor or above ceiling will be reset to 0. + /// + /// + /// Max vertex height allowed + /// Lowest vertex height allowed public void SetClipSpikes(bool setFunction, float ceiling, float floor) { clipSpikes = setFunction; @@ -84,8 +85,13 @@ public void SetClipSpikes(bool setFunction, float ceiling, float floor) spikeFloor = floor; } + /// + /// Sets the square tile size + /// + /// Value used for width and height of the tiles public void SetTileSize(int tilesize) { + Console.WriteLine($"Tilesize set to: {tilesize}x{tilesize}m"); tileSize = tilesize; } @@ -95,17 +101,10 @@ public void SetTileSize(int tilesize) /// Defaults to 0 public void SetLOD(float targetLOD) { + Console.WriteLine($"Filtering on LOD: {targetLOD}"); lod = targetLOD; } - /// - /// Define what kind of cityobject type you want to parse - /// - public void SetFilterType(string type) - { - filterType = type; - } - /// /// Determines the property that will be used as unique object identifier /// @@ -155,46 +154,46 @@ public void SetExportUV(bool exportUV) } /// - /// Create an OBJ model file next to the binary file + /// Create a brotli compressed version of the binary tiles /// - public void CreateOBJ(bool createObjFiles) + public void AddBrotliCompressedFile(bool brotliCompress) { - this.createOBJFiles = createObjFiles; + this.brotliCompress = brotliCompress; } /// - /// Create a brotli compressed version of the binary tiles + /// Set the filter types for CityObjects /// - public void AddBrotliCompressedFile(bool brotliCompress) + /// + public void SetObjectFilters(CityObjectFilter[] cityObjectFilters) { - this.brotliCompress = brotliCompress; + this.cityObjectFilters = cityObjectFilters; } - - private CityJSON cityJson; - /// /// Start converting the cityjson files into binary tile files /// /// - - private int filecounter = 0; - private int totalFiles = 0; public void Convert() { -#if DEBUG - Console.WriteLine($"Converting with Debug mode ON"); -#endif - //If no specific filename or wildcard was supplied, default to .json files var filter = Path.GetFileName(sourcePath); if (filter == "") filter = "*.json"; + //Check if source path exists + if(!Directory.Exists(sourcePath)) + { + Console.WriteLine($"Source path does not exist: {sourcePath}"); + Console.WriteLine($"Aborted."); + return; + } + //List the files that we are going to parse string[] sourceFiles = Directory.GetFiles(sourcePath, filter); if (sourceFiles.Length == 0) { - Console.WriteLine($"No \"{filter}\" files found in {sourcePath}"); + Console.WriteLine($"No \"{filter}\" files found in {sourcePath}."); + Console.WriteLine($"Please check if the sourceFolder in your config file is correct."); return; } @@ -205,63 +204,75 @@ public void Convert() { cityJson = new CityJSON(sourceFiles[0], true, true); } - for (int i = 0; i < sourceFiles.Length; i++) - { + { + //Start reading the next CityJSON in a seperate thread to prepare for the next loop CityJSON nextCityJSON = null; - int nextJsonID = i + 1; - if (i + 1 == sourceFiles.Length) - { - nextJsonID = i; - } - - Thread thread; - thread = new Thread(() => - { - nextCityJSON = new CityJSON(sourceFiles[nextJsonID], true, true); - } - ); - - thread.Start(); - - Stopwatch watch = new Stopwatch(); - watch.Start(); - tiles = new List(); - var index = i; + int nextJsonID = i + 1; + if (i + 1 == sourceFiles.Length) + { + nextJsonID = i; + } + Thread thread; + thread = new Thread(() => { nextCityJSON = new CityJSON(sourceFiles[nextJsonID], true, true); }); + thread.Start(); + + //Start reading current CityJSON filecounter++; - Console.WriteLine($"processing file {filecounter} of {sourceFiles.Length}"); - var cityObjects = CityJSONParseProcess(cityJson); - allSubObjects.Clear(); - allSubObjects = cityObjects; - Console.WriteLine($"\r{allSubObjects.Count} CityObjects imported "); - PrepareTiles(); - WriteTileData(); - allSubObjects.Clear(); - cityJson = null; - GC.Collect(); - watch.Stop(); - var result = watch.Elapsed; - string elapsedTimeString = string.Format("{0}:{1} minutes", - result.Minutes.ToString("00"), - result.Seconds.ToString("00")); - Console.WriteLine($"duration: {elapsedTimeString}"); - - thread.Join(); - cityJson = nextCityJSON; - } + Console.WriteLine($"\nProcessing file {filecounter}/{sourceFiles.Length}"); + ReadCityJSON(); + + //Wait untill the thread reading the next CityJSON is read so we can start reading it + thread.Join(); + cityJson = nextCityJSON; + } - if (brotliCompress) + //Optional compressed variant + if (brotliCompress) { CompressFiles(); } } - private void PrepareTiles() + /// + /// Read the CityObjects from the current CityJSON + /// + private void ReadCityJSON() + { + Stopwatch watch = new Stopwatch(); + watch.Start(); + tiles = new List(); + + var cityObjects = CityJSONParseProcess(cityJson); + allSubObjects.Clear(); + allSubObjects = cityObjects; + + Console.WriteLine($"\n{allSubObjects.Count} CityObjects with LOD{lod} were imported"); + PrepareTiles(); + WriteTileData(); + + //Clean up + allSubObjects.Clear(); + cityJson = null; + GC.Collect(); + watch.Stop(); + var result = watch.Elapsed; + string elapsedTimeString = string.Format("{0}:{1} minutes", + result.Minutes.ToString("00"), + result.Seconds.ToString("00")); + Console.WriteLine($"Duration: {elapsedTimeString}"); + } + + + private void PrepareTiles() { TileSubobjects(); AddObjectsFromBinaryTile(); } + /// + /// Group the SubObjects into tiles using their centroids + /// private void TileSubobjects() { tiles.Clear(); @@ -303,6 +314,9 @@ private void TileSubobjects() } } + /// + /// Parse the existing binary Tile files + /// private void AddObjectsFromBinaryTile() { foreach (Tile tile in tiles) @@ -319,66 +333,90 @@ private void WriteTileData() { //Create binary files (if we added subobjects to it) Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); - Console.Write($"\rBaking {tiles.Count} tiles"); + Console.WriteLine($"\nBaking {tiles.Count} tiles"); //Threaded writing of binary meshes + compression - Console.Write("\rSaving files... "); int counter = 0; + int written = 0; + int skipped = 0; + int total = tiles.Count; Parallel.ForEach(tiles, tile => { - if (tile.SubObjects.Count == 0) + if (tile.SubObjects.Count == 0 || tile.filePath.Contains("NaN")) { - Console.WriteLine($"Skipping {tile.filePath} containing {tile.SubObjects.Count} SubObjects"); + Interlocked.Increment(ref skipped); } else { BinaryMeshData bmd = new BinaryMeshData(); - if (tile.filePath.Contains("NaN") == false) - { - bmd.ExportData(tile, exportUVCoordinates); - - //Optionaly write other format(s) for previewing purposes - if (createOBJFiles) OBJWriter.Save(tile); - } - Interlocked.Increment(ref counter); - Console.Write($"\rSaving files...{counter}"); + bmd.ExportData(tile, exportUVCoordinates); + Interlocked.Increment(ref written); } + Interlocked.Increment(ref counter); + WriteBakingLog(counter, written, skipped, total); }); - Console.WriteLine($"\r{counter} files saved "); + WriteBakingLog(counter, written, skipped, total); + Console.WriteLine($"\n{written} tiles saved"); } - public void CompressFiles() + public static void WriteBakingLog(int done, int written, int skipped, int total) + { + float percentageDone = ((float)done / total) * 100.0f; + Console.Write($"\rTile baking process: {(percentageDone):F0}% | Baked: {written} | Skipped empty tiles: {skipped}" + " "); + } + + /// + /// Rewrite threaded compressing status message on the current console line. + /// + /// Total binary tiles compressed + /// Total binary tiles to compress + public static void WriteCompressingLog(int done, int total) { - var filter = $"*{lod}.bin"; + float percentageDone = ((float)done / total) * 100.0f; + Console.WriteLine($"\rCompressing process: {(percentageDone):F0}% | Compressed: {done}/{total} "); + } + + private static void WriteParsingStatusToConsole(int skipped, int done, int parsing, int simplifying, int tiling) + { + Console.Write("\rDone: " + done + " | Skipped: " + skipped + " | Parsing: " + parsing + " | Simplifying: " + simplifying + " | Tiling: " + tiling + " "); + } + + /// + /// Compress all files binary files in the output folder into brotli compressed files with .br extention + /// + public void CompressFiles() + { + var filter = "*{lod}.bin"; //List the files that we are going to parse string[] binFiles = Directory.GetFiles(Path.GetDirectoryName(outputPath), filter); Stopwatch watch = new Stopwatch(); watch.Start(); - int counter = 0; + int compressedCount = 0; int totalcount = binFiles.Length; - Console.Write("\rCompressing files"); + if(totalcount == 0) + { + Console.WriteLine("\nNo tile files found to compress. It appears no tiles were baked."); + return; + } + + Console.WriteLine("\nCompressing files"); Parallel.ForEach(binFiles, filename => { if (brotliCompress) { BrotliCompress.Compress(filename); } - Interlocked.Increment(ref counter); - Console.Write($"\rCompressing files {counter} of {totalcount}"); + Interlocked.Increment(ref compressedCount); + WriteCompressingLog(compressedCount, totalcount); }); watch.Stop(); var result = watch.Elapsed; string elapsedTimeString = string.Format("{0}:{1} minutes", result.Minutes.ToString("00"), result.Seconds.ToString("00")); - Console.WriteLine($"Duration: {elapsedTimeString}"); - } - - public void SetObjectFilters(CityObjectFilter[] cityObjectFilters) - { - this.cityObjectFilters = cityObjectFilters; + Console.WriteLine($"\nDuration: {elapsedTimeString}"); } /// @@ -389,18 +427,15 @@ private void ParseExistingBinaryTile(Tile tile) BinaryMeshData bmd = new BinaryMeshData(); bmd.ImportData(tile, replaceExistingIDs); bmd = null; - // Console.WriteLine($"Parsed existing tile {tile.filePath} with {tile.SubObjects.Count} subobjects"); } - private List CityJSONParseProcess(CityJSON cityJson) { List filteredObjects = new List(); Console.WriteLine(""); - - Console.Write("\r reading cityobjects"); + Console.WriteLine("Reading CityObjects from CityJSON"); int cityObjectCount = cityJson.CityObjectCount(); - Console.WriteLine($"\r CityObjects found: {cityObjectCount}"); + Console.WriteLine($"\rCityObjects found: {cityObjectCount}"); Console.Write("---"); int skipped = 0; int done = 0; @@ -408,19 +443,19 @@ private List CityJSONParseProcess(CityJSON cityJson) int simplifying = 0; int tiling = 0; var filterObjectsBucket = new ConcurrentBag(); - int[] indices = Enumerable.Range(0, cityObjectCount).ToArray(); ; + int[] indices = Enumerable.Range(0, cityObjectCount).ToArray(); + //Turn cityobjects (and their children) into SubObject mesh data var partitioner = Partitioner.Create(indices, EnumerablePartitionerOptions.NoBuffering); Parallel.ForEach(partitioner, i => { Interlocked.Increment(ref parsing); + WriteParsingStatusToConsole(skipped, done, parsing, simplifying, tiling); + CityObject cityObject = cityJson.LoadCityObjectByIndex(i, lod); - WriteStatusToConsole(skipped, done, parsing, simplifying, tiling); var subObject = ToSubObjectMeshData(cityObject); - //cityJson.ClearCityObject(cityObject.keyName); cityObject = null; Interlocked.Decrement(ref parsing); - cityObject = null; if (subObject == null) { @@ -428,15 +463,15 @@ private List CityJSONParseProcess(CityJSON cityJson) Interlocked.Increment(ref skipped); return; } - WriteStatusToConsole(skipped, done, parsing, simplifying, tiling); + WriteParsingStatusToConsole(skipped, done, parsing, simplifying, tiling); if (subObject.maxVerticesPerSquareMeter > 0) { Interlocked.Increment(ref simplifying); - WriteStatusToConsole(skipped, done, parsing, simplifying, tiling); + WriteParsingStatusToConsole(skipped, done, parsing, simplifying, tiling); subObject.SimplifyMesh(); Interlocked.Decrement(ref simplifying); - WriteStatusToConsole(skipped, done, parsing, simplifying, tiling); + WriteParsingStatusToConsole(skipped, done, parsing, simplifying, tiling); } else { @@ -451,7 +486,7 @@ private List CityJSONParseProcess(CityJSON cityJson) if (TilingMethod == "TILED") { Interlocked.Increment(ref tiling); - WriteStatusToConsole(skipped, done, parsing, simplifying, tiling); + WriteParsingStatusToConsole(skipped, done, parsing, simplifying, tiling); var newSubobjects = subObject.ClipSubobject(new Vector2(tileSize, tileSize)); if (newSubobjects.Count == 0) { @@ -469,7 +504,7 @@ private List CityJSONParseProcess(CityJSON cityJson) } } Interlocked.Decrement(ref tiling); - WriteStatusToConsole(skipped, done, parsing, simplifying, tiling); + WriteParsingStatusToConsole(skipped, done, parsing, simplifying, tiling); } else { @@ -477,18 +512,13 @@ private List CityJSONParseProcess(CityJSON cityJson) } Interlocked.Increment(ref done); - WriteStatusToConsole(skipped, done, parsing, simplifying, tiling); + WriteParsingStatusToConsole(skipped, done, parsing, simplifying, tiling); } ); return filterObjectsBucket.ToList(); } - private static void WriteStatusToConsole(int skipped, int done, int parsing, int simplifying, int tiling) - { - Console.Write("\r" + done + " done; " + skipped + " skipped; " + parsing + " parsing; " + simplifying + " simplifying; " + tiling + " tiling;"); - } - private SubObject ToSubObjectMeshData(CityObject cityObject) { var subObject = new SubObject(); diff --git a/TileBakeLibrary/Config/ConfigFile.cs b/TileBakeLibrary/Config/ConfigFile.cs index e058074..4cd383f 100644 --- a/TileBakeLibrary/Config/ConfigFile.cs +++ b/TileBakeLibrary/Config/ConfigFile.cs @@ -24,7 +24,8 @@ public class ConfigFile public string identifier { get; set; } public string removePartOfIdentifier { get; set; } public bool exportUVCoordinates { get; set; } - public float lod { get; set; } + public float lod { get; set; } = -1.0f; + public string tilingMethod { get; set; } diff --git a/TileBakeLibrary/OBJWriter.cs b/TileBakeLibrary/OBJWriter.cs deleted file mode 100644 index 2eafe78..0000000 --- a/TileBakeLibrary/OBJWriter.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Numerics; -using System.Text; -using System.Threading.Tasks; - -namespace TileBakeLibrary -{ - class OBJWriter - { - public static void Save(Tile tile, bool addGltfWrapper = true) - { - // needs adapting to binaryMeshDAta - - // using (FileStream file = File.Create(tile.filePath + ".obj")) - // { - // using (StreamWriter writer = new StreamWriter(file)) - // { - // writer.WriteLine($"#NL3D Simple OBJ"); - // //Verts - // var vertices = tile.vertices; - // foreach (Vector3 vert in tile.vertices) - // { - // writer.WriteLine($"v {vert.X} {vert.Y} {vert.Z}"); - // } - - // var normals = tile.normals; - // //Normals - // foreach (Vector3 normal in normals) - // { - // writer.WriteLine($"vn {normal.X} {normal.Y} {normal.Z}"); - // } - - // //UV - // var uvs = tile.uvs; - // foreach (Vector2 uv in uvs) - // { - // writer.WriteLine($"vt {uv.X} {uv.Y}"); - // } - - // //Every triangle list per submesh - // writer.WriteLine("o SubMesh"); - // for (int i = 0; i < tile.submeshes.Count; i++) - // { - // List submeshTriangleList = tile.submeshes[i].triangleIndices; - //for (int j = 0; j < submeshTriangleList.Count; j+=3) - //{ - // var index1 = submeshTriangleList[j] + 1; - // var index2 = submeshTriangleList[j + 1] + 1; - // var index3 = submeshTriangleList[j + 2] + 1; - // writer.WriteLine($"f {index1}/{((uvs.Count>0) ? index1 : "")}/{index1} {index2}/{((uvs.Count > 0) ? index2 : "")}/{index2} {index3}/{((uvs.Count > 0) ? index3 : "")}/{index3}"); - // } - // } - // } - // } - } - } -} diff --git a/TileBakeTool/Constants.cs b/TileBakeTool/Constants.cs index cbae3d6..e6c320b 100644 --- a/TileBakeTool/Constants.cs +++ b/TileBakeTool/Constants.cs @@ -31,27 +31,29 @@ class Constants This tool parses CityJSON files and bakes them into single-mesh binary tile files. -Seperate metadata files contain the seperation of sub-objects. -Check out http:/3d.amsterdam.nl/netherlands3d for help. +Seperate metadata files contain the description and geometry location of sub-objects. +Check out https://github.com/Amsterdam/CityDataToBinaryModel for example config files and help. -Required options: +Required parameter: ---source ---output +--config -Extra options: +Optional options with values: ---replace Replace objects with the same ID ---id Unique ID property name ---type Filter this type ---id-remove Remove this substring from the ID's ---lod Target LOD. For example 2.2 ---config Apply advanced settings above via config file ---brotli Write a brotli compressed .br variant of the .bin +--source +--output +--lod -Pipeline example: -TileBakeTool.exe --source ""C:/CityJSON/Source/"" --output ""C:/CityJSON/Output/buildings_""--id ""identificatie"" --lod 2.2 --type Building --replace --brotli --id-remove ""NL.IMBAG.Pand."" +Pipeline example 1 +TileBakeTool.exe --config Buildings.json +TileBakeTool.exe --config Terrain.json +TileBakeTool.exe --config Trees.json +Pipeline example 2 +Exporting two LOD datasets with same config template: + +TileBakeTool.exe --config Buildings.json --lod 1.2 --output ""C:/buildings/buildings_1.2_"" +TileBakeTool.exe --config Buildings.json --lod 2.0 --output ""C:/Buildings/buildings_2.0_"" "; } diff --git a/TileBakeTool/Program.cs b/TileBakeTool/Program.cs index 8c86fd1..560813e 100644 --- a/TileBakeTool/Program.cs +++ b/TileBakeTool/Program.cs @@ -18,198 +18,184 @@ using System; using System.IO; using System.Reflection; +using System.Text; using System.Text.Json; using System.Threading; using TileBakeLibrary; namespace TileBakeTool { - class Program - { - private static string configFilePath = ""; - private static ConfigFile configFile; + class Program + { + private static ConfigFile configFile; - private static string sourcePath = ""; - private static string outputPath = ""; - private static string newline = "\n"; + private static string sourcePathOverride = ""; + private static string outputPathOverride = ""; + private static float lodOverride = 1; - private static string identifier = "id"; - private static string removeFromIdentifier = ""; + private static int peakLength = 20000; + private static bool waitForUserInputOnFinish = false; - private static bool replaceExistingIDs = false; - - private static bool createBrotliCompressedFiles = false; - private static bool createObjFiles = false; - - private static bool exportUVCoordinates = false; - private static float lod = 0; - private static string filterType = ""; - - private static bool removeSpikes = false; - private static float mergeVerticesBelowAngle = 0; - private static float spikeCeiling = 0; - private static float spikeFloor = 0; - - private static bool sliceGeometry = false; - - static void Main(string[] args) - { + static void Main(string[] args) + { Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture; + //No parameters or an attempt to call for help? Show help in console. if (args.Length == 0 || (args.Length == 1 && args[0].ToLower().Contains("help"))) { - //No parameters or an attempt to call for help? Show help in console. ShowHelp(); } + //One parameter? Assume its a config file path. (Dragging file on .exe) else if (args.Length == 1) { - //One parameter? Assume its a config file path + waitForUserInputOnFinish = true; ApplyConfigFileSettings(args[0]); } + //More parameters? Parse them else { - //More parameters? Parse them ParseArguments(args); } //If we received the minimal settings to start, start converting! - if (sourcePath != "" && outputPath != "") - StartConverting(sourcePath, outputPath); + if (configFile != null) + StartConverting(); + + Console.WriteLine("TileBakeTool is done."); + if (waitForUserInputOnFinish) + WaitForUserInput(); + } + + private static void ParseArguments(string[] args) + { + //Read the arguments and apply corresponding settings + for (int i = 0; i < args.Length; i++) + { + var argument = args[i]; + if (argument.Contains("--")) + { + var value = (i + 1 < args.Length) ? args[i + 1] : ""; + ApplySetting(argument, value); + } + } + } + + /// + /// Load a .json config file and apply it to our settings object + /// + /// Path to config file + private static void ApplyConfigFileSettings(string configFilePath) + { + if (File.Exists(configFilePath)) + { + var configJsonText = File.ReadAllText(configFilePath); + configFile = JsonSerializer.Deserialize(configJsonText + , new JsonSerializerOptions() + { + AllowTrailingCommas = true + } + ); + Console.WriteLine($"Loaded config file: {Path.GetFileName(configFilePath)}"); + } + else + { + Console.WriteLine($"Could not open config file."); + Console.WriteLine($"Please check if the path is correct: {configFilePath}"); + } } - private static void DefaultArgument(string sourcePath) - { - if(!Path.IsPathFullyQualified(sourcePath)){ - Console.WriteLine($"Not a valid path: {sourcePath}"); - Console.WriteLine($"This might help: "); - ShowHelp(); - return; - } - - StartConverting(sourcePath, sourcePath); - } - - private static void ParseArguments(string[] args) - { - //Read the arguments and apply corresponding settings - for (int i = 0; i < args.Length; i++) - { - var argument = args[i]; - if (argument.Contains("--")){ - var value = (i + 1 < args.Length) ? args[i + 1] : ""; - ApplySetting(argument,value); - } - } - } - - private static void ApplyConfigFileSettings(string configFilePath){ - if(File.Exists(configFilePath)) - { - var configJsonText = File.ReadAllText(configFilePath); - configFile = JsonSerializer.Deserialize(configJsonText - , new JsonSerializerOptions() - { - AllowTrailingCommas = true } - ); - - sourcePath = configFile.sourceFolder; - outputPath = configFile.outputFolder; - - removeSpikes = configFile.removeSpikes; - spikeCeiling = configFile.removeSpikesAbove; - spikeFloor = configFile.removeSpikesBelow; - replaceExistingIDs = configFile.replaceExistingObjects; - mergeVerticesBelowAngle = configFile.mergeVerticesBelowAngle; - identifier = configFile.identifier; - removeFromIdentifier = configFile.removePartOfIdentifier; - exportUVCoordinates = configFile.exportUVCoordinates; - if (configFile.lod != 0.0f) lod = configFile.lod; - createBrotliCompressedFiles = configFile.brotliCompression; - - sliceGeometry = (configFile.tilingMethod == "SLICED"); //TILED or SLICED - - Console.WriteLine($"Loaded config file with settings"); - } - } - - private static void ApplySetting(string argument, string value) - { - switch (argument) - { - case "--config": - ApplyConfigFileSettings(value); - break; - case "--source": - sourcePath = value; - Console.WriteLine($"Source: {value}"); - break; - case "--output": - outputPath = value; - Console.WriteLine($"Output directory: {value}"); - break; - case "--replace": - replaceExistingIDs = true; - Console.WriteLine($"Replacing existing IDs"); - break; - case "--lod": - lod = float.Parse(value,System.Globalization.CultureInfo.InvariantCulture); - Console.WriteLine($"LOD filter: {lod}"); - break; - case "--type": - filterType = value; - Console.WriteLine($"Type filter: {filterType}"); - break; - case "--id": - identifier = value; - Console.WriteLine($"Object identifier: {identifier}"); - break; - case "--id-remove": - removeFromIdentifier = value; - Console.WriteLine($"Remove from identifier: {removeFromIdentifier}"); - break; - case "--brotli": - createBrotliCompressedFiles = true; - break; - case "--obj": - createObjFiles = true; - break; - default: - - break; - } - } - - private static void StartConverting(string sourcePath, string targetPath) - { - Console.WriteLine("Starting..."); - - //Here we use the .dll. This usage is the same as in Unity3D. - var tileBaker = new CityJSONToTileConverter(); - tileBaker.SetSourcePath(sourcePath); - tileBaker.SetTargetPath(targetPath); - tileBaker.SetLOD(lod); - tileBaker.SetVertexMergeAngleThreshold(mergeVerticesBelowAngle); - tileBaker.SetFilterType(filterType); - tileBaker.SetID(identifier, removeFromIdentifier); - tileBaker.SetReplace(replaceExistingIDs); - tileBaker.SetExportUV(exportUVCoordinates); - tileBaker.CreateOBJ(createObjFiles); - tileBaker.AddBrotliCompressedFile(createBrotliCompressedFiles); - - if (configFile != null) - { - tileBaker.SetClipSpikes(removeSpikes, spikeCeiling, spikeFloor); - tileBaker.SetObjectFilters(configFile.cityObjectFilters); - tileBaker.SetTileSize(configFile.tileSize); - tileBaker.TilingMethod = configFile.tilingMethod; - } - - tileBaker.Convert(); - } - - private static void ShowHelp() - { - Console.Write(Constants.helpText); - } - } + + /// + /// Apply commandline parameters as settings + /// + /// Commandline parameter argument + /// Commandline parameter value + private static void ApplySetting(string argument, string value) + { + switch (argument) + { + case "--config": + ApplyConfigFileSettings(value); + break; + case "--source": + sourcePathOverride = value; + Console.WriteLine($"Source: {value}"); + break; + case "--output": + outputPathOverride = value; + Console.WriteLine($"Output directory: {value}"); + break; + case "--lod": + lodOverride = float.Parse(value, System.Globalization.CultureInfo.InvariantCulture); + Console.WriteLine($"LOD filter: {lodOverride}"); + break; + case "--peak": + PeakInFile(value); + break; + default: + break; + } + } + + private static void PeakInFile(string filename) + { + if(!File.Exists(filename)) + { + Console.WriteLine(filename + " does not exist. Cant peak."); + return; + } + using var stream = File.OpenRead(filename); + using var reader = new StreamReader(stream, Encoding.UTF8); + char[] buffer = new char[peakLength]; + int n = reader.ReadBlock(buffer, 0, peakLength); + char[] result = new char[n]; + + Array.Copy(buffer, result, n); + Console.WriteLine(""); + Console.Write(result); + Console.WriteLine("....."); + } + + /// + /// Start the converting process using the current configuration + /// + private static void StartConverting() + { + Console.WriteLine("Starting..."); + + //Here we use the .dll. This way we may use this library in Unity, or an Azure C# Function + var tileBaker = new CityJSONToTileConverter(); + tileBaker.SetSourcePath((sourcePathOverride != "") ? sourcePathOverride : configFile.sourceFolder); + tileBaker.SetTargetPath((outputPathOverride != "") ? outputPathOverride : configFile.outputFolder); + tileBaker.SetLOD((lodOverride != 1) ? lodOverride : configFile.lod); + tileBaker.SetVertexMergeAngleThreshold(configFile.mergeVerticesBelowAngle); + tileBaker.SetID(configFile.identifier, configFile.removePartOfIdentifier); + tileBaker.SetReplace(configFile.replaceExistingObjects); + tileBaker.SetExportUV(configFile.exportUVCoordinates); + tileBaker.AddBrotliCompressedFile(configFile.brotliCompression); + tileBaker.SetClipSpikes(configFile.removeSpikes, configFile.removeSpikesAbove, configFile.removeSpikesBelow); + tileBaker.SetObjectFilters(configFile.cityObjectFilters); + tileBaker.SetTileSize(configFile.tileSize); + tileBaker.TilingMethod = configFile.tilingMethod; + + tileBaker.Convert(); + } + + private static void WaitForUserInput() + { + Console.WriteLine("Press to exit"); + while (Console.ReadKey().Key != ConsoleKey.Enter) { } + + Console.WriteLine("Closed."); + } + + + /// + /// Draw the help text in the commandline + /// + private static void ShowHelp() + { + Console.Write(Constants.helpText); + } + } }