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