Solved Reverting from Anvil world format to support WGenUtils.

Discussion in 'Plugin Development' started by Derthmonuter, Aug 3, 2014.

Thread Status:
Not open for further replies.
  1. Offline

    Derthmonuter

    Hello everyone,

    My work in developing a world generation plugin would be made extraordinarily easier if I were able to use WGenUtils, detailed here. However, it has never been updated to work properly with the post-Anvil map format generation of block sections. Does anyone know of an alternative to WGenUtils (fire and forget functions for generation of caves and ravines), or a way to rework the block section generation to still work with WGenUtils?

    Thanks, any help would be tremendously appreciated.
     
  2. Offline

    pookeythekid

    Since I have no experience of this whatsoever, I cannot help you, but I hope someone responds to this with an answer.
     
  3. Offline

    Derthmonuter

    Well, even if you don't have any experience with world generation specifically, perhaps you could still help me--I think I've found the changes needed to use this library. The new method of generating world uses a 2D array of bytes--the old method used a 1D array. If this 2D byte array could be repacked into a 1D byte array, it could be run through the methods of WGenUtils. Reversing the process and converting back to a 2D array would allow us to generate the world with the modifications needed for it.

    I'm going to begin experimenting.
     
  4. Offline

    fireblast709

    Derthmonuter
    Code:
    y = index % 128
    index -= y
    index /= 128
    z = index % 16
    index -= z
    x = index / 16
    from array index to chunk local coords
     
  5. Offline

    Derthmonuter

    fireblast709
    Are those chunk local coordinates in the newer system or the older one? Because I've managed to take apart the 2D array and stitch it into a 1D, and undo the process, but I'm clearly not constructing my 1D array the way it should be. I'm currently just reattaching the current system of 16 16x16x16 chunks into one large 16x16x256 chunk as if they were all stacked on top of one another.

    Do you know how the old chunks positioned their block coordinates into the 1D array?
     
  6. Offline

    fireblast709

  7. Offline

    Derthmonuter

    fireblast709
    Ah, I see. So this is a handy way to get the old-world coordinates from the array index. Is there an equivalent you know of to get the new local coordinates of one of the 16x16x16 world pieces?

    Basically, I need to know how to undo this operation:
    ((y & 0xF) << 8) | (z << 4) | x
     
  8. Offline

    fireblast709

    Derthmonuter in bits, yyyyzzzzxxxx. So 001001000001 is 1,2+(section*16),4
     
  9. Offline

    Derthmonuter

    fireblast709
    I know absolutely nothing about bitwise operators. I'm reading about them right now in an attempt to properly break that byte apart into the y, z, and x components, but it's definitely a struggle. If you happen to have a grasp of this far better than I do, I'd appreciate you simply throwing the proper unpacking code at me. :)
     
  10. Offline

    fireblast709

    Derthmonuter the first 4 bits can be retrieved with a simple bitwise AND operation on the bitmask 0x0F. If you want the middle set (z), shift 4 to the right (>>) and use the bitwise AND to extract. Likewise for the leftmost group (y), aside that you shift twice (or once 8, obviously). Lastly you need to fix the y, since it is section-local. The first array dimension should be the slice index, so adding 16 * index should give the correct y (16 slices of 16 height, like you already mentioned before)
     
  11. Offline

    Derthmonuter

    fireblast709
    What do I seem to be doing wrong? I want to write some utility classes to assist with this packing and unpacking before I try delving back into the library work.

    Code:java
    1. public class BitPacking {
    2.  
    3. /**
    4.   * @param args the command line arguments
    5.   */
    6. public static void main(String[] args) {
    7. byte[][] chunk = new byte[16][];
    8. Scanner scanner = new Scanner(System.in);
    9. System.out.println("Please enter the x, y, then z coordinates.");
    10. int x = scanner.nextInt();
    11. int y = scanner.nextInt();
    12. int z = scanner.nextInt();
    13.  
    14. if (chunk[y >> 4] == null) {
    15. chunk[y >> 4] = new byte[16 * 16 * 16];
    16. }
    17. // chunk[y >> 4][((y & 0xF) << 8) | (z << 4) | x] = 1;
    18. // System.out.println("Packing a y of " + ((y & 0xF) << 8));
    19. int index = (((y & 0xF) << 8) | (z << 4) | x);
    20. System.out.println("Inner index of " + index);
    21. int unpackedY = index & 0x0f;
    22. int unpackedZ = index >> 4 & 0x0f;
    23. int unpackedX = index >> 8 & 0x0f;
    24.  
    25. System.out.println("Unpacking the information entered...");
    26. System.out.println("Unpacked: " + unpackedX + " " + unpackedY + " " + unpackedZ);
    27. }
    28.  
    29. }


    However, this is giving an unexpected result:
    Please enter the x, y, then z coordinates.
    2
    3
    6
    Inner index of 866
    Unpacking the information entered...
    Unpacked: 3 2 6
    BUILD SUCCESSFUL (total time: 5 seconds)
     
  12. Offline

    fireblast709

    Derthmonuter your unpackedY is actually unpackedX and vice versa. Also try () around the bitshift
     
  13. Offline

    Derthmonuter

    Modified it slightly and now I'm getting the expected results. I know I still haven't handled fixing the y's, but that's not too difficult to do.

    Code:java
    1. public class BitPacking {
    2.  
    3. /**
    4.   * @param args the command line arguments
    5.   */
    6. public static void main(String[] args) {
    7. byte[][] chunk = new byte[16][];
    8. Scanner scanner = new Scanner(System.in);
    9. System.out.println("Please enter the x, y, then z coordinates.");
    10. int x = scanner.nextInt();
    11. int y = scanner.nextInt();
    12. int z = scanner.nextInt();
    13. System.out.println("Working in the " + (y >> 4) + " chunk");
    14. if (chunk[y >> 4] == null) {
    15. chunk[y >> 4] = new byte[16 * 16 * 16];
    16. }
    17. // chunk[y >> 4][((y & 0xF) << 8) | (z << 4) | x] = 1;
    18. // System.out.println("Packing a y of " + ((y & 0xF) << 8));
    19. int index = (((y & 0xF) << 8) | (z << 4) | x);
    20. System.out.println("Inner index of " + index);
    21. int unpackedY = index >> 8 & 0x0f;
    22. int unpackedZ = index >> 4 & 0x0f;
    23. int unpackedX = index & 0x0f;
    24.  
    25. System.out.println("Unpacking the information entered...");
    26. System.out.println("Unpacked: " + unpackedX + " " + unpackedY + " " + unpackedZ);
    27. }
    28.  
    29. }


    So at this point I think I have everything I'll need, just gonna try thinking aloud here. Any input from anyone is welcome to correct me--
    1) I have a 2D array packed in the new way.
    2) I chop this in half to turn it into another 2D array with a 128 maximum height. That's all the old style supported so that's all the library will probably support as well.
    3) I will loop through the entire 2D array, and for each element unpack it to get its 0-15, 0-127, 0-15 chunk coordinates.
    4) I will encode these coordinates in the old style to find their appropriate indices for the 1D array, and then copy the material data from their points in the 2D array into those new indices.
    5) The 1D array will be properly converted to and is ready for use with WGenUtils.
    6) I will decode the 1D array as fireblast709 gave the equations to above.
    7) These will give the chunk coordinates as needed to be re-encoded (with WGenUtil changes) into the new style once more--a 2D array.
    8) This is only the bottom half of the 2D array. It will be reattached to the top 128 blocks it was broken off of in step 2.
    9) Once reassembled, the 2D array will be ready to be passed to further world generation.
    10) I heave a sigh of relief if this all works.

    Alright. I've done everything on my list above and it's not working. I've run into a snag--the new method which generates block sections does just that--it generates sections. My approach needs the entire chunk to be completed before it gets processed by the cave generator.

    Does anyone know of a good approach which would allow me to know for certain that the entire chunk has finished being generated?

    Update: I finally got it all working.
    It required a bit of tweaking of WGenUtils code itself.
    The limitation remains that it can't seem to change any blocks above 128.
    Once I polish it up a bit more I'll see about a release.

    I've bundled what I've created into a little package here for anyone who would like to continue trying to use WGenUtils. What's included in that zip folder is a modified WGenUtils .jar which is hardcoded to use 128 height, rather than break when trying to use 256 height. There is also a copy of the Example.java file shown below.
    Code:java
    1. import java.util.Random;
    2. import org.bukkit.Material;
    3. import org.bukkit.World;
    4. import org.bukkit.generator.ChunkGenerator;
    5. import world.generation.utilities.MapGenBase;
    6. import world.generation.utilities.MapGenCaves;
    7. import world.generation.utilities.MapGenOre;
    8. import world.generation.utilities.MapGenRavine;
    9.  
    10. /**
    11. * A class to demonstrate some features of the recreated WGenUtils.
    12. *
    13. * @author Tim Clancy
    14. * @version 8.5.2014
    15. */
    16. public class Example extends ChunkGenerator {
    17.  
    18. @Override
    19. public byte[][] generateBlockSections(World world, Random random, int chunkX, int chunkZ, BiomeGrid biomeGrid) {
    20. byte[][] chunk = new byte[world.getMaxHeight() / 16][]; //Create the 2D byte array for terrain.
    21. //
    22. //Typically here is where one would do the bulk of terrain generation--such as the creation of the Perlin noise heightmap.
    23. //
    24. //Convert the 2D array to a 1D array so WGenUtils can work.
    25. byte[] singleByte = toSingleByte(chunk);
    26.  
    27. //Generate caves with WGenUtils
    28. MapGenBase cavesGen = new MapGenCaves();
    29. cavesGen.generate(world, chunkX, chunkZ, singleByte);
    30.  
    31. //Generate ravines with WGenUtils
    32. MapGenBase ravineGen = new MapGenRavine();
    33. ravineGen.generate(world, chunkX, chunkZ, singleByte);
    34.  
    35. //Generate ore last
    36. MapGenOre oreGen = new MapGenOre();
    37. oreGen.generate(world, chunkX, chunkZ, singleByte);
    38.  
    39. //Reattach the pieces of array.
    40. byte[][] newBottomHalf = toDoubleByte(singleByte);
    41. for (int i = 0; i < 8; ++i) {
    42. for (int j = 0; j < 4096; ++j) {
    43. if (chunk[I] != null) {[/I]
    44. [I] chunk[I][j] = newBottomHalf[I][j];[/I][/I][/I]
    45. [I] }[/I]
    46. [I] }[/I]
    47. [I] }[/I]
    48.  
    49. [I] //Return the entire 2D array[/I]
    50. [I] return chunk;[/I]
    51. [I] }[/I]
    52.  
    53. [I] //The conversion methods.[/I]
    54. [I] private byte[] toSingleByte(byte[][] chunk) {[/I]
    55. [I] byte[] single = new byte[32768];[/I]
    56. [I] //Only grab up to 128 y to respect old height limit.[/I]
    57. [I] for (int sector = 0; sector < 8; ++sector) {[/I]
    58.  
    59. [I] if (chunk[sector] != null) {[/I]
    60. [I] for (int index = 0; index < 4096; ++index) {[/I]
    61. [I] int y = (index >> 8 & 0x0f) + 16 * sector;[/I]
    62. [I] int z = index >> 4 & 0x0f;[/I]
    63. [I] int x = index & 0x0f;[/I]
    64.  
    65. [I] int oldIndex = (x * 16 + z) * 128 + y;[/I]
    66. [I] single[oldIndex] = chunk[sector][index];[/I]
    67. [I] }[/I]
    68. [I] } else {[/I]
    69. [I] for (int index = 0; index < 4096; ++index) {[/I]
    70. [I] int y = (index >> 8 & 0x0f) + 16 * sector;[/I]
    71. [I] int z = index >> 4 & 0x0f;[/I]
    72. [I] int x = index & 0x0f;[/I]
    73.  
    74. [I] int oldIndex = (x * 16 + z) * 128 + y;[/I]
    75. [I] single[oldIndex] = (byte) Material.AIR.getId();[/I]
    76. [I] }[/I]
    77. [I] }[/I]
    78. [I] }[/I]
    79. [I] return single;[/I]
    80. [I] }[/I]
    81.  
    82. [I] private byte[][] toDoubleByte(byte[] singleByte) {[/I]
    83. [I] byte[][] doubleArray = new byte[8][4096];[/I]
    84. [I] for (int index = 0; index < 32768; ++index) {[/I]
    85. [I] int x;[/I]
    86. [I] int y;[/I]
    87. [I] int z;[/I]
    88.  
    89. [I] int calculationIndex = index;[/I]
    90. [I] y = calculationIndex % 128;[/I]
    91. [I] calculationIndex -= y;[/I]
    92. [I] calculationIndex /= 128;[/I]
    93. [I] z = calculationIndex % 16;[/I]
    94. [I] calculationIndex -= z;[/I]
    95. [I] x = calculationIndex / 16;[/I]
    96.  
    97. [I] if (doubleArray[y >> 4] == null) {[/I]
    98. [I] doubleArray[y >> 4] = new byte[16 * 16 * 16];[/I]
    99. [I] }[/I]
    100. [I] doubleArray[y >> 4][((y & 0xF) << 8) | (z << 4) | x] = singleByte[index];[/I]
    101. [I] }[/I]
    102. [I] return doubleArray;[/I]
    103. [I] }[/I]
    104. [I]}[/I]

    Currently I've only fixed cave, ore, and ravine generation. The caves look pretty good, the ravines are stellar, but the ore generation is lackluster for me. However, it might still work for you because I'm testing all of this on MCPC+ 1.6.4.

    The limitation remains that WGenUtils can only consider blocks below 128 for modification. For most worlds, this is acceptable. A special thanks to fireblast709 for help wrapping my head around the byte-packing nonsense, and a notification to pookeythekid that it's done. At some point I might consider modifying all of the library to revive its other world generation features, but if someone wants to do that before me, one simply needs to go to the Github page, download the source, and replace any instances of "worldObj.getMaxHeight()" with 128 before using with my converters.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Jun 9, 2016
  14. Offline

    pookeythekid

Thread Status:
Not open for further replies.

Share This Page