From 104be7804cd0c43375156f97b1fbee1fcf631f9d Mon Sep 17 00:00:00 2001 From: James Seibel Date: Tue, 5 Dec 2023 07:15:09 -0600 Subject: [PATCH] Add RenderDataPointUtil method renames and fast path for single target size --- .../util/RenderDataPointReducingList.java | 166 +++++++++++++----- .../core/util/RenderDataPointUtil.java | 28 ++- 2 files changed, 147 insertions(+), 47 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java index df48d7d05..800763083 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java @@ -277,8 +277,9 @@ public class RenderDataPointReducingList * 3: if we still fail, force the lowest segment to merge with the segment above it, * with no restrictions on alpha. * the highest alpha of the two segments takes priority though. - * 4: repeat until our size is less than or equal to the target size. - * notes: + * 4: repeat until our size is less than or equal to the target size.

+ * + * notes:
* changing the size of a node requires re-sorting that node, * but it does not require re-sorting the whole list. * additionally, because of the fact that nodes are sorted smallest to biggest, @@ -301,14 +302,11 @@ public class RenderDataPointReducingList */ public void reduce(int target) { - if (this.reduceStep1SpecialCases(target)) return; + if (this.mergeVerySmallConnectedSegments(target)) return; - while (true) - { - if (this.reduceStep1GeneralCases(target)) return; - if (this.reduceStep2(target)) return; - if (this.reduceStep3(target)) return; - } + if (this.mergeConnectedSegments(target)) return; + if (this.removeLeastImportantSegments(target)) return; + this.forceBottomToMerge(target); } @@ -662,7 +660,7 @@ public class RenderDataPointReducingList * the list's size down to less than or equal to target, * or false if more steps need to be performed. */ - private boolean reduceStep1SpecialCases(int target) + private boolean mergeVerySmallConnectedSegments(int target) { for (int specialCase = 1; specialCase <= SPECIAL_CASES; specialCase++) { @@ -690,13 +688,13 @@ public class RenderDataPointReducingList * in other words, handles all the nodes whose size * is strictly greater than {@link #SPECIAL_CASES}, * and all the nodes which are smaller, but failed - * to be merged in {@link #reduceStep1SpecialCases(int)} + * to be merged in {@link #mergeVerySmallConnectedSegments(int)} * * returns true if this step single-handedly brought * the list's size down to less than or equal to target, * or false if more steps need to be performed. */ - private boolean reduceStep1GeneralCases(int target) + private boolean mergeConnectedSegments(int target) { for (int current = this.getSmallest(); current != NULL;) { @@ -718,7 +716,7 @@ public class RenderDataPointReducingList * the list's size down to less than or equal to target, * or false if more steps need to be performed. */ - private boolean reduceStep2(int target) + private boolean removeLeastImportantSegments(int target) { for (int center = this.getSmallest(); center != NULL; ) { @@ -752,42 +750,124 @@ public class RenderDataPointReducingList * are forced to merge in order to fit the desired target, * even if they normally shouldn't merge because it would look bad.

* - * returns true if this step brought the list's - * size down to less than or equal to target, - * or false if we need to go back to step 1. + * returns after this step brings the list's + * size down to less than or equal to target. */ - private boolean reduceStep3(int target) + private void forceBottomToMerge(int target) { - if (this.getSizeWithoutAir() <= target) + for (int lowest = this.getLowest(); lowest != NULL; ) { - return true; - } - - - int lowest = this.getLowest(); - int higher = this.getHigher(lowest); - if (higher != NULL) - { - this.setMinY(higher, this.getMinY(lowest)); - this.resortSize(higher); - this.remove(lowest); - if (ASSERTS) this.checkLinks(); + if (this.getSizeWithoutAir() <= target) + { + return; + } - return false; // go back to step 1. - } - else - { - // if we reach this line, then target is 0 or negative. - this.setLowest(NULL); - this.setHighest(NULL); - this.setSmallest(NULL); - this.setBiggest(NULL); - this.setSizeWithAir(0); - this.setSizeWithoutAir(0); - return true; + int lowY = this.getMinY(lowest); + int higher = this.getHigher(lowest); + inner: + while (true) + { + if (higher == NULL) + { + //if we reach this line, then target is 0 or negative. + this.setLowest(NULL); + this.setHighest(NULL); + this.setSmallest(NULL); + this.setBiggest(NULL); + this.setSizeWithAir(0); + this.setSizeWithoutAir(0); + + if (ASSERTS) this.checkLinks(); + + return; + } + + // don't merge the lowest segment with an invisible segment. + // in other words, we don't want + // visible + // invisible + // visible + // to be replaced with + // visible + // invisible + // instead, we want to eliminate the invisible segment too, + // and set the minY of the top visible segment + // to the minY of the bottom visible segment. + if (this.isIndexVisible(higher)) + { + this.setMinY(higher, lowY); + this.resortSize(higher); + this.remove(lowest); + + if (ASSERTS) this.checkLinks(); + + lowest = this.getLowest(); + break inner; + } + else + { + this.remove(lowest); + lowest = higher; + higher = this.getHigher(higher); + //don't update lowY. + } + } } } + /** + * reduces the view to a single data point, + * whose min Y is the lowest of all data points in the provided view, + * and every other property of the returned data point + * matches those of the data point with the highest + * Y level in the provided view. + * + * @implNote this method does not allocate any objects. + */ + public static long reduceToOne(IColumnDataView view) + { + int size = view.size(); + if (size <= 0) + { + return RenderDataPointUtil.createVoidDataPoint((byte)(1)); + } + + long highest; + int lowest; + int index = 0; + //first loop: find the first visible segment. + foundVisible: + { + for (; index < size; index++) + { + long dataPoint = view.get(index); + if (isDataVisible(dataPoint)) + { + highest = dataPoint; + lowest = RenderDataPointUtil.getYMin(dataPoint); + break foundVisible; + } + } + //no visible segments, return void. + return RenderDataPointUtil.createVoidDataPoint((byte) (1)); + } + + //second loop: merge the rest of the segments. + for (; index < size; index++) + { + long dataPoint = view.get(index); + if (isDataVisible(dataPoint)) + { + int y = RenderDataPointUtil.getYMin(dataPoint); + if (y > highest) highest = dataPoint; + else if (y < lowest) lowest = y; + } + } + + return (highest & ~RenderDataPointUtil.DEPTH_SHIFTED_MASK) | ((lowest & RenderDataPointUtil.DEPTH_MASK) << RenderDataPointUtil.DEPTH_SHIFT); + } + + /** transfers the contents of this list to the provided view, in order of highest to lowest. */ public void copyTo(ColumnArrayView view) { @@ -812,7 +892,7 @@ public class RenderDataPointReducingList for (int size = view.size(); writeIndex < size; writeIndex++) { - view.set(writeIndex, 0L); + view.set(writeIndex, RenderDataPointUtil.EMPTY_DATA); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointUtil.java index 3cee54b34..77f6459c8 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointUtil.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointUtil.java @@ -276,10 +276,30 @@ public class RenderDataPointUtil */ public static void mergeMultiData(IColumnDataView sourceData, ColumnArrayView output) { - RenderDataPointReducingList list = new RenderDataPointReducingList(sourceData); - list.reduce(output.verticalSize()); - list.copyTo(output); - + int target = output.verticalSize(); + if (target <= 0) + { + // I expect this to never be the case, + // but RenderDataPointReducingList handles it sanely, + // so I might as well handle it sanely here too. + output.fill(EMPTY_DATA); + } + else if (target == 1) + { + output.set(0, RenderDataPointReducingList.reduceToOne(sourceData)); + for (int index = 1, size = output.size(); index < size; index++) + { + output.set(index, EMPTY_DATA); + } + } + else + { + RenderDataPointReducingList list = new RenderDataPointReducingList(sourceData); + list.reduce(output.verticalSize()); + list.copyTo(output); + } + + //old logic left here in case it's ever needed again. /* if (output.dataCount() != 1)