mirror of
https://github.com/webbukkit/dynmap.git
synced 2024-11-28 21:25:46 +01:00
Add support for non-cube block models, subblock tracing for them
This commit is contained in:
parent
c5c699ae61
commit
b72cc2063a
333
src/main/java/org/dynmap/hdmap/HDBlockModels.java
Normal file
333
src/main/java/org/dynmap/hdmap/HDBlockModels.java
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
package org.dynmap.hdmap;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import org.bukkit.Material;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom block models - used for non-cube blocks to represent the physical volume associated with the block
|
||||||
|
* Used by perspectives to determine if rays have intersected a block that doesn't occupy its whole block
|
||||||
|
*/
|
||||||
|
public class HDBlockModels {
|
||||||
|
private int blockid;
|
||||||
|
private int databits;
|
||||||
|
private long blockflags[];
|
||||||
|
private int nativeres;
|
||||||
|
private HashMap<Integer, short[]> scaledblocks;
|
||||||
|
|
||||||
|
private static HashMap<Integer, HDBlockModels> models_by_id_data = new HashMap<Integer, HDBlockModels>();
|
||||||
|
|
||||||
|
public static class HDScaledBlockModels {
|
||||||
|
private short[][][] modelvectors;
|
||||||
|
|
||||||
|
public final short[] getScaledModel(int blocktype, int blockdata) {
|
||||||
|
if((blocktype > modelvectors.length) || (modelvectors[blocktype] == null) ||
|
||||||
|
(modelvectors[blocktype][blockdata] == null)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return modelvectors[blocktype][blockdata];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HashMap<Integer, HDScaledBlockModels> scaled_models_by_scale = new HashMap<Integer, HDScaledBlockModels>();
|
||||||
|
|
||||||
|
/* Block models */
|
||||||
|
private static HDBlockModels WOOD_STAIR_UP_NORTH = new HDBlockModels(Material.WOOD_STAIRS, 1<<1, 2, new long[] {
|
||||||
|
0x03, 0x03,
|
||||||
|
0x01, 0x01 });
|
||||||
|
private static HDBlockModels WOOD_STAIR_UP_SOUTH = new HDBlockModels(Material.WOOD_STAIRS, 1<<0, 2, new long[] {
|
||||||
|
0x03, 0x03,
|
||||||
|
0x02, 0x02 });
|
||||||
|
private static HDBlockModels WOOD_STAIR_UP_WEST = new HDBlockModels(Material.WOOD_STAIRS, 1<<2, 2, new long[] {
|
||||||
|
0x03, 0x03,
|
||||||
|
0x00, 0x03 });
|
||||||
|
private static HDBlockModels WOOD_STAIR_UP_EAST = new HDBlockModels(Material.WOOD_STAIRS, 1<<3, 2, new long[] {
|
||||||
|
0x03, 0x03,
|
||||||
|
0x03, 0x00 });
|
||||||
|
private static HDBlockModels COBBLE_STAIR_UP_NORTH = new HDBlockModels(Material.COBBLESTONE_STAIRS, 1<<1, WOOD_STAIR_UP_NORTH);
|
||||||
|
private static HDBlockModels COBBLE_STAIR_UP_SOUTH = new HDBlockModels(Material.COBBLESTONE_STAIRS, 1<<0, WOOD_STAIR_UP_SOUTH);
|
||||||
|
private static HDBlockModels COBBLE_STAIR_UP_WEST = new HDBlockModels(Material.COBBLESTONE_STAIRS, 1<<2, WOOD_STAIR_UP_WEST);
|
||||||
|
private static HDBlockModels COBBLE_STAIR_UP_EAST = new HDBlockModels(Material.COBBLESTONE_STAIRS, 1<<3, WOOD_STAIR_UP_EAST);
|
||||||
|
private static HDBlockModels STEP = new HDBlockModels(Material.STEP, 0x0F, 2, new long[] {
|
||||||
|
0x03,
|
||||||
|
0x03 });
|
||||||
|
private static HDBlockModels SNOW = new HDBlockModels(Material.SNOW, 0x0F, 4, new long[] {
|
||||||
|
0x0F, 0x0F, 0x0F, 0x0F });
|
||||||
|
private static HDBlockModels TORCH_UP = new HDBlockModels(Material.TORCH, (1<<5) | (1 << 0), 4, new long[] {
|
||||||
|
0x00, 0x02, 0x00, 0x00,
|
||||||
|
0x00, 0x02, 0x00, 0x00 });
|
||||||
|
private static HDBlockModels TORCH_SOUTH = new HDBlockModels(Material.TORCH, (1<<1), 4, new long[] {
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x01, 0x00, 0x00,
|
||||||
|
0x00, 0x02, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00 });
|
||||||
|
private static HDBlockModels TORCH_NORTH = new HDBlockModels(Material.TORCH, (1<<2), 4, new long[] {
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x08, 0x00,
|
||||||
|
0x00, 0x00, 0x04, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00 });
|
||||||
|
private static HDBlockModels TORCH_WEST = new HDBlockModels(Material.TORCH, (1<<3), 4, new long[] {
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x02, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x02, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00 });
|
||||||
|
private static HDBlockModels TORCH_EAST = new HDBlockModels(Material.TORCH, (1<<4), 4, new long[] {
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x02,
|
||||||
|
0x00, 0x00, 0x02, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00 });
|
||||||
|
private static HDBlockModels REDSTONETORCHON_UP = new HDBlockModels(Material.REDSTONE_TORCH_ON, (1<<5) | (1 << 0), TORCH_UP);
|
||||||
|
private static HDBlockModels REDSTONETORCHOFF_UP = new HDBlockModels(Material.REDSTONE_TORCH_OFF, (1<<5) | (1 << 0), TORCH_UP);
|
||||||
|
private static HDBlockModels REDSTONETORCHON_NORTH = new HDBlockModels(Material.REDSTONE_TORCH_ON, (1<<2), TORCH_NORTH);
|
||||||
|
private static HDBlockModels REDSTONETORCHOFF_NORTH = new HDBlockModels(Material.REDSTONE_TORCH_OFF, (1<<2), TORCH_NORTH);
|
||||||
|
private static HDBlockModels REDSTONETORCHON_SOUTH = new HDBlockModels(Material.REDSTONE_TORCH_ON, (1<<1), TORCH_SOUTH);
|
||||||
|
private static HDBlockModels REDSTONETORCHOFF_SOUTH = new HDBlockModels(Material.REDSTONE_TORCH_OFF, (1<<1), TORCH_SOUTH);
|
||||||
|
private static HDBlockModels REDSTONETORCHON_EAST = new HDBlockModels(Material.REDSTONE_TORCH_ON, (1<<4), TORCH_EAST);
|
||||||
|
private static HDBlockModels REDSTONETORCHOFF_EAST = new HDBlockModels(Material.REDSTONE_TORCH_OFF, (1<<4), TORCH_EAST);
|
||||||
|
private static HDBlockModels REDSTONETORCHON_WEST = new HDBlockModels(Material.REDSTONE_TORCH_ON, (1<<3), TORCH_WEST);
|
||||||
|
private static HDBlockModels REDSTONETORCHOFF_WEST = new HDBlockModels(Material.REDSTONE_TORCH_OFF, (1<<3), TORCH_WEST);
|
||||||
|
private static HDBlockModels FENCE = new HDBlockModels(Material.FENCE, 0xFFFF, 4, new long[] {
|
||||||
|
0x00, 0x06, 0x06, 0x00,
|
||||||
|
0x00, 0x06, 0x06, 0x00,
|
||||||
|
0x00, 0x06, 0x06, 0x00,
|
||||||
|
0x00, 0x06, 0x06, 0x00 });
|
||||||
|
private static HDBlockModels TRAPDOOR = new HDBlockModels(Material.TRAP_DOOR, 0xFFFF, 4, new long[] {
|
||||||
|
0x0F, 0x0F, 0x0F, 0x0F });
|
||||||
|
private static HDBlockModels WOODPRESSPLATE = new HDBlockModels(Material.WOOD_PLATE, 0xFFFF, 4, new long[] {
|
||||||
|
0x0F, 0x0F, 0x0F, 0x0F });
|
||||||
|
private static HDBlockModels STONEPRESSPLATE = new HDBlockModels(Material.STONE_PLATE, 0xFFFF, 4, new long[] {
|
||||||
|
0x0F, 0x0F, 0x0F, 0x0F });
|
||||||
|
private static HDBlockModels WALLSIGN_NORTH = new HDBlockModels(Material.WALL_SIGN, (1<<4), 4, new long[] {
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x08, 0x08, 0x08, 0x08,
|
||||||
|
0x08, 0x08, 0x08, 0x08,
|
||||||
|
0x00, 0x00, 0x00, 0x00 });
|
||||||
|
private static HDBlockModels WALLSIGN_SOUTH = new HDBlockModels(Material.WALL_SIGN, (1<<5), 4, new long[] {
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x01, 0x01, 0x01, 0x01,
|
||||||
|
0x01, 0x01, 0x01, 0x01,
|
||||||
|
0x00, 0x00, 0x00, 0x00 });
|
||||||
|
private static HDBlockModels WALLSIGN_EAST = new HDBlockModels(Material.WALL_SIGN, (1<<2), 4, new long[] {
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x0F,
|
||||||
|
0x00, 0x00, 0x00, 0x0F,
|
||||||
|
0x00, 0x00, 0x00, 0x00 });
|
||||||
|
private static HDBlockModels WALLSIGN_WEST = new HDBlockModels(Material.WALL_SIGN, (1<<3), 4, new long[] {
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x0F, 0x00, 0x00, 0x00,
|
||||||
|
0x0F, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00 });
|
||||||
|
private static HDBlockModels REDSTONEWIRE = new HDBlockModels(Material.REDSTONE_WIRE, 0xFFFF, 8, new long[] {
|
||||||
|
0x18, 0x18, 0x18, 0xFF, 0xFF, 0x18, 0x18, 0x18 });
|
||||||
|
private static HDBlockModels SIGNPOST_EASTWEST = new HDBlockModels(Material.SIGN_POST, 0xC7C7, 8, new long[] {
|
||||||
|
0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
|
||||||
|
private static HDBlockModels SIGHPOST_NORTHSOUTH = new HDBlockModels(Material.SIGN_POST, 0x3838, 8, new long[] {
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
|
||||||
|
0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
|
||||||
|
0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
|
||||||
|
0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block definition - copy from other
|
||||||
|
*/
|
||||||
|
public HDBlockModels(Material blocktype, int databits, HDBlockModels m) {
|
||||||
|
this.blockid = blocktype.getId();
|
||||||
|
this.databits = databits;
|
||||||
|
this.nativeres = m.nativeres;
|
||||||
|
this.blockflags = m.blockflags;
|
||||||
|
for(int i = 0; i < 16; i++) {
|
||||||
|
if((databits & (1<<i)) != 0)
|
||||||
|
models_by_id_data.put((blockid<<4)+i, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Block definition - positions correspond to Bukkit coordinates (+X is south, +Y is up, +Z is west)
|
||||||
|
* @param blockid - block ID
|
||||||
|
* @param databits - bitmap of block data bits matching this model (bit N is set if data=N would match)
|
||||||
|
* @param nativeres - native subblocks per edge of cube (up to 64)
|
||||||
|
* @param blockflags - array of native^2 long integers representing volume of block (bit X of element (nativeres*Y+Z) is set if that subblock is filled)
|
||||||
|
* if array is short, other elements area are assumed to be zero (fills from bottom of block up)
|
||||||
|
*/
|
||||||
|
public HDBlockModels(Material blocktype, int databits, int nativeres, long[] blockflags) {
|
||||||
|
this.blockid = blocktype.getId();
|
||||||
|
this.databits = databits;
|
||||||
|
this.nativeres = nativeres;
|
||||||
|
this.blockflags = new long[nativeres * nativeres];
|
||||||
|
System.arraycopy(blockflags, 0, this.blockflags, 0, blockflags.length);
|
||||||
|
for(int i = 0; i < 16; i++) {
|
||||||
|
if((databits & (1<<i)) != 0)
|
||||||
|
models_by_id_data.put((blockid<<4)+i, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Test if given native block is filled
|
||||||
|
*/
|
||||||
|
public final boolean isSubblockSet(int x, int y, int z) {
|
||||||
|
return ((blockflags[nativeres*y+z] & (1 << x)) != 0);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get scaled map of block: will return array of alpha levels, corresponding to how much of the
|
||||||
|
* scaled subblocks are occupied by the original blocks (indexed by Y*res*res + Z*res + X)
|
||||||
|
* @param res - requested scale (res subblocks per edge of block)
|
||||||
|
* @return array of alpha values (0-255), corresponding to resXresXres subcubes of block
|
||||||
|
*/
|
||||||
|
public short[] getScaledMap(int res) {
|
||||||
|
if(scaledblocks == null) { scaledblocks = new HashMap<Integer, short[]>(); }
|
||||||
|
short[] map = scaledblocks.get(Integer.valueOf(res));
|
||||||
|
if(map == null) {
|
||||||
|
map = new short[res*res*res];
|
||||||
|
if(res == nativeres) {
|
||||||
|
for(int i = 0; i < blockflags.length; i++) {
|
||||||
|
for(int j = 0; j < nativeres; j++) {
|
||||||
|
if((blockflags[i] & (1 << j)) != 0)
|
||||||
|
map[res*i+j] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* If scaling from smaller sub-blocks to larger, each subblock contributes to 1-2 blocks
|
||||||
|
* on each axis: need to calculate crossovers for each, and iterate through smaller
|
||||||
|
* blocks to accumulate contributions
|
||||||
|
*/
|
||||||
|
else if(res > nativeres) {
|
||||||
|
int weights[] = new int[res];
|
||||||
|
int offsets[] = new int[res];
|
||||||
|
/* LCM of resolutions is used as length of line (res * nativeres)
|
||||||
|
* Each native block is (res) long, each scaled block is (nativeres) long
|
||||||
|
* Each scaled block overlaps 1 or 2 native blocks: starting with native block 'offsets[]' with
|
||||||
|
* 'weights[]' of its (res) width in the first, and the rest in the second
|
||||||
|
*/
|
||||||
|
for(int v = 0, idx = 0; v < res*nativeres; v += nativeres, idx++) {
|
||||||
|
offsets[idx] = (v/res); /* Get index of the first native block we draw from */
|
||||||
|
if((v+nativeres-1)/res == offsets[idx]) { /* If scaled block ends in same native block */
|
||||||
|
weights[idx] = nativeres;
|
||||||
|
}
|
||||||
|
else { /* Else, see how much is in first one */
|
||||||
|
weights[idx] = (offsets[idx] + res) - v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Now, use weights and indices to fill in scaled map */
|
||||||
|
for(int y = 0, off = 0; y < res; y++) {
|
||||||
|
int ind_y = offsets[y];
|
||||||
|
int wgt_y = weights[y];
|
||||||
|
for(int z = 0; z < res; z++) {
|
||||||
|
int ind_z = offsets[z];
|
||||||
|
int wgt_z = weights[z];
|
||||||
|
for(int x = 0; x < res; x++, off++) {
|
||||||
|
int ind_x = offsets[x];
|
||||||
|
int wgt_x = weights[x];
|
||||||
|
int raw_w = 0;
|
||||||
|
for(int xx = 0; xx < 2; xx++) {
|
||||||
|
int wx = (xx==0)?wgt_x:(nativeres-wgt_x);
|
||||||
|
if(wx == 0) continue;
|
||||||
|
for(int yy = 0; yy < 2; yy++) {
|
||||||
|
int wy = (yy==0)?wgt_y:(nativeres-wgt_y);
|
||||||
|
if(wy == 0) continue;
|
||||||
|
for(int zz = 0; zz < 2; zz++) {
|
||||||
|
int wz = (zz==0)?wgt_z:(nativeres-wgt_z);
|
||||||
|
if(wz == 0) continue;
|
||||||
|
if(isSubblockSet(ind_x+xx, ind_y+yy, ind_z+zz)) {
|
||||||
|
raw_w += wx*wy*wz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
map[off] = (short)((255*raw_w) / (nativeres*nativeres*nativeres));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { /* nativeres > res */
|
||||||
|
int weights[] = new int[nativeres];
|
||||||
|
int offsets[] = new int[nativeres];
|
||||||
|
/* LCM of resolutions is used as length of line (res * nativeres)
|
||||||
|
* Each native block is (res) long, each scaled block is (nativeres) long
|
||||||
|
* Each native block overlaps 1 or 2 scaled blocks: starting with scaled block 'offsets[]' with
|
||||||
|
* 'weights[]' of its (res) width in the first, and the rest in the second
|
||||||
|
*/
|
||||||
|
for(int v = 0, idx = 0; v < res*nativeres; v += res, idx++) {
|
||||||
|
offsets[idx] = (v/nativeres); /* Get index of the first scaled block we draw to */
|
||||||
|
if((v+res-1)/nativeres == offsets[idx]) { /* If native block ends in same scaled block */
|
||||||
|
weights[idx] = res;
|
||||||
|
}
|
||||||
|
else { /* Else, see how much is in first one */
|
||||||
|
weights[idx] = (offsets[idx] + nativeres) - v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Now, use weights and indices to fill in scaled map */
|
||||||
|
for(int y = 0; y < nativeres; y++) {
|
||||||
|
int ind_y = offsets[y];
|
||||||
|
int wgt_y = weights[y];
|
||||||
|
for(int z = 0; z < nativeres; z++) {
|
||||||
|
int ind_z = offsets[z];
|
||||||
|
int wgt_z = weights[z];
|
||||||
|
for(int x = 0; x < nativeres; x++) {
|
||||||
|
if(isSubblockSet(x, y, z)) {
|
||||||
|
int ind_x = offsets[x];
|
||||||
|
int wgt_x = weights[x];
|
||||||
|
for(int xx = 0; xx < 2; xx++) {
|
||||||
|
int wx = (xx==0)?wgt_x:(res-wgt_x);
|
||||||
|
if(wx == 0) continue;
|
||||||
|
for(int yy = 0; yy < 2; yy++) {
|
||||||
|
int wy = (yy==0)?wgt_y:(res-wgt_y);
|
||||||
|
if(wy == 0) continue;
|
||||||
|
for(int zz = 0; zz < 2; zz++) {
|
||||||
|
int wz = (zz==0)?wgt_z:(res-wgt_z);
|
||||||
|
if(wz == 0) continue;
|
||||||
|
map[(ind_y+yy)*res*res + (ind_z+zz)*res + (ind_x+xx)] +=
|
||||||
|
wx*wy*wz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(int i = 0; i < map.length; i++) {
|
||||||
|
map[i] = (short)(255*map[i]/(nativeres*nativeres*nativeres));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scaledblocks.put(Integer.valueOf(res), map);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get scaled set of models for all modelled blocks
|
||||||
|
* @param scale
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static HDScaledBlockModels getModelsForScale(int scale) {
|
||||||
|
HDScaledBlockModels model = scaled_models_by_scale.get(Integer.valueOf(scale));
|
||||||
|
if(model == null) {
|
||||||
|
model = new HDScaledBlockModels();
|
||||||
|
short[][][] blockmodels = new short[256][][];
|
||||||
|
for(HDBlockModels m : models_by_id_data.values()) {
|
||||||
|
short[][] row = blockmodels[m.blockid];
|
||||||
|
if(row == null) {
|
||||||
|
row = new short[16][];
|
||||||
|
blockmodels[m.blockid] = row;
|
||||||
|
}
|
||||||
|
short[] smod = null;
|
||||||
|
for(int i = 0; i < 16; i++) {
|
||||||
|
if((m.databits & (1 << i)) != 0) {
|
||||||
|
if(smod == null) smod = m.getScaledMap(scale);
|
||||||
|
row[i] = smod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
model.modelvectors = blockmodels;
|
||||||
|
scaled_models_by_scale.put(scale, model);
|
||||||
|
}
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
}
|
@ -49,6 +49,10 @@ public class IsoHDPerspective implements HDPerspective {
|
|||||||
private Matrix3D world_to_map;
|
private Matrix3D world_to_map;
|
||||||
private Matrix3D map_to_world;
|
private Matrix3D map_to_world;
|
||||||
|
|
||||||
|
/* Scaled models for non-cube blocks */
|
||||||
|
private HDBlockModels.HDScaledBlockModels scalemodels;
|
||||||
|
private int modscale;
|
||||||
|
|
||||||
/* dimensions of a map tile */
|
/* dimensions of a map tile */
|
||||||
public static final int tileWidth = 128;
|
public static final int tileWidth = 128;
|
||||||
public static final int tileHeight = 128;
|
public static final int tileHeight = 128;
|
||||||
@ -74,6 +78,14 @@ public class IsoHDPerspective implements HDPerspective {
|
|||||||
Vector3D top, bottom;
|
Vector3D top, bottom;
|
||||||
int px, py;
|
int px, py;
|
||||||
BlockStep laststep = BlockStep.Y_MINUS;
|
BlockStep laststep = BlockStep.Y_MINUS;
|
||||||
|
/* Raytrace state variables */
|
||||||
|
double dx, dy, dz;
|
||||||
|
int x, y, z;
|
||||||
|
double dt_dx, dt_dy, dt_dz, t;
|
||||||
|
int n;
|
||||||
|
int x_inc, y_inc, z_inc;
|
||||||
|
double t_next_y, t_next_x, t_next_z;
|
||||||
|
boolean nonairhit;
|
||||||
/**
|
/**
|
||||||
* Get sky light level - only available if shader requested it
|
* Get sky light level - only available if shader requested it
|
||||||
*/
|
*/
|
||||||
@ -115,6 +127,258 @@ public class IsoHDPerspective implements HDPerspective {
|
|||||||
*/
|
*/
|
||||||
public final int getPixelY() { return py; }
|
public final int getPixelY() { return py; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize raytrace state variables
|
||||||
|
*/
|
||||||
|
private void raytrace_init() {
|
||||||
|
/* Compute total delta on each axis */
|
||||||
|
dx = Math.abs(bottom.x - top.x);
|
||||||
|
dy = Math.abs(bottom.y - top.y);
|
||||||
|
dz = Math.abs(bottom.z - top.z);
|
||||||
|
/* Initial block coord */
|
||||||
|
x = (int) (Math.floor(top.x));
|
||||||
|
y = (int) (Math.floor(top.y));
|
||||||
|
z = (int) (Math.floor(top.z));
|
||||||
|
/* Compute parametric step (dt) per step on each axis */
|
||||||
|
dt_dx = 1.0 / dx;
|
||||||
|
dt_dy = 1.0 / dy;
|
||||||
|
dt_dz = 1.0 / dz;
|
||||||
|
/* Initialize parametric value to 0 (and we're stepping towards 1) */
|
||||||
|
t = 0;
|
||||||
|
/* Compute number of steps and increments for each */
|
||||||
|
n = 1;
|
||||||
|
|
||||||
|
/* If perpendicular to X axis */
|
||||||
|
if (dx == 0) {
|
||||||
|
x_inc = 0;
|
||||||
|
t_next_x = Double.MAX_VALUE;
|
||||||
|
}
|
||||||
|
/* If bottom is right of top */
|
||||||
|
else if (bottom.x > top.x) {
|
||||||
|
x_inc = 1;
|
||||||
|
n += (int) (Math.floor(bottom.x)) - x;
|
||||||
|
t_next_x = (Math.floor(top.x) + 1 - top.x) * dt_dx;
|
||||||
|
}
|
||||||
|
/* Top is right of bottom */
|
||||||
|
else {
|
||||||
|
x_inc = -1;
|
||||||
|
n += x - (int) (Math.floor(bottom.x));
|
||||||
|
t_next_x = (top.x - Math.floor(top.x)) * dt_dx;
|
||||||
|
}
|
||||||
|
/* If perpendicular to Y axis */
|
||||||
|
if (dy == 0) {
|
||||||
|
y_inc = 0;
|
||||||
|
t_next_y = Double.MAX_VALUE;
|
||||||
|
}
|
||||||
|
/* If bottom is above top */
|
||||||
|
else if (bottom.y > top.y) {
|
||||||
|
y_inc = 1;
|
||||||
|
n += (int) (Math.floor(bottom.y)) - y;
|
||||||
|
t_next_y = (Math.floor(top.y) + 1 - top.y) * dt_dy;
|
||||||
|
}
|
||||||
|
/* If top is above bottom */
|
||||||
|
else {
|
||||||
|
y_inc = -1;
|
||||||
|
n += y - (int) (Math.floor(bottom.y));
|
||||||
|
t_next_y = (top.y - Math.floor(top.y)) * dt_dy;
|
||||||
|
}
|
||||||
|
/* If perpendicular to Z axis */
|
||||||
|
if (dz == 0) {
|
||||||
|
z_inc = 0;
|
||||||
|
t_next_z = Double.MAX_VALUE;
|
||||||
|
}
|
||||||
|
/* If bottom right of top */
|
||||||
|
else if (bottom.z > top.z) {
|
||||||
|
z_inc = 1;
|
||||||
|
n += (int) (Math.floor(bottom.z)) - z;
|
||||||
|
t_next_z = (Math.floor(top.z) + 1 - top.z) * dt_dz;
|
||||||
|
}
|
||||||
|
/* If bottom left of top */
|
||||||
|
else {
|
||||||
|
z_inc = -1;
|
||||||
|
n += z - (int) (Math.floor(bottom.z));
|
||||||
|
t_next_z = (top.z - Math.floor(top.z)) * dt_dz;
|
||||||
|
}
|
||||||
|
/* Walk through scene */
|
||||||
|
laststep = BlockStep.Y_MINUS; /* Last step is down into map */
|
||||||
|
skylightlevel = 15;
|
||||||
|
emittedlightlevel = 0;
|
||||||
|
nonairhit = false;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Process visit of ray to block
|
||||||
|
*/
|
||||||
|
private boolean visit_block(MapIterator mapiter, HDShaderState[] shaderstate, boolean[] shaderdone) {
|
||||||
|
blocktypeid = mapiter.getBlockTypeID();
|
||||||
|
if(nonairhit || (blocktypeid != 0)) {
|
||||||
|
blockdata = mapiter.getBlockData();
|
||||||
|
boolean missed = false;
|
||||||
|
/* Look up to see if block is modelled */
|
||||||
|
short[] model = scalemodels.getScaledModel(blocktypeid, blockdata);
|
||||||
|
if(model != null) {
|
||||||
|
missed = raytraceSubblock(model);
|
||||||
|
}
|
||||||
|
if(!missed) {
|
||||||
|
boolean done = true;
|
||||||
|
for(int i = 0; i < shaderstate.length; i++) {
|
||||||
|
if(!shaderdone[i])
|
||||||
|
shaderdone[i] = shaderstate[i].processBlock(this);
|
||||||
|
done = done && shaderdone[i];
|
||||||
|
}
|
||||||
|
/* If all are done, we're out */
|
||||||
|
if(done)
|
||||||
|
return true;
|
||||||
|
nonairhit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(need_skylightlevel)
|
||||||
|
skylightlevel = mapiter.getBlockSkyLight();
|
||||||
|
if(need_emittedlightlevel)
|
||||||
|
emittedlightlevel = mapiter.getBlockEmittedLight();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Trace ray, based on "Voxel Tranversal along a 3D line"
|
||||||
|
*/
|
||||||
|
private void raytrace(MapChunkCache cache, MapIterator mapiter, HDShaderState[] shaderstate, boolean[] shaderdone) {
|
||||||
|
/* Initialize raytrace state variables */
|
||||||
|
raytrace_init();
|
||||||
|
|
||||||
|
mapiter.initialize(x, y, z);
|
||||||
|
|
||||||
|
boolean nonairhit = false;
|
||||||
|
for (; n > 0; --n) {
|
||||||
|
/* Visit block */
|
||||||
|
if(visit_block(mapiter, shaderstate, shaderdone)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* If X step is next best */
|
||||||
|
if((t_next_x <= t_next_y) && (t_next_x <= t_next_z)) {
|
||||||
|
x += x_inc;
|
||||||
|
t = t_next_x;
|
||||||
|
t_next_x += dt_dx;
|
||||||
|
if(x_inc > 0) {
|
||||||
|
laststep = BlockStep.X_PLUS;
|
||||||
|
mapiter.incrementX();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
laststep = BlockStep.X_MINUS;
|
||||||
|
mapiter.decrementX();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* If Y step is next best */
|
||||||
|
else if((t_next_y <= t_next_x) && (t_next_y <= t_next_z)) {
|
||||||
|
y += y_inc;
|
||||||
|
t = t_next_y;
|
||||||
|
t_next_y += dt_dy;
|
||||||
|
if(y_inc > 0) {
|
||||||
|
laststep = BlockStep.Y_PLUS;
|
||||||
|
mapiter.incrementY();
|
||||||
|
if(mapiter.getY() > 127)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
laststep = BlockStep.Y_MINUS;
|
||||||
|
mapiter.decrementY();
|
||||||
|
if(mapiter.getY() < 0)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Else, Z step is next best */
|
||||||
|
else {
|
||||||
|
z += z_inc;
|
||||||
|
t = t_next_z;
|
||||||
|
t_next_z += dt_dz;
|
||||||
|
if(z_inc > 0) {
|
||||||
|
laststep = BlockStep.Z_PLUS;
|
||||||
|
mapiter.incrementZ();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
laststep = BlockStep.Z_MINUS;
|
||||||
|
mapiter.decrementZ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean raytraceSubblock(short[] model) {
|
||||||
|
int mx = 0, my = 0, mz = 0;
|
||||||
|
double xx, yy, zz;
|
||||||
|
double mt = t + 0.00000001;
|
||||||
|
xx = top.x + mt *(bottom.x - top.x);
|
||||||
|
yy = top.y + mt *(bottom.y - top.y);
|
||||||
|
zz = top.z + mt *(bottom.z - top.z);
|
||||||
|
mx = (int)((xx - Math.floor(xx)) * modscale);
|
||||||
|
my = (int)((yy - Math.floor(yy)) * modscale);
|
||||||
|
mz = (int)((zz - Math.floor(zz)) * modscale);
|
||||||
|
double mdt_dx = dt_dx / modscale;
|
||||||
|
double mdt_dy = dt_dy / modscale;
|
||||||
|
double mdt_dz = dt_dz / modscale;
|
||||||
|
double togo;
|
||||||
|
double mt_next_x = t_next_x, mt_next_y = t_next_y, mt_next_z = t_next_z;
|
||||||
|
if(mt_next_x != Double.MAX_VALUE) {
|
||||||
|
togo = ((t_next_x - t) / mdt_dx);
|
||||||
|
mt_next_x = mt + (togo - Math.floor(togo)) * mdt_dx;
|
||||||
|
}
|
||||||
|
if(mt_next_y != Double.MAX_VALUE) {
|
||||||
|
togo = ((t_next_y - t) / mdt_dy);
|
||||||
|
mt_next_y = mt + (togo - Math.floor(togo)) * mdt_dy;
|
||||||
|
}
|
||||||
|
if(mt_next_z != Double.MAX_VALUE) {
|
||||||
|
togo = ((t_next_z - t) / mdt_dz);
|
||||||
|
mt_next_z = mt + (togo - Math.floor(togo)) * mdt_dz;
|
||||||
|
}
|
||||||
|
double mtend = Math.min(t_next_x, Math.min(t_next_y, t_next_z));
|
||||||
|
while(mt < mtend) {
|
||||||
|
if(model[modscale*modscale*my + modscale*mz + mx] > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/* If X step is next best */
|
||||||
|
if((mt_next_x <= mt_next_y) && (mt_next_x <= mt_next_z)) {
|
||||||
|
mx += x_inc;
|
||||||
|
mt = mt_next_x;
|
||||||
|
mt_next_x += mdt_dx;
|
||||||
|
if(x_inc > 0) {
|
||||||
|
laststep = BlockStep.X_PLUS;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
laststep = BlockStep.X_MINUS;
|
||||||
|
if(mx < 0)
|
||||||
|
mx += modscale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* If Y step is next best */
|
||||||
|
else if((mt_next_y <= mt_next_x) && (mt_next_y <= mt_next_z)) {
|
||||||
|
my += y_inc;
|
||||||
|
mt = mt_next_y;
|
||||||
|
mt_next_y += mdt_dy;
|
||||||
|
if(y_inc > 0) {
|
||||||
|
laststep = BlockStep.Y_PLUS;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
laststep = BlockStep.Y_MINUS;
|
||||||
|
if(my < 0)
|
||||||
|
my += modscale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Else, Z step is next best */
|
||||||
|
else {
|
||||||
|
mz += z_inc;
|
||||||
|
mt = mt_next_z;
|
||||||
|
mt_next_z += mdt_dz;
|
||||||
|
if(z_inc > 0) {
|
||||||
|
laststep = BlockStep.Z_PLUS;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
laststep = BlockStep.Z_MINUS;
|
||||||
|
if(mz < 0)
|
||||||
|
mz += modscale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IsoHDPerspective(ConfigurationNode configuration) {
|
public IsoHDPerspective(ConfigurationNode configuration) {
|
||||||
@ -130,7 +394,6 @@ public class IsoHDPerspective implements HDPerspective {
|
|||||||
scale = configuration.getDouble("scale", MIN_SCALE);
|
scale = configuration.getDouble("scale", MIN_SCALE);
|
||||||
if(scale < MIN_SCALE) scale = MIN_SCALE;
|
if(scale < MIN_SCALE) scale = MIN_SCALE;
|
||||||
if(scale > MAX_SCALE) scale = MAX_SCALE;
|
if(scale > MAX_SCALE) scale = MAX_SCALE;
|
||||||
Log.info("azimuth=" + azimuth + ", inclination=" + inclination + ", scale=" + scale);
|
|
||||||
|
|
||||||
/* Generate transform matrix for world-to-tile coordinate mapping */
|
/* Generate transform matrix for world-to-tile coordinate mapping */
|
||||||
/* First, need to fix basic coordinate mismatches before rotation - we want zero azimuth to have north to top
|
/* First, need to fix basic coordinate mismatches before rotation - we want zero azimuth to have north to top
|
||||||
@ -155,6 +418,9 @@ public class IsoHDPerspective implements HDPerspective {
|
|||||||
Matrix3D coordswap = new Matrix3D(0.0, -1.0, 0.0, 0.0, 0.0, 1.0, -1.0, 0.0, 0.0);
|
Matrix3D coordswap = new Matrix3D(0.0, -1.0, 0.0, 0.0, 0.0, 1.0, -1.0, 0.0, 0.0);
|
||||||
transform.multiply(coordswap);
|
transform.multiply(coordswap);
|
||||||
map_to_world = transform;
|
map_to_world = transform;
|
||||||
|
/* Scaled models for non-cube blocks */
|
||||||
|
modscale = (int)Math.ceil(scale);
|
||||||
|
scalemodels = HDBlockModels.getModelsForScale(modscale);;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -397,7 +663,7 @@ public class IsoHDPerspective implements HDPerspective {
|
|||||||
for(int i = 0; i < numshaders; i++) {
|
for(int i = 0; i < numshaders; i++) {
|
||||||
shaderstate[i].reset(ps);
|
shaderstate[i].reset(ps);
|
||||||
}
|
}
|
||||||
raytrace(cache, mapiter, ps, shaderstate, shaderdone);
|
ps.raytrace(cache, mapiter, shaderstate, shaderdone);
|
||||||
for(int i = 0; i < numshaders; i++) {
|
for(int i = 0; i < numshaders; i++) {
|
||||||
if(shaderdone[i] == false) {
|
if(shaderdone[i] == false) {
|
||||||
shaderstate[i].rayFinished(ps);
|
shaderstate[i].rayFinished(ps);
|
||||||
@ -492,157 +758,6 @@ public class IsoHDPerspective implements HDPerspective {
|
|||||||
}
|
}
|
||||||
return renderone;
|
return renderone;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Trace ray, based on "Voxel Tranversal along a 3D line"
|
|
||||||
*/
|
|
||||||
private void raytrace(MapChunkCache cache, MapIterator mapiter, OurPerspectiveState ps,
|
|
||||||
HDShaderState[] shaderstate, boolean[] shaderdone) {
|
|
||||||
Vector3D top = ps.top;
|
|
||||||
Vector3D bottom = ps.bottom;
|
|
||||||
/* Compute total delta on each axis */
|
|
||||||
double dx = Math.abs(bottom.x - top.x);
|
|
||||||
double dy = Math.abs(bottom.y - top.y);
|
|
||||||
double dz = Math.abs(bottom.z - top.z);
|
|
||||||
/* Initial block coord */
|
|
||||||
int x = (int) (Math.floor(top.x));
|
|
||||||
int y = (int) (Math.floor(top.y));
|
|
||||||
int z = (int) (Math.floor(top.z));
|
|
||||||
/* Compute parametric step (dt) per step on each axis */
|
|
||||||
double dt_dx = 1.0 / dx;
|
|
||||||
double dt_dy = 1.0 / dy;
|
|
||||||
double dt_dz = 1.0 / dz;
|
|
||||||
/* Initialize parametric value to 0 (and we're stepping towards 1) */
|
|
||||||
double t = 0;
|
|
||||||
/* Compute number of steps and increments for each */
|
|
||||||
int n = 1;
|
|
||||||
int x_inc, y_inc, z_inc;
|
|
||||||
|
|
||||||
double t_next_y, t_next_x, t_next_z;
|
|
||||||
/* If perpendicular to X axis */
|
|
||||||
if (dx == 0) {
|
|
||||||
x_inc = 0;
|
|
||||||
t_next_x = Double.MAX_VALUE;
|
|
||||||
}
|
|
||||||
/* If bottom is right of top */
|
|
||||||
else if (bottom.x > top.x) {
|
|
||||||
x_inc = 1;
|
|
||||||
n += (int) (Math.floor(bottom.x)) - x;
|
|
||||||
t_next_x = (Math.floor(top.x) + 1 - top.x) * dt_dx;
|
|
||||||
}
|
|
||||||
/* Top is right of bottom */
|
|
||||||
else {
|
|
||||||
x_inc = -1;
|
|
||||||
n += x - (int) (Math.floor(bottom.x));
|
|
||||||
t_next_x = (top.x - Math.floor(top.x)) * dt_dx;
|
|
||||||
}
|
|
||||||
/* If perpendicular to Y axis */
|
|
||||||
if (dy == 0) {
|
|
||||||
y_inc = 0;
|
|
||||||
t_next_y = Double.MAX_VALUE;
|
|
||||||
}
|
|
||||||
/* If bottom is above top */
|
|
||||||
else if (bottom.y > top.y) {
|
|
||||||
y_inc = 1;
|
|
||||||
n += (int) (Math.floor(bottom.y)) - y;
|
|
||||||
t_next_y = (Math.floor(top.y) + 1 - top.y) * dt_dy;
|
|
||||||
}
|
|
||||||
/* If top is above bottom */
|
|
||||||
else {
|
|
||||||
y_inc = -1;
|
|
||||||
n += y - (int) (Math.floor(bottom.y));
|
|
||||||
t_next_y = (top.y - Math.floor(top.y)) * dt_dy;
|
|
||||||
}
|
|
||||||
/* If perpendicular to Z axis */
|
|
||||||
if (dz == 0) {
|
|
||||||
z_inc = 0;
|
|
||||||
t_next_z = Double.MAX_VALUE;
|
|
||||||
}
|
|
||||||
/* If bottom right of top */
|
|
||||||
else if (bottom.z > top.z) {
|
|
||||||
z_inc = 1;
|
|
||||||
n += (int) (Math.floor(bottom.z)) - z;
|
|
||||||
t_next_z = (Math.floor(top.z) + 1 - top.z) * dt_dz;
|
|
||||||
}
|
|
||||||
/* If bottom left of top */
|
|
||||||
else {
|
|
||||||
z_inc = -1;
|
|
||||||
n += z - (int) (Math.floor(bottom.z));
|
|
||||||
t_next_z = (top.z - Math.floor(top.z)) * dt_dz;
|
|
||||||
}
|
|
||||||
/* Walk through scene */
|
|
||||||
ps.laststep = BlockStep.Y_MINUS; /* Last step is down into map */
|
|
||||||
mapiter.initialize(x, y, z);
|
|
||||||
ps.skylightlevel = 15;
|
|
||||||
ps.emittedlightlevel = 0;
|
|
||||||
boolean nonairhit = false;
|
|
||||||
for (; n > 0; --n) {
|
|
||||||
ps.blocktypeid = mapiter.getBlockTypeID();
|
|
||||||
if(nonairhit || (ps.blocktypeid != 0)) {
|
|
||||||
ps.blockdata = mapiter.getBlockData();
|
|
||||||
boolean done = true;
|
|
||||||
for(int i = 0; i < shaderstate.length; i++) {
|
|
||||||
if(!shaderdone[i])
|
|
||||||
shaderdone[i] = shaderstate[i].processBlock(ps);
|
|
||||||
done = done && shaderdone[i];
|
|
||||||
}
|
|
||||||
/* If all are done, we're out */
|
|
||||||
if(done)
|
|
||||||
return;
|
|
||||||
nonairhit = true;
|
|
||||||
}
|
|
||||||
if(need_skylightlevel)
|
|
||||||
ps.skylightlevel = mapiter.getBlockSkyLight();
|
|
||||||
if(need_emittedlightlevel)
|
|
||||||
ps.emittedlightlevel = mapiter.getBlockEmittedLight();
|
|
||||||
/* If X step is next best */
|
|
||||||
if((t_next_x <= t_next_y) && (t_next_x <= t_next_z)) {
|
|
||||||
x += x_inc;
|
|
||||||
t = t_next_x;
|
|
||||||
t_next_x += dt_dx;
|
|
||||||
if(x_inc > 0) {
|
|
||||||
ps.laststep = BlockStep.X_PLUS;
|
|
||||||
mapiter.incrementX();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ps.laststep = BlockStep.X_MINUS;
|
|
||||||
mapiter.decrementX();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* If Y step is next best */
|
|
||||||
else if((t_next_y <= t_next_x) && (t_next_y <= t_next_z)) {
|
|
||||||
y += y_inc;
|
|
||||||
t = t_next_y;
|
|
||||||
t_next_y += dt_dy;
|
|
||||||
if(y_inc > 0) {
|
|
||||||
ps.laststep = BlockStep.Y_PLUS;
|
|
||||||
mapiter.incrementY();
|
|
||||||
if(mapiter.getY() > 127)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ps.laststep = BlockStep.Y_MINUS;
|
|
||||||
mapiter.decrementY();
|
|
||||||
if(mapiter.getY() < 0)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Else, Z step is next best */
|
|
||||||
else {
|
|
||||||
z += z_inc;
|
|
||||||
t = t_next_z;
|
|
||||||
t_next_z += dt_dz;
|
|
||||||
if(z_inc > 0) {
|
|
||||||
ps.laststep = BlockStep.Z_PLUS;
|
|
||||||
mapiter.incrementZ();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ps.laststep = BlockStep.Z_MINUS;
|
|
||||||
mapiter.decrementZ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isBiomeDataNeeded() {
|
public boolean isBiomeDataNeeded() {
|
||||||
|
Loading…
Reference in New Issue
Block a user