From be169f916c8ae427c9c157d196d082ee68240449 Mon Sep 17 00:00:00 2001 From: Dylancyclone Date: Tue, 30 Jun 2020 17:50:27 -0700 Subject: [PATCH 1/6] Improve exception reporting This should help with bug reporting and generally make it easier to diagnose a problem without having to look at the source code. This should help with issues like #11 --- src/main/java/com/lathrum/VMF2OBJ/App.java | 72 ++++++++++++---------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/lathrum/VMF2OBJ/App.java b/src/main/java/com/lathrum/VMF2OBJ/App.java index eef634b..85bb730 100644 --- a/src/main/java/com/lathrum/VMF2OBJ/App.java +++ b/src/main/java/com/lathrum/VMF2OBJ/App.java @@ -57,7 +57,8 @@ public static void extractLibraries(String dir) throws URISyntaxException { try { uri = App.class.getProtectionDomain().getCodeSource().getLocation().toURI(); } catch (Exception e) { - System.err.println("Exception: " + e); + System.err.println("Failed to get executable's location, do you have permissions?"); + System.err.println(e.toString()); } for (String el : files) { @@ -79,7 +80,8 @@ public static void extractLibraries(String dir) throws URISyntaxException { zipFile.close(); } } catch (Exception e) { - System.err.println("Exception: " + e); + System.err.println("Failed to extract tools, do you have permissions?"); + System.err.println(e.toString()); } } } @@ -96,7 +98,7 @@ public static URI extractFile(ZipFile zipFile, String fileName, String dir) thro entry = zipFile.getEntry(fileName); if (entry == null) { - throw new FileNotFoundException("cannot find file: " + fileName + " in archive: " + zipFile.getName()); + throw new FileNotFoundException("Cannot find file: " + fileName + " in archive: " + zipFile.getName()); } zipStream = zipFile.getInputStream(entry); @@ -236,7 +238,8 @@ public static ArrayList addExtraFiles(String start, File dir) { } } } catch (IOException e) { - e.printStackTrace(); + System.err.println("Failed to load external resources"); + System.err.println(e.toString()); } return entries; } @@ -325,6 +328,12 @@ public static void main(String args[]) throws Exception { System.exit(0); } + // Check for valid arguments + if (Paths.get(outPath).getParent() == null) { + System.err.println("Invalid output file. Make sure it's either an absolute or relative path"); + System.exit(0); + } + // Clean working directory try { deleteRecursiveByExtension(new File(Paths.get(outPath).getParent().resolve("materials").toString()),"vtf"); @@ -337,7 +346,8 @@ public static void main(String args[]) throws Exception { try { extractLibraries(Paths.get(outPath).getParent().resolve("temp").toString()); } catch (Exception e) { - System.err.println("Exception: " + e); + System.err.println("Failed to extract tools, do you have permissions?"); + System.err.println(e.toString()); } // @@ -365,26 +375,13 @@ public static void main(String args[]) throws Exception { } } - // Open infile - File workingFile = new File(args[0]); - if (!workingFile.exists()) { - try { - File directory = new File(workingFile.getParent()); - if (!directory.exists()) { - directory.mkdirs(); - } - workingFile.createNewFile(); - } catch (IOException e) { - System.out.println("Exception Occured: " + e.toString()); - } - } - - // Read File + // Read input file String text = ""; try { text = readFile(args[0]); } catch (IOException e) { - System.out.println("Exception Occured: " + e.toString()); + System.err.println("Failed to read file: " + args[0] + ", does file exist?"); + System.err.println(e.toString()); } // System.out.println(text); @@ -504,7 +501,8 @@ public static void main(String args[]) throws Exception { } VMTText = new String(vpkEntries.get(index).readData()); } catch (IOException e) { - System.out.println("Exception Occured: " + e.toString()); + System.out.println("Failed to read material: " + el); + System.err.println(e.toString()); } VMT vmt = new VMT(); @@ -537,7 +535,8 @@ public static void main(String args[]) throws Exception { directory.mkdirs(); } } catch (Exception e) { - System.out.println("Exception Occured: " + e.toString()); + System.out.println("Failed to create directory: " + materialOutPath.getParent()); + System.err.println(e.toString()); } try { vpkEntries.get(index).extract(materialOutPath); @@ -581,7 +580,8 @@ public static void main(String args[]) throws Exception { // System.out.println("Adding Material: "+ el); textures.add(new Texture(el, vmt.basetexture, materialOutPath.toString(), width, height)); } catch (Exception e) { - System.err.println("Exception on extract: " + e); + System.err.println("Failed to extract material: " + vmt.basetexture); + System.err.println(e.toString()); } if (vmt.bumpmap != null) { // If the material has a bump map associated with it @@ -602,7 +602,8 @@ public static void main(String args[]) throws Exception { directory.mkdirs(); } } catch (Exception e) { - System.out.println("Exception Occured: " + e.toString()); + System.out.println("Failed to create directory: " + materialOutPath.getParent()); + System.err.println(e.toString()); } try { vpkEntries.get(bumpMapIndex).extract(bumpMapOutPath); @@ -614,7 +615,8 @@ public static void main(String args[]) throws Exception { proc = Runtime.getRuntime().exec(command); proc.waitFor(); } catch (Exception e) { - System.err.println("Exception on extract: " + e); + System.err.println("Failed to extract bump material: " + vmt.bumpmap); + System.err.println(e.toString()); } } } @@ -853,12 +855,14 @@ public static void main(String args[]) throws Exception { directory.mkdirs(); } } catch (Exception e) { - System.out.println("Exception Occured: " + e.toString()); + System.out.println("Failed to create directory: " + fileOutPath.getParent()); + System.err.println(e.toString()); } try { vpkEntries.get(index).extract(fileOutPath); } catch (Exception e) { - System.err.println("Exception on extract: " + e); + System.err.println("Failed to extract: " + fileOutPath); + System.err.println(e.toString()); } } } @@ -967,7 +971,8 @@ public static void main(String args[]) throws Exception { } // Could not find it VMTText = new String(vpkEntries.get(index).readData()); } catch (IOException e) { - System.out.println("Exception Occured: " + e.toString()); + System.out.println("Failed to read material: " + el); + System.err.println(e.toString()); } if (!VMTText.isEmpty()) { break; @@ -1052,7 +1057,8 @@ public static void main(String args[]) throws Exception { // System.out.println("Adding Material: "+ el); textures.add(new Texture(el, vmt.basetexture, materialOutPath.toString(), width, height)); } catch (Exception e) { - System.err.println("Exception on extract: " + e); + System.err.println("Failed to extract material: " + vmt.basetexture); + System.err.println(e.toString()); } if (vmt.bumpmap != null) { // If the material has a bump map associated with it @@ -1073,7 +1079,8 @@ public static void main(String args[]) throws Exception { directory.mkdirs(); } } catch (Exception e) { - System.out.println("Exception Occured: " + e.toString()); + System.out.println("Failed to create directory: " + bumpMapOutPath.getParent()); + System.err.println(e.toString()); } try { vpkEntries.get(bumpMapIndex).extract(bumpMapOutPath); @@ -1085,7 +1092,8 @@ public static void main(String args[]) throws Exception { proc = Runtime.getRuntime().exec(convertCommand); proc.waitFor(); } catch (Exception e) { - System.err.println("Exception on extract: " + e); + System.err.println("Failed to extract bump material: " + vmt.bumpmap); + System.err.println(e.toString()); } } } From 9f3034e745e8254171b75324c73794919bc27e8d Mon Sep 17 00:00:00 2001 From: Dylancyclone Date: Sun, 5 Jul 2020 11:50:36 -0700 Subject: [PATCH 2/6] Fix Crowbar subfolder setting causing problems Crowbar has a setting called `DecompileFolderForEachModelIsChecked` that when set to true will cause decompiled models to always be placed in a subfolder with the model's name. While this setting is false by default, if it is true no models would be imported successfully. Huge thanks to @Devostated for helping track down this issue --- src/main/java/com/lathrum/VMF2OBJ/App.java | 39 ++++++++++++++----- .../VMF2OBJ/dataStructure/map/Entity.java | 2 + 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/lathrum/VMF2OBJ/App.java b/src/main/java/com/lathrum/VMF2OBJ/App.java index 85bb730..22b9bc5 100644 --- a/src/main/java/com/lathrum/VMF2OBJ/App.java +++ b/src/main/java/com/lathrum/VMF2OBJ/App.java @@ -836,12 +836,19 @@ public static void main(String args[]) throws Exception { faces.clear(); materials.clear(); - ArrayList indicies = getEntryIndiciesByPattern(vpkEntries, - entity.model.substring(0, entity.model.lastIndexOf('.')) + "."); + // Crowbar has a setting that puts all decompiled models in a subfolder + // called `DecompileFolderForEachModelIsChecked`. This is false by default, + // but must be handled otherwise all model will fail to convert + Boolean crowbarSubfolderSetting = false; + + String modelWithoutExtension = entity.model.substring(0, entity.model.lastIndexOf('.')); + entity.modelName = modelWithoutExtension.substring(modelWithoutExtension.lastIndexOf("/")); + ArrayList indicies = getEntryIndiciesByPattern(vpkEntries, modelWithoutExtension + "."); + indicies.removeAll( - getEntryIndiciesByPattern(vpkEntries, entity.model.substring(0, entity.model.lastIndexOf('.')) + ".vmt")); + getEntryIndiciesByPattern(vpkEntries, modelWithoutExtension + ".vmt")); indicies.removeAll( - getEntryIndiciesByPattern(vpkEntries, entity.model.substring(0, entity.model.lastIndexOf('.')) + ".vtf")); + getEntryIndiciesByPattern(vpkEntries, modelWithoutExtension + ".vtf")); for (int index : indicies) { if (index != -1) { @@ -884,16 +891,25 @@ public static void main(String args[]) throws Exception { String qcText = ""; try { - qcText = readFile(new File(outPath).getParent() + File.separator - + entity.model.substring(0, entity.model.lastIndexOf('.')) + ".qc"); // This line may cause errors if - // the qc file does not have the - // same name as the mdl file + // This line may cause errors if the qc file does not have the same name as the mdl file + qcText = readFile(new File(outPath).getParent() + File.separator + modelWithoutExtension + ".qc"); } catch (IOException e) { + //This will be caught by detecting a blank string // System.out.println("Exception Occured: " + e.toString()); } if (qcText.matches("")) { - printProgressBar("Error: Could not find QC file for model, skipping: " + entity.model); - continue; + try { + crowbarSubfolderSetting = true; + // This line may cause errors if the qc file does not have the same name as the mdl file + qcText = readFile(new File(outPath).getParent() + File.separator + modelWithoutExtension + File.separator + entity.modelName + ".qc"); + } catch (IOException e) { + //This will be caught by detecting a blank string + // System.out.println("Exception Occured: " + e.toString()); + } + if (qcText.matches("")) { + printProgressBar("Error: Could not find QC file for model, skipping: " + entity.model); + continue; + } } QC qc = QC.parseQC(qcText); @@ -905,6 +921,9 @@ public static void main(String args[]) throws Exception { if (qc.ModelName.contains("/")) { path += qc.ModelName.substring(0, qc.ModelName.lastIndexOf('/')); } + if (crowbarSubfolderSetting) { + path += File.separator + entity.modelName; + } String smdText = readFile(formatPath( new File(outPath).getParent() + File.separator + "models" + path + File.separator + bodyGroup)); diff --git a/src/main/java/com/lathrum/VMF2OBJ/dataStructure/map/Entity.java b/src/main/java/com/lathrum/VMF2OBJ/dataStructure/map/Entity.java index 7c84f99..bc44178 100644 --- a/src/main/java/com/lathrum/VMF2OBJ/dataStructure/map/Entity.java +++ b/src/main/java/com/lathrum/VMF2OBJ/dataStructure/map/Entity.java @@ -9,4 +9,6 @@ public class Entity { public String model; public String skin; + + public String modelName; } From ff0d35677d5e053ba4033c6b039b2c0ab84a61c8 Mon Sep 17 00:00:00 2001 From: Dylancyclone Date: Sun, 5 Jul 2020 13:36:41 -0700 Subject: [PATCH 3/6] Lint fixes and code style improvements Mostly adding JavaDocs and spaces, but this also removes creating new gson objects in loops, which could boost performance somewhat This begins to address issue #10, but mostly focus on stylistic things rather than java specific things like changing public variables to private. It's a process --- src/main/java/com/lathrum/VMF2OBJ/App.java | 291 +++++++++++------- .../VMF2OBJ/dataStructure/map/Side.java | 6 +- .../VMF2OBJ/dataStructure/map/VMF.java | 7 +- .../VMF2OBJ/dataStructure/texture/VMT.java | 9 +- 4 files changed, 194 insertions(+), 119 deletions(-) diff --git a/src/main/java/com/lathrum/VMF2OBJ/App.java b/src/main/java/com/lathrum/VMF2OBJ/App.java index 22b9bc5..72d93e1 100644 --- a/src/main/java/com/lathrum/VMF2OBJ/App.java +++ b/src/main/java/com/lathrum/VMF2OBJ/App.java @@ -32,6 +32,10 @@ public class App { public static boolean quietMode = false; public static boolean ignoreTools = false; + /** + * Extract Libraries to temporary directory. + * @param dir Directory to extra to + */ public static void extractLibraries(String dir) throws URISyntaxException { // Spooky scary, but I don't want to reinvent the wheel. ArrayList files = new ArrayList(); @@ -61,31 +65,35 @@ public static void extractLibraries(String dir) throws URISyntaxException { System.err.println(e.toString()); } - for (String el : files) { - ZipFile zipFile; - - try { - zipFile = new ZipFile(new File(uri)); - try { - fileURI = extractFile(zipFile, el, dir); - switch (el) { - case ("VTFCmd.exe"): - VTFLibPath = Paths.get(fileURI).toString(); - break; - case ("CrowbarCommandLineDecomp.exe"): - CrowbarLibPath = Paths.get(fileURI).toString(); - break; - } - } finally { - zipFile.close(); + try { + ZipFile zipFile = new ZipFile(new File(uri)); + for (String el : files) { + fileURI = extractFile(zipFile, el, dir); + switch (el) { + case ("VTFCmd.exe"): + VTFLibPath = Paths.get(fileURI).toString(); + break; + case ("CrowbarCommandLineDecomp.exe"): + CrowbarLibPath = Paths.get(fileURI).toString(); + break; + default: + break; } - } catch (Exception e) { - System.err.println("Failed to extract tools, do you have permissions?"); - System.err.println(e.toString()); } + zipFile.close(); + } catch (Exception e) { + System.err.println("Failed to extract tools, do you have permissions?"); + System.err.println(e.toString()); } } + /** + * Extract a particular file from a zip to a particular location. + * @param zipFile The zip file to extract from + * @param fileName the file to extract from the zip + * @param dir the destination of the extracted file + * @return URI of extracted file + */ public static URI extractFile(ZipFile zipFile, String fileName, String dir) throws IOException { File tempFile; ZipEntry entry; @@ -123,12 +131,22 @@ public static URI extractFile(ZipFile zipFile, String fileName, String dir) thro return (tempFile.toURI()); } + /** + * Gets the extension of a file. + * @param file File to get extension of + * @return Extension of file + */ public static String getFileExtension(File file) { String fileName = file.getName(); int dotIndex = fileName.lastIndexOf('.'); return (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1); } + /** + * Deletes all files and folders starting at a directory. + * @param path Folder to delete + * @return If the delete was successful + */ public static boolean deleteRecursive(File path) throws FileNotFoundException { // if (!path.exists()) // throw new FileNotFoundException(path.getAbsolutePath()); @@ -141,6 +159,13 @@ public static boolean deleteRecursive(File path) throws FileNotFoundException { return ret && path.delete(); } + + /** + * Deletes all files with a certain extension recursively starting at a directory. + * @param path Root folder to delete from + * @param ext Extension of files to delete + * @return If the delete was successful + */ public static boolean deleteRecursiveByExtension(File path, String ext) throws FileNotFoundException { // if (!path.exists()) // throw new FileNotFoundException(path.getAbsolutePath()); @@ -155,14 +180,25 @@ public static boolean deleteRecursiveByExtension(File path, String ext) throws F return ret; } + /** + * Get contents of a file. + * @param path location of the file + * @return content of the file + */ static String readFile(String path) throws IOException { byte[] encoded = Files.readAllBytes(Paths.get(path)); return new String(encoded, StandardCharsets.UTF_8); } + /** + * Format a path to use correct file separators. + * @param res Path to format + * @return Formatted path + */ public static String formatPath(String res) { - if (res == null) + if (res == null) { return null; + } if (File.separatorChar == '\\') { // From Windows to Linux/Mac return res.replace('/', File.separatorChar); @@ -172,6 +208,12 @@ public static String formatPath(String res) { } } + /** + * Get index of an entry in an ArrayList by it's path. + * @param object ArrayList of Entries to search + * @param path Path to find + * @return index of entry + */ public static int getEntryIndexByPath(ArrayList object, String path) { for (int i = 0; i < object.size(); i++) { if (object != null && object.get(i).getFullPath().equalsIgnoreCase(path)) { @@ -182,6 +224,12 @@ public static int getEntryIndexByPath(ArrayList object, String path) { return -1; } + /** + * Get index of an entry in an ArrayList by an arbitrary pattern. + * @param object ArrayList of Entries to search + * @param pattern Path to find + * @return index of entry + */ public static ArrayList getEntryIndiciesByPattern(ArrayList object, String pattern) { ArrayList indicies = new ArrayList(); for (int i = 0; i < object.size(); i++) { @@ -193,6 +241,12 @@ public static ArrayList getEntryIndiciesByPattern(ArrayList obje return indicies; } + /** + * Get index of an Texture in an ArrayList by it's name. + * @param object ArrayList of Textures to search + * @param name Name to find + * @return index of entry + */ public static int getTextureIndexByName(ArrayList object, String name) { for (int i = 0; i < object.size(); i++) { if (object != null && object.get(i).name.equalsIgnoreCase(name)) { @@ -202,6 +256,12 @@ public static int getTextureIndexByName(ArrayList object, String name) return -1; } + /** + * Get index of an Texture in an ArrayList by it's file name. + * @param object ArrayList of Textures to search + * @param name File name to find + * @return index of entry + */ public static int getTextureIndexByFileName(ArrayList object, String name) { for (int i = 0; i < object.size(); i++) { if (object != null && object.get(i).fileName.equalsIgnoreCase(name)) { @@ -211,6 +271,13 @@ public static int getTextureIndexByFileName(ArrayList object, String na return -1; } + + /** + * Prepare external files to be used as resources. + * @param start Root path to start pulling from + * @param dir Current directory + * @return ArrayList of new Entries + */ public static ArrayList addExtraFiles(String start, File dir) { ArrayList entries = new ArrayList(); @@ -219,22 +286,25 @@ public static ArrayList addExtraFiles(String start, File dir) { for (File file : files) { if (file.isDirectory()) { String path = file.getCanonicalPath().substring(start.length()); - if (path.charAt(0) == File.separatorChar) + if (path.charAt(0) == File.separatorChar) { path = path.substring(1); + } // System.out.println("directory: " + path); entries.addAll(addExtraFiles(start, file)); } else { String path = file.getCanonicalPath().substring(start.length()); - if (path.charAt(0) == File.separatorChar) + if (path.charAt(0) == File.separatorChar) { path = path.substring(1); + } path = path.replaceAll("\\\\", "/"); // System.out.println("file: " + path); - if (path.lastIndexOf("/") == -1) + if (path.lastIndexOf("/") == -1) { entries.add(new FileEntry(file.getName().substring(0, file.getName().lastIndexOf('.')), getFileExtension(file), "", file.toString())); - else + } else { entries.add(new FileEntry(file.getName().substring(0, file.getName().lastIndexOf('.')), getFileExtension(file), path.substring(0, path.lastIndexOf("/")), file.toString())); + } } } } catch (IOException e) { @@ -244,9 +314,14 @@ public static ArrayList addExtraFiles(String start, File dir) { return entries; } + /** + * Print line to console without messing up the progress bar. + * @param text line to print + */ public static void printProgressBar(String text) { - if (quietMode) + if (quietMode) { return; // Supress warnings + } Terminal terminal; int consoleWidth = 80; try { @@ -255,26 +330,34 @@ public static void printProgressBar(String text) { // created // see https://github.com/jline/jline3/issues/291 terminal = TerminalBuilder.builder().dumb(true).build(); - if (terminal.getWidth() >= 10) // Workaround for issue #23 under IntelliJ + if (terminal.getWidth() >= 10) { // Workaround for issue #23 under IntelliJ consoleWidth = terminal.getWidth(); - } catch (IOException ignored) { - } + } + } catch (IOException ignored) { /* */ } String pad = new String(new char[Math.max(consoleWidth - text.length(), 0)]).replace("\0", " "); System.out.println("\r" + text + pad); } - public static void main(String args[]) throws Exception { + /** + * Convert Source .vmf files to generic .obj + * @param args Launch options for the program. Read the first couple lines of main to see valid inputs + * @throws Exception Unhandled exception + */ + public static void main(String[] args) throws Exception { + // The General outline of the program is a follows: + // Read Geometry // Collapse Vertices - // Write objects + // Write Objects // Extract Models - // Extract materials + // Extract Materials // Convert Materials - // Convert models to SMD - // Convert models to OBJ + // Convert Models to SMD + // Convert Models to OBJ // Write Models // Write Materials + // Clean Up CommandLineParser parser = new DefaultParser(); Options options = new Options(); @@ -336,7 +419,7 @@ public static void main(String args[]) throws Exception { // Clean working directory try { - deleteRecursiveByExtension(new File(Paths.get(outPath).getParent().resolve("materials").toString()),"vtf"); + deleteRecursiveByExtension(new File(Paths.get(outPath).getParent().resolve("materials").toString()), "vtf"); deleteRecursive(new File(Paths.get(outPath).getParent().resolve("models").toString())); } catch (Exception e) { // System.err.println("Exception: "+e); @@ -440,8 +523,9 @@ public static void main(String args[]) throws Exception { } materials.add(side.material.trim()); if (side.dispinfo == null) { - if (Solid.isDisplacementSolid(solid)) + if (Solid.isDisplacementSolid(solid)) { continue; + } for (Vector3 point : side.points) { verticies.add(point); } @@ -454,14 +538,12 @@ public static void main(String args[]) throws Exception { // B C int startIndex = side.dispinfo.startposition.closestIndex(side.points); //Get adjacent points by going around counter-clockwise - Vector3 ad = side.points[(startIndex+1)%4].subtract(side.points[startIndex]); - Vector3 ab = side.points[(startIndex+3)%4].subtract(side.points[startIndex]); + Vector3 ad = side.points[(startIndex + 1) % 4].subtract(side.points[startIndex]); + Vector3 ab = side.points[(startIndex + 3) % 4].subtract(side.points[startIndex]); // System.out.println(ad); // System.out.println(ab); - for (int i = 0; i < side.dispinfo.normals.length; i++) // rows - { - for (int j = 0; j < side.dispinfo.normals[0].length; j++) // columns - { + for (int i = 0; i < side.dispinfo.normals.length; i++) { // rows + for (int j = 0; j < side.dispinfo.normals[0].length; j++) { // columns Vector3 point = side.points[startIndex] .add(ad.normalize().multiply(ad.divide(side.dispinfo.normals[0].length - 1).abs().multiply(j))) .add(ab.normalize().multiply(ab.divide(side.dispinfo.normals.length - 1).abs().multiply(i))) @@ -515,10 +597,10 @@ public static void main(String args[]) throws Exception { vmt.name = el; // System.out.println(gson.toJson(vmt)); // System.out.println(vmt.basetexture); - if (vmt.basetexture == null || vmt.basetexture.isEmpty()) { - printProgressBar("Material has no texture: " + el); - continue; - } + if (vmt.basetexture == null || vmt.basetexture.isEmpty()) { + printProgressBar("Material has no texture: " + el); + continue; + } if (vmt.basetexture.endsWith(".vtf")) { vmt.basetexture = vmt.basetexture.substring(0, vmt.basetexture.lastIndexOf('.')); // snip the extension } @@ -622,63 +704,61 @@ public static void main(String args[]) throws Exception { } } - materialFile.println("\n" + - "newmtl "+el+"\n"+ - "Ka 1.000 1.000 1.000\n"+ - "Kd 1.000 1.000 1.000\n"+ - "Ks 0.000 0.000 0.000\n"+ - "d 1.0\n"+ + materialFile.println("\n" + + "newmtl " + el + "\n" + + "Ka 1.000 1.000 1.000\n" + + "Kd 1.000 1.000 1.000\n" + + "Ks 0.000 0.000 0.000\n" + + "d 1.0\n" + "illum 2"); - if (vmt.translucent==1||vmt.alphatest==1) { + if (vmt.translucent == 1 || vmt.alphatest == 1) { materialFile.println( - "map_Ka "+"materials/"+vmt.basetexture+".tga"+"\n"+ - "map_Kd "+"materials/"+vmt.basetexture+".tga"); + "map_Ka " + "materials/" + vmt.basetexture + ".tga" + "\n" + + "map_Kd " + "materials/" + vmt.basetexture + ".tga"); } else { materialFile.println( - "map_Ka "+"materials/"+vmt.basetexture+".jpg"+"\n"+ - "map_Kd "+"materials/"+vmt.basetexture+".jpg"); + "map_Ka " + "materials/" + vmt.basetexture + ".jpg" + "\n" + + "map_Kd " + "materials/" + vmt.basetexture + ".jpg"); } if (vmt.bumpmap != null) { //If the material has a bump map associated with it materialFile.println( - "map_bump "+"materials/"+vmt.bumpmap+".jpg"); + "map_bump " + "materials/" + vmt.bumpmap + ".jpg"); } materialFile.println(); } else { // File has already been extracted int textureIndex = getTextureIndexByName(textures, el); - if (textureIndex == -1) // But this is a new material - { + if (textureIndex == -1) { // But this is a new material textureIndex = getTextureIndexByFileName(textures, vmt.basetexture); // System.out.println("Adding Material: "+ el); textures.add(new Texture(el, vmt.basetexture, materialOutPath.toString(), textures.get(textureIndex).width, textures.get(textureIndex).height)); - materialFile.println("\n"+ - "newmtl "+el+"\n"+ - "Ka 1.000 1.000 1.000\n"+ - "Kd 1.000 1.000 1.000\n"+ - "Ks 0.000 0.000 0.000\n"+ - "d 1.0\n"+ + materialFile.println("\n" + + "newmtl " + el + "\n" + + "Ka 1.000 1.000 1.000\n" + + "Kd 1.000 1.000 1.000\n" + + "Ks 0.000 0.000 0.000\n" + + "d 1.0\n" + "illum 2"); - if (vmt.translucent==1||vmt.alphatest==1) { + if (vmt.translucent == 1 || vmt.alphatest == 1) { materialFile.println( - "map_Ka "+"materials/"+vmt.basetexture+".tga"+"\n"+ - "map_Kd "+"materials/"+vmt.basetexture+".tga"); + "map_Ka " + "materials/" + vmt.basetexture + ".tga" + "\n" + + "map_Kd " + "materials/" + vmt.basetexture + ".tga"); } else { materialFile.println( - "map_Ka "+"materials/"+vmt.basetexture+".jpg"+"\n"+ - "map_Kd "+"materials/"+vmt.basetexture+".jpg"); + "map_Ka " + "materials/" + vmt.basetexture + ".jpg" + "\n" + + "map_Kd " + "materials/" + vmt.basetexture + ".jpg"); } if (vmt.bumpmap != null) { //If the material has a bump map associated with it materialFile.println( - "map_bump "+"materials/"+vmt.bumpmap+".jpg"); + "map_bump " + "materials/" + vmt.bumpmap + ".jpg"); } materialFile.println(); } } } else { // Cant find material int textureIndex = getTextureIndexByName(textures, el); - if (textureIndex == -1) // But this is a new material - { + if (textureIndex == -1) { // But this is a new material printProgressBar("Missing Material: " + vmt.basetexture); textures.add(new Texture(el, vmt.basetexture, "", 1, 1)); } @@ -710,8 +790,9 @@ public static void main(String args[]) throws Exception { String buffer = ""; if (side.dispinfo == null) { - if (Solid.isDisplacementSolid(solid)) + if (Solid.isDisplacementSolid(solid)) { continue; + } for (int i = 0; i < side.points.length; i++) { double u = Vector3.dot(side.points[i], side.uAxisVector) / (texture.width * side.uAxisScale) + side.uAxisTranslation / texture.width; @@ -734,12 +815,10 @@ public static void main(String args[]) throws Exception { // B C int startIndex = side.dispinfo.startposition.closestIndex(side.points); //Get adjacent points by going around counter-clockwise - Vector3 ad = side.points[(startIndex+1)%4].subtract(side.points[startIndex]); - Vector3 ab = side.points[(startIndex+3)%4].subtract(side.points[startIndex]); - for (int i = 0; i < side.dispinfo.normals.length - 1; i++) // all rows but last - { - for (int j = 0; j < side.dispinfo.normals[0].length - 1; j++) // all columns but last - { + Vector3 ad = side.points[(startIndex + 1) % 4].subtract(side.points[startIndex]); + Vector3 ab = side.points[(startIndex + 3) % 4].subtract(side.points[startIndex]); + for (int i = 0; i < side.dispinfo.normals.length - 1; i++) { // all rows but last + for (int j = 0; j < side.dispinfo.normals[0].length - 1; j++) { // all columns but last buffer = ""; Vector3 point = side.points[startIndex] .add(ad.normalize().multiply(ad.divide(side.dispinfo.normals[0].length - 1).abs().multiply(j))) @@ -877,7 +956,7 @@ public static void main(String args[]) throws Exception { String[] command = new String[] { CrowbarLibPath, - "-p", formatPath(new File(outPath).getParent()+File.separator+entity.model)}; + "-p", formatPath(new File(outPath).getParent() + File.separator + entity.model)}; proc = Runtime.getRuntime().exec(command); // BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream())); @@ -1119,16 +1198,16 @@ public static void main(String args[]) throws Exception { } materialFile.println("\n" + - "newmtl "+el+"\n"+ - "Ka 1.000 1.000 1.000\n"+ - "Kd 1.000 1.000 1.000\n"+ - "Ks 0.000 0.000 0.000\n"+ - "d 1.0\n"+ + "newmtl " + el + "\n" + + "Ka 1.000 1.000 1.000\n" + + "Kd 1.000 1.000 1.000\n" + + "Ks 0.000 0.000 0.000\n" + + "d 1.0\n" + "illum 2"); - if (vmt.translucent==1||vmt.alphatest==1) { + if (vmt.translucent == 1 || vmt.alphatest == 1) { materialFile.println( - "map_Ka "+"materials/"+vmt.basetexture+".tga"+"\n"+ - "map_Kd "+"materials/"+vmt.basetexture+".tga"); + "map_Ka " + "materials/" + vmt.basetexture + ".tga" + "\n" + + "map_Kd " + "materials/" + vmt.basetexture + ".tga"); } else { materialFile.println("map_Ka " + "materials/" + vmt.basetexture + ".jpg" + "\n" + "map_Kd " + "materials/" + vmt.basetexture + ".jpg"); @@ -1139,28 +1218,27 @@ public static void main(String args[]) throws Exception { materialFile.println(); } else { // File has already been extracted int textureIndex = getTextureIndexByName(textures, el); - if (textureIndex == -1) // But this is a new material - { + if (textureIndex == -1) { // But this is a new material textureIndex = getTextureIndexByFileName(textures, vmt.basetexture); // System.out.println("Adding Material: "+ el); textures.add(new Texture(el, vmt.basetexture, materialOutPath.toString(), textures.get(textureIndex).width, textures.get(textureIndex).height)); - materialFile.println("\n"+ - "newmtl "+el+"\n"+ - "Ka 1.000 1.000 1.000\n"+ - "Kd 1.000 1.000 1.000\n"+ - "Ks 0.000 0.000 0.000\n"+ - "d 1.0\n"+ + materialFile.println("\n" + + "newmtl " + el + "\n" + + "Ka 1.000 1.000 1.000\n" + + "Kd 1.000 1.000 1.000\n" + + "Ks 0.000 0.000 0.000\n" + + "d 1.0\n" + "illum 2"); - if (vmt.translucent==1||vmt.alphatest==1) { + if (vmt.translucent == 1 || vmt.alphatest == 1) { materialFile.println( - "map_Ka "+"materials/"+vmt.basetexture+".tga"+"\n"+ - "map_Kd "+"materials/"+vmt.basetexture+".tga"); + "map_Ka " + "materials/" + vmt.basetexture + ".tga" + "\n" + + "map_Kd " + "materials/" + vmt.basetexture + ".tga"); } else { materialFile.println( - "map_Ka "+"materials/"+vmt.basetexture+".jpg"+"\n"+ - "map_Kd "+"materials/"+vmt.basetexture+".jpg"); + "map_Ka " + "materials/" + vmt.basetexture + ".jpg" + "\n" + + "map_Kd " + "materials/" + vmt.basetexture + ".jpg"); } if (vmt.bumpmap != null) { // If the material has a bump map associated with it materialFile.println("map_bump " + "materials/" + vmt.bumpmap + ".jpg"); @@ -1170,8 +1248,7 @@ public static void main(String args[]) throws Exception { } } else { // Cant find material int textureIndex = getTextureIndexByName(textures, el); - if (textureIndex == -1) // But this is a new material - { + if (textureIndex == -1) { // But this is a new material printProgressBar("Missing Material: " + vmt.basetexture); textures.add(new Texture(el, vmt.basetexture, "", 1, 1)); } @@ -1219,9 +1296,9 @@ public static void main(String args[]) throws Exception { System.out.println("[5/5] Cleaning up..."); if (vmf.entities != null) { //There are no entities in this VMF - deleteRecursive(new File(Paths.get(outPath).getParent().resolve("models").toString())); //Delete models. Everything is now in the OBJ file + deleteRecursive(new File(Paths.get(outPath).getParent().resolve("models").toString())); // Delete models. Everything is now in the OBJ file } - deleteRecursiveByExtension(new File(Paths.get(outPath).getParent().resolve("materials").toString()),"vtf"); //Delete unconverted textures + deleteRecursiveByExtension(new File(Paths.get(outPath).getParent().resolve("materials").toString()), "vtf"); // Delete unconverted textures in.close(); objFile.close(); diff --git a/src/main/java/com/lathrum/VMF2OBJ/dataStructure/map/Side.java b/src/main/java/com/lathrum/VMF2OBJ/dataStructure/map/Side.java index 331dff3..5989ff4 100644 --- a/src/main/java/com/lathrum/VMF2OBJ/dataStructure/map/Side.java +++ b/src/main/java/com/lathrum/VMF2OBJ/dataStructure/map/Side.java @@ -6,7 +6,8 @@ import java.util.Comparator; import java.util.LinkedList; import java.util.List; -import com.google.gson.Gson; + +import com.lathrum.VMF2OBJ.App; import com.lathrum.VMF2OBJ.dataStructure.Plane; import com.lathrum.VMF2OBJ.dataStructure.Vector3; @@ -31,7 +32,6 @@ public class Side { public Displacement dispinfo; public static Side completeSide(Side side, Solid solid) { - Gson gson = new Gson(); Collection intersections = new LinkedList(); for (Side side2 : solid.sides) { @@ -91,7 +91,7 @@ public int compare(Vector3 o1, Vector3 o2) { } }); - Side newSide = gson.fromJson(gson.toJson(side, Side.class), Side.class); + Side newSide = App.gson.fromJson(App.gson.toJson(side, Side.class), Side.class); newSide.points = IntersectionsList.toArray(new Vector3[IntersectionsList.size()]); diff --git a/src/main/java/com/lathrum/VMF2OBJ/dataStructure/map/VMF.java b/src/main/java/com/lathrum/VMF2OBJ/dataStructure/map/VMF.java index d07165f..4c5e4fb 100644 --- a/src/main/java/com/lathrum/VMF2OBJ/dataStructure/map/VMF.java +++ b/src/main/java/com/lathrum/VMF2OBJ/dataStructure/map/VMF.java @@ -4,8 +4,8 @@ import java.util.LinkedList; import java.util.regex.Matcher; import java.util.regex.Pattern; -import com.google.gson.Gson; +import com.lathrum.VMF2OBJ.App; import com.lathrum.VMF2OBJ.dataStructure.Vector3; public class VMF { @@ -240,7 +240,7 @@ public static VMF parseVMF(String text) { text = text.replaceAll(",,", ","); // System.out.println(text); - VMF vmf = new Gson().fromJson(text, VMF.class); + VMF vmf = App.gson.fromJson(text, VMF.class); return vmf; } @@ -248,7 +248,6 @@ public static VMF parseSolids(VMF vmf) { if (vmf.solids == null) { return vmf; } // There are no brushes in this VMF - Gson gson = new Gson(); String planeRegex = "\\((.+?) (.+?) (.+?)\\) \\((.+?) (.+?) (.+?)\\) \\((.+?) (.+?) (.+?)\\)"; Pattern planePattern = Pattern.compile(planeRegex); Matcher planeMatch; @@ -307,7 +306,7 @@ public static VMF parseSolids(VMF vmf) { } j = 0; - Solid solidProxy = gson.fromJson(gson.toJson(solid, Solid.class), Solid.class); + Solid solidProxy = App.gson.fromJson(App.gson.toJson(solid, Solid.class), Solid.class); for (Side side : solidProxy.sides) { Side newSide = Side.completeSide(side, solidProxy); if (newSide != null) { diff --git a/src/main/java/com/lathrum/VMF2OBJ/dataStructure/texture/VMT.java b/src/main/java/com/lathrum/VMF2OBJ/dataStructure/texture/VMT.java index 198cbcf..1b60c94 100644 --- a/src/main/java/com/lathrum/VMF2OBJ/dataStructure/texture/VMT.java +++ b/src/main/java/com/lathrum/VMF2OBJ/dataStructure/texture/VMT.java @@ -3,7 +3,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import com.google.gson.Gson; +import com.lathrum.VMF2OBJ.App; public class VMT { public String name; @@ -40,7 +40,6 @@ public static int findClosingBracketMatchIndex(String str, int pos) { } public static VMT parseVMT(String text) { - Gson gson = new Gson(); if (text.length() > 6) { if (text.substring(1, 6).equals("Water")) // If water texture @@ -50,7 +49,7 @@ public static VMT parseVMT(String text) { // which shouldn't really be used anyways in this case. So we'll give it an // obvious texture // So it can be easily changed - VMT vmt = gson.fromJson("{\"basetexture\":\"TOOLS/TOOLSDOTTED\"}", VMT.class); + VMT vmt = App.gson.fromJson("{\"basetexture\":\"TOOLS/TOOLSDOTTED\"}", VMT.class); return vmt; } } @@ -83,7 +82,7 @@ public static VMT parseVMT(String text) { int endIndex = findClosingBracketMatchIndex(text, startIndex); if (endIndex == -1) // Invalid vmt { - VMT vmt = gson.fromJson("{\"basetexture\":\"TOOLS/TOOLSDOTTED\"}", VMT.class); + VMT vmt = App.gson.fromJson("{\"basetexture\":\"TOOLS/TOOLSDOTTED\"}", VMT.class); return vmt; } text = text.substring(startIndex, endIndex + 1); @@ -107,7 +106,7 @@ public static VMT parseVMT(String text) { text = text.replaceAll("([a-zA-Z_]+dx[6-9])", "\"$1\":"); // Fix fallback shaders // System.out.println(text); - VMT vmt = gson.fromJson(text, VMT.class); + VMT vmt = App.gson.fromJson(text, VMT.class); return vmt; } From 272a5fcd30dbdf1a4acab4cb59122ddb1b803214 Mon Sep 17 00:00:00 2001 From: Dylancyclone Date: Tue, 7 Jul 2020 19:23:09 -0700 Subject: [PATCH 4/6] MASSIVELY improve conversion time by caching models and materials A conversion that would take almost an hour will now take around 9 minutes. This is done by caching the decompiled models and parsed materials so that they only have to be processed once. Resolves issue #5 --- src/main/java/com/lathrum/VMF2OBJ/App.java | 317 ++++++++++-------- .../VMF2OBJ/dataStructure/model/QC.java | 2 + 2 files changed, 171 insertions(+), 148 deletions(-) diff --git a/src/main/java/com/lathrum/VMF2OBJ/App.java b/src/main/java/com/lathrum/VMF2OBJ/App.java index 72d93e1..f6e7ac4 100644 --- a/src/main/java/com/lathrum/VMF2OBJ/App.java +++ b/src/main/java/com/lathrum/VMF2OBJ/App.java @@ -369,6 +369,8 @@ public static void main(String[] args) throws Exception { Scanner in; ProgressBarBuilder pbb; ArrayList vpkEntries = new ArrayList(); + HashMap materialCache = new HashMap(); + HashMap modelCache = new HashMap(); PrintWriter objFile; PrintWriter materialFile; String outPath = ""; @@ -490,7 +492,6 @@ public static void main(String[] args) throws Exception { VMF vmf = VMF.parseVMF(text); vmf = VMF.parseSolids(vmf); - // System.out.println(gson.toJson(vmf)); // // Write brushes @@ -573,37 +574,43 @@ public static void main(String[] args) throws Exception { for (String el : uniqueMaterialsList) { el = el.toLowerCase(); - // Read File - String VMTText = ""; - try { - int index = getEntryIndexByPath(vpkEntries, "materials/" + el + ".vmt"); - if (index == -1) { - printProgressBar("Missing Material: " + el); + VMT vmt = materialCache.get(el); // Check if material has been processed before + if (vmt != null) { + vmt = gson.fromJson(gson.toJson(vmt, VMT.class), VMT.class); // Deep copy so the cache isn't modified + } else { // If it has not yet been processed, process it + // Read File + String VMTText = ""; + try { + int index = getEntryIndexByPath(vpkEntries, "materials/" + el + ".vmt"); + if (index == -1) { + printProgressBar("Missing Material: " + el); + continue; + } + VMTText = new String(vpkEntries.get(index).readData()); + } catch (IOException e) { + System.out.println("Failed to read material: " + el); + System.err.println(e.toString()); + } + + try { + vmt = VMT.parseVMT(VMTText); + } catch (Exception ex) { + printProgressBar("Failed to parse Material: " + el); continue; } - VMTText = new String(vpkEntries.get(index).readData()); - } catch (IOException e) { - System.out.println("Failed to read material: " + el); - System.err.println(e.toString()); - } + vmt.name = el; - VMT vmt = new VMT(); - try { - vmt = VMT.parseVMT(VMTText); - } catch (Exception ex) { - printProgressBar("Failed to parse Material: " + el); - continue; - } - vmt.name = el; - // System.out.println(gson.toJson(vmt)); - // System.out.println(vmt.basetexture); - if (vmt.basetexture == null || vmt.basetexture.isEmpty()) { - printProgressBar("Material has no texture: " + el); - continue; - } - if (vmt.basetexture.endsWith(".vtf")) { - vmt.basetexture = vmt.basetexture.substring(0, vmt.basetexture.lastIndexOf('.')); // snip the extension + if (vmt.basetexture == null || vmt.basetexture.isEmpty()) { + printProgressBar("Material has no texture: " + el); + continue; + } + if (vmt.basetexture.endsWith(".vtf")) { + vmt.basetexture = vmt.basetexture.substring(0, vmt.basetexture.lastIndexOf('.')); // snip the extension + } + materialCache.put(el, vmt); + vmt = gson.fromJson(gson.toJson(vmt, VMT.class), VMT.class); // Deep copy so the cache isn't modified } + int index = getEntryIndexByPath(vpkEntries, "materials/" + vmt.basetexture + ".vtf"); // System.out.println(index); if (index != -1) { @@ -915,100 +922,109 @@ public static void main(String[] args) throws Exception { faces.clear(); materials.clear(); - // Crowbar has a setting that puts all decompiled models in a subfolder - // called `DecompileFolderForEachModelIsChecked`. This is false by default, - // but must be handled otherwise all model will fail to convert - Boolean crowbarSubfolderSetting = false; - - String modelWithoutExtension = entity.model.substring(0, entity.model.lastIndexOf('.')); - entity.modelName = modelWithoutExtension.substring(modelWithoutExtension.lastIndexOf("/")); - ArrayList indicies = getEntryIndiciesByPattern(vpkEntries, modelWithoutExtension + "."); - - indicies.removeAll( - getEntryIndiciesByPattern(vpkEntries, modelWithoutExtension + ".vmt")); - indicies.removeAll( - getEntryIndiciesByPattern(vpkEntries, modelWithoutExtension + ".vtf")); - for (int index : indicies) { - - if (index != -1) { - File fileOutPath = new File(outPath); - fileOutPath = new File( - formatPath(fileOutPath.getParent() + File.separator + vpkEntries.get(index).getFullPath())); - if (!fileOutPath.exists()) { - try { - File directory = new File(fileOutPath.getParent()); - if (!directory.exists()) { - directory.mkdirs(); + QC qc = modelCache.get(entity.model); // Check if model has been processed before + if (qc != null) { + qc = gson.fromJson(gson.toJson(qc, QC.class), QC.class); // Deep copy so the cache isn't modified + } else { // If it has not yet been processed, process it + + // Crowbar has a setting that puts all decompiled models in a subfolder + // called `DecompileFolderForEachModelIsChecked`. This is false by default, + // but must be handled otherwise all model will fail to convert + Boolean crowbarSubfolderSetting = false; + + String modelWithoutExtension = entity.model.substring(0, entity.model.lastIndexOf('.')); + entity.modelName = modelWithoutExtension.substring(modelWithoutExtension.lastIndexOf("/")); + ArrayList indicies = getEntryIndiciesByPattern(vpkEntries, modelWithoutExtension + "."); + + indicies.removeAll( + getEntryIndiciesByPattern(vpkEntries, modelWithoutExtension + ".vmt")); + indicies.removeAll( + getEntryIndiciesByPattern(vpkEntries, modelWithoutExtension + ".vtf")); + for (int index : indicies) { + + if (index != -1) { + File fileOutPath = new File(outPath); + fileOutPath = new File( + formatPath(fileOutPath.getParent() + File.separator + vpkEntries.get(index).getFullPath())); + if (!fileOutPath.exists()) { + try { + File directory = new File(fileOutPath.getParent()); + if (!directory.exists()) { + directory.mkdirs(); + } + } catch (Exception e) { + System.out.println("Failed to create directory: " + fileOutPath.getParent()); + System.err.println(e.toString()); + } + try { + vpkEntries.get(index).extract(fileOutPath); + } catch (Exception e) { + System.err.println("Failed to extract: " + fileOutPath); + System.err.println(e.toString()); } - } catch (Exception e) { - System.out.println("Failed to create directory: " + fileOutPath.getParent()); - System.err.println(e.toString()); - } - try { - vpkEntries.get(index).extract(fileOutPath); - } catch (Exception e) { - System.err.println("Failed to extract: " + fileOutPath); - System.err.println(e.toString()); } } } - } - - String[] command = new String[] { - CrowbarLibPath, - "-p", formatPath(new File(outPath).getParent() + File.separator + entity.model)}; - - proc = Runtime.getRuntime().exec(command); - // BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream())); - // String line = ""; - // while ((line = reader.readLine()) != null) { - // System.out.println(line); - // } - BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream())); - while ((reader.readLine()) != null) {} - proc.waitFor(); - - String qcText = ""; - try { - // This line may cause errors if the qc file does not have the same name as the mdl file - qcText = readFile(new File(outPath).getParent() + File.separator + modelWithoutExtension + ".qc"); - } catch (IOException e) { - //This will be caught by detecting a blank string - // System.out.println("Exception Occured: " + e.toString()); - } - if (qcText.matches("")) { + + String[] command = new String[] { + CrowbarLibPath, + "-p", formatPath(new File(outPath).getParent() + File.separator + entity.model)}; + + proc = Runtime.getRuntime().exec(command); + // BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream())); + // String line = ""; + // while ((line = reader.readLine()) != null) { + // System.out.println(line); + // } + BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream())); + while ((reader.readLine()) != null) {} + proc.waitFor(); + + String qcText = ""; try { - crowbarSubfolderSetting = true; // This line may cause errors if the qc file does not have the same name as the mdl file - qcText = readFile(new File(outPath).getParent() + File.separator + modelWithoutExtension + File.separator + entity.modelName + ".qc"); + qcText = readFile(new File(outPath).getParent() + File.separator + modelWithoutExtension + ".qc"); } catch (IOException e) { //This will be caught by detecting a blank string // System.out.println("Exception Occured: " + e.toString()); } if (qcText.matches("")) { - printProgressBar("Error: Could not find QC file for model, skipping: " + entity.model); - continue; - } - } - - QC qc = QC.parseQC(qcText); - - ArrayList SMDTriangles = new ArrayList(); - - for (String bodyGroup : qc.BodyGroups) { - String path = "/"; - if (qc.ModelName.contains("/")) { - path += qc.ModelName.substring(0, qc.ModelName.lastIndexOf('/')); + try { + crowbarSubfolderSetting = true; + // This line may cause errors if the qc file does not have the same name as the mdl file + qcText = readFile(new File(outPath).getParent() + File.separator + modelWithoutExtension + File.separator + entity.modelName + ".qc"); + } catch (IOException e) { + //This will be caught by detecting a blank string + // System.out.println("Exception Occured: " + e.toString()); + } + if (qcText.matches("")) { + printProgressBar("Error: Could not find QC file for model, skipping: " + entity.model); + continue; + } } - if (crowbarSubfolderSetting) { - path += File.separator + entity.modelName; + + qc = QC.parseQC(qcText); + + ArrayList SMDTriangles = new ArrayList(); + + for (String bodyGroup : qc.BodyGroups) { + String path = "/"; + if (qc.ModelName.contains("/")) { + path += qc.ModelName.substring(0, qc.ModelName.lastIndexOf('/')); + } + if (crowbarSubfolderSetting) { + path += File.separator + entity.modelName; + } + String smdText = readFile(formatPath( + new File(outPath).getParent() + File.separator + "models" + path + File.separator + bodyGroup)); + + SMDTriangles.addAll(Arrays.asList(SMDTriangle.parseSMD(smdText))); } - String smdText = readFile(formatPath( - new File(outPath).getParent() + File.separator + "models" + path + File.separator + bodyGroup)); - - SMDTriangles.addAll(Arrays.asList(SMDTriangle.parseSMD(smdText))); + qc.triangles = SMDTriangles.toArray(new SMDTriangle[] {}); + modelCache.put(entity.model, qc); + qc = gson.fromJson(gson.toJson(qc, QC.class), QC.class); // Deep copy so the cache isn't modified } - + // Transform model String[] angles = entity.angles.split(" "); double[] radAngles = new double[3]; @@ -1019,8 +1035,8 @@ public static void main(String[] args) throws Exception { String[] origin = entity.origin.split(" "); Vector3 transform = new Vector3(Double.parseDouble(origin[0]), Double.parseDouble(origin[1]), Double.parseDouble(origin[2])); - for (int i = 0; i < SMDTriangles.size(); i++) { - SMDTriangle temp = SMDTriangles.get(i); + for (int i = 0; i < qc.triangles.length; i++) { + SMDTriangle temp = qc.triangles[i]; materials.add(temp.materialName); for (int j = 0; j < temp.points.length; j++) { // VMF stores rotations as: YZX @@ -1033,7 +1049,7 @@ public static void main(String[] args) throws Exception { temp.points[j].position = temp.points[j].position.add(transform); verticies.add(temp.points[j].position); } - SMDTriangles.set(i, temp); + qc.triangles[i] = temp; } // TODO: Margin of error? @@ -1056,48 +1072,53 @@ public static void main(String[] args) throws Exception { for (String el : uniqueMaterialsList) { el = el.toLowerCase(); - // Read File - String VMTText = ""; - for (String cdMaterial : qc.CDMaterials) { // Our material can be in multiple directories, we gotta find it - if (cdMaterial.endsWith("/")) { - cdMaterial = cdMaterial.substring(0, cdMaterial.lastIndexOf('/')); + VMT vmt = materialCache.get(el); // Check if material has been processed before + if (vmt != null) { + vmt = gson.fromJson(gson.toJson(vmt, VMT.class), VMT.class); // Deep copy so the cache isn't modified + } else { // If it has not yet been processed, process it + // Read File + String VMTText = ""; + for (String cdMaterial : qc.CDMaterials) { // Our material can be in multiple directories, we gotta find it + if (cdMaterial.endsWith("/")) { + cdMaterial = cdMaterial.substring(0, cdMaterial.lastIndexOf('/')); + } + try { + int index = getEntryIndexByPath(vpkEntries, "materials/" + cdMaterial + "/" + el + ".vmt"); + if (index == -1) { + continue; + } // Could not find it + VMTText = new String(vpkEntries.get(index).readData()); + } catch (IOException e) { + System.out.println("Failed to read material: " + el); + System.err.println(e.toString()); + } + if (!VMTText.isEmpty()) { + break; + } + } + if (VMTText.isEmpty()) { + printProgressBar("Could not find material: " + el); + continue; } + try { - int index = getEntryIndexByPath(vpkEntries, "materials/" + cdMaterial + "/" + el + ".vmt"); - if (index == -1) { - continue; - } // Could not find it - VMTText = new String(vpkEntries.get(index).readData()); - } catch (IOException e) { - System.out.println("Failed to read material: " + el); - System.err.println(e.toString()); + vmt = VMT.parseVMT(VMTText); + } catch (Exception ex) { + printProgressBar("Failed to parse Material: " + el); + continue; } - if (!VMTText.isEmpty()) { - break; + vmt.name = el; + if (vmt.basetexture == null || vmt.basetexture.isEmpty()) { + printProgressBar("Material has no texture: " + el); + continue; } - } - if (VMTText.isEmpty()) { - printProgressBar("Could not find material: " + el); - continue; + if (vmt.basetexture.endsWith(".vtf")) { + vmt.basetexture = vmt.basetexture.substring(0, vmt.basetexture.lastIndexOf('.')); // snip the extension + } + materialCache.put(el, vmt); + vmt = gson.fromJson(gson.toJson(vmt, VMT.class), VMT.class); // Deep copy so the cache isn't modified } - VMT vmt = new VMT(); - try { - vmt = VMT.parseVMT(VMTText); - } catch (Exception ex) { - printProgressBar("Failed to parse Material: " + el); - continue; - } - vmt.name = el; - // System.out.println(gson.toJson(vmt)); - // System.out.println(vmt.basetexture); - if (vmt.basetexture == null || vmt.basetexture.isEmpty()) { - printProgressBar("Material has no texture: " + el); - continue; - } - if (vmt.basetexture.endsWith(".vtf")) { - vmt.basetexture = vmt.basetexture.substring(0, vmt.basetexture.lastIndexOf('.')); // snip the extension - } int index = getEntryIndexByPath(vpkEntries, "materials/" + vmt.basetexture + ".vtf"); // System.out.println(index); if (index != -1) { @@ -1256,7 +1277,7 @@ public static void main(String[] args) throws Exception { } objFile.println(); - for (SMDTriangle SMDTriangle : SMDTriangles) { + for (SMDTriangle SMDTriangle : qc.triangles) { String buffer = ""; for (int i = 0; i < SMDTriangle.points.length; i++) { diff --git a/src/main/java/com/lathrum/VMF2OBJ/dataStructure/model/QC.java b/src/main/java/com/lathrum/VMF2OBJ/dataStructure/model/QC.java index 8311758..f61c3e7 100644 --- a/src/main/java/com/lathrum/VMF2OBJ/dataStructure/model/QC.java +++ b/src/main/java/com/lathrum/VMF2OBJ/dataStructure/model/QC.java @@ -12,6 +12,8 @@ public class QC { public String[] CDMaterials; + public SMDTriangle[] triangles; + // public String TextureGroup; //oof public QC(String ModelName, String[] BodyGroups, String[] CDMaterials) { From 3c9421489bba06c4f188807a379b5d2da5ec99f3 Mon Sep 17 00:00:00 2001 From: Dylancyclone Date: Wed, 8 Jul 2020 17:08:49 -0700 Subject: [PATCH 5/6] Print app version at start of conversion and at top of generated files When debugging it's always useful to know what version of the app is being used. Same goes for the generated files, if a file is found to have a problem, it's worth checking if it was generated with a version of the app from before the issue was fixed. --- pom.xml | 4 ++++ src/main/java/com/lathrum/VMF2OBJ/App.java | 11 ++++++++++- src/main/resources/project.properties | 2 ++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/project.properties diff --git a/pom.xml b/pom.xml index 30c0cfb..a396b80 100644 --- a/pom.xml +++ b/pom.xml @@ -46,6 +46,10 @@ **/*.exe + + src/main/resources + true + diff --git a/src/main/java/com/lathrum/VMF2OBJ/App.java b/src/main/java/com/lathrum/VMF2OBJ/App.java index f6e7ac4..c0d370b 100644 --- a/src/main/java/com/lathrum/VMF2OBJ/App.java +++ b/src/main/java/com/lathrum/VMF2OBJ/App.java @@ -31,6 +31,7 @@ public class App { public static String CrowbarLibPath; public static boolean quietMode = false; public static boolean ignoreTools = false; + public static String appVersion; /** * Extract Libraries to temporary directory. @@ -366,6 +367,11 @@ public static void main(String[] args) throws Exception { options.addOption("q", "quiet", false, "Suppress warnings"); options.addOption("t", "tools", false, "Ignore tool brushes"); + // Load app version + final Properties properties = new Properties(); + properties.load(App.class.getClassLoader().getResourceAsStream("project.properties")); + appVersion = properties.getProperty("version"); + Scanner in; ProgressBarBuilder pbb; ArrayList vpkEntries = new ArrayList(); @@ -435,6 +441,8 @@ public static void main(String[] args) throws Exception { System.err.println(e.toString()); } + System.out.println("Starting VMF2OBJ conversion v" + appVersion); + // // Read VPK // @@ -507,7 +515,8 @@ public static void main(String[] args) throws Exception { int vertexNormalOffset = 1; System.out.println("[3/5] Writing brushes..."); - objFile.println("# Decompiled with VMF2OBJ by Dylancyclone\n"); + objFile.println("# Decompiled with VMF2OBJ v" + appVersion + " by Dylancyclone\n"); + materialFile.println("# Decompiled with VMF2OBJ v" + appVersion + " by Dylancyclone\n"); objFile.println("mtllib " + matLibName.substring(formatPath(matLibName).lastIndexOf(File.separatorChar) + 1, matLibName.length())); diff --git a/src/main/resources/project.properties b/src/main/resources/project.properties new file mode 100644 index 0000000..a2273e2 --- /dev/null +++ b/src/main/resources/project.properties @@ -0,0 +1,2 @@ +version=${project.version} +artifactId=${project.artifactId} \ No newline at end of file From 22922c7d14ff6253741b64941397015aed5252ad Mon Sep 17 00:00:00 2001 From: Dylancyclone Date: Wed, 8 Jul 2020 17:14:08 -0700 Subject: [PATCH 6/6] Bump version to v1.1.2 7/08/2020 1.1.2: * Massively improve conversion speed (up to 670% on large maps) * Fix Crowbar `DecompileFolderForEachModelIsChecked` setting causing issues * Improved exception reporting * The app will now print the version number at the beginning of the conversion process and at the top of generated files --- README.md | 2 +- changelog.txt | 6 ++++++ pom.xml | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9e7eeee..a03de9c 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Watch a demonstration video: From the root directory, run: -`mvn package;java -jar ./target/VMF2OBJ-1.1.1-jar-with-dependencies.jar [VMF_FILE] [OUTPUT_FILE] [VPK_PATHS]` +`mvn package;java -jar ./target/VMF2OBJ-1.1.2-jar-with-dependencies.jar [VMF_FILE] [OUTPUT_FILE] [VPK_PATHS]` ``` usage: vmf2obj [VMF_FILE] [OUTPUT_FILE] [VPK_PATHS] [args...] diff --git a/changelog.txt b/changelog.txt index 5911f8e..0c087d2 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +7/08/2020 1.1.2: +* Massively improve conversion speed (up to 670% on large maps) +* Fix Crowbar `DecompileFolderForEachModelIsChecked` setting causing issues +* Improved exception reporting +* The app will now print the version number at the beginning of the conversion process and at the top of generated files + 6/29/2020 1.1.1: * Fix displacements * Fix some models' QC being unfindable diff --git a/pom.xml b/pom.xml index a396b80..f4ee8c3 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.lathrum.VMF2OBJ VMF2OBJ - 1.1.1 + 1.1.2 jar VMF2OBJ