// Copyright 2021 The MediaPipe Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import static java.nio.charset.StandardCharsets.UTF_8; import java.io.BufferedWriter; import java.io.File; import java.io.FileFilter; import java.io.FileOutputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; /** * Class for running desktop-side parsing/packing routines on .obj AR assets. Usage: ObjParser * --input_dir=[INPUT_DIRECTORY] --output_dir=[OUTPUT_DIRECTORY] where INPUT_DIRECTORY is the folder * with asset obj files to process, and OUTPUT_DIRECTORY is the folder where processed asset uuu * file should be placed. * *

NOTE: Directories are assumed to be absolute paths. */ public final class ObjParserMain { // Simple FileFilter implementation to let us walk over only our .obj files in a particular // directory. private static final class ObjFileFilter implements FileFilter { ObjFileFilter() { // Nothing to do here. } @Override public boolean accept(File file) { return file.getName().endsWith(".obj"); } } // File extension for binary output files; tagged onto end of initial file extension. private static final String BINARY_FILE_EXT = ".uuu"; private static final String INPUT_DIR_FLAG = "--input_dir="; private static final String OUTPUT_DIR_FLAG = "--output_dir="; private static final float DEFAULT_VERTEX_SCALE_FACTOR = 30.0f; private static final double NS_TO_SECONDS = 1e9; public final PrintWriter writer; public ObjParserMain() { super(); this.writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out, UTF_8))); } // Simple overridable logging function. protected void logString(String infoLog) { writer.println(infoLog); } /* * Main program logic: parse command-line arguments and perform actions. */ public void run(String inDirectory, String outDirectory) { if (inDirectory.isEmpty()) { logString("Error: Must provide input directory with " + INPUT_DIR_FLAG); return; } if (outDirectory.isEmpty()) { logString("Error: Must provide output directory with " + OUTPUT_DIR_FLAG); return; } File dirAsFile = new File(inDirectory); ObjFileFilter objFileFilter = new ObjFileFilter(); File[] objFiles = dirAsFile.listFiles(objFileFilter); FileOutputStream outputStream = null; logString("Parsing directory: " + inDirectory); // We need frames processed in correct order. Arrays.sort(objFiles); for (File objFile : objFiles) { String fileName = objFile.getAbsolutePath(); // Just take the file name of the first processed frame. if (outputStream == null) { String outputFileName = outDirectory + objFile.getName() + BINARY_FILE_EXT; try { // Create new file here, if we can. outputStream = new FileOutputStream(outputFileName); logString("Created outfile: " + outputFileName); } catch (Exception e) { logString("Error creating outfile: " + e.toString()); e.printStackTrace(writer); return; } } // Process each file into the stream. logString("Processing file: " + fileName); processFile(fileName, outputStream); } // Finally close the stream out. try { if (outputStream != null) { outputStream.close(); } } catch (Exception e) { logString("Error trying to close output stream: " + e.toString()); e.printStackTrace(writer); } } /* * Entrypoint for command-line executable. */ public static void main(String[] args) { // Parse flags String inDirectory = ""; String outDirectory = ""; for (int i = 0; i < args.length; i++) { if (args[i].startsWith(INPUT_DIR_FLAG)) { inDirectory = args[i].substring(INPUT_DIR_FLAG.length()); // Make sure this will be treated as a directory if (!inDirectory.endsWith("/")) { inDirectory += "/"; } } if (args[i].startsWith(OUTPUT_DIR_FLAG)) { outDirectory = args[i].substring(OUTPUT_DIR_FLAG.length()); // Make sure this will be treated as a directory if (!outDirectory.endsWith("/")) { outDirectory += "/"; } } } ObjParserMain parser = new ObjParserMain(); parser.run(inDirectory, outDirectory); parser.writer.flush(); } /* * Internal helper function to parse a .obj from an infile name and stream the resulting data * directly out in binary-dump format to outputStream. */ private void processFile(String infileName, OutputStream outputStream) { long start = System.nanoTime(); // First we parse the obj. SimpleObjParser objParser = new SimpleObjParser(infileName, DEFAULT_VERTEX_SCALE_FACTOR); if (!objParser.parse()) { logString("Error parsing .obj file before processing"); return; } final float[] vertices = objParser.getVertices(); final float[] textureCoords = objParser.getTextureCoords(); final ArrayList triangleList = objParser.getTriangles(); // Overall byte count to stream: 12 for the 3 list-length ints, and then 4 for each vertex and // texCoord int, and finally 2 for each triangle index short. final int bbSize = 12 + 4 * vertices.length + 4 * textureCoords.length + 2 * triangleList.size(); // Ensure ByteBuffer is native order, just like we want to read it in, but is NOT direct, so // we can call .array() on it. ByteBuffer bb = ByteBuffer.allocate(bbSize); bb.order(ByteOrder.nativeOrder()); bb.putInt(vertices.length); bb.putInt(textureCoords.length); bb.putInt(triangleList.size()); logString(String.format("Writing... Vertices: %d, TextureCoords: %d, Indices: %d.%n", vertices.length, textureCoords.length, triangleList.size())); for (float vertex : vertices) { bb.putFloat(vertex); } for (float textureCoord : textureCoords) { bb.putFloat(textureCoord); } for (Short vertexIndex : triangleList) { bb.putShort(vertexIndex.shortValue()); } bb.position(0); try { outputStream.write(bb.array(), 0, bbSize); logString(String.format("Processing successful! Took %.4f seconds.%n", (System.nanoTime() - start) / NS_TO_SECONDS)); } catch (Exception e) { logString("Error writing during processing: " + e.toString()); e.printStackTrace(writer); } } }