package speedytools.common.utilities; import io.netty.buffer.ByteBuf; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; /** * User: The Grey Ghost * Date: 12/07/2014 * This class is used to help keep track of the location and orientation of a quad within the world. * The quad is a 2D array indexed by [x, z] - its initial coordinates are [0,0] to [xsize-1, zsize-1] * It is mapped to a 2D location in the world by providing an [wxOrigin, wzOrigin] for the bottom left corner. * The quad can be flipped and rotated, and the class can be used to convert between the [x,z] and the [wx,wz] * The x, z axes are oriented as per the minecraft world, i.e. a 90 degrees clockwise rotation from the x axis [1,0] gives the z axis [0,1] * For example: * If the quad is set up with size [7, 6] and origin at [100, 200]: * An unflipped, unrotated quad: * [x,z]->[wx, wz] gives [0,0] -> [100, 200], and [2,3] -> [102, 203] * If the quad is then flipped left-right so that xneg becomes xpos: * [x,z]->[wz, wz] gives [0,0] -> [104, 200], and [2,3] -> [104, 203] * If the quad is rotated and the origin becomes a half a block, it is rounded down; eg * Rotating the quad above by 90 clockwise gives * [x,z]->[wx, wz] gives [0,0] -> [100.5, 199.5] but this is rounded to [100, 199] */ public class QuadOrientation { public QuadOrientation(int i_wxOrigin, int i_wzOrigin, int i_xSize, int i_zSize) { this(i_wxOrigin, i_wzOrigin, i_xSize, i_zSize, false, (byte)0); } public QuadOrientation(int i_wxOrigin, int i_wzOrigin, int i_xSize, int i_zSize, boolean i_flippedX, byte i_clockwiseRotationCount) { wxOrigin = i_wxOrigin; wzOrigin = i_wzOrigin; xSize = i_xSize; zSize = i_zSize; flippedX = i_flippedX; clockwiseRotationCount = i_clockwiseRotationCount; transformationIndex = (flippedX ? 4 : 0) | (clockwiseRotationCount & 3); calculateOriginTransformed(); } public QuadOrientation(ByteBuf byteBuf) { transformationIndex = byteBuf.readInt(); wxOrigin = byteBuf.readInt(); wzOrigin = byteBuf.readInt(); xSize = byteBuf.readInt(); zSize = byteBuf.readInt(); flippedX = (transformationIndex & 4) != 0; clockwiseRotationCount = (byte)(transformationIndex & 3); calculateOriginTransformed(); } /** * convert the quad array coordinates into world coordinates * @param x * @param z * @return wx - no boundary checking performed */ public int calcWXfromXZ(int x, int z) { return wxOriginTransformed + x * WX_MULTIPLIER_FROM_X[transformationIndex] + z * WX_MULTIPLIER_FROM_Z[transformationIndex]; } /** * convert the quad array coordinates into world coordinates * @param x * @param z * @return wx - no boundary checking performed */ public int calcWZfromXZ(int x, int z) { return wzOriginTransformed + x * WZ_MULTIPLIER_FROM_X[transformationIndex] + z * WZ_MULTIPLIER_FROM_Z[transformationIndex]; } /** * convert the world coordinates into array coordinates * @param wx * @param wz * @return x */ public int calcXfromWXZ(int wx, int wz) { return (wx - wxOriginTransformed) * X_MULTIPLIER_FROM_WX[transformationIndex] + (wz - wzOriginTransformed) * X_MULTIPLIER_FROM_WZ[transformationIndex]; } /** * convert the world coordinates into array coordinates * @param wx * @param wz * @return x */ public int calcZfromWXZ(int wx, int wz) { return (wx - wxOriginTransformed) * Z_MULTIPLIER_FROM_WX[transformationIndex] + (wz - wzOriginTransformed) * Z_MULTIPLIER_FROM_WZ[transformationIndex]; } /** * convert the world coordinates into array coordinates * @param wx * @param wz * @return x */ public double calcXfromWXZ(double wx, double wz) { double rotationPointX = wxOrigin + xSize / 2.0; double rotationPointZ = wzOrigin + zSize / 2.0; double radiusWX = wx - rotationPointX; double radiusWZ = wz - rotationPointZ; return rotationPointX + radiusWX * X_MULTIPLIER_FROM_WX[transformationIndex] + radiusWZ * X_MULTIPLIER_FROM_WZ[transformationIndex]; } /** * convert the world coordinates into array coordinates * @param wx * @param wz * @return z */ public double calcZfromWXZ(double wx, double wz) { wx -= wxNudge; wz -= wzNudge; double rotationPointX = wxOrigin + xSize / 2.0; double rotationPointZ = wzOrigin + zSize / 2.0; double radiusWX = wx - rotationPointX; double radiusWZ = wz - rotationPointZ; return rotationPointZ + radiusWX * Z_MULTIPLIER_FROM_WX[transformationIndex] + radiusWZ * Z_MULTIPLIER_FROM_WZ[transformationIndex]; } /** If the quad is rotated by 90 or 270 degrees, and the xSize is odd and the zSize is even (or vica versa) then * the quad is nudged to align with the grid. * This function returns that "nudge" in world coordinates * @return [wxOffset, wzOffset] - eg if the origin has been rounded from 15.5 to 15, offset is -0.5 */ public Pair<Float, Float> getWXZNudge() { return new Pair<Float, Float>(wxNudge, wzNudge); } /** * Flip the quad left-right in the world coordinates i.e wxneg becomes wxpos */ public QuadOrientation flipWX() { flipX(); // System.out.println("flipWX:clockwiseRotationCount" + clockwiseRotationCount); // if ((clockwiseRotationCount & 1) == 0) { // System.out.println("flipX"); // flipX(); // } else { // System.out.println("flipZ"); // flipZ(); // } return this; } /** * Flip the quad front-back in the world coordinates i.e wzneg becomes wzpos */ public QuadOrientation flipWZ() { flipZ(); // System.out.println("flipWZ:clockwiseRotationCount" + clockwiseRotationCount); // if ((clockwiseRotationCount & 1) == 0) { // System.out.println("flipZ"); // flipZ(); // } else { // System.out.println("flipX"); // flipX(); // } return this; } private final int [] FLIP_X_NEW_INDEX = {4, 7, 6, 5, 0, 3, 2, 1}; /** flip the quad left-right i.e xneg becomes xpos */ public QuadOrientation flipX() { transformationIndex = FLIP_X_NEW_INDEX[transformationIndex]; flippedX = (transformationIndex & 4) != 0; clockwiseRotationCount = (byte)(transformationIndex & 3); calculateOriginTransformed(); return this; } private final int [] FLIP_Z_NEW_INDEX = {6, 5, 4, 7, 2, 1, 0, 3}; /** flip the quad front-back i.e zneg becomes zpos */ public QuadOrientation flipZ() { transformationIndex = FLIP_Z_NEW_INDEX[transformationIndex]; flippedX = (transformationIndex & 4) != 0; clockwiseRotationCount = (byte)(transformationIndex & 3); calculateOriginTransformed(); return this; } /** * rotate the quad by 0, 90, 180, or 270 degrees * @param rotationCount the number of quadrants to rotate clockwise */ public QuadOrientation rotateClockwise(int rotationCount) { clockwiseRotationCount = (byte)((clockwiseRotationCount + rotationCount) & 3); transformationIndex = (flippedX ? 4 : 0) | (clockwiseRotationCount & 3); calculateOriginTransformed(); return this; } public byte getClockwiseRotationCount() { return clockwiseRotationCount; } public boolean isFlippedX() { return flippedX; } public void writeToStream(ByteBuf byteBuf) { byteBuf.writeInt(transformationIndex); byteBuf.writeInt(wxOrigin); byteBuf.writeInt(wzOrigin); byteBuf.writeInt(xSize); byteBuf.writeInt(zSize); } /** return the current x size of the quad in world coordinates * @return */ public int getWXsize() { return ((clockwiseRotationCount & 1) == 0) ? xSize : zSize; } /** return the current z size of the quad in world coordinates * @return */ public int getWZSize() { return ((clockwiseRotationCount & 1) == 0) ? zSize : xSize; } /** * returns the new WXZ ranges ([min, max] inclusive) after the transformation * @param wxRange takes the current [xMin, xMax] inclusive and returns the new [wxMin, wxMax] inclusive * @param wzRange takes the current [zMin, zMax] inclusive and returns the new [wzMin, wzMax] inclusive */ public void getWXZranges(Pair<Integer, Integer> wxRange, Pair<Integer, Integer> wzRange) { int wx1 = calcWXfromXZ(wxRange.getFirst(), wzRange.getFirst()); int wz1 = calcWZfromXZ(wxRange.getFirst(), wzRange.getFirst()); int wx2 = calcWXfromXZ(wxRange.getSecond(), wzRange.getSecond()); int wz2 = calcWZfromXZ(wxRange.getSecond(), wzRange.getSecond()); wxRange.setFirst(Math.min(wx1, wx2)); wxRange.setSecond(Math.max(wx1, wx2)); wzRange.setFirst(Math.min(wz1, wz2)); wzRange.setSecond(Math.max(wz1, wz2)); } /** * Make a copy of the original that has the same origin, rotation and flip but different size * @param newXsize the new x size * @param newZsize the new z size * @return a new QuadOrientation (this is unchanged.) */ public QuadOrientation getResizedCopy(int newXsize, int newZsize) { // Pair<Integer, Integer> wxRange = new Pair<Integer, Integer>(0,0); // Pair<Integer, Integer> wzRange = new Pair<Integer, Integer>(0,0); // getWXZranges(wxRange, wzRange); return new QuadOrientation(wxOrigin, wzOrigin, newXsize, newZsize, flippedX, clockwiseRotationCount); } /** * This method calculates the placement position of the newQuadOrientation that will cause it to overlay correctly onto * the oldQuadOrientation (this). * For example for an unflipped, unrotated quad: * the old QuadOrientation has size 5,7. It maps to source world coordinates [30,40] to [34, 46] * the new QuadOrientation has size 10,14. It maps to source world coordinates [26, 38] to [35, 51]. It is composed of * old QuadOrientation plus a border. * What placement position for newQuadOrientation would cause the "internal" oldQuadOrientation to be placed in the * same location as the oldQuadOrientation by itself. * @param newQuadOrientation the orientation of the new quad. must match this for flipped and rotation! * @param xRelativeOrigin the origin of the old relative to the new; for the example above, 30 - 26 = +4 * @param zRelativeOrigin the origin of the old relative to the new: for the example above, 40 - 38 = +2 * @return the relative placement position <dx, dz> of the newQuadOrientation compared to the old (for example above: * <-4, -5> ) */ public Pair<Integer, Integer> calculateDisplacementForExpandedSelection(QuadOrientation newQuadOrientation, int xRelativeOrigin, int zRelativeOrigin) { assert (flippedX == newQuadOrientation.flippedX); assert (clockwiseRotationCount == newQuadOrientation.clockwiseRotationCount); // Pair<Integer, Integer> oldWXRange = new Pair<Integer, Integer>(0, xSize-1); // Pair<Integer, Integer> oldWZRange = new Pair<Integer, Integer>(0, zSize-1); // getWXZranges(oldWXRange, oldWZRange); // System.out.println("oldWXZmin = [" + oldWXRange.getFirst() + ", " + oldWZRange.getFirst() + "]"); // // // find the world x and z range of the new using the old reference frame //// Pair<Integer, Integer> newWXRange = new Pair<Integer, Integer>(-xRelativeOrigin, -xRelativeOrigin + newQuadOrientation.xSize - 1); //// Pair<Integer, Integer> newWZRange = new Pair<Integer, Integer>(-zRelativeOrigin, -zRelativeOrigin + newQuadOrientation.zSize - 1); // Pair<Integer, Integer> newWXRange = new Pair<Integer, Integer>(xRelativeOrigin, xRelativeOrigin + xSize - 1); // Pair<Integer, Integer> newWZRange = new Pair<Integer, Integer>(zRelativeOrigin, zRelativeOrigin + zSize - 1); // System.out.println("xRange:" + newWXRange); // System.out.println("zRange:" + newWZRange); // newQuadOrientation.getWXZranges(newWXRange, newWZRange); // System.out.println("new WXZmin = [" + newWXRange.getFirst() + ", " + newWZRange.getFirst() + "]"); // // return new Pair<Integer, Integer>(newWXRange.getFirst() - oldWXRange.getFirst(), // newWZRange.getFirst() - oldWZRange.getFirst()); int oldWX0 = calcWXfromXZ(0,0); int oldWZ0 = calcWZfromXZ(0,0); int newWX0 = newQuadOrientation.calcWXfromXZ(xRelativeOrigin, zRelativeOrigin); int newWZ0 = newQuadOrientation.calcWZfromXZ(xRelativeOrigin, zRelativeOrigin); // System.out.println("old WXZ0 = [" + oldWX0 + ", " + oldWZ0 + "]"); // System.out.println("new WXZ0 = [" + newWX0 + ", " + newWZ0 + "]"); int dWXa = newWX0 - oldWX0; int dWZa = newWZ0 - oldWZ0; // // we also need to make a correction for the change in minWX, minWZ when the quad is rotated by 90 or 270 // // Pair<Integer, Integer> oldWXrange = new Pair<Integer, Integer>(0, xSize - 1); // Pair<Integer, Integer> oldWZrange = new Pair<Integer, Integer>(0, zSize - 1); // getWXZranges(oldWXrange, oldWZrange); // System.out.println("old WXrange = " + oldWXrange); // System.out.println("old WZrange = " + oldWZrange); // // Pair<Integer, Integer> newWXrange = new Pair<Integer, Integer>(0, newQuadOrientation.xSize - 1); // Pair<Integer, Integer> newWZrange = new Pair<Integer, Integer>(0, newQuadOrientation.zSize - 1); // newQuadOrientation.getWXZranges(newWXrange, newWZrange); // System.out.println("new WXrange = " + newWXrange); // System.out.println("new WZrange = " + newWZrange); // // int dWXb = newWXrange.getFirst() - oldWXrange.getFirst(); // int dWZb = newWZrange.getFirst() - oldWZrange.getFirst(); // // System.out.println("dx = dWXa + dWXb = " + dWXa + " + " + dWXb + " = " + (dWXa + dWXb)); // System.out.println("dz = dWZa + dWZb = " + dWZa + " + " + dWZb + " = " + (dWZa + dWZb)); return new Pair<Integer, Integer>(dWXa , dWZa); } private void calculateOriginTransformed() { // algorithm is: // find the centre point, calculate the radius of the origin from the centrepoint (use the centre of the origin block as the origin) // then round the origin back to the bottom left corner again. // if the quad no longer aligns to the grid, i.e. because // a) the quad is rotated by 90 or 270; and // b) xSize is odd and zSize is even (or vica versa); // then nudge down and left by half a block in world coordinates float xRadius = (xSize-1) / 2.0F; float xRotationPoint = xSize / 2.0F; float zRadius = (zSize-1) / 2.0F; float zRotationPoint = zSize / 2.0F; float xNewOrigin = xRotationPoint - xRadius * WX_MULTIPLIER_FROM_X[transformationIndex] - zRadius * WX_MULTIPLIER_FROM_Z[transformationIndex]; float zNewOrigin = zRotationPoint - xRadius * WZ_MULTIPLIER_FROM_X[transformationIndex] - zRadius * WZ_MULTIPLIER_FROM_Z[transformationIndex]; final float ROUND_DELTA = 0.1F; // ensure correct rounding int xRounded = Math.round(xNewOrigin - ROUND_DELTA - 0.5F); // add back the half block to move from centre of origin block to edge; nudge if necessary to align with grid int zRounded = Math.round(zNewOrigin - ROUND_DELTA - 0.5F); wxOriginTransformed = xRounded + wxOrigin; wzOriginTransformed = zRounded + wzOrigin; boolean parityMatches = ((xSize ^ zSize) & 1) == 0; wxNudge = (!parityMatches && ((clockwiseRotationCount & 1) != 0)) ? -0.5F : 0; wzNudge = wxNudge; } // 0 - 3 = rotations, 4 - 7 = flipX followed by rotations (& 0x03) private final int [] WX_MULTIPLIER_FROM_X = { 1, 0, -1, 0, -1, 0, 1, 0}; private final int [] WX_MULTIPLIER_FROM_Z = { 0, -1, 0, 1, 0, -1, 0, 1}; private final int [] WZ_MULTIPLIER_FROM_X = { 0, 1, 0, -1, 0, -1, 0, 1}; private final int [] WZ_MULTIPLIER_FROM_Z = { 1, 0, -1, 0, 1, 0, -1, 0}; private final int [] X_MULTIPLIER_FROM_WX = { 1, 0, -1, 0, -1, 0, 1, 0}; private final int [] X_MULTIPLIER_FROM_WZ = { 0, 1, 0, -1, 0, -1, 0, 1}; private final int [] Z_MULTIPLIER_FROM_WX = { 0, -1, 0, 1, 0, -1, 0, 1}; private final int [] Z_MULTIPLIER_FROM_WZ = { 1, 0, -1, 0, 1, 0, -1, 0}; // the quad is stored so that, when unrotated and unflipped, the world coordinates covered by the quad are // [wxOrigin, wzOrigin] - [wxOrigin + xSize - 1, wzOrigin + zSize - 1] inclusive. // The possible transformations of the quad are: // a) an optional left-right flip (xneg becomes xpos) // followed by // b) an optional number of clockwise rotations (0 - 3 quadrants) // Any transformations applied to the quad will be converted to this format private int wxOrigin; private int wzOrigin; private int xSize; private int zSize; private boolean flippedX; private byte clockwiseRotationCount; // number of quadrants rotated clockwise private int transformationIndex; // combined index for flippedX followed by clockwiseRotationCount private int wxOriginTransformed; private int wzOriginTransformed; private float wxNudge; private float wzNudge; }