From d1ab1c6ca0c7e36e8bc08ab1a2662708ebfe986b Mon Sep 17 00:00:00 2001 From: "Lukas Rieger (Blue)" Date: Wed, 19 Oct 2022 19:16:34 +0200 Subject: [PATCH] Switch face-sorting to merge-sort --- .../core/map/hires/HiresTileModel.java | 64 ++++++++++--------- .../bluemap/core/util/IntComparator.java | 15 +++++ .../bluemap/core/util/MergeSort.java | 49 ++++++++++++++ 3 files changed, 98 insertions(+), 30 deletions(-) create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/IntComparator.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MergeSort.java diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresTileModel.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresTileModel.java index 68c42c2a..e0d10783 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresTileModel.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresTileModel.java @@ -29,14 +29,12 @@ import com.google.gson.GsonBuilder; import com.google.gson.stream.JsonWriter; import de.bluecolored.bluemap.core.util.InstancePool; +import de.bluecolored.bluemap.core.util.MergeSort; import de.bluecolored.bluemap.core.util.math.MatrixM3f; import de.bluecolored.bluemap.core.util.math.MatrixM4f; import de.bluecolored.bluemap.core.util.math.VectorM3f; -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; +import java.io.*; import java.nio.charset.StandardCharsets; import java.util.UUID; @@ -64,7 +62,7 @@ public class HiresTileModel { private double[] position; private float[] color, uv, ao; private byte[] sunlight, blocklight; - private int[] materialIndex; + private int[] materialIndex, materialIndexSort, materialIndexSortSupport; public HiresTileModel(int initialCapacity) { if (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity is negative"); @@ -388,6 +386,9 @@ private void setCapacity(int capacity) { sunlight = new byte [capacity * FI_SUNLIGHT]; blocklight = new byte [capacity * FI_BLOCKLIGHT]; materialIndex = new int [capacity * FI_MATERIAL_INDEX]; + + materialIndexSort = new int[materialIndex.length]; + materialIndexSortSupport = new int [materialIndex.length]; } public void writeBufferGeometryJson(OutputStream out) throws IOException { @@ -396,6 +397,8 @@ public void writeBufferGeometryJson(OutputStream out) throws IOException { Gson gson = new GsonBuilder().create(); JsonWriter json = gson.newJsonWriter(new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8), 81920)); + Writer w = null; + json.beginObject(); // main-object json.name("tileGeometry").beginObject(); // tile-geometry-object @@ -583,20 +586,20 @@ private void writeMaterialGroups(JsonWriter json) throws IOException { material = materialIndex[i]; if (material != lastMaterial) { - json.name("count").value((i - groupStart) * 3); + json.name("count").value((i - groupStart) * 3L); json.endObject(); groupStart = i; json.beginObject(); json.name("materialIndex").value(material); - json.name("start").value(groupStart * 3); + json.name("start").value(groupStart * 3L); } lastMaterial = material; } - json.name("count").value((miSize - groupStart) * 3); + json.name("count").value((miSize - groupStart) * 3L); json.endObject(); } @@ -611,32 +614,28 @@ private void writeRounded(JsonWriter json, double value) throws IOException { else json.value(d); } - /** - * Does an optimized selection sort to sort all faces based on their material-index. - * A selection sort is chosen, because it requires the least amount of swaps, which seem (untested) to be the most expensive operation here - */ private void sort() { if (size <= 1) return; // nothing to sort - int prev = Integer.MIN_VALUE, min, minIndex, i, j; - for (i = 0; i < size - 1; i++){ - minIndex = i; - min = materialIndex[minIndex]; - if (min <= prev) continue; // shortcut - - for (j = i + 1; j < size; j++){ - if (materialIndex[j] < min){ - minIndex = j; - min = materialIndex[minIndex]; - } - } - - if (minIndex != i) { - swap(minIndex, i); - } - - prev = min; + // initialize material-index-sort + for (int i = 0; i < size; i++) { + materialIndexSort[i] = i; + materialIndexSortSupport[i] = i; } + + // sort + MergeSort.mergeSortInt(materialIndexSort, 0, size, this::compareMaterialIndex, materialIndexSortSupport); + + // move + for (int i = 0; i < size; i++) { + while (materialIndexSort[i] != i) { + swap(i, materialIndexSort[i]); + } + } + } + + private int compareMaterialIndex(int i1, int i2) { + return Integer.compare(materialIndex[i1], materialIndex[i2]); } private void swap(int face1, int face2) { @@ -695,6 +694,11 @@ private void swap(int face1, int face2) { vi = materialIndex[face1]; materialIndex[face1] = materialIndex[face2]; materialIndex[face2] = vi; + + //swap material-index-sort (assuming FI_MATERIAL_INDEX = 1) + vi = materialIndexSort[face1]; + materialIndexSort[face1] = materialIndexSort[face2]; + materialIndexSort[face2] = vi; } private static void calculateSurfaceNormal( diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/IntComparator.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/IntComparator.java new file mode 100644 index 00000000..17833117 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/IntComparator.java @@ -0,0 +1,15 @@ +package de.bluecolored.bluemap.core.util; + +import java.util.Comparator; + +@FunctionalInterface +public interface IntComparator extends Comparator { + + int compare(int o1, int o2); + + @Override + default int compare(Integer o1, Integer o2) { + return compare(o1.intValue(), o2.intValue()); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MergeSort.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MergeSort.java new file mode 100644 index 00000000..cef063f4 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MergeSort.java @@ -0,0 +1,49 @@ +package de.bluecolored.bluemap.core.util; + +public class MergeSort { + + /* + * Adapted from: https://github.com/vigna/fastutil + */ + public static void mergeSortInt(final int[] a, final int from, final int to, IntComparator comp, int[] supp) { + int len = to - from; + + if (len < 16) { + insertionSortInt(a, from, to, comp); + return; + } + if (supp == null) supp = java.util.Arrays.copyOf(a, to); + + final int mid = (from + to) >>> 1; + mergeSortInt(supp, from, mid, comp, a); + mergeSortInt(supp, mid, to, comp, a); + + if (comp.compare(supp[mid - 1], supp[mid]) <= 0) { + System.arraycopy(supp, from, a, from, len); + return; + } + + for (int i = from, p = from, q = mid; i < to; i++) { + if (q >= to || p < mid && comp.compare(supp[p], supp[q]) <= 0) a[i] = supp[p++]; + else a[i] = supp[q++]; + } + } + + /* + * Adapted from: https://github.com/vigna/fastutil + */ + private static void insertionSortInt(final int[] a, final int from, final int to, final IntComparator comp) { + for (int i = from; ++i < to; ) { + int t = a[i], j = i; + for (int u = a[j - 1]; comp.compare(t, u) < 0; u = a[--j - 1]) { + a[j] = u; + if (from == j - 1) { + --j; + break; + } + } + a[j] = t; + } + } + +}