/* ** 2011 April 5 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. */ package info.ata4.bspsrc; import info.ata4.bsplib.entity.Entity; import info.ata4.bsplib.entity.KeyValue; import info.ata4.bsplib.vector.Vector3f; import info.ata4.bspsrc.modules.texture.Texture; import info.ata4.bspsrc.modules.texture.TextureAxis; import info.ata4.log.LogUtils; import java.io.Closeable; import java.io.File; import java.io.FileNotFoundException; import java.io.OutputStream; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.Collections; import java.util.EmptyStackException; import java.util.Locale; import java.util.Map; import java.util.Stack; import java.util.logging.Level; import java.util.logging.Logger; /** * Class to write formatted VMF files. * * @author Nico Bergemann <barracuda415 at yahoo.de> */ public class VmfWriter implements Closeable { private static final Logger L = LogUtils.getLogger(); private final PrintWriter pw; private final Stack<String> section = new Stack<>(); private final DecimalFormat decimalFormat = new DecimalFormat("0.####", new DecimalFormatSymbols(Locale.ENGLISH)); public VmfWriter(File file) throws FileNotFoundException, UnsupportedEncodingException { pw = new PrintWriter(file, "US-ASCII"); } public VmfWriter(OutputStream os) { pw = new PrintWriter(os); } private void indent() { for (int i = 0; i < section.size(); i++) { pw.print("\t"); } } public void start(String name) { indent(); pw.print(name); pw.print("\r\n"); indent(); pw.print("{\r\n"); section.push(name); } public void end(String name) { try { if (!section.peek().equals(name)) { throw new IllegalArgumentException("VMF section end name mismatch: " + name + ", expected " + section.peek()); } } catch (EmptyStackException ex) { throw new IllegalArgumentException("No open sections left"); } section.pop(); indent(); pw.print("}\r\n"); } public void put(String key, Object value) { indent(); pw.printf("\"%s\" \"%s\"\r\n", key, value); } public void put(String key, int value) { put(key, String.valueOf(value)); } public void put(String key, long value) { put(key, String.valueOf(value)); } public void put(String key, float value) { put(key, formatFloat(value)); } public void put(String key, double value) { put(key, formatFloat(value)); } public void put(String key, boolean value) { put(key, value ? "1" : "0"); } public void put(String key, char value) { put(key, String.valueOf(value)); } public void put(String key, Vector3f v, int p) { put(key, formatVector3f(v, p)); } public void put(String key, Vector3f v) { put(key, formatVector3f(v, 0)); } public void put(String key, Vector3f v1, Vector3f v2, Vector3f v3) { StringBuilder sb = new StringBuilder(); sb.append(formatVector3f(v1, 1)); sb.append(' '); sb.append(formatVector3f(v2, 1)); sb.append(' '); sb.append(formatVector3f(v3, 1)); put(key, sb.toString()); } public void put(String key, TextureAxis axis) { put(key, formatTextureAxis(axis)); } public void put(Map<String, String> stringMap) { for (String key : stringMap.keySet()) { put(key, stringMap.get(key)); } } public void put(Entity entity) { for (Map.Entry<String, String> kv : entity.getEntrySet()) { put(kv.getKey(), kv.getValue()); } } public void put(Texture tex) { put("material", tex.getTexture()); put("uaxis", tex.getUAxis()); put("vaxis", tex.getVAxis()); put("lightmapscale", tex.getLightmapScale()); } public void put(KeyValue keyValue) { put(keyValue.getKey(), keyValue.getValue()); } private String formatVector3f(Vector3f v, int p) { StringBuilder sb = new StringBuilder(); if (p == 1) { sb.append('('); } else if (p == 2) { sb.append('['); } if (!v.isValid()) { L.log(Level.WARNING, "Invalid vector: {0}", v); sb.append("0 0 0"); } else { sb.append(formatFloat(v.x)).append(' '); sb.append(formatFloat(v.y)).append(' '); sb.append(formatFloat(v.z)); } if (p == 1) { sb.append(')'); } else if (p == 2) { sb.append(']'); } return sb.toString(); } private String formatTextureAxis(TextureAxis tx) { StringBuilder sb = new StringBuilder(); sb.append('['); if (!tx.axis.isValid()) { L.log(Level.WARNING, "Invalid vector: {0}", tx.axis); sb.append("0 0 0 "); } else { sb.append(formatFloat(tx.axis.x)).append(' '); sb.append(formatFloat(tx.axis.y)).append(' '); sb.append(formatFloat(tx.axis.z)).append(' '); } sb.append(formatFloat(tx.shift)); sb.append("] "); sb.append(formatFloat(tx.tw)); return sb.toString(); } private String formatFloat(double f) { return decimalFormat.format(f); } @Override public void close() { pw.close(); // stack should be empty, otherwise someone forgot to call end() at least once if (!section.isEmpty()) { StringBuilder sb = new StringBuilder(); // get stack trace Collections.reverse(section); while (true) { sb.append(section.pop()); if (section.isEmpty()) { break; } sb.append(" -> "); } L.log(Level.WARNING, "Unclosed VMF chunk: {0}", sb.toString()); } } }