package mhfc.net.common.world.controller; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.ListIterator; import mhfc.net.common.util.CyclicIterator; import mhfc.net.common.util.ICyclicList; import mhfc.net.common.util.RewindableListIterator; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraftforge.common.util.Constants.NBT; /** * This rectangler packer organizes the rectangles as follows: * * <ul> * <li>Corners are always placed onto existing corners. * <li>Corners are said outgoing be outer corners if their inner angle is 90 degrees, and inner corners if theirs is 270 * degrees. * <li>There shall never be two outer corners following each other on the bounding line. * </ul> * * @author WorldSEnder * */ public class SimpleRectanglePlacer implements IRectanglePlacer { private static long rectangleSize(CornerPosition downLeft, CornerPosition upRight) { long diffX = Integer.toUnsignedLong(upRight.posX - downLeft.posX); long diffY = Integer.toUnsignedLong(upRight.posY - downLeft.posY); return diffX * diffY; } private static Corner forward(ListIterator<Corner> it, boolean forward) { return forward ? it.next() : it.previous(); } private static Corner backward(ListIterator<Corner> it, boolean forward) { return forward ? it.previous() : it.next(); } private static void addAfter(ListIterator<Corner> it, Corner corner, boolean forward) { if (forward) { it.add(corner); it.previous(); } else { it.add(corner); } } // Never two adjacent Bs, thus the sequence S making up the bounding // line // must be // O := B A // I := A // S := (I|O)+ // with count(I) == 4 public static interface IRectanglePlacingFunction { CornerPosition tryPlace( Corner prev, Corner curr, Corner next, long currentBestSize, CornerPosition currentMinCorner, CornerPosition currentMaxCorner, int incomingSize, int outgoingSize, List<Corner> replacementOut); } public static class CornerList extends ArrayList<Corner> implements ICyclicList<Corner> { /** * */ private static final long serialVersionUID = 5500364714034117962L; public CornerList() { super(); } public CornerList(Collection<Corner> collection) { super(collection); } @Override public CyclicIterator<Corner> cyclicIterator() { return new CyclicIterator<Corner>(this); } @Override public CyclicIterator<Corner> cyclicIterator(int index) { int listSize = size(); int realIndex = index % listSize; if (realIndex < 0) { realIndex += listSize; } return new CyclicIterator<Corner>(this, realIndex); } } public static final IRectanglePlacingFunction AfterOuterCorner = ( Corner prev, Corner curr, Corner next, long currentBestSize, CornerPosition currentMinCorner, CornerPosition currentMaxCorner, int incomingSize, int outgoingSize, List<Corner> replacementOut) -> { if (!curr.type.isOuter() || !next.type.isOuter()) { return null; } int cmpOut = Integer.compare(curr.type.outgoing.edgeLength(curr.pos, next.pos), outgoingSize); CornerPosition diagonalCorner = curr.type.incoming.add(curr.pos, incomingSize); diagonalCorner = curr.type.outgoing.add(diagonalCorner, outgoingSize); CornerPosition currMin = CornerPosition.totalMin(diagonalCorner, currentMinCorner); CornerPosition currMax = CornerPosition.totalMax(diagonalCorner, currentMaxCorner); long currRectSize = SimpleRectanglePlacer.rectangleSize(currMin, currMax); if (Long.compareUnsigned(currRectSize, currentBestSize) >= 0) { return null; } CornerPosition placedAt = CornerPosition.totalMin(curr.pos, diagonalCorner); CornerPosition upper = curr.type.incoming.add(curr.pos, incomingSize); Corner upperC = new Corner(curr.type, upper); Corner diagonal = new Corner(upperC.type.followUp(true), diagonalCorner); CornerPosition branching = curr.type.outgoing.add(curr.pos, outgoingSize); if (cmpOut > 0) { Corner branchC = new Corner(diagonal.type.followUp(false), branching); replacementOut.add(prev); replacementOut.add(upperC); replacementOut.add(diagonal); replacementOut.add(branchC); replacementOut.add(next); } else if (cmpOut == 0) { replacementOut.add(prev); replacementOut.add(upperC); replacementOut.add(diagonal); } else { Corner newBranch = new Corner(diagonal.type.followUp(true), branching); Corner newNext = new Corner(next.type.rebranch(true), next.pos); replacementOut.add(prev); replacementOut.add(upperC); replacementOut.add(diagonal); replacementOut.add(newBranch); replacementOut.add(newNext); } return placedAt; }; public static final IRectanglePlacingFunction BeforeOuterCorner = ( Corner prev, Corner curr, Corner next, long currentBestSize, CornerPosition currentMinCorner, CornerPosition currentMaxCorner, int incomingSize, int outgoingSize, List<Corner> replacementOut) -> { if (!curr.type.isOuter() || !prev.type.isOuter()) { return null; } int cmpInc = Integer.compare(curr.type.incoming.edgeLength(prev.pos, curr.pos), incomingSize); CornerPosition diagonalCorner = curr.type.incoming.inverted().add(curr.pos, incomingSize); diagonalCorner = curr.type.outgoing.inverted().add(diagonalCorner, outgoingSize); CornerPosition currMin = CornerPosition.totalMin(diagonalCorner, currentMinCorner); CornerPosition currMax = CornerPosition.totalMax(diagonalCorner, currentMaxCorner); long currRectSize = SimpleRectanglePlacer.rectangleSize(currMin, currMax); if (Long.compareUnsigned(currRectSize, currentBestSize) >= 0) { return null; } CornerPosition placedAt = CornerPosition.totalMin(curr.pos, diagonalCorner); CornerPosition branching = curr.type.incoming.inverted().add(curr.pos, incomingSize); Corner branchC = new Corner(prev.type.followUp(false), branching); Corner diagonal = new Corner(branchC.type.followUp(true), diagonalCorner); CornerPosition upper = curr.type.outgoing.inverted().add(curr.pos, outgoingSize); Corner upperC = new Corner(curr.type, upper); if (cmpInc > 0) { replacementOut.add(prev); replacementOut.add(branchC); replacementOut.add(diagonal); replacementOut.add(upperC); replacementOut.add(next); } else if (cmpInc == 0) { replacementOut.add(diagonal); replacementOut.add(upperC); replacementOut.add(next); } else { Corner newPrev = new Corner(prev.type.rebranch(false), prev.pos); Corner newBranch = new Corner(newPrev.type.followUp(true), branchC.pos); replacementOut.add(newPrev); replacementOut.add(newBranch); replacementOut.add(diagonal); replacementOut.add(upperC); replacementOut.add(next); } return placedAt; }; public static final IRectanglePlacingFunction AtInnerCorner = ( Corner prev, Corner curr, Corner next, long currentBestSize, CornerPosition currentMinCorner, CornerPosition currentMaxCorner, int incomingSize, int outgoingSize, List<Corner> replacementOut) -> { if (curr.type.isOuter()) { return null; } CornerPosition diagonalCorner = curr.type.incoming.inverted().add(curr.pos, incomingSize); diagonalCorner = curr.type.outgoing.add(diagonalCorner, outgoingSize); CornerPosition currMin = CornerPosition.totalMin(diagonalCorner, currentMinCorner); CornerPosition currMax = CornerPosition.totalMax(diagonalCorner, currentMaxCorner); long currRectSize = SimpleRectanglePlacer.rectangleSize(currMin, currMax); if (Long.compareUnsigned(currRectSize, currentBestSize) >= 0) { return null; // Marker for failure } CornerPosition placedAt = CornerPosition.totalMin(curr.pos, diagonalCorner); int cmpInc = Integer.compare(curr.type.incoming.edgeLength(prev.pos, curr.pos), incomingSize); int cmpOut = Integer.compare(curr.type.outgoing.edgeLength(curr.pos, next.pos), outgoingSize); if (cmpInc > 0) { CornerPosition branching = curr.type.incoming.inverted().add(curr.pos, incomingSize); Corner branchingC = new Corner(prev.type.followUp(false), branching); replacementOut.add(prev); replacementOut.add(branchingC); } else if (cmpInc == 0) { // Don't add anything } else { CornerPosition branching = curr.type.incoming.inverted().add(curr.pos, incomingSize); Corner newPrev = new Corner(prev.type.rebranch(false), prev.pos); Corner branchC = new Corner(newPrev.type.followUp(true), branching); replacementOut.add(newPrev); replacementOut.add(branchC); } replacementOut.add(new Corner(curr.type.followUp(true), diagonalCorner)); if (cmpOut > 0) { CornerPosition merging = curr.type.outgoing.add(curr.pos, outgoingSize); Corner mergingC = new Corner(curr.type.followUp(true).followUp(false), merging); replacementOut.add(mergingC); replacementOut.add(next); } else if (cmpOut == 0) { // Add nothing } else { CornerPosition merging = curr.type.outgoing.add(curr.pos, outgoingSize); Corner newNext = new Corner(next.type.rebranch(true), next.pos); Corner mergeC = new Corner(curr.type.followUp(true).followUp(true), merging); replacementOut.add(mergeC); replacementOut.add(newNext); } return placedAt; }; private static final List<IRectanglePlacingFunction> placingFunctions = Arrays.asList( SimpleRectanglePlacer.AfterOuterCorner, SimpleRectanglePlacer.BeforeOuterCorner, SimpleRectanglePlacer.AtInnerCorner); private CornerList corners = new CornerList(); private CornerPosition minCorner, maxCorner; public SimpleRectanglePlacer() { minCorner = new CornerPosition(0, 0); maxCorner = new CornerPosition(0, 0); } @Override public CornerPosition addRectangle(final int sizeX, final int sizeY) { if (corners.isEmpty()) { corners.add(new Corner(CornerType.DOWN_RIGHT, 0, 0)); corners.add(new Corner(CornerType.RIGHT_UP, sizeX, 0)); corners.add(new Corner(CornerType.UP_LEFT, sizeX, sizeY)); corners.add(new Corner(CornerType.LEFT_DOWN, 0, sizeY)); minCorner = new CornerPosition(0, 0); maxCorner = new CornerPosition(sizeX, sizeY); return corners.get(0).pos; } CornerPosition newMinCorner = minCorner, newMaxCorner = maxCorner; long bestSize = -1; // !!Unsigned!! CornerPosition placedAt = null; int midCornerIndex = -1; List<Corner> replacementResult = new ArrayList<>(); boolean hasDoneIndex0 = false; ListIterator<Corner> previousCorner, currentCorner, nextCorner; List<Corner> replacementBuffer = new ArrayList<>(); previousCorner = corners.cyclicIterator(); currentCorner = corners.cyclicIterator(); nextCorner = corners.cyclicIterator(); previousCorner.previous(); nextCorner.next(); while (currentCorner.nextIndex() != 0 || !hasDoneIndex0) { hasDoneIndex0 = true; Corner prev = previousCorner.next(); Corner curr = currentCorner.next(); Corner next = nextCorner.next(); boolean incHorizontal = curr.type.incoming.isHorizontal(); // boolean outHorizontal = !incHorizontal; int incomingSize = incHorizontal ? sizeX : sizeY; int outgoingSize = !incHorizontal ? sizeX : sizeY; for (IRectanglePlacingFunction func : SimpleRectanglePlacer.placingFunctions) { replacementBuffer.clear(); CornerPosition placedAtInner = func.tryPlace( prev, curr, next, bestSize, newMinCorner, newMaxCorner, incomingSize, outgoingSize, replacementBuffer); if (placedAtInner == null) { continue; } replacementResult.clear(); replacementResult.addAll(replacementBuffer); placedAt = placedAtInner; midCornerIndex = currentCorner.previousIndex(); for (Corner c : replacementBuffer) { newMaxCorner = CornerPosition.totalMax(c.pos, newMaxCorner); newMinCorner = CornerPosition.totalMin(c.pos, newMinCorner); } bestSize = SimpleRectanglePlacer.rectangleSize(newMinCorner, newMaxCorner); } } this.minCorner = newMinCorner; this.maxCorner = newMaxCorner; ListIterator<Corner> replacingIt = corners.cyclicIterator(midCornerIndex); replacingIt.previous(); replacingIt.remove(); // Before replacingIt.next(); replacingIt.remove(); // Current replacingIt.next(); replacingIt.remove(); // Next RewindableListIterator<Corner> beforeIt = new RewindableListIterator<>(replacingIt); beforeIt.mark(); for (Corner c : replacementResult) { beforeIt.add(c); } beforeIt.previous(); SimpleRectanglePlacer.fixInvariant(beforeIt, true); beforeIt.rewind(); beforeIt.next(); SimpleRectanglePlacer.fixInvariant(beforeIt, false); return placedAt; } static void fixInvariant(ListIterator<Corner> iterator, boolean forward) { while (true) { Corner firstB = SimpleRectanglePlacer.forward(iterator, forward); Corner secondB = SimpleRectanglePlacer.forward(iterator, forward); if (firstB.type.isOuter() || secondB.type.isOuter()) { return; } Corner afterBB = SimpleRectanglePlacer.forward(iterator, forward); SimpleRectanglePlacer.backward(iterator, forward); // AfterBB SimpleRectanglePlacer.backward(iterator, forward); // SecondBB SimpleRectanglePlacer.backward(iterator, forward); // FirstBB Corner beforeBB = SimpleRectanglePlacer.backward(iterator, forward); int cmp = secondB.type.outgoing.compare(beforeBB.pos, afterBB.pos); if (cmp < 0) { SimpleRectanglePlacer.forward(iterator, forward); // BeforeBB iterator.remove(); SimpleRectanglePlacer.forward(iterator, forward); // FirstBB iterator.remove(); SimpleRectanglePlacer.forward(iterator, forward); // SecondBB iterator.remove(); CornerPosition newCorner = secondB.type.outgoing.isHorizontal() ? new CornerPosition(beforeBB.pos.posX, afterBB.pos.posY) : new CornerPosition(afterBB.pos.posX, beforeBB.pos.posY); SimpleRectanglePlacer.addAfter(iterator, new Corner(secondB.type, newCorner), forward); } else if (cmp == 0) { iterator.remove(); SimpleRectanglePlacer.forward(iterator, forward); // FirstBB iterator.remove(); SimpleRectanglePlacer.forward(iterator, forward); // SecondBB iterator.remove(); SimpleRectanglePlacer.forward(iterator, forward); // AfterBB iterator.remove(); } else { SimpleRectanglePlacer.forward(iterator, forward); // BeforeBB SimpleRectanglePlacer.forward(iterator, forward); // FirstBB iterator.remove(); SimpleRectanglePlacer.forward(iterator, forward); // SecondBB iterator.remove(); SimpleRectanglePlacer.forward(iterator, forward); // AfterBB iterator.remove(); CornerPosition newCorner = secondB.type.outgoing.isHorizontal() ? new CornerPosition(afterBB.pos.posX, beforeBB.pos.posY) : new CornerPosition(beforeBB.pos.posX, afterBB.pos.posY); SimpleRectanglePlacer.addAfter(iterator, new Corner(firstB.type, newCorner), forward); } } } private void writeCornerPosition(NBTTagCompound nbtTag, CornerPosition pos) { nbtTag.setInteger("x", pos.posX); nbtTag.setInteger("y", pos.posY); } private CornerPosition readCornerPosition(NBTTagCompound nbtTag) { return new CornerPosition(nbtTag.getInteger("x"), nbtTag.getInteger("y")); } @Override public void saveTo(NBTTagCompound nbtTag) { NBTTagList listOfCornerPos = new NBTTagList(); for (Corner c : this.corners) { NBTTagCompound cornerTag = new NBTTagCompound(); writeCornerPosition(cornerTag, c.pos); cornerTag.setString("type", c.type.name()); listOfCornerPos.appendTag(cornerTag); } NBTTagCompound minCornerTag = new NBTTagCompound(); NBTTagCompound maxCornerTag = new NBTTagCompound(); writeCornerPosition(minCornerTag, minCorner); writeCornerPosition(maxCornerTag, maxCorner); nbtTag.setTag("minCorner", minCornerTag); nbtTag.setTag("maxCorner", maxCornerTag); nbtTag.setTag("corners", listOfCornerPos); } @Override public void readFrom(NBTTagCompound nbtTag) { NBTTagList listOfCornerPos = nbtTag.getTagList("corners", NBT.TAG_COMPOUND); NBTTagCompound minCornerTag = nbtTag.getCompoundTag("minCorner"); NBTTagCompound maxCornerTag = nbtTag.getCompoundTag("maxCorner"); maxCorner = readCornerPosition(maxCornerTag); minCorner = readCornerPosition(minCornerTag); corners.clear(); for (int i = 0; i < listOfCornerPos.tagCount(); i++) { NBTTagCompound cornerTag = listOfCornerPos.getCompoundTagAt(i); CornerType type = CornerType.valueOf(cornerTag.getString("type")); CornerPosition pos = readCornerPosition(cornerTag); corners.add(new Corner(type, pos)); } } /** * Returns a copy of the current corners of the outline of the kept area. All corners are in anti-clockwise order. * * @return */ public CornerList getCorners() { return new CornerList(corners); } @Override public String toString() { return corners.toString(); } }