@Override
  public ByteBuffer getTile(final int clipmapLevel, final Tile tile) throws Exception {
    final int tileX = tile.getX();
    final int tileY = tile.getY();

    final byte[] heightMap = maps.get(clipmapLevel);
    final int heightMapSize = heightMapSizes.get(clipmapLevel);

    final ByteBuffer data = tileDataPool.get();
    for (int y = 0; y < tileSize; y++) {
      for (int x = 0; x < tileSize; x++) {
        final int index = x + y * tileSize;

        final int heightX = tileX * tileSize + x;
        final int heightY = tileY * tileSize + y;
        if (heightX < 0 || heightX >= heightMapSize || heightY < 0 || heightY >= heightMapSize) {
          data.put(index * 3 + 0, (byte) 0);
          data.put(index * 3 + 1, (byte) 0);
          data.put(index * 3 + 2, (byte) 0);
        } else {
          data.put(index * 3 + 0, heightMap[3 * (heightY * heightMapSize + heightX) + 0]);
          data.put(index * 3 + 1, heightMap[3 * (heightY * heightMapSize + heightX) + 1]);
          data.put(index * 3 + 2, heightMap[3 * (heightY * heightMapSize + heightX) + 2]);
        }
      }
    }
    return data;
  }
  @Override
  public float[] getTile(final int clipmapLevel, final Tile tile) throws Exception {
    final int tileX = tile.getX();
    final int tileY = tile.getY();

    final int baseClipmapLevel = availableClipmapLevels - clipmapLevel - 1;

    final int levelSize = 1 << baseClipmapLevel;

    final int size = inMemoryTerrainData.getSide();

    final float[] heightData = inMemoryTerrainData.getHeightData();

    final float[] data = new float[tileSize * tileSize];
    for (int y = 0; y < tileSize; y++) {
      for (int x = 0; x < tileSize; x++) {
        final int index = x + y * tileSize;

        final int heightX = (tileX * tileSize + x) * levelSize;
        final int heightY = (tileY * tileSize + y) * levelSize;
        data[index] = getHeight(heightData, size, heightX, heightY);
      }
    }
    return data;
  }
  @Override
  public Set<Tile> getInvalidTiles(
      final int clipmapLevel,
      final int tileX,
      final int tileY,
      final int numTilesX,
      final int numTilesY)
      throws Exception {
    final Set<Tile> updatedTiles[] = inMemoryTerrainData.getUpdatedTerrainTiles();
    if (updatedTiles == null) {
      return null;
    }

    final int baseClipmapLevel = availableClipmapLevels - clipmapLevel - 1;

    final Set<Tile> tiles = Sets.newHashSet();

    synchronized (updatedTiles[baseClipmapLevel]) {
      if (updatedTiles[baseClipmapLevel].isEmpty()) {
        return null;
      }

      int checkX, checkY;
      for (final Iterator<Tile> it = updatedTiles[baseClipmapLevel].iterator(); it.hasNext(); ) {
        final Tile tile = it.next();
        checkX = tile.getX();
        checkY = tile.getY();
        if (checkX >= tileX
            && checkX < tileX + numTilesX
            && checkY >= tileY
            && checkY < tileY + numTilesY) {
          tiles.add(tile);
          it.remove();
        }
      }
    }

    return tiles;
  }
  @Override
  public ByteBuffer getTile(final int clipmapLevel, final Tile tile) throws Exception {
    final ByteBuffer data = tileDataPool.get();
    final int tileX = tile.getX();
    final int tileY = tile.getY();

    final int baseClipmapLevel = availableClipmapLevels - clipmapLevel - 1;

    textureLock.lock();
    try {
      for (int y = 0; y < tileSize; y++) {
        for (int x = 0; x < tileSize; x++) {
          if (Thread.interrupted()) {
            return null;
          }

          final int heightX = tileX * tileSize + x;
          final int heightY = tileY * tileSize + y;

          final double eval =
              function.eval(heightX << baseClipmapLevel, heightY << baseClipmapLevel, 0) * 0.4167f
                  + 0.5f;
          final byte colIndex = (byte) (eval * 255);

          final ReadOnlyColorRGBA c = terrainColors[colIndex & 0xFF];

          final int index = (x + y * tileSize) * 3;
          data.put(index, (byte) (c.getRed() * 255));
          data.put(index + 1, (byte) (c.getGreen() * 255));
          data.put(index + 2, (byte) (c.getBlue() * 255));
        }
      }
    } finally {
      textureLock.unlock();
    }
    return data;
  }