From c778b14e4e290e3dbbf84cd7c860e90c6a7f17c7 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Mon, 17 Jun 2024 21:46:04 -0700 Subject: [PATCH] Implement light mesh generator --- .../bintools/EaglerBinaryTools.java | 3 +- .../eaglercraft/bintools/LightMeshGen.java | 158 ++++++++++++++++++ .../eaglercraft/bintools/SkyboxGen.java | 41 +---- .../eaglercraft/bintools/utils/IEEE754.java | 32 ++++ 4 files changed, 200 insertions(+), 34 deletions(-) create mode 100644 src/main/java/net/lax1dude/eaglercraft/bintools/LightMeshGen.java create mode 100644 src/main/java/net/lax1dude/eaglercraft/bintools/utils/IEEE754.java diff --git a/src/main/java/net/lax1dude/eaglercraft/bintools/EaglerBinaryTools.java b/src/main/java/net/lax1dude/eaglercraft/bintools/EaglerBinaryTools.java index 5ce719c..e1e3baa 100644 --- a/src/main/java/net/lax1dude/eaglercraft/bintools/EaglerBinaryTools.java +++ b/src/main/java/net/lax1dude/eaglercraft/bintools/EaglerBinaryTools.java @@ -65,6 +65,7 @@ public class EaglerBinaryTools { return; case "light-mesh-gen": case "lightmeshgen": + LightMeshGen._main(argz); return; case "eagler-moon-gen": case "eaglermoongen": @@ -79,7 +80,7 @@ public class EaglerBinaryTools { } private static void usage() { - System.out.println("Usage: java -jar EaglerBinaryTools.jar [args...]"); + System.out.println("Usage: java -jar EaglerBinaryTools.jar [args...]"); System.out.println(" - 'epkcompiler': Compile an EPK file from a folder"); System.out.println(" - 'legacy-epkcompiler': Compile an EPK file in legacy format"); System.out.println(" - 'epkdecompiler': Decompile an EPK file into a folder"); diff --git a/src/main/java/net/lax1dude/eaglercraft/bintools/LightMeshGen.java b/src/main/java/net/lax1dude/eaglercraft/bintools/LightMeshGen.java new file mode 100644 index 0000000..a2a1c45 --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/bintools/LightMeshGen.java @@ -0,0 +1,158 @@ +package net.lax1dude.eaglercraft.bintools; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import net.lax1dude.eaglercraft.bintools.utils.IEEE754; + +/** + * Copyright (c) 2023-2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class LightMeshGen { + + public static void _main(String[] args) throws IOException { + if(args.length != 2 && args.length != 3) { + System.out.println("Usage: light-mesh-gen [mesh name]"); + System.out.println("Input file format is Wavefront OBJ file exported from your 3D modeling program"); + System.out.println("The only mesh name currently used by eagler is \"light_point_mesh\""); + return; + } + System.out.println("Reading input file..."); + List lns = new ArrayList(); + try(BufferedReader bu = new BufferedReader(new FileReader(new File(args[0])))) { + String s; + while((s = bu.readLine()) != null) { + lns.add(s); + } + } + File output = new File(args[1]); + System.out.println("Exporting light mesh: " + output.getAbsolutePath()); + try(FileOutputStream fs = new FileOutputStream(output)) { + convertModel(lns, args.length == 3 ? args[2] : "light_point_mesh", fs); + } + System.out.println("Light mesh export complete!"); + } + + public static void convertModel(Collection lines, String name, OutputStream out) throws IOException { + + List vertexes = new ArrayList(); + List faces = new ArrayList(); + List vboentries = new ArrayList(); + List vboentriesF = new ArrayList(); + List indexablevboentries = new ArrayList(); + List indexbuffer = new ArrayList(); + for(String ul : lines) { + String[] l = ul.split(" "); + if(l[0].equals("v")) { + vertexes.add(new float[] {Float.parseFloat(l[1]), Float.parseFloat(l[2]), Float.parseFloat(l[3])}); + } + if(l[0].equals("f")) { + if(l.length != 4) { + OBJConverter.printTriangulationMessage(); + throw new IOException("Incompatible model! (This can be fixed)"); + } + String[] v1 = l[1].split("/"); + String[] v2 = l[2].split("/"); + String[] v3 = l[3].split("/"); + faces.add(new int[][] { + {Integer.parseInt(v1[0]), Integer.parseInt(v1[1]), Integer.parseInt(v1[2])}, + {Integer.parseInt(v2[0]), Integer.parseInt(v2[1]), Integer.parseInt(v2[2])}, + {Integer.parseInt(v3[0]), Integer.parseInt(v3[1]), Integer.parseInt(v3[2])} + }); + } + } + + int w = 32; + int h = 16; + int[] normalsLookupTexture = new int[w * h]; + int normalsId = 0; + + + for(int[][] f : faces) { + + for(int i = 0; i < 3; i++) { + byte[] b = new byte[6]; + + float[] v = vertexes.get(f[i][0]-1); + + int ix = IEEE754.encodeHalfFloat(v[0]); + int iy = IEEE754.encodeHalfFloat(v[1]); + int iz = IEEE754.encodeHalfFloat(v[2]); + + int idx = 0; + + b[idx++] = (byte)(ix); b[idx++] = (byte)(ix >> 8); + b[idx++] = (byte)(iy); b[idx++] = (byte)(iy >> 8); + b[idx++] = (byte)(iz); b[idx++] = (byte)(iz >> 8); + + vboentries.add(b); + vboentriesF.add(v); + } + } + + for(int j = 0; j < vboentries.size(); ++j) { + byte v[] = vboentries.get(j); + int l = indexablevboentries.size(); + boolean flag = true; + for(int i = 0; i < l; i++) { + if(Arrays.equals(v, indexablevboentries.get(i))) { + indexbuffer.add(i); + flag = false; + break; + } + } + if(flag) { + if(l > 255) { + throw new UnsupportedOperationException("Too many vertices!"); + } + indexbuffer.add(l); + indexablevboentries.add(v); + } + } + + DataOutputStream o = new DataOutputStream(out); + o.write(0xEE); + o.write(0xAA); + o.write(0x66); + o.write('%'); + o.write(name.length()); + o.write(name.getBytes(StandardCharsets.US_ASCII)); + o.writeInt(indexablevboentries.size()); + for(int i = 0; i < indexablevboentries.size(); ++i) { + byte[] b = indexablevboentries.get(i); + o.write(b, 0, b.length); + } + + o.writeInt(indexbuffer.size()); + o.write(1); // 1 = byte, 2 = short, 4 = int + + for(int i : indexbuffer) { + o.write(i & 0xFF); + } + + o.close(); + } + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/bintools/SkyboxGen.java b/src/main/java/net/lax1dude/eaglercraft/bintools/SkyboxGen.java index a0d5e21..3c34165 100644 --- a/src/main/java/net/lax1dude/eaglercraft/bintools/SkyboxGen.java +++ b/src/main/java/net/lax1dude/eaglercraft/bintools/SkyboxGen.java @@ -12,6 +12,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import net.lax1dude.eaglercraft.bintools.utils.IEEE754; + /** * Copyright (c) 2023-2024 lax1dude. All Rights Reserved. * @@ -98,9 +100,9 @@ public class SkyboxGen { float[] v = vertexes.get(f[i][0]-1); - int ix = encodeHalfFloat(v[0]); - int iy = encodeHalfFloat(v[1]); - int iz = encodeHalfFloat(v[2]); + int ix = IEEE754.encodeHalfFloat(v[0]); + int iy = IEEE754.encodeHalfFloat(v[1]); + int iz = IEEE754.encodeHalfFloat(v[2]); int idx = 0; @@ -147,9 +149,9 @@ public class SkyboxGen { float[] v = vertexes.get(f[i][0]-1); - int ix = encodeHalfFloat(v[0]); - int iy = encodeHalfFloat(v[1]); - int iz = encodeHalfFloat(v[2]); + int ix = IEEE754.encodeHalfFloat(v[0]); + int iy = IEEE754.encodeHalfFloat(v[1]); + int iz = IEEE754.encodeHalfFloat(v[2]); int idx = 0; @@ -289,31 +291,4 @@ public class SkyboxGen { o.close(); } - - //source: https://stackoverflow.com/questions/6162651/half-precision-floating-point-in-java - - public static int encodeHalfFloat(float fval) { - int fbits = Float.floatToIntBits(fval); - int sign = fbits >>> 16 & 0x8000; // sign only - int val = (fbits & 0x7fffffff) + 0x1000; // rounded value - - if (val >= 0x47800000) // might be or become NaN/Inf - { // avoid Inf due to rounding - if ((fbits & 0x7fffffff) >= 0x47800000) { // is or must become NaN/Inf - if (val < 0x7f800000) // was value but too large - return sign | 0x7c00; // make it +/-Inf - return sign | 0x7c00 | // remains +/-Inf or NaN - (fbits & 0x007fffff) >>> 13; // keep NaN (and Inf) bits - } - return sign | 0x7bff; // unrounded not quite Inf - } - if (val >= 0x38800000) // remains normalized value - return sign | val - 0x38000000 >>> 13; // exp - 127 + 15 - if (val < 0x33000000) // too small for subnormal - return sign; // becomes +/-0 - val = (fbits & 0x7fffffff) >>> 23; // tmp exp for subnormal calc - return sign | ((fbits & 0x7fffff | 0x800000) // add subnormal bit - + (0x800000 >>> val - 102) // round depending on cut off - >>> 126 - val); // div by 2^(1-(exp-127+15)) and >> 13 | exp=0 - } } diff --git a/src/main/java/net/lax1dude/eaglercraft/bintools/utils/IEEE754.java b/src/main/java/net/lax1dude/eaglercraft/bintools/utils/IEEE754.java new file mode 100644 index 0000000..9b50d42 --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/bintools/utils/IEEE754.java @@ -0,0 +1,32 @@ +package net.lax1dude.eaglercraft.bintools.utils; + +public class IEEE754 { + + // source: https://stackoverflow.com/questions/6162651/half-precision-floating-point-in-java + + public static int encodeHalfFloat(float fval) { + int fbits = Float.floatToIntBits(fval); + int sign = fbits >>> 16 & 0x8000; // sign only + int val = (fbits & 0x7fffffff) + 0x1000; // rounded value + + if (val >= 0x47800000) // might be or become NaN/Inf + { // avoid Inf due to rounding + if ((fbits & 0x7fffffff) >= 0x47800000) { // is or must become NaN/Inf + if (val < 0x7f800000) // was value but too large + return sign | 0x7c00; // make it +/-Inf + return sign | 0x7c00 | // remains +/-Inf or NaN + (fbits & 0x007fffff) >>> 13; // keep NaN (and Inf) bits + } + return sign | 0x7bff; // unrounded not quite Inf + } + if (val >= 0x38800000) // remains normalized value + return sign | val - 0x38000000 >>> 13; // exp - 127 + 15 + if (val < 0x33000000) // too small for subnormal + return sign; // becomes +/-0 + val = (fbits & 0x7fffffff) >>> 23; // tmp exp for subnormal calc + return sign | ((fbits & 0x7fffff | 0x800000) // add subnormal bit + + (0x800000 >>> val - 102) // round depending on cut off + >>> 126 - val); // div by 2^(1-(exp-127+15)) and >> 13 | exp=0 + } + +}