From 9460fc9b04ccb3e134a2931cdaa21d83e0b14aec Mon Sep 17 00:00:00 2001 From: James Seibel Date: Mon, 4 Dec 2023 07:46:18 -0600 Subject: [PATCH] reformat --- .../util/RenderDataPointReducingList.java | 1120 +++++++++-------- 1 file changed, 615 insertions(+), 505 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 c19f6f149..df48d7d05 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 @@ -1,3 +1,22 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020-2023 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + package com.seibel.distanthorizons.core.util; import com.google.common.annotations.VisibleForTesting; @@ -8,89 +27,98 @@ import it.unimi.dsi.fastutil.longs.LongArrays; import it.unimi.dsi.fastutil.shorts.ShortArrays; /** -a list of data points whose sole purpose is to {@link #reduce(int)} them. -each data point, henceforth referred to as a "node", is represented by 2 packed longs. -the "data" long contains the data point itself, as encoded by -{@link RenderDataPointUtil#createDataPoint(int, int, int, int, int, int, int, int, int)}. -the "links" long contains 4 packed 16-bit integers, which "point" to other nodes -in the sense that the index represented by the integer is another node in this list. -the 4 links are: bigger, smaller, higher, and lower. -all nodes are stored in 2 parallel long[]'s, namely {@link #data} and {@link #links}. - -all nodes are internally sorted in 2 different orders at the same time: -lowest-to-highest, and smallest-to-biggest. -both of these orders are important for reduction logic. -traversal in both orders is equally possible and important. - -@author Builderb0y -*/ -public class RenderDataPointReducingList { + * A list of data points whose sole purpose is to {@link #reduce(int)} them. + * Each data point, henceforth referred to as a "node", is represented by 2 packed longs. + * The "data" long contains the data point itself, as encoded by + * {@link RenderDataPointUtil#createDataPoint(int, int, int, int, int, int, int, int, int)}. + * The "links" long contains 4 packed 16-bit integers, which "point" to other nodes + * In the sense that the index represented by the integer is another node in this list. + * The 4 links are: bigger, smaller, higher, and lower. + * All nodes are stored in 2 parallel long[]'s, namely {@link #data} and {@link #links}.

+ * + * All nodes are internally sorted in 2 different orders at the same time: + * lowest-to-highest, and smallest-to-biggest. + * Both of these orders are important for reduction logic. + * Traversal in both orders is equally possible and important. + * + * @author Builderb0y + */ +public class RenderDataPointReducingList +{ /** - setting this to true will cause the list to sanity-check - its own links automatically every time it modifies itself. - this is mostly just useful for debugging. - this should be set to false in production, - because these sanity checks are slow and happen often. - */ + * Setting this to true will cause the list to sanity-check + * its own links automatically every time it modifies itself. + * This is mostly just useful for debugging. + * This should be set to false in production, + * because these sanity checks are slow and happen often. + */ private static final boolean ASSERTS = false; /** - number of special cases to use for step 1 of {@link #reduce(int)}. - 2 works well for big globe worlds. - 0 is probably better for vanilla, but vanilla has vastly fewer segments/nodes - than big globe does, so the difference in efficiency matters a lot less. - */ + * Number of special cases to use for step 1 of {@link #reduce(int)}. + * 2 works well for big globe worlds. + * 0 is probably better for vanilla, but vanilla has vastly fewer segments/nodes + * than big globe does, so the difference in efficiency matters a lot less. + */ private static final int SPECIAL_CASES = 2; - public static final int - /** the bit offset of {@link #links} where the lower link is stored. */ - LOWER_SHIFT = 0, - /** the bit offset of {@link #links} where the higher link is stored. */ - HIGHER_SHIFT = 16, - /** the bit offset of {@link #links} where the smaller link is stored. */ - SMALLER_SHIFT = 32, - /** the bit offset of {@link #links} where the bigger link is stored. */ - BIGGER_SHIFT = 48, - /** - a bit mask for extracting links from elements of {@link #links}. - all links are 16 bits in length, so this constant has the lower 16 bits set, - and all remaining bits cleared. - */ - LINK_MASK = 0xFFFF, - /** a constant to indicate that a link is non-existent. */ - NULL = LINK_MASK; - public static final long - /** the default element of {@link #data} to indicate that there is no data. */ - DEFAUlT_DATA = 0L, - /** the default element of {@link #links} to indicate that a node is not linked to any other nodes. */ - DEFAULT_LINKS = -1L; - + + /** the bit offset of {@link #links} where the lower link is stored. */ + public static final int LOWER_SHIFT = 0; + /** the bit offset of {@link #links} where the higher link is stored. */ + public static final int HIGHER_SHIFT = 16; + /** the bit offset of {@link #links} where the smaller link is stored. */ + public static final int SMALLER_SHIFT = 32; + /** the bit offset of {@link #links} where the bigger link is stored. */ + public static final int BIGGER_SHIFT = 48; /** - indexes of the nodes at the ends of this list. - access these fields through the getters, - not by the backing fields. the getters will - perform automatic short <-> int conversions. - - @implNote these fields behave as if they were unsigned, - and the getters will behave accordingly. - not that DH supports a wide enough Y range - to overflow these fields, but still. - */ + * a bit mask for extracting links from elements of {@link #links}. + * all links are 16 bits in length, so this constant has the lower 16 bits set, + * and all remaining bits cleared. + */ + public static final int LINK_MASK = 0xFFFF; + /** a constant to indicate that a link is non-existent. */ + public static final int NULL = LINK_MASK; + + /** the default element of {@link #data} to indicate that there is no data. */ + public static final long DEFAUlT_DATA = 0L; + /** the default element of {@link #links} to indicate that a node is not linked to any other nodes. */ + public static final long DEFAULT_LINKS = -1L; + + /** + * indexes of the nodes at the ends of this list. + * access these fields through the getters, + * not by the backing fields. the getters will + * perform automatic short <-> int conversions. + * + * @implNote these fields behave as if they were unsigned, + * and the getters will behave accordingly. + * not that DH supports a wide enough Y range + * to overflow these fields, but still. + */ private short lowest, highest, smallest, biggest; private short sizeWithAir, sizeWithoutAir; private final long[] links, data; /** - a temporary array to be used for sorting nodes. - the array is first populated such that every index - up to our current size represents a valid index. - then this array is sorted. - finally, the nodes are re-linked according - to the order of elements in this array. - */ + * a temporary array to be used for sorting nodes. + * the array is first populated such that every index + * up to our current size represents a valid index. + * then this array is sorted. + * finally, the nodes are re-linked according + * to the order of elements in this array. + */ private final short[] sortingArray; - - public RenderDataPointReducingList(IColumnDataView view) { + + + + //=============// + // constructor // + //=============// + + public RenderDataPointReducingList(IColumnDataView view) + { int size = view.size(); - if (size == 0) { + if (size == 0) + { this.setLowest(NULL); this.setHighest(NULL); this.setSmallest(NULL); @@ -99,39 +127,45 @@ public class RenderDataPointReducingList { this.data = LongArrays.EMPTY_ARRAY; this.sortingArray = ShortArrays.EMPTY_ARRAY; if (ASSERTS) this.checkLinks(); + return; } - //allocate an array big enough to hold 2 * size - 1 nodes. - //this is the number of nodes we would have if none - //of the nodes in the provided view are touching, - //and we need to add air nodes between all of them. - //we will use this array for sorting the nodes, - //first by lowest-to-highest, then by smallest-to-biggest. + + // Allocate an array big enough to hold (2 * size - 1) nodes. + // This is the number of nodes we would have if none + // of the nodes in the provided view are touching, + // and we need to add air nodes between all of them. + // We will use this array for sorting the nodes, + // first by lowest-to-highest, then by smallest-to-biggest. int arrayCapacity = (size << 1) - 1; this.sortingArray = new short[arrayCapacity]; this.links = new long[arrayCapacity]; java.util.Arrays.fill(this.links, DEFAULT_LINKS); this.data = new long[arrayCapacity]; int sizeWithoutAir = 0; - for (int index = 0; index < size; index++) { + for (int index = 0; index < size; index++) + { long packedData = view.get(index); - //first "pass" (if you can call it that) skips nodes with 0 height, and nodes that aren't visible. - //air nodes will be inserted *after* the nodes have been sorted by Y level. - if (isDataVisible(packedData) && RenderDataPointUtil.getYMin(packedData) < RenderDataPointUtil.getYMax(packedData)) { + // first "pass" (if you can call it that) skips nodes with 0 height, and nodes that aren't visible. + // air nodes will be inserted *after* the nodes have been sorted by Y level. + if (isDataVisible(packedData) && RenderDataPointUtil.getYMin(packedData) < RenderDataPointUtil.getYMax(packedData)) + { this.setData(sizeWithoutAir, packedData); this.setSortingIndex(sizeWithoutAir, sizeWithoutAir); sizeWithoutAir++; } } - //check if all segments to merge are air or otherwise invisible (barriers). - //if they are, then this list can stay empty. - if (sizeWithoutAir == 0) { + // Check if all segments to merge are air or otherwise invisible (barriers). + // If they are, then this list can stay empty. + if (sizeWithoutAir == 0) + { this.setLowest(NULL); this.setHighest(NULL); this.setSmallest(NULL); this.setBiggest(NULL); if (ASSERTS) this.checkLinks(); + return; } @@ -139,18 +173,24 @@ public class RenderDataPointReducingList { this.sortByPosition(sizeWithoutAir); //next pass: link the nodes together, and insert air nodes as necessary. int sizeWithAir = sizeWithoutAir; - for (int sortingIndex = 1; sortingIndex < sizeWithoutAir; sortingIndex++) { + for (int sortingIndex = 1; sortingIndex < sizeWithoutAir; sortingIndex++) + { int lowerIndex = this.getSortingIndex(sortingIndex - 1); int higherIndex = this.getSortingIndex(sortingIndex); long lowerData = this.getData(lowerIndex); long higherData = this.getData(higherIndex); int lowerMaxY = RenderDataPointUtil.getYMax(lowerData); int higherMinY = RenderDataPointUtil.getYMin(higherData); - if (lowerMaxY == higherMinY) { //the two nodes touch. + + if (lowerMaxY == higherMinY) + { + //the two nodes touch. this.setHigher(lowerIndex, higherIndex); this.setLower(higherIndex, lowerIndex); } - else if (lowerMaxY < higherMinY) { //the two nodes do not touch. + else if (lowerMaxY < higherMinY) + { + //the two nodes do not touch. this.setData( sizeWithAir, RenderDataPointUtil.createDataPoint( @@ -165,6 +205,7 @@ public class RenderDataPointReducingList { RenderDataPointUtil.getGenerationMode(higherData) ) ); + this.setSortingIndex(sizeWithAir, sizeWithAir); this.setLower(higherIndex, sizeWithAir); this.setHigher(lowerIndex, sizeWithAir); @@ -172,21 +213,25 @@ public class RenderDataPointReducingList { this.setHigher(sizeWithAir, higherIndex); sizeWithAir++; } - else { //the two nodes overlap. + else + { + // the two nodes overlap. throw new IllegalArgumentException(RenderDataPointUtil.toString(lowerData) + " overlaps with " + RenderDataPointUtil.toString(higherData)); } } this.lowest = this.sortingArray[0]; this.highest = this.sortingArray[sizeWithoutAir - 1]; - //now sort by size. + // now sort by size. this.sortBySize(sizeWithAir); - for (int sortingIndex = 1; sortingIndex < sizeWithAir; sortingIndex++) { + for (int sortingIndex = 1; sortingIndex < sizeWithAir; sortingIndex++) + { int smallerIndex = this.getSortingIndex(sortingIndex - 1); int biggerIndex = this.getSortingIndex(sortingIndex); this.setBigger(smallerIndex, biggerIndex); this.setSmaller(biggerIndex, smallerIndex); } + this.smallest = this.sortingArray[0]; this.biggest = this.sortingArray[sizeWithAir - 1]; @@ -195,39 +240,120 @@ public class RenderDataPointReducingList { if (ASSERTS) this.checkLinks(); } - - //////////////////////////////// operations //////////////////////////////// - + + + + //========// + // reduce // + //========// + /** - verifies that this list is in the "correct" state, - and throws an {@link AssertFailureException} if it isn't. - */ + * merges and/or eliminates nodes until our {@link #sizeWithoutAir} + * is less than or equal to the provided target size. + * this method assumes that the list is already sorted by size. + * if it is not sorted, you should call {@link #sortBySizeAndReLink()} first. + * note also that the list is sorted in its constructor, + * so if this is a new, unmodified list, then it is safe to call this method.

+ * + * algorithm: + * 1: try to merge the smallest segment with the segment above or below it. + * this will only succeed if the adjacent node has the same alpha as it. + * 1a: if there is only one adjacent node which matches this criteria, + * we will merge with that node.

+ * + * 1b: if both adjacent nodes match this criteria, + * attempt to merge with the smaller one. + * 1b1: if both adjacent nodes are the same height, + * merge with the higher one.

+ * + * 1c: if there are no adjacent nodes which match this criteria, + * repeat step 1 with the next smallest segment instead. + * continue trying bigger and bigger segments until we either: + * * have a success, or + * * reach the end of this list. + * 2: if we reach the end of the list before having a success, try again, + * but this time, we are allowed to erase a segment entirely without merging it + * if there are equal-alpha'd segments above and below it. + * 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: + * 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, + * when a node is expanded, its new size will be + * strictly less than or equal to twice its old size. + * the significance of this is that in practice, + * nodes should not need to be moved very far to be re-sorted.

+ * + * special case: there are a lot of segments of length 1 in big globe worlds. + * these will genuinely have a long way to move on re-sort. + * so, they are handled in a separate loop.

+ * + * after step 1 is completed, step 2 can't change the + * list in a way which would give step 1 more work to do, + * so step 2 is repeated as many times as necessary, + * without jumping back to the start. + * step 3 however can change the list in a way which gives previous + * steps more work to do, so after step 3 merges something, + * we jump back to step 1 and start over. + */ + public void reduce(int target) + { + if (this.reduceStep1SpecialCases(target)) return; + + while (true) + { + if (this.reduceStep1GeneralCases(target)) return; + if (this.reduceStep2(target)) return; + if (this.reduceStep3(target)) return; + } + } + + + + //======================// + // reduction operations // + //======================// + + /** + * verifies that this list is in the "correct" state, + * and throws an {@link AssertFailureException} if it isn't. + */ @VisibleForTesting - public void checkLinks() { + public void checkLinks() + { LodUtil.assertTrue(this.getSizeWithAir() >= 0, "size with air < 0"); LodUtil.assertTrue(this.getSizeWithoutAir() >= 0, "size without air < 0"); LodUtil.assertTrue(this.getSizeWithoutAir() <= this.getSizeWithAir(), "more segments without air than with air"); - if (this.getSizeWithAir() == 0) { + + if (this.getSizeWithAir() == 0) + { LodUtil.assertTrue(this.getSmallest() == NULL, "size is 0, but we have a smallest node"); LodUtil.assertTrue(this.getBiggest() == NULL, "size is 0, but we have a biggest node"); LodUtil.assertTrue(this.getLowest() == NULL, "size is 0, but we have a lowest node"); LodUtil.assertTrue(this.getHighest() == NULL, "size is 0, but we have a highest node"); } - else { + else + { int sizeWithAir = 0, sizeWithoutAir = 0; - for (int index = this.getSmallest(); index != NULL; index = this.getBigger(index)) { + for (int index = this.getSmallest(); index != NULL; index = this.getBigger(index)) + { int smaller = this.getSmaller(index); int bigger = this.getBigger(index); LodUtil.assertTrue((smaller != NULL ? this.getBigger(smaller) : this.getSmallest()) == index, "one-way link"); LodUtil.assertTrue((bigger != NULL ? this.getSmaller(bigger) : this.getBiggest()) == index, "one-way link"); LodUtil.assertTrue(smaller == NULL || this.getSize(index) >= this.getSize(smaller), "node is not sorted by size"); sizeWithAir++; + if (this.isIndexVisible(index)) sizeWithoutAir++; } LodUtil.assertTrue(sizeWithAir == this.getSizeWithAir() && sizeWithoutAir == this.getSizeWithoutAir(), "node count does not match size"); sizeWithAir = sizeWithoutAir = 0; - for (int index = this.getLowest(); index != NULL; index = this.getHigher(index)) { + for (int index = this.getLowest(); index != NULL; index = this.getHigher(index)) + { int lower = this.getLower(index); int higher = this.getHigher(index); LodUtil.assertTrue((lower != NULL ? this.getHigher(lower) : this.getLowest()) == index, "one-way link"); @@ -235,6 +361,7 @@ public class RenderDataPointReducingList { LodUtil.assertTrue(this.getMaxY(index) > this.getMinY(index), "node has inverted Y levels"); LodUtil.assertTrue(lower == NULL || this.getMinY(index) == this.getMaxY(lower), "node does not touch its lower neighbor"); sizeWithAir++; + if (this.isIndexVisible(index)) sizeWithoutAir++; } LodUtil.assertTrue(sizeWithAir == this.getSizeWithAir() && sizeWithoutAir == this.getSizeWithoutAir(), "node count does not match size"); @@ -242,331 +369,415 @@ public class RenderDataPointReducingList { } /** removes the node at the given index from this list. */ - public void remove(int index) { - int - lower = this.getLower (index), - higher = this.getHigher (index), - smaller= this.getSmaller(index), - bigger = this.getBigger (index), - alpha = this.getAlpha (index); + public void remove(int index) + { + int lower = this.getLower (index); + int higher = this.getHigher (index); + int smaller= this.getSmaller(index); + int bigger = this.getBigger (index); + int alpha = this.getAlpha (index); + + if (lower != NULL) this.setHigher(lower, higher); else this.setLowest(higher); + if (higher != NULL) this.setLower(higher, lower); else this.setHighest(lower); + if (smaller != NULL) this.setBigger(smaller, bigger); else this.setSmallest(bigger); + if (bigger != NULL) this.setSmaller(bigger, smaller); else this.setBiggest(smaller); + this.setData(index, DEFAUlT_DATA); this.links[index] = DEFAULT_LINKS; this.sizeWithAir--; + if (isAlphaVisible(alpha)) this.sizeWithoutAir--; } - + /** - refreshes the smallest-to-biggest order of this list. - as a reminder, the list is internally sorted from smallest-to-biggest - and lowest-to-highest at the same time. part of reduction logic - can invalidate the smallest-to-biggest order, so this method re-computes it. - this method does not touch the lowest-to-highest order of the list. - - this method requires that all nodes are already sorted from - lowest-to-highest, so it is not applicable to use this method in - the constructor before the lowest-to-highest order is initialized. - */ + * refreshes the smallest-to-biggest order of this list. + * as a reminder, the list is internally sorted from smallest-to-biggest + * and lowest-to-highest at the same time. part of reduction logic + * can invalidate the smallest-to-biggest order, so this method re-computes it. + * this method does not touch the lowest-to-highest order of the list.

+ * + * this method requires that all nodes are already sorted from + * lowest-to-highest, so it is not applicable to use this method in + * the constructor before the lowest-to-highest order is initialized. + */ @VisibleForTesting - public void sortBySizeAndReLink() { - if (this.getSizeWithAir() <= 1) return; + public void sortBySizeAndReLink() + { + if (this.getSizeWithAir() <= 1) + { + return; + } + + long[] datas = this.data; int writeIndex = 0; - for (int readIndex = this.getLowest(); readIndex != NULL; readIndex = this.getHigher(readIndex)) { - if (datas[readIndex] != DEFAUlT_DATA) { + for (int readIndex = this.getLowest(); readIndex != NULL; readIndex = this.getHigher(readIndex)) + { + if (datas[readIndex] != DEFAUlT_DATA) + { this.setSortingIndex(writeIndex++, readIndex); } } + this.sortBySize(writeIndex); - for (int index = 1; index < writeIndex; index++) { + for (int index = 1; index < writeIndex; index++) + { int smaller = this.getSortingIndex(index - 1); int bigger = this.getSortingIndex(index); this.setSmaller(bigger, smaller); this.setBigger(smaller, bigger); } + this.smallest = this.sortingArray[0]; this.biggest = this.sortingArray[writeIndex - 1]; this.setSmaller(this.getSmallest(), NULL); this.setBigger(this.getBiggest(), NULL); } - + /** - sorts our {@link #sortingArray} in order of smallest-to-biggest, - but does NOT update our links accordingly. - */ + * sorts our {@link #sortingArray} in order of smallest-to-biggest, + * but does NOT update our links accordingly. + */ @VisibleForTesting - public void sortBySize(int size) { + public void sortBySize(int size) + { short[] array = this.sortingArray; it.unimi.dsi.fastutil.Arrays.quickSort( 0, size, - (int index1, int index2) -> { + // comparator + (int index1, int index2) -> + { return Integer.compare( this.getSize(this.getSortingIndex(index1)), this.getSize(this.getSortingIndex(index2)) ); }, - (int index1, int index2) -> { + // swapper + (int index1, int index2) -> + { ShortArrays.swap(array, index1, index2); } ); } - + /** - sorts our {@link #sortingArray} in order of lowest-to-highest, - but does NOT update our links accordingly. - */ + * sorts our {@link #sortingArray} in order of lowest-to-highest, + * but does NOT update our links accordingly. + */ @VisibleForTesting - public void sortByPosition(int size) { + public void sortByPosition(int size) + { short[] array = this.sortingArray; it.unimi.dsi.fastutil.Arrays.quickSort( 0, size, - (int index1, int index2) -> { + // comparator + (int index1, int index2) -> + { return Integer.compare( this.getMinY(this.getSortingIndex(index1)), this.getMinY(this.getSortingIndex(index2)) ); }, - (int index1, int index2) -> { + // swapper + (int index1, int index2) -> + { ShortArrays.swap(array, index1, index2); } ); } - + /** - moves the smaller node to the correct position in the list, - under the assumption that all other nodes are already sorted. - this method should be called when the smaller node is - merged with another node, causing it to become bigger. - - important: this method ONLY handles the case where a node - is made bigger. it does NOT handle the case where a node - is made smaller. if the node is made smaller, it will be - left in its current position, even if that position is wrong. - */ - public void resortSize(int smaller) { + * moves the smaller node to the correct position in the list, + * under the assumption that all other nodes are already sorted. + * this method should be called when the smaller node is + * merged with another node, causing it to become bigger.

+ * + * important: this method ONLY handles the case where a node + * is made bigger. it does NOT handle the case where a node + * is made smaller. if the node is made smaller, it will be + * left in its current position, even if that position is wrong. + */ + public void resortSize(int smaller) + { int bigger = this.getBigger(smaller); - - //check if the node needs to be moved at all, - //and return if it doesn't. - if (bigger == NULL || this.getSize(smaller) <= this.getSize(bigger)) return; - - //first remove smaller from before bigger. + + // check if the node needs to be moved at all, + // and return if it doesn't. + if (bigger == NULL || this.getSize(smaller) <= this.getSize(bigger)) + { + return; + } + + // first remove smaller from before bigger. int smallest = this.getSmaller(smaller); if (smallest != NULL) this.setBigger(smallest, bigger); else this.setSmallest(bigger); this.setSmaller(bigger, smallest); - - //next, find the position to re-insert the node. + + // next, find the position to re-insert the node. do bigger = this.getBigger(bigger); while (bigger != NULL && this.getSize(smaller) > this.getSize(bigger)); - - //lastly, re-insert the node where it belongs. + + // lastly, re-insert the node where it belongs. this.setSmaller(smaller, bigger != NULL ? this.getSmaller(bigger) : this.getBiggest()); this.setBigger(smaller, bigger); + if (bigger != NULL) this.setSmaller(bigger, smaller); else this.setBiggest(smaller); + smallest = this.getSmaller(smaller); if (smallest != NULL) this.setBigger(smallest, smaller); else this.setSmallest(smaller); } - + /** - shared logic for merging segments in step 1 documented in {@link #reduce(int)}. - - returns the index of the next node to be used for iteration. - - @param fastPath if true, we are in the "fast path" for removing - segments whose size is less than or equal to {@link #SPECIAL_CASES}. - this fast path functions somewhat differently from the normal path, - the important things to note for this method are: - - the fast path does not re-sort nodes when their size changes. - this leaves the list in an invalid state, and it is up to the caller to re-sort - the list via {@link #sortBySizeAndReLink()} after the fast path is done. - - at the time of writing this, the fast path iterates in reverse order. - as such, when fastPath is set to true, this method will return - current's smaller neighbor, when fastPath is set to false, - this method will return current's bigger neighbor instead. - */ - private int tryMergeStep1(int current, boolean fastPath) { - int - result = fastPath ? this.getSmaller(current) : this.getBigger(current), - higher = this.getHigher(current), - lower = this.getLower(current), - toExtendDownwards, - toRemove; - if (higher != NULL && this.getAlpha(higher) == this.getAlpha(current)) { - if (lower != NULL && this.getAlpha(lower) == this.getAlpha(current)) { - if (this.getSize(higher) <= this.getSize(lower)) { + * shared logic for merging segments in step 1 documented in {@link #reduce(int)}. + * + * returns the index of the next node to be used for iteration. + * + * @param fastPath if true, we are in the "fast path" for removing + * segments whose size is less than or equal to {@link #SPECIAL_CASES}. + * this fast path functions somewhat differently from the normal path, + * the important things to note for this method are: + * + * the fast path does not re-sort nodes when their size changes. + * this leaves the list in an invalid state, and it is up to the caller to re-sort + * the list via {@link #sortBySizeAndReLink()} after the fast path is done. + * + * at the time of writing this, the fast path iterates in reverse order. + * as such, when fastPath is set to true, this method will return + * current's smaller neighbor, when fastPath is set to false, + * this method will return current's bigger neighbor instead. + */ + private int tryMergeStep1(int current, boolean fastPath) + { + int result = fastPath ? this.getSmaller(current) : this.getBigger(current); + int higher = this.getHigher(current); + int lower = this.getLower(current); + int toExtendDownwards; + int toRemove; + + + if (higher != NULL && this.getAlpha(higher) == this.getAlpha(current)) + { + if (lower != NULL && this.getAlpha(lower) == this.getAlpha(current)) + { + if (this.getSize(higher) <= this.getSize(lower)) + { toExtendDownwards = higher; toRemove = current; } - else { + else + { toExtendDownwards = current; toRemove = lower; } } - else { + else + { toExtendDownwards = higher; toRemove = current; } } - else { - if (lower != NULL && this.getAlpha(lower) == this.getAlpha(current)) { + else + { + if (lower != NULL && this.getAlpha(lower) == this.getAlpha(current)) + { toExtendDownwards = current; toRemove = lower; } - else { + else + { return result; } } - //if we're about to remove the next node for iteration, - //then we need to continue iterating at the node after that. - //result will only be returned if fastPath is true, - //so the node after that is always the smaller one. that's why I don't need to do - //if (result == toRemove) result = fastPath ? this.getSmaller(result) : this.getBigger(result); + + // if we're about to remove the next node for iteration, + // then we need to continue iterating at the node after that. + // result will only be returned if fastPath is true, + // so the node after that is always the smaller one. that's why I don't need to do + // if (result == toRemove) result = fastPath ? this.getSmaller(result) : this.getBigger(result); if (result == toRemove) result = this.getSmaller(result); + this.setMinY(toExtendDownwards, this.getMinY(toRemove)); if (!fastPath) this.resortSize(toExtendDownwards); + this.remove(toRemove); - //if we're NOT on the fast path, and we reach this line, - //then we have just modified the list in a way which may - //invalidate assumptions made by the step 1 loop. - //so, return smallest to signal that the loop should start over. - //starting over is not usually a big deal, - //because small nodes are usually merged quite quickly. - //in my testing, I didn't see the step 1 loop run more - //than twice as many times as the starting list size. + + // if we're NOT on the fast path, and we reach this line, + // then we have just modified the list in a way which may + // invalidate assumptions made by the step 1 loop. + // so, return smallest to signal that the loop should start over. + // starting over is not usually a big deal, + // because small nodes are usually merged quite quickly. + // in my testing, I didn't see the step 1 loop run more + // than twice as many times as the starting list size. return fastPath ? result : this.getSmallest(); } - + /** - returns the largest node whose height is strictly less than the provided size, - or null if all contained nodes are greater than or equal to the provided size. - - special cases: - if the list is empty, then null is returned, - because the loop will not run and biggest will be null. - - if all nodes are less tall than size, then the largest node is returned, - because the loop will run for all nodes, but will not return any of them, - so the fallback path of returning the biggest node is used. - - if all nodes are at least as tall as size, then null is returned, - because the loop will immediately return the - smallest node's smaller neighbor, which is null. - */ - private int lowerNode(int size) { - for (int node = this.getSmallest(); node != NULL; node = this.getBigger(node)) { - if (this.getSize(node) >= size) return this.getSmaller(node); + * returns the largest node whose height is strictly less than the provided size, + * or null if all contained nodes are greater than or equal to the provided size. + * + * special cases: + * if the list is empty, then null is returned, + * because the loop will not run and biggest will be null. + * + * if all nodes are less tall than size, then the largest node is returned, + * because the loop will run for all nodes, but will not return any of them, + * so the fallback path of returning the biggest node is used. + * + * if all nodes are at least as tall as size, then null is returned, + * because the loop will immediately return the + * smallest node's smaller neighbor, which is null. + */ + private int lowerNode(int size) + { + for (int node = this.getSmallest(); node != NULL; node = this.getBigger(node)) + { + if (this.getSize(node) >= size) + { + return this.getSmaller(node); + } } return this.getBiggest(); } - + /** - handles special cases for step 1 of {@link #reduce(int)}. - in other words, handles all the nodes whose size - is less than or equal to {@link #SPECIAL_CASES}. - - 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 reduceStep1SpecialCases(int target) { - for (int specialCase = 1; specialCase <= SPECIAL_CASES; specialCase++) { - for (int current = this.lowerNode(specialCase + 1); current != NULL;) { - if (this.getSizeWithoutAir() <= target) { + * handles special cases for step 1 of {@link #reduce(int)}. + * in other words, handles all the nodes whose size + * is less than or equal to {@link #SPECIAL_CASES}.

+ * + * 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 reduceStep1SpecialCases(int target) + { + for (int specialCase = 1; specialCase <= SPECIAL_CASES; specialCase++) + { + for (int current = this.lowerNode(specialCase + 1); current != NULL; ) + { + if (this.getSizeWithoutAir() <= target) + { this.sortBySizeAndReLink(); if (ASSERTS) this.checkLinks(); + return true; } current = this.tryMergeStep1(current, true); } this.sortBySizeAndReLink(); + if (ASSERTS) this.checkLinks(); } + return false; } - + /** - handles the general case for step 1 of {@link #reduce(int)}. - 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)} - - 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) { - for (int current = this.getSmallest(); current != NULL;) { - if (this.getSizeWithoutAir() <= target) return true; + * handles the general case for step 1 of {@link #reduce(int)}. + * 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)} + * + * 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) + { + for (int current = this.getSmallest(); current != NULL;) + { + if (this.getSizeWithoutAir() <= target) + { + return true; + } + current = this.tryMergeStep1(current, false); if (ASSERTS) this.checkLinks(); } return false; } - + /** - handles step 2 of {@link #reduce(int)}, where nodes are allowed to be erased. - - 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 reduceStep2(int target) { - for (int center = this.getSmallest(); center != NULL;) { - if (this.getSizeWithoutAir() <= target) return true; - int lower = this.getLower (center); + * handles step 2 of {@link #reduce(int)}, where nodes are allowed to be erased.

+ * + * 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 reduceStep2(int target) + { + for (int center = this.getSmallest(); center != NULL; ) + { + if (this.getSizeWithoutAir() <= target) + { + return true; + } + + int lower = this.getLower(center); int higher = this.getHigher(center); - if (lower != NULL && higher != NULL && this.getAlpha(lower) == this.getAlpha(higher)) { + if (lower != NULL && higher != NULL && this.getAlpha(lower) == this.getAlpha(higher)) + { this.setMinY(higher, this.getMinY(lower)); this.resortSize(higher); this.remove(lower); this.remove(center); if (ASSERTS) this.checkLinks(); + center = this.getSmallest(); } - else { + else + { center = this.getBigger(center); } } return false; } - + /** - handles step 3 of {@link #reduce(int)}, where nodes - 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. - */ - private boolean reduceStep3(int target) { - if (this.getSizeWithoutAir() <= target) return true; + * handles step 3 of {@link #reduce(int)}, where nodes + * 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. + */ + private boolean reduceStep3(int target) + { + if (this.getSizeWithoutAir() <= target) + { + return true; + } + + int lowest = this.getLowest(); int higher = this.getHigher(lowest); - if (higher != NULL) { + if (higher != NULL) + { this.setMinY(higher, this.getMinY(lowest)); this.resortSize(higher); this.remove(lowest); if (ASSERTS) this.checkLinks(); - return false; //go back to step 1. + + return false; // go back to step 1. } - else { - //if we reach this line, then target is 0 or negative. + else + { + // if we reach this line, then target is 0 or negative. this.setLowest(NULL); this.setHighest(NULL); this.setSmallest(NULL); @@ -576,284 +787,183 @@ public class RenderDataPointReducingList { return true; } } - - /** - merges and/or eliminates nodes until our {@link #sizeWithoutAir} - is less than or equal to the provided target size. - this method assumes that the list is already sorted by size. - if it is not sorted, you should call {@link #sortBySizeAndReLink()} first. - note also that the list is sorted in its constructor, - so if this is a new, unmodified list, then it is safe to call this method. - - algorithm: - 1: try to merge the smallest segment with the segment above or below it. - this will only succeed if the adjacent node has the same alpha as it. - 1a: if there is only one adjacent node which matches this criteria, - we will merge with that node. - - 1b: if both adjacent nodes match this criteria, - attempt to merge with the smaller one. - 1b1: if both adjacent nodes are the same height, - merge with the higher one. - - 1c: if there are no adjacent nodes which match this criteria, - repeat step 1 with the next smallest segment instead. - continue trying bigger and bigger segments until we either: - * have a success, or - * reach the end of this list. - 2: if we reach the end of the list before having a success, try again, - but this time, we are allowed to erase a segment entirely without merging it - if there are equal-alpha'd segments above and below it. - 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: - 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, - when a node is expanded, its new size will be - strictly less than or equal to twice its old size. - the significance of this is that in practice, - nodes should not need to be moved very far to be re-sorted. - - special case: there are a lot of segments of length 1 in big globe worlds. - these will genuinely have a long way to move on re-sort. - so, they are handled in a separate loop. - - after step 1 is completed, step 2 can't change the - list in a way which would give step 1 more work to do, - so step 2 is repeated as many times as necessary, - without jumping back to the start. - step 3 however can change the list in a way which gives previous - steps more work to do, so after step 3 merges something, - we jump back to step 1 and start over. - */ - public void reduce(int target) { - if (this.reduceStep1SpecialCases(target)) return; - - while (true) { - if (this.reduceStep1GeneralCases(target)) return; - if (this.reduceStep2(target)) return; - if (this.reduceStep3(target)) return; - } - } - + /** transfers the contents of this list to the provided view, in order of highest to lowest. */ - public void copyTo(ColumnArrayView view) { - //reminder: DH explodes horribly when I copy the nodes - //from lowest to highest instead of highest to lowest. + public void copyTo(ColumnArrayView view) + { + // reminder: DH explodes horribly when I copy the nodes + // from lowest to highest instead of highest to lowest. int writeIndex = 0; - for (int node = this.getHighest(); node != NULL; node = this.getLower(node)) { - if (this.isIndexVisible(node)) { + for (int node = this.getHighest(); node != NULL; node = this.getLower(node)) + { + if (this.isIndexVisible(node)) + { view.set(writeIndex++, this.getData(node)); } } - //this list could be empty if all the segments for merging are invisible, - //but we must ensure that the view is non-empty. - //so, if we didn't set any data points, add a void data point. - if (writeIndex == 0) { - view.set(writeIndex++, RenderDataPointUtil.createVoidDataPoint((byte)(1))); + + // this list could be empty if all the segments for merging are invisible, + // but we must ensure that the view is non-empty. + // so, if we didn't set any data points, add a void data point. + if (writeIndex == 0) + { + view.set(writeIndex++, RenderDataPointUtil.createVoidDataPoint((byte) (1))); } - for (int size = view.size(); writeIndex < size; writeIndex++) { + + for (int size = view.size(); writeIndex < size; writeIndex++) + { view.set(writeIndex, 0L); } } - - //////////////////////////////// getters //////////////////////////////// - - public int getSmallest() { - return Short.toUnsignedInt(this.smallest); - } - - public int getBiggest() { - return Short.toUnsignedInt(this.biggest); - } - - public int getLowest() { - return Short.toUnsignedInt(this.lowest); - } - - public int getHighest() { - return Short.toUnsignedInt(this.highest); - } - - public int getSizeWithAir() { - return Short.toUnsignedInt(this.sizeWithAir); - } - - public int getSizeWithoutAir() { - return Short.toUnsignedInt(this.sizeWithoutAir); - } - - public int getSortingIndex(int index) { - return Short.toUnsignedInt(this.sortingArray[index]); - } - - public int getLower(int index) { - return ((int)(this.links[index] >>> LOWER_SHIFT)) & LINK_MASK; - } - - public int getHigher(int index) { - return ((int)(this.links[index] >>> HIGHER_SHIFT)) & LINK_MASK; - } - - public int getSmaller(int index) { - return ((int)(this.links[index] >>> SMALLER_SHIFT)) & LINK_MASK; - } - - public int getBigger(int index) { - return ((int)(this.links[index] >>> BIGGER_SHIFT)) & LINK_MASK; - } - - public long getData(int index) { - return this.data[index]; - } - - public int getMinY(int index) { - return RenderDataPointUtil.getYMin(this.getData(index)); - } - - public int getMaxY(int index) { - return RenderDataPointUtil.getYMax(this.getData(index)); - } - - public int getSize(int index) { + + + + //=========// + // getters // + //=========// + + public int getSmallest() { return Short.toUnsignedInt(this.smallest); } + public int getBiggest() { return Short.toUnsignedInt(this.biggest); } + + public int getLowest() { return Short.toUnsignedInt(this.lowest); } + public int getHighest() { return Short.toUnsignedInt(this.highest); } + + public int getSizeWithAir() { return Short.toUnsignedInt(this.sizeWithAir); } + public int getSizeWithoutAir() { return Short.toUnsignedInt(this.sizeWithoutAir); } + + public int getSortingIndex(int index) { return Short.toUnsignedInt(this.sortingArray[index]); } + + public int getLower(int index) { return ((int) (this.links[index] >>> LOWER_SHIFT)) & LINK_MASK; } + public int getHigher(int index) { return ((int) (this.links[index] >>> HIGHER_SHIFT)) & LINK_MASK; } + + public int getSmaller(int index) { return ((int) (this.links[index] >>> SMALLER_SHIFT)) & LINK_MASK; } + public int getBigger(int index) { return ((int) (this.links[index] >>> BIGGER_SHIFT)) & LINK_MASK; } + + public long getData(int index) { return this.data[index]; } + + public int getMinY(int index) { return RenderDataPointUtil.getYMin(this.getData(index)); } + public int getMaxY(int index) { return RenderDataPointUtil.getYMax(this.getData(index)); } + + public int getSize(int index) + { long data = this.getData(index); return RenderDataPointUtil.getYMax(data) - RenderDataPointUtil.getYMin(data); } + + public int getRed(int index) { return RenderDataPointUtil.getRed(this.getData(index)); } + public int getGreen(int index) { return RenderDataPointUtil.getGreen(this.getData(index)); } + public int getBlue(int index) { return RenderDataPointUtil.getBlue(this.getData(index)); } + public int getAlpha(int index) { return RenderDataPointUtil.getAlpha(this.getData(index)); } + + public int getBlockLight(int index) { return RenderDataPointUtil.getLightBlock(this.getData(index)); } + public int getSkyLight(int index) { return RenderDataPointUtil.getLightSky(this.getData(index)); } + + + + //=========// + // setters // + //=========// + + public void setSmallest(int smallest) { this.smallest = (short)(smallest); } + public void setBiggest(int biggest) { this.biggest = (short)(biggest); } + + public void setLowest(int lowest) { this.lowest = (short)(lowest); } + public void setHighest(int highest) { this.highest = (short)(highest); } + + public void setSizeWithAir(int sizeWithAir) { this.sizeWithAir = (short)(sizeWithAir); } + public void setSizeWithoutAir(int sizeWithoutAir) { this.sizeWithoutAir = (short)(sizeWithoutAir); } - public int getRed(int index) { - return RenderDataPointUtil.getRed(this.getData(index)); - } + public void setSortingIndex(int index, int to) { this.sortingArray[index] = (short)(to); } - public int getGreen(int index) { - return RenderDataPointUtil.getGreen(this.getData(index)); - } - - public int getBlue(int index) { - return RenderDataPointUtil.getBlue(this.getData(index)); - } - - public int getAlpha(int index) { - return RenderDataPointUtil.getAlpha(this.getData(index)); - } - - public int getBlockLight(int index) { - return RenderDataPointUtil.getLightBlock(this.getData(index)); - } - - public int getSkyLight(int index) { - return RenderDataPointUtil.getLightSky(this.getData(index)); - } - - //////////////////////////////// setters //////////////////////////////// - - public void setSmallest(int smallest) { - this.smallest = (short)(smallest); - } - - public void setBiggest(int biggest) { - this.biggest = (short)(biggest); - } - - public void setLowest(int lowest) { - this.lowest = (short)(lowest); - } - - public void setHighest(int highest) { - this.highest = (short)(highest); - } - - public void setSizeWithAir(int sizeWithAir) { - this.sizeWithAir = (short)(sizeWithAir); - } - - public void setSizeWithoutAir(int sizeWithoutAir) { - this.sizeWithoutAir = (short)(sizeWithoutAir); - } - - public void setSortingIndex(int index, int to) { - this.sortingArray[index] = (short)(to); - } - - public void setLower(int index, int lowerIndex) { + public void setLower(int index, int lowerIndex) + { this.links[index] = (this.links[index] & ~(((long)(LINK_MASK)) << LOWER_SHIFT)) | (((long)(lowerIndex & LINK_MASK)) << LOWER_SHIFT); } - - public void setHigher(int index, int higherIndex) { + public void setHigher(int index, int higherIndex) + { this.links[index] = (this.links[index] & ~(((long)(LINK_MASK)) << HIGHER_SHIFT)) | (((long)(higherIndex & LINK_MASK)) << HIGHER_SHIFT); } - public void setSmaller(int index, int smallerIndex) { + public void setSmaller(int index, int smallerIndex) + { this.links[index] = (this.links[index] & ~(((long)(LINK_MASK)) << SMALLER_SHIFT)) | (((long)(smallerIndex & LINK_MASK)) << SMALLER_SHIFT); } - - public void setBigger(int index, int biggerIndex) { + public void setBigger(int index, int biggerIndex) + { this.links[index] = (this.links[index] & ~(((long)(LINK_MASK)) << BIGGER_SHIFT)) | (((long)(biggerIndex & LINK_MASK)) << BIGGER_SHIFT); } - public void setData(int index, long data) { - this.data[index] = data; - } + public void setData(int index, long data) { this.data[index] = data; } - public void setMinY(int index, int minY) { + public void setMinY(int index, int minY) + { this.data[index] = (this.data[index] & ~RenderDataPointUtil.DEPTH_SHIFTED_MASK) | ((minY & RenderDataPointUtil.DEPTH_MASK) << RenderDataPointUtil.DEPTH_SHIFT); } - - public void setMaxY(int index, int maxY) { + public void setMaxY(int index, int maxY) + { this.data[index] = (this.data[index] & ~RenderDataPointUtil.HEIGHT_SHIFTED_MASK) | ((maxY & RenderDataPointUtil.HEIGHT_MASK) << RenderDataPointUtil.HEIGHT_SHIFT); } - public void setRed(int index, int red) { + public void setRed(int index, int red) + { this.data[index] = (this.data[index] & ~(RenderDataPointUtil.RED_MASK << RenderDataPointUtil.RED_SHIFT)) | ((red & RenderDataPointUtil.RED_MASK) << RenderDataPointUtil.RED_SHIFT); } - - public void setGreen(int index, int green) { + public void setGreen(int index, int green) + { this.data[index] = (this.data[index] & ~(RenderDataPointUtil.GREEN_MASK << RenderDataPointUtil.GREEN_SHIFT)) | ((green & RenderDataPointUtil.GREEN_MASK) << RenderDataPointUtil.GREEN_SHIFT); } public void setBlue(int index, int blue) { this.data[index] = (this.data[index] & ~(RenderDataPointUtil.BLUE_MASK << RenderDataPointUtil.BLUE_SHIFT)) | ((blue & RenderDataPointUtil.BLUE_MASK) << RenderDataPointUtil.BLUE_SHIFT); } - - public void setAlpha(int index, int alpha) { + public void setAlpha(int index, int alpha) + { alpha >>>= RenderDataPointUtil.ALPHA_DOWNSIZE_SHIFT; this.data[index] = (this.data[index] & ~(RenderDataPointUtil.ALPHA_MASK << RenderDataPointUtil.ALPHA_SHIFT)) | ((alpha & RenderDataPointUtil.ALPHA_MASK) << RenderDataPointUtil.ALPHA_SHIFT); } - - public void setBlockLight(int index, int blockLight) { + + public void setBlockLight(int index, int blockLight) + { this.data[index] = (this.data[index] & ~(RenderDataPointUtil.BLOCK_LIGHT_MASK << RenderDataPointUtil.BLOCK_LIGHT_SHIFT)) | ((blockLight & RenderDataPointUtil.BLOCK_LIGHT_MASK) << RenderDataPointUtil.BLOCK_LIGHT_SHIFT); } - - public void setSkyLight(int index, int skyLight) { + public void setSkyLight(int index, int skyLight) + { this.data[index] = (this.data[index] & ~(RenderDataPointUtil.SKY_LIGHT_MASK << RenderDataPointUtil.SKY_LIGHT_SHIFT)) | ((skyLight & RenderDataPointUtil.SKY_LIGHT_MASK) << RenderDataPointUtil.SKY_LIGHT_SHIFT); } + + + //================// + // helper methods // + //================// + + public boolean isIndexVisible(int index) { return isDataVisible(this.getData(index)); } - //////////////////////////////// utility //////////////////////////////// - - public boolean isIndexVisible(int index) { - return isDataVisible(this.getData(index)); - } - - public static boolean isDataVisible(long data) { - return isAlphaVisible(RenderDataPointUtil.getAlpha(data)); - } - - public static boolean isAlphaVisible(int alpha) { - return alpha >= 16; - } + public static boolean isDataVisible(long data) { return isAlphaVisible(RenderDataPointUtil.getAlpha(data)); } + public static boolean isAlphaVisible(int alpha) { return alpha >= 16; } + + + + //==============// + // base methods // + //==============// + @Override - public String toString() { + public String toString() + { StringBuilder builder = new StringBuilder(this.sizeWithAir << 8).append("lowest to highest:"); - for (int index = this.lowest; index != NULL; index = this.getHigher(index)) { + for (int index = this.lowest; index != NULL; index = this.getHigher(index)) + { builder.append('\n').append(RenderDataPointUtil.toString(this.getData(index))); } + builder.append("\nsmallest to biggest:"); - for (int index = this.smallest; index != NULL; index = this.getBigger(index)) { + for (int index = this.smallest; index != NULL; index = this.getBigger(index)) + { builder.append('\n').append(RenderDataPointUtil.toString(this.getData(index))); } + return builder.toString(); } + } \ No newline at end of file