package speedytools.common.selections;
import net.minecraftforge.fml.common.FMLLog;
import speedytools.common.utilities.ErrorLog;
import speedytools.common.utilities.Pair;
import speedytools.common.utilities.QuadOrientation;
import java.io.*;
import java.util.BitSet;
/**
* User: The Grey Ghost
* Date: 15/02/14
*/
public class VoxelSelection
{
public static final int MAX_X_SIZE = 256;
public static final int MAX_Y_SIZE = 256;
public static final int MAX_Z_SIZE = 256;
public VoxelSelection(int xSize, int ySize, int zSize)
{
resize(xSize, ySize, zSize);
}
/** deep copy
*
* @param source
*/
public VoxelSelection(VoxelSelection source)
{
resize(source.xSize, source.ySize, source.zSize);
voxels = (BitSet)source.voxels.clone();
}
public void clearAll()
{
voxels.clear();
}
public void resizeAndClear(int x, int y, int z)
{
resize(x, y, z);
}
public void setAll()
{
voxels.set(0, xSize * ySize * zSize);
}
/**
* set the value of this voxel (or does nothing if x,y,z out of range)
* @param x
* @param y
* @param z
*/
public void setVoxel(int x, int y, int z)
{
if ( x < 0 || x >= xSize
|| y < 0 || y >= ySize
|| z < 0 || z >= zSize) {
return;
}
voxels.set(x + xSize * (y + ySize * z));
}
/**
* set the value of this voxel (or does nothing if x,y,z out of range)
* @param x
* @param y
* @param z
*/
public void clearVoxel(int x, int y, int z)
{
if ( x < 0 || x >= xSize
|| y < 0 || y >= ySize
|| z < 0 || z >= zSize) {
return;
}
voxels.clear(x + xSize * (y + ySize * z));
}
/**
* gets the value of this voxel
* @param x
* @param y
* @param z
* @return the voxel state, or false if x, y, or z is out of range
*/
public boolean getVoxel(int x, int y, int z)
{
if ( x < 0 || x >= xSize
|| y < 0 || y >= ySize
|| z < 0 || z >= zSize) {
return false;
}
return voxels.get(x + xSize *(y + ySize * z) );
}
private void resize(int x, int y, int z)
{
if ( x <= 0 || x > MAX_X_SIZE
|| y <= 0 || y > MAX_Y_SIZE
|| z <= 0 || z > MAX_Z_SIZE ) {
FMLLog.severe("Out-of-range [x,y,z] in VoxelSelection constructor: [%d, %d, %d]", x, y, z);
x = 1;
y = 1;
z = 1;
}
xSize = x;
ySize = y;
zSize = z;
if (voxels == null) {
voxels = new BitSet(xSize * ySize * zSize); // default to all false
} else {
voxels.clear();
}
}
/** serialise the VoxelSelection to a byte array
* @return the serialised VoxelSelection, or null for failure
*/
public ByteArrayOutputStream writeToBytes()
{
ByteArrayOutputStream bos = null;
try {
bos = new ByteArrayOutputStream();
DataOutputStream outputStream = new DataOutputStream(bos);
outputStream.writeInt(xSize);
outputStream.writeInt(ySize);
outputStream.writeInt(zSize);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(voxels);
objectOutputStream.close();
} catch (IOException ioe) {
ErrorLog.defaultLog().debug("Exception while converting VoxelSelection toDataArray:" + ioe);
bos = null;
}
return bos;
}
/** fill this VoxelSelection using the serialised VoxelSelection byte array
* @param byteArrayInputStream the bytearray containing the serialised VoxelSelection
* @return true for success, false for failure (leaves selection untouched)
*/
public boolean readFromBytes(ByteArrayInputStream byteArrayInputStream) {
try {
DataInputStream inputStream = new DataInputStream(byteArrayInputStream);
int newXsize = inputStream.readInt();
int newYsize = inputStream.readInt();
int newZsize = inputStream.readInt();
if (newXsize < 1 || newXsize > MAX_X_SIZE || newYsize < 1 || newYsize > MAX_Y_SIZE || newZsize < 1 || newZsize > MAX_Z_SIZE) {
return false;
}
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
Object newVoxels = objectInputStream.readObject();
if (! (newVoxels instanceof BitSet)) return false;
xSize = newXsize;
ySize = newYsize;
zSize = newZsize;
voxels = (BitSet)newVoxels;
} catch (ClassNotFoundException cnfe) {
ErrorLog.defaultLog().debug("Exception while VoxelSelection.readFromDataArray: " + cnfe);
return false;
} catch (IOException ioe) {
ErrorLog.defaultLog().debug("Exception while VoxelSelection.readFromDataArray: " + ioe);
return false;
}
return true;
}
/**
* Creates a copy of this VoxelSelection, adding a border of blank voxels on all faces.
* For example - if the VoxelSelection has size 5x6x7, and borderWidth is 2, the resulting VoxelSelection is 9x10x11 in size
* And (eg) if the initial VoxelSelection is all set, the new selection will be all clear except from the box from [2,2,2] to [6,7,8] inclusive which will be all set
* @param borderWidth the number of voxels in the border added to all faces
* @return the new VoxelSelection
*/
public VoxelSelection makeCopyWithEmptyBorder(int borderWidth)
{
VoxelSelection copy = new VoxelSelection(xSize + 2 * borderWidth, ySize + 2 * borderWidth, zSize + 2 * borderWidth);
for (int x = 0; x < xSize; ++x) {
for (int y = 0; y < ySize; ++y) {
for (int z = 0; z < zSize; ++z) {
if (getVoxel(x,y,z)) {
copy.setVoxel(x + borderWidth, y + borderWidth, z + borderWidth);
}
}
}
}
return copy;
}
/** splits this VoxelSelection into two, in accordance with the supplied mask:
* For each set voxel in this:
* 1) if the mask is clear, the voxel is removed from this and added to the return value
* 2) if the mask is set, the voxel remains in this
* @param mask
* @param xOffsetOfMask the origin of the mask relative to the origin of this fragment
* @param yOffsetOfMask
* @param zOffsetOfMask
* @return a new VoxelSelection containing those voxels that aren't also present in the mask.
*/
public VoxelSelection splitByMask(VoxelSelection mask, int xOffsetOfMask, int yOffsetOfMask, int zOffsetOfMask)
{
VoxelSelection notOverlapped = new VoxelSelection(this);
notOverlapped.clearAll();
int xSize = mask.getxSize();
int ySize = mask.getySize();
int zSize = mask.getzSize();
for (int x = 0; x < xSize; ++x) {
for (int z = 0; z < zSize; ++z) {
for (int y = 0; y < ySize; ++y) {
if (getVoxel(x - xOffsetOfMask, y - yOffsetOfMask, z - zOffsetOfMask)) {
if (!mask.getVoxel(x, y, z)) {
notOverlapped.setVoxel(x - xOffsetOfMask, y - yOffsetOfMask, z - zOffsetOfMask);
this.clearVoxel(x - xOffsetOfMask, y - yOffsetOfMask, z - zOffsetOfMask);
}
}
}
}
}
return notOverlapped;
}
/**
* Creates a reoriented copy of this VoxelSelection and add a border of blank voxels on all faces.
* @param borderWidth the number of voxels in the border added to all faces
* @param orientation the new orientation (flip, rotate)
* @param wxzOrigin takes the current [wxMin, wzMin] and returns the new [wxMin, wzMin] - if the selection is rotated this may change
* @return the new VoxelSelection
*/
public VoxelSelection makeReorientedCopyWithBorder(QuadOrientation orientation, int borderWidth, Pair<Integer, Integer> wxzOrigin)
{
int wxMin = wxzOrigin.getFirst();
int wzMin = wxzOrigin.getSecond();
Pair<Integer, Integer> xrange = new Pair<Integer, Integer>(wxMin, wxMin + xSize - 1);
Pair<Integer, Integer> zrange = new Pair<Integer, Integer>(wzMin, wzMin + zSize - 1);
orientation.getWXZranges(xrange, zrange);
int wxNewMin = xrange.getFirst();
int wzNewMin = zrange.getFirst();
wxzOrigin.setFirst(wxNewMin);
wxzOrigin.setSecond(wzNewMin);
int newXsize = (xrange.getSecond() - xrange.getFirst() + 1) + 2 * borderWidth;
int newYsize = ySize + 2* borderWidth;
int newZsize = (zrange.getSecond() - zrange.getFirst() + 1) + 2 * borderWidth;
VoxelSelection copy = new VoxelSelection(newXsize, newYsize, newZsize);
for (int x = 0; x < xSize; ++x) {
for (int y = 0; y < ySize; ++y) {
for (int z = 0; z < zSize; ++z) {
if (getVoxel(x,y,z)) {
copy.setVoxel(orientation.calcWXfromXZ(x, z) + borderWidth - wxNewMin,
y + borderWidth,
orientation.calcWZfromXZ(x, z) + borderWidth - wzNewMin);
}
}
}
}
return copy;
}
/** For the given VoxelSelection, make a "BorderMask" copy where all the empty voxels adjacent to a set voxel are marked as set.
* i.e. for a given [x,y,z]:
* 1) if the voxel is set, the BorderMask voxel is clear
* 2) if all of the six adjacent voxels are clear, the BorderMask voxel is clear
* 3) otherwise, the BorderMask voxel is set.
*
* @return
*/
public VoxelSelection generateBorderMask()
{
VoxelSelection copy = new VoxelSelection(xSize, ySize, zSize);
for (int x = 0; x < xSize; ++x) {
for (int y = 0; y < ySize; ++y) {
for (int z = 0; z < zSize; ++z) {
if (!getVoxel(x, y, z)) {
if (getVoxel(x-1, y, z) || getVoxel(x+1, y, z) || getVoxel(x, y-1, z) || getVoxel(x, y+1, z) || getVoxel(x, y, z-1) || getVoxel(x, y, z+1)) {
copy.setVoxel(x, y, z);
}
}
}
}
}
return copy;
}
/**
* clear all voxels outside of the given ranges (inclusive)
* @param yMin
* @param yMax
*/
public void clipToYrange(int yMin, int yMax)
{
for (int y = 0; y < ySize; ++y) {
if (y < yMin || y > yMax) {
for (int x = 0; x < xSize; ++x) {
for (int z = 0; z < zSize; ++z) {
clearVoxel(x,y,z);
}
}
}
}
}
/** checks whether all of the set voxels in voxelSelection are also set in this VoxelSelection
* @param voxelSelection the voxels to test against. must be the same size as 'this'.
* @return true if all of the set voxels in voxelSelection are also set in this VoxelSelection
*/
public boolean containsAllOfThisMask(VoxelSelection voxelSelection)
{
assert(voxelSelection.xSize == this.xSize && voxelSelection.ySize == this.ySize && voxelSelection.zSize == this.zSize);
BitSet maskBitsNotInThis = (BitSet)voxelSelection.voxels.clone();
maskBitsNotInThis.andNot(this.voxels);
return maskBitsNotInThis.length() == 0;
}
/**
* updates this to include all set Voxels in both this and in voxelSelection
* @param voxelSelection the voxels to be set. Must be the same size as 'this'.
*/
public void union(VoxelSelection voxelSelection)
{
assert(voxelSelection.xSize == this.xSize && voxelSelection.ySize == this.ySize && voxelSelection.zSize == this.zSize);
voxels.or(voxelSelection.voxels);
}
private BitSet voxels;
public int getxSize() {
return xSize;
}
public int getySize() {
return ySize;
}
public int getzSize() {
return zSize;
}
public int getSetVoxelsCount()
{
return voxels.cardinality();
}
protected int xSize;
protected int ySize;
protected int zSize;
}