/** 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);

      for (; n > 0; --n) {
        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;
          laststep = stepx;
          mapiter.stepPosition(laststep);
        }
        /* 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;
          laststep = stepy;
          mapiter.stepPosition(laststep);
          /* If outside 0-127 range */
          if ((y & (~0x7F)) != 0) return;
        }
        /* Else, Z step is next best */
        else {
          z += z_inc;
          t = t_next_z;
          t_next_z += dt_dz;
          laststep = stepz;
          mapiter.stepPosition(laststep);
        }
      }
    }
 /** Process visit of ray to block */
 private final boolean visit_block(
     MapIterator mapiter, HDShaderState[] shaderstate, boolean[] shaderdone) {
   lastblocktypeid = blocktypeid;
   blocktypeid = mapiter.getBlockTypeID();
   if (skiptoair) {
       /* If skipping until we see air */
     if (blocktypeid == 0) /* If air, we're done */ skiptoair = false;
   } else if (nonairhit || (blocktypeid != 0)) {
     switch (blocktypeid) {
       case FENCE_BLKTYPEID: /* Special case for fence - need to fake data so we can render properly */
         blockdata = generateFenceBlockData(mapiter);
         break;
       case CHEST_BLKTYPEID: /* Special case for chest - need to fake data so we can render */
         blockdata = generateChestBlockData(mapiter);
         break;
       case REDSTONE_BLKTYPEID: /* Special case for redstone - fake data for wire pattern */
         blockdata = generateRedstoneWireBlockData(mapiter);
         break;
       default:
         blockdata = mapiter.getBlockData();
         break;
     }
     /* Look up to see if block is modelled */
     short[] model = scalemodels.getScaledModel(blocktypeid, blockdata);
     if (model != null) {
       return handleSubModel(model, shaderstate, shaderdone);
     } else {
       boolean done = true;
       skylevel = emitlevel = -1;
       subalpha = -1;
       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;
     }
   }
   return false;
 }
 /**
  * Generate redstone wire model data: 0 = NSEW wire 1 = NS wire 2 = EW wire 3 = NE wire 4 = NW
  * wire 5 = SE wire 6 = SW wire 7 = NSE wire 8 = NSW wire 9 = NEW wire 10 = SEW wire
  *
  * @param mapiter
  * @return
  */
 private int generateRedstoneWireBlockData(MapIterator mapiter) {
   /* Check adjacent block IDs */
   int ids[] = {
     mapiter.getBlockTypeIDAt(BlockStep.Z_PLUS), /* To west */
     mapiter.getBlockTypeIDAt(BlockStep.X_PLUS), /* To south */
     mapiter.getBlockTypeIDAt(BlockStep.Z_MINUS), /* To east */
     mapiter.getBlockTypeIDAt(BlockStep.X_MINUS)
   }; /* To north */
   int flags = 0;
   for (int i = 0; i < 4; i++) if (ids[i] == REDSTONE_BLKTYPEID) flags |= (1 << i);
   switch (flags) {
     case 0: /* Nothing nearby */
     case 15: /* NSEW */
       return 0; /* NSEW graphic */
     case 2: /* S */
     case 8: /* N */
     case 10: /* NS */
       return 1; /* NS graphic */
     case 1: /* W */
     case 4: /* E */
     case 5: /* EW */
       return 2; /* EW graphic */
     case 12: /* NE */
       return 3;
     case 9: /* NW */
       return 4;
     case 6: /* SE */
       return 5;
     case 3: /* SW */
       return 6;
     case 14: /* NSE */
       return 7;
     case 11: /* NSW */
       return 8;
     case 13: /* NEW */
       return 9;
     case 7: /* SEW */
       return 10;
   }
   return 0;
 }
    /**
     * Generate chest block to drive model selection: 0 = single facing west 1 = single facing south
     * 2 = single facing east 3 = single facing north 4 = left side facing west 5 = left side facing
     * south 6 = left side facing east 7 = left side facing north 8 = right side facing west 9 =
     * right side facing south 10 = right side facing east 11 = right side facing north
     *
     * @param mapiter
     * @return
     */
    private int generateChestBlockData(MapIterator mapiter) {
      ChestData cd = ChestData.SINGLE_WEST; /* Default to single facing west */
      /* Check adjacent block IDs */
      int ids[] = {
        mapiter.getBlockTypeIDAt(BlockStep.Z_PLUS), /* To west */
        mapiter.getBlockTypeIDAt(BlockStep.X_PLUS), /* To south */
        mapiter.getBlockTypeIDAt(BlockStep.Z_MINUS), /* To east */
        mapiter.getBlockTypeIDAt(BlockStep.X_MINUS)
      }; /* To north */
      /* First, check if we're a double - see if any adjacent chests */
      if (ids[0] == CHEST_BLKTYPEID) {
          /* Another to west - assume we face south */
        cd = ChestData.RIGHT_SOUTH; /* We're right side */
      } else if (ids[1] == CHEST_BLKTYPEID) {
          /* Another to south - assume west facing */
        cd = ChestData.LEFT_WEST; /* We're left side */
      } else if (ids[2] == CHEST_BLKTYPEID) {
          /* Another to east - assume south facing */
        cd = ChestData.LEFT_SOUTH; /* We're left side */
      } else if (ids[3] == CHEST_BLKTYPEID) {
          /* Another to north - assume west facing */
        cd = ChestData.RIGHT_WEST; /* We're right side */
      } else {
          /* Else, single - build index into lookup table */
        int idx = 0;
        for (int i = 0; i < ids.length; i++) {
          if ((ids[i] != 0)
              && (HDTextureMap.getTransparency(ids[i]) != BlockTransparency.TRANSPARENT)) {
            idx |= (1 << i);
          }
        }
        cd = SINGLE_LOOKUP[idx];
      }

      return cd.ordinal();
    }
 private int generateFenceBlockData(MapIterator mapiter) {
   int blockdata = 0;
   /* Check north */
   if (mapiter.getBlockTypeIDAt(BlockStep.X_MINUS) == FENCE_BLKTYPEID) {
       /* Fence? */
     blockdata |= 1;
   }
   /* Look east */
   if (mapiter.getBlockTypeIDAt(BlockStep.Z_MINUS) == FENCE_BLKTYPEID) {
       /* Fence? */
     blockdata |= 2;
   }
   /* Look south */
   if (mapiter.getBlockTypeIDAt(BlockStep.X_PLUS) == FENCE_BLKTYPEID) {
       /* Fence? */
     blockdata |= 4;
   }
   /* Look west */
   if (mapiter.getBlockTypeIDAt(BlockStep.Z_PLUS) == FENCE_BLKTYPEID) {
       /* Fence? */
     blockdata |= 8;
   }
   return blockdata;
 }
  protected void scan(
      DynmapWorld world,
      int seq,
      boolean isnether,
      final Color result,
      final Color result_day,
      MapIterator mapiter) {
    int lightlevel = 15;
    int lightlevel_day = 15;
    BiomeMap bio = null;
    double rain = 0.0;
    double temp = 0.0;
    result.setTransparent();
    if (result_day != null) result_day.setTransparent();
    for (; ; ) {
      if (mapiter.getY() < 0) {
        return;
      }
      int id = mapiter.getBlockTypeID();
      int data = 0;
      if (isnether) {
          /* Make bedrock ceiling into air in nether */
        if (id != 0) {
          /* Remember first color we see, in case we wind up solid */
          if (result.isTransparent()) {
            try {
              if (colorScheme.colors[id] != null) {
                result.setColor(colorScheme.colors[id][seq]);
              }
            } catch (ArrayIndexOutOfBoundsException aioobx) {
              colorScheme.resizeColorArray(id);
            }
          }
          id = 0;
        } else isnether = false;
      }
      if (id != 0) {
          /* No update needed for air */
        switch (biomecolored) {
          case NONE:
            try {
              if (colorScheme.datacolors[id] != null) {
                  /* If data colored */
                data = mapiter.getBlockData();
              }
            } catch (ArrayIndexOutOfBoundsException aioobx) {
              colorScheme.resizeColorArray(id);
            }
            break;
          case BIOME:
            bio = mapiter.getBiome();
            break;
          case RAINFALL:
            rain = mapiter.getRawBiomeRainfall();
            break;
          case TEMPERATURE:
            temp = mapiter.getRawBiomeTemperature();
            break;
        }
        if ((shadowscale != null) && (mapiter.getY() < (mapiter.getWorldHeight() - 1))) {
          /* Find light level of previous chunk */
          BlockStep last = mapiter.unstepPosition();
          lightlevel = lightlevel_day = mapiter.getBlockSkyLight();
          if (lightscale != null) lightlevel = lightscale[lightlevel];
          if ((lightlevel < 15) || (lightlevel_day < 15)) {
            int emitted = mapiter.getBlockEmittedLight();
            lightlevel = Math.max(emitted, lightlevel);
            lightlevel_day = Math.max(emitted, lightlevel_day);
          }
          mapiter.stepPosition(last);
        }
      }

      switch (seq) {
        case 0:
          mapiter.stepPosition(BlockStep.X_MINUS);
          break;
        case 1:
        case 3:
          mapiter.stepPosition(BlockStep.Y_MINUS);
          break;
        case 2:
          mapiter.stepPosition(BlockStep.Z_PLUS);
          break;
      }

      seq = (seq + 1) & 3;

      if (id != 0) {
        if (highlightBlocks.contains(id)) {
          result.setColor(highlightColor);
          return;
        }
        Color[] colors = null;
        switch (biomecolored) {
          case NONE:
            try {
              if (data != 0) colors = colorScheme.datacolors[id][data];
              else colors = colorScheme.colors[id];
            } catch (ArrayIndexOutOfBoundsException aioobx) {
              colorScheme.resizeColorArray(id);
            }
            break;
          case BIOME:
            if (bio != null) colors = colorScheme.biomecolors[bio.ordinal()];
            break;
          case RAINFALL:
            colors = colorScheme.getRainColor(rain);
            break;
          case TEMPERATURE:
            colors = colorScheme.getTempColor(temp);
            break;
        }
        if (colors != null) {
          Color c = colors[seq];
          if (c.getAlpha() > 0) {
            /* we found something that isn't transparent, or not doing transparency */
            if ((!transparency) || (c.getAlpha() == 255)) {
              /* it's opaque - the ray ends here */
              result.setARGB(c.getARGB() | 0xFF000000);
              if (lightlevel < 15) {
                  /* Not full light? */
                shadowColor(result, lightlevel);
              }
              if (result_day != null) {
                if (lightlevel_day == lightlevel) /* Same light = same result */
                  result_day.setColor(result);
                else {
                  result_day.setColor(c);
                  if (lightlevel_day < 15) shadowColor(result_day, lightlevel_day);
                }
              }
              return;
            }

            /* this block is transparent, so recurse */
            scan(world, seq, isnether, result, result_day, mapiter);

            int cr = c.getRed();
            int cg = c.getGreen();
            int cb = c.getBlue();
            int ca = c.getAlpha();
            if (lightlevel < 15) {
              int scale = shadowscale[lightlevel];
              cr = (cr * scale) >> 8;
              cg = (cg * scale) >> 8;
              cb = (cb * scale) >> 8;
            }
            cr *= ca;
            cg *= ca;
            cb *= ca;
            int na = 255 - ca;
            result.setRGBA(
                (result.getRed() * na + cr) >> 8,
                (result.getGreen() * na + cg) >> 8,
                (result.getBlue() * na + cb) >> 8,
                255);
            /* Handle day also */
            if (result_day != null) {
              cr = c.getRed();
              cg = c.getGreen();
              cb = c.getBlue();
              if (lightlevel_day < 15) {
                int scale = shadowscale[lightlevel_day];
                cr = (cr * scale) >> 8;
                cg = (cg * scale) >> 8;
                cb = (cb * scale) >> 8;
              }
              cr *= ca;
              cg *= ca;
              cb *= ca;
              result_day.setRGBA(
                  (result_day.getRed() * na + cr) >> 8,
                  (result_day.getGreen() * na + cg) >> 8,
                  (result_day.getBlue() * na + cb) >> 8,
                  255);
            }
            return;
          }
        }
      }
    }
  }
  public boolean render(MapChunkCache cache, KzedMapTile tile, File outputFile) {
    DynmapWorld world = tile.getDynmapWorld();
    boolean isnether = world.isNether();
    DynmapBufferedImage im =
        DynmapBufferedImage.allocateBufferedImage(KzedMap.tileWidth, KzedMap.tileHeight);
    DynmapBufferedImage zim =
        DynmapBufferedImage.allocateBufferedImage(KzedMap.tileWidth / 2, KzedMap.tileHeight / 2);

    DynmapBufferedImage im_day = null;
    DynmapBufferedImage zim_day = null;
    if (night_and_day) {
      im_day = DynmapBufferedImage.allocateBufferedImage(KzedMap.tileWidth, KzedMap.tileHeight);
      zim_day =
          DynmapBufferedImage.allocateBufferedImage(KzedMap.tileWidth / 2, KzedMap.tileHeight / 2);
    }

    if (cache.getWorld().worldheight > 128) {
      if (maximumHeight == 127) maximumHeight = cache.getWorld().worldheight - 1;
    }
    int ix = tile.px / 2 + tile.py / 2 - ((127 - maximumHeight) / 2);
    int iy = maximumHeight;
    int iz = tile.px / 2 - tile.py / 2 + ((127 - maximumHeight) / 2);

    /* Don't mess with existing height-clipped renders */
    if (maximumHeight < 127) isnether = false;

    int jx, jz;

    int x, y;

    MapIterator mapiter = cache.getIterator(ix, iy, iz);

    Color c1 = new Color();
    Color c2 = new Color();
    int[] argb = im.argb_buf;
    int[] zargb = zim.argb_buf;
    Color c1_day = null;
    Color c2_day = null;
    int[] argb_day = null;
    int[] zargb_day = null;
    if (night_and_day) {
      c1_day = new Color();
      c2_day = new Color();
      argb_day = im_day.argb_buf;
      zargb_day = zim_day.argb_buf;
    }
    int rowoff = 0;
    /* draw the map */
    for (y = 0; y < KzedMap.tileHeight; ) {
      jx = ix;
      jz = iz;

      for (x = KzedMap.tileWidth - 1; x >= 0; x -= 2) {
        mapiter.initialize(jx, iy, jz);
        scan(world, 0, isnether, c1, c1_day, mapiter);
        mapiter.initialize(jx, iy, jz);
        scan(world, 2, isnether, c2, c2_day, mapiter);

        argb[rowoff + x] = c1.getARGB();
        argb[rowoff + x - 1] = c2.getARGB();

        if (night_and_day) {
          argb_day[rowoff + x] = c1_day.getARGB();
          argb_day[rowoff + x - 1] = c2_day.getARGB();
        }

        jx++;
        jz++;
      }

      y++;
      rowoff += KzedMap.tileWidth;

      jx = ix;
      jz = iz - 1;

      for (x = KzedMap.tileWidth - 1; x >= 0; x -= 2) {
        mapiter.initialize(jx, iy, jz);
        scan(world, 2, isnether, c1, c1_day, mapiter);
        jx++;
        jz++;
        mapiter.initialize(jx, iy, jz);
        scan(world, 0, isnether, c2, c2_day, mapiter);

        argb[rowoff + x] = c1.getARGB();
        argb[rowoff + x - 1] = c2.getARGB();

        if (night_and_day) {
          argb_day[rowoff + x] = c1_day.getARGB();
          argb_day[rowoff + x - 1] = c2_day.getARGB();
        }
      }
      y++;
      rowoff += KzedMap.tileWidth;

      ix++;
      iz--;
    }
    /* Now, compute zoomed tile - bilinear filter 2x2 -> 1x1 */
    doScaleWithBilinear(argb, zargb, KzedMap.tileWidth, KzedMap.tileHeight);
    if (night_and_day) {
      doScaleWithBilinear(argb_day, zargb_day, KzedMap.tileWidth, KzedMap.tileHeight);
    }

    /* Hand encoding and writing file off to MapManager */
    KzedZoomedMapTile zmtile = new KzedZoomedMapTile(tile.getDynmapWorld(), tile);
    File zoomFile = MapManager.mapman.getTileFile(zmtile);

    return doFileWrites(outputFile, tile, im, im_day, zmtile, zoomFile, zim, zim_day);
  }
 /** Update sky and emitted light */
 private final void updateLightLevel() {
   /* Look up transparency for current block */
   BlockTransparency bt = HDTextureMap.getTransparency(blocktypeid);
   if (bt == BlockTransparency.TRANSPARENT) {
     skylevel = mapiter.getBlockSkyLight();
     emitlevel = mapiter.getBlockEmittedLight();
   } else if (HDTextureMap.getTransparency(lastblocktypeid)
       != BlockTransparency.SEMITRANSPARENT) {
     mapiter.unstepPosition(laststep); /* Back up to block we entered on */
     if (mapiter.getY() < 128) {
       emitlevel = mapiter.getBlockEmittedLight();
       skylevel = mapiter.getBlockSkyLight();
     } else {
       emitlevel = 0;
       skylevel = 15;
     }
     mapiter.stepPosition(laststep);
   } else {
     mapiter.unstepPosition(laststep); /* Back up to block we entered on */
     if (mapiter.getY() < 128) {
       mapiter.stepPosition(BlockStep.Y_PLUS); /* Look above */
       emitlevel = mapiter.getBlockEmittedLight();
       skylevel = mapiter.getBlockSkyLight();
       mapiter.stepPosition(BlockStep.Y_MINUS);
     } else {
       emitlevel = 0;
       skylevel = 15;
     }
     mapiter.stepPosition(laststep);
   }
 }