/*
* This file is part of Cubic Chunks Mod, licensed under the MIT License (MIT).
*
* Copyright (c) 2015 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package cubicchunks;
import com.google.common.collect.Lists;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.spongepowered.test.launch.LaunchWrapperTestRunner;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import cubicchunks.util.Bits;
import cubicchunks.util.Coords;
import cubicchunks.world.ServerHeightMap;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@RunWith(LaunchWrapperTestRunner.class)
public class TestOpacityIndex {
private static Field YminField;
private static Field YmaxField;
private static Field SegmentsField;
static {
try {
YminField = ServerHeightMap.class.getDeclaredField("ymin");
YminField.setAccessible(true);
YmaxField = ServerHeightMap.class.getDeclaredField("ymax");
YmaxField.setAccessible(true);
SegmentsField = ServerHeightMap.class.getDeclaredField("segments");
SegmentsField.setAccessible(true);
} catch (NoSuchFieldException | SecurityException ex) {
throw new Error(ex);
}
}
@Test
public void getWithAllTransparent() {
ServerHeightMap index = new ServerHeightMap();
assertEquals(Coords.NO_HEIGHT, index.getBottomBlockY(0, 0));
assertEquals(Coords.NO_HEIGHT, index.getTopBlockY(0, 0));
}
@Test
public void getWithoutDataSingleBlock() {
ServerHeightMap index = makeIndex(10, 10);
assertEquals(10, index.getBottomBlockY(0, 0));
assertEquals(10, index.getTopBlockY(0, 0));
}
@Test
public void getWithoutDataMultipleBlocks() {
ServerHeightMap index = makeIndex(8, 10);
assertEquals(8, index.getBottomBlockY(0, 0));
assertEquals(10, index.getTopBlockY(0, 0));
}
@Test
public void getWith1Data() {
ServerHeightMap index = makeIndex(8, 10,
8, 1
);
assertEquals(8, index.getBottomBlockY(0, 0));
assertEquals(10, index.getTopBlockY(0, 0));
}
@Test
public void setSingleOpaqueFromEmpty() {
ServerHeightMap index = new ServerHeightMap();
index.onOpacityChange(0, 10, 0, 255);
assertEquals(10, index.getBottomBlockY(0, 0));
assertEquals(10, index.getTopBlockY(0, 0));
assertEquals(null, getSegments(index));
}
@Test
public void setSingleTransparentFromSingleOpaque() {
ServerHeightMap index = makeIndex(10, 10);
index.onOpacityChange(0, 10, 0, 0);
assertEquals(Coords.NO_HEIGHT, index.getBottomBlockY(0, 0));
assertEquals(Coords.NO_HEIGHT, index.getTopBlockY(0, 0));
assertEquals(null, getSegments(index));
}
@Test
public void setExpandSingleOpaque() {
ServerHeightMap index = makeIndex(10, 10);
index.onOpacityChange(0, 11, 0, 255);
assertEquals(10, index.getBottomBlockY(0, 0));
assertEquals(11, index.getTopBlockY(0, 0));
assertEquals(null, getSegments(index));
index.onOpacityChange(0, 9, 0, 255);
assertEquals(9, index.getBottomBlockY(0, 0));
assertEquals(11, index.getTopBlockY(0, 0));
assertEquals(null, getSegments(index));
}
@Test
public void setShrinkOpaque() {
ServerHeightMap index = makeIndex(9, 11);
index.onOpacityChange(0, 9, 0, 0);
assertEquals(10, index.getBottomBlockY(0, 0));
assertEquals(11, index.getTopBlockY(0, 0));
assertEquals(null, getSegments(index));
index.onOpacityChange(0, 11, 0, 0);
assertEquals(10, index.getBottomBlockY(0, 0));
assertEquals(10, index.getTopBlockY(0, 0));
assertEquals(null, getSegments(index));
}
@Test
public void setDisjointOpaqueAboveOpaue() {
ServerHeightMap index = makeIndex(9, 11);
index.onOpacityChange(0, 16, 0, 255);
assertEquals(9, index.getBottomBlockY(0, 0));
assertEquals(16, index.getTopBlockY(0, 0));
assertEquals(Arrays.asList(
9, 1,
12, 0,
16, 1
), getSegments(index));
}
@Test
public void setDisjointOpaqueBelowOpaue() {
ServerHeightMap index = makeIndex(9, 11);
index.onOpacityChange(0, 4, 0, 255);
assertEquals(4, index.getBottomBlockY(0, 0));
assertEquals(11, index.getTopBlockY(0, 0));
assertEquals(Arrays.asList(
4, 1,
5, 0,
9, 1
), getSegments(index));
}
@Test
public void setDisjointOpaqueAboveOpaques() {
ServerHeightMap index = makeIndex(9, 16,
9, 1,
12, 0,
16, 1
);
index.onOpacityChange(0, 20, 0, 255);
assertEquals(9, index.getBottomBlockY(0, 0));
assertEquals(20, index.getTopBlockY(0, 0));
assertEquals(Arrays.asList(
9, 1,
12, 0,
16, 1,
17, 0,
20, 1
), getSegments(index));
}
@Test
public void setDisjointOpaqueBelowOpaques() {
ServerHeightMap index = makeIndex(9, 16,
9, 1,
12, 0,
16, 1
);
index.onOpacityChange(0, 3, 0, 255);
assertEquals(3, index.getBottomBlockY(0, 0));
assertEquals(16, index.getTopBlockY(0, 0));
assertEquals(Arrays.asList(
3, 1,
4, 0,
9, 1,
12, 0,
16, 1
), getSegments(index));
}
@Test
public void extendTopOpaqueUp() {
ServerHeightMap index = makeIndex(9, 16,
9, 1,
12, 0,
16, 1
);
index.onOpacityChange(0, 17, 0, 255);
assertEquals(9, index.getBottomBlockY(0, 0));
assertEquals(17, index.getTopBlockY(0, 0));
assertEquals(Arrays.asList(
9, 1,
12, 0,
16, 1
), getSegments(index));
}
@Test
public void extendBottomOpaqueDown() {
ServerHeightMap index = makeIndex(9, 16,
9, 1,
12, 0,
16, 1
);
index.onOpacityChange(0, 8, 0, 255);
assertEquals(8, index.getBottomBlockY(0, 0));
assertEquals(16, index.getTopBlockY(0, 0));
assertEquals(Arrays.asList(
8, 1,
12, 0,
16, 1
), getSegments(index));
}
@Test
public void setBisectOpaue() {
ServerHeightMap index = makeIndex(9, 11);
index.onOpacityChange(0, 10, 0, 0);
assertEquals(9, index.getBottomBlockY(0, 0));
assertEquals(11, index.getTopBlockY(0, 0));
assertEquals(Arrays.asList(
9, 1,
10, 0,
11, 1
), getSegments(index));
}
@Test
public void setDataStartSameAsBottomRoomBeforeTop() {
ServerHeightMap index = makeIndex(4, 7,
4, 1
);
index.onOpacityChange(0, 4, 0, 0);
assertEquals(5, index.getBottomBlockY(0, 0));
assertEquals(7, index.getTopBlockY(0, 0));
assertEquals(Arrays.asList(
5, 1
), getSegments(index));
}
@Test
public void setDataNotStartSameAsNextRoomAfter() {
ServerHeightMap index = makeIndex(2, 7,
2, 1
);
index.onOpacityChange(0, 4, 0, 0);
assertEquals(2, index.getBottomBlockY(0, 0));
assertEquals(7, index.getTopBlockY(0, 0));
assertEquals(Arrays.asList(
2, 1,
4, 0,
5, 1
), getSegments(index));
}
@Test
public void setDataNotStartSameAsTopNoRoomAfter() {
ServerHeightMap index = makeIndex(2, 4,
2, 1
);
index.onOpacityChange(0, 4, 0, 0);
assertEquals(2, index.getBottomBlockY(0, 0));
assertEquals(3, index.getTopBlockY(0, 0));
assertEquals(Arrays.asList(
2, 1
), getSegments(index));
}
@Test
public void setDataAfterTopSameAsPrevious() {
ServerHeightMap index = makeIndex(2, 4,
2, 1
);
index.onOpacityChange(0, 4, 0, 0);
assertEquals(2, index.getBottomBlockY(0, 0));
assertEquals(3, index.getTopBlockY(0, 0));
assertEquals(Arrays.asList(
2, 1
), getSegments(index));
}
@Test
public void setTransparentInOpaqueAndClear() {
ServerHeightMap index = new ServerHeightMap();
// place blocks
index.onOpacityChange(0, 0, 0, 255);
index.onOpacityChange(0, 1, 0, 255);
index.onOpacityChange(0, 2, 0, 255);
assertEquals(2, index.getTopBlockY(0, 0));
assertEquals(0, index.getBottomBlockY(0, 0));
// remove the middle one
index.onOpacityChange(0, 1, 0, 0);
assertEquals(2, index.getTopBlockY(0, 0));
assertEquals(0, index.getBottomBlockY(0, 0));
// remove the bottom one
index.onOpacityChange(0, 0, 0, 0);
assertEquals(2, index.getTopBlockY(0, 0));
assertEquals(2, index.getBottomBlockY(0, 0));
// remove the top one
index.onOpacityChange(0, 2, 0, 0);
assertEquals(Coords.NO_HEIGHT, index.getTopBlockY(0, 0));
assertEquals(Coords.NO_HEIGHT, index.getBottomBlockY(0, 0));
}
@Test
public void checkFloatingIsland() {
// make an index with a surface
ServerHeightMap index = makeIndex(100, 102);
// start setting blocks in the sky
index.onOpacityChange(0, 200, 0, 255);
assertEquals(200, index.getTopBlockY(0, 0));
assertEquals(100, index.getBottomBlockY(0, 0));
index.onOpacityChange(0, 201, 0, 255);
assertEquals(201, index.getTopBlockY(0, 0));
assertEquals(100, index.getBottomBlockY(0, 0));
index.onOpacityChange(0, 202, 0, 255);
assertEquals(202, index.getTopBlockY(0, 0));
assertEquals(100, index.getBottomBlockY(0, 0));
}
//test 21011120
@Test
public void testMergeSegmentsIntoNoSegmentsAndRemoveTop_generated() {
ServerHeightMap index = new ServerHeightMap();
index.onOpacityChange(0, 2, 0, 1);
assertEquals(2, index.getTopBlockY(0, 0));
assertEquals(2, index.getBottomBlockY(0, 0));
index.onOpacityChange(0, 0, 0, 1);
assertEquals(2, index.getTopBlockY(0, 0));
assertEquals(0, index.getBottomBlockY(0, 0));
index.onOpacityChange(0, 1, 0, 1);
assertEquals(2, index.getTopBlockY(0, 0));
assertEquals(0, index.getBottomBlockY(0, 0));
index.onOpacityChange(0, 2, 0, 0);
assertEquals(1, index.getTopBlockY(0, 0));
assertEquals(0, index.getBottomBlockY(0, 0));
}
@Test
public void test41210140110000() {
ServerHeightMap index = new ServerHeightMap();
index.onOpacityChange(0, 4, 0, 1);
assertEquals(4, index.getTopBlockY(0, 0));
assertEquals(4, index.getBottomBlockY(0, 0));
index.onOpacityChange(0, 2, 0, 1);
assertEquals(4, index.getTopBlockY(0, 0));
assertEquals(2, index.getBottomBlockY(0, 0));
index.onOpacityChange(0, 0, 0, 1);
assertEquals(4, index.getTopBlockY(0, 0));
assertEquals(0, index.getBottomBlockY(0, 0));
index.onOpacityChange(0, 4, 0, 0);
assertEquals(2, index.getTopBlockY(0, 0));
assertEquals(0, index.getBottomBlockY(0, 0));
index.onOpacityChange(0, 1, 0, 1);
assertEquals(2, index.getTopBlockY(0, 0));
assertEquals(0, index.getBottomBlockY(0, 0));
index.onOpacityChange(0, 0, 0, 0);
assertEquals(2, index.getTopBlockY(0, 0));
assertEquals(1, index.getBottomBlockY(0, 0));
index.onOpacityChange(0, 0, 0, 0);
assertEquals(2, index.getTopBlockY(0, 0));
assertEquals(1, index.getBottomBlockY(0, 0));
}
@Test
public void testSetAndClear_generated() {
ServerHeightMap index = new ServerHeightMap();
index.onOpacityChange(0, 4, 0, 1);
assertEquals(4, index.getTopBlockY(0, 0));
assertEquals(4, index.getBottomBlockY(0, 0));
index.onOpacityChange(0, 2, 0, 1);
assertEquals(4, index.getTopBlockY(0, 0));
assertEquals(2, index.getBottomBlockY(0, 0));
index.onOpacityChange(0, 0, 0, 1);
assertEquals(4, index.getTopBlockY(0, 0));
assertEquals(0, index.getBottomBlockY(0, 0));
index.onOpacityChange(0, 4, 0, 0);
assertEquals(2, index.getTopBlockY(0, 0));
assertEquals(0, index.getBottomBlockY(0, 0));
index.onOpacityChange(0, 2, 0, 0);
assertEquals(0, index.getTopBlockY(0, 0));
assertEquals(0, index.getBottomBlockY(0, 0));
index.onOpacityChange(0, 0, 0, 0);
assertEquals(Coords.NO_HEIGHT, index.getTopBlockY(0, 0));
assertEquals(Coords.NO_HEIGHT, index.getBottomBlockY(0, 0));
index.onOpacityChange(0, 0, 0, 0);
assertEquals(Coords.NO_HEIGHT, index.getTopBlockY(0, 0));
assertEquals(Coords.NO_HEIGHT, index.getBottomBlockY(0, 0));
}
@Test
public void test31110000hmaplim_generated() {
ServerHeightMap index = new ServerHeightMap();
index.onOpacityChange(0, 3, 0, 1);
index.onOpacityChange(0, 1, 0, 1);
int height = index.getTopBlockYBelow(0, 0, 3);
assertEquals(1, height);
}
@Test
public void allCombinationsTest() {
//tested with value up to 6 (takes a lot of time)
final int maxHeight = 5, numBlocks = 4;
int[] yPosOpacityEncoded = new int[numBlocks];
while (true) {
ServerHeightMap index = new ServerHeightMap();
ArrayOpacityIndexImpl test = new ArrayOpacityIndexImpl();
StringBuilder msg = new StringBuilder();
for (int encoded : yPosOpacityEncoded) {
msg.append("i[").append(encoded/2).append("]=").append(encoded%2).append(", ");
}
String message = msg.toString();
try {
for (int i = 0; i < yPosOpacityEncoded.length; i++) {
int opacity = yPosOpacityEncoded[i]%2;
index.onOpacityChange(0, yPosOpacityEncoded[i]/2, 0, opacity);
test.set(yPosOpacityEncoded[i]/2, opacity);
}
//store-read-store test
byte[] b = index.getData();
ServerHeightMap newIndex = new ServerHeightMap();
newIndex.readData(b);
assertArrayEquals("Got different data after creating index based on read data\n" + message + "\n", b, newIndex.getData());
} catch (Throwable t) {
System.out.println(message + "exception");
throw t;
}
for (int i = 0; i < maxHeight; i++) {
assertEquals(message + ", maxHBelow(" + i + ")", (Integer) test.getMaxYBelow(i), (Integer) index.getTopBlockYBelow(0, 0, i));
}
assertEquals(message + "minY", (Integer) test.getMinY(), (Integer) index.getBottomBlockY(0, 0));
assertEquals(message + "maxY", (Integer) test.getMaxY(), (Integer) index.getTopBlockY(0, 0));
yPosOpacityEncoded[0]++;
for (int i = 0; i < numBlocks - 1; i++) {
if (yPosOpacityEncoded[i] == maxHeight*2) {
yPosOpacityEncoded[i] = 0;
yPosOpacityEncoded[i + 1]++;
}
}
if (yPosOpacityEncoded[numBlocks - 1] == maxHeight*2) {
break;
}
}
}
private ServerHeightMap makeIndex(int ymin, int ymax, int... segments) {
ServerHeightMap index = new ServerHeightMap();
// pack the segments
int[] packedSegments = null;
if (segments.length > 0) {
packedSegments = new int[segments.length/2];
for (int i = 0; i < segments.length/2; i++) {
packedSegments[i] = Bits.packSignedToInt(segments[i*2 + 0], 24, 0) | Bits.packUnsignedToInt(segments[i*2 + 1], 8, 24);
}
}
set(index, ymin, ymax, packedSegments);
return index;
}
private void set(ServerHeightMap index, int ymin, int ymax, int[] segments) {
try {
YminField.set(index, new int[]{ymin});
YmaxField.set(index, new int[]{ymax});
SegmentsField.set(index, new int[][]{segments});
} catch (IllegalArgumentException | IllegalAccessException ex) {
throw new Error(ex);
}
}
private List<Integer> getSegments(ServerHeightMap index) {
try {
int[] packedSegments = ((int[][]) SegmentsField.get(index))[0];
if (packedSegments == null) {
return null;
}
final int NoneSegment = 0x7fffff;
// unpack the segments
List<Integer> segments = Lists.newArrayList();
for (int i = 0; i < packedSegments.length && packedSegments[i] != NoneSegment; i++) {
segments.add(Bits.unpackSigned(packedSegments[i], 24, 0));
segments.add(Bits.unpackUnsigned(packedSegments[i], 8, 24));
}
return segments;
} catch (IllegalArgumentException | IllegalAccessException ex) {
throw new Error(ex);
}
}
private static class ArrayOpacityIndexImpl {
private int[] arr = new int[100];
private void set(int y, int val) {
arr[y] = val;
}
public int getMinY() {
for (int i = 0; i < arr.length; i++) {
if (arr[i] != 0) {
return i;
}
}
return Coords.NO_HEIGHT;
}
public int getMaxY() {
for (int i = arr.length - 1; i >= 0; i--) {
if (arr[i] != 0) {
return i;
}
}
return Coords.NO_HEIGHT;
}
public int getMaxYBelow(int y) {
for (int i = y - 1; i >= 0; i--) {
if (arr[i] != 0) {
return i;
}
}
return Coords.NO_HEIGHT;
}
}
}