public void setZoom(int zoom) {
   this.zoom = zoom;
   scale = (int) Math.pow(2.0, Math.abs(zoom - 1));
   //        System.out.println("Zoom " + zoom + " -> scale " + scale);
   int width = dimension.getWidth() * TILE_SIZE + dimension.getHeight() * TILE_SIZE;
   int height = width / 2 + maxHeight - 1;
   java.awt.Dimension preferredSize = zoom(new java.awt.Dimension(width, height));
   setPreferredSize(preferredSize);
   setMinimumSize(preferredSize);
   setMaximumSize(preferredSize);
   setSize(preferredSize);
   repaint();
 }
 @Override
 protected void paintComponent(Graphics g) {
   //        System.out.println("Drawing");
   Graphics2D g2 = (Graphics2D) g;
   if (zoom != 1) {
     double scaleFactor = Math.pow(2.0, zoom - 1);
     //            System.out.println("Scaling with factor " + scaleFactor);
     g2.scale(scaleFactor, scaleFactor);
     if (zoom > 1) {
       g2.setRenderingHint(
           RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
     } else {
       g2.setRenderingHint(
           RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
     }
   }
   if (upsideDown) {
     g2.scale(1.0, -1.0);
     g2.translate(0, -getHeight());
   }
   Rectangle visibleRect = unzoom(getVisibleRect());
   //        System.out.println("Unzoomed visible rectangle: " + visibleRect);
   int centerX = visibleRect.x + visibleRect.width / 2;
   int centerY = visibleRect.y + visibleRect.height / 2 + waterLevel;
   Tile mostCentredTile = null;
   int smallestDistance = Integer.MAX_VALUE;
   Rectangle clipBounds = g.getClipBounds();
   for (Tile tile : zSortedTiles) {
     Rectangle tileBounds = getTileBounds(tile.getX(), tile.getY());
     //            System.out.print("Tile bounds: " + tileBounds);
     if (tileBounds.intersects(clipBounds)) {
       //                System.out.println(" intersects");
       int dx = tileBounds.x + tileBounds.width / 2 - centerX;
       int dy = tileBounds.y + tileBounds.height - TILE_SIZE / 2 - centerY;
       int dist = (int) Math.sqrt((dx * dx) + (dy * dy));
       if (dist < smallestDistance) {
         smallestDistance = dist;
         mostCentredTile = tile;
       }
       BufferedImage tileImg = renderedTiles.get(tile);
       if (tileImg != null) {
         g.drawImage(tileImg, tileBounds.x, tileBounds.y, null);
       } else {
         tilesWaitingToBeRendered.add(0, tile);
       }
       //            } else {
       //                System.out.println(" does NOT intersect");
     }
   }
   if (mostCentredTile != null) {
     centreTile = new Point(mostCentredTile.getX(), mostCentredTile.getY());
   }
   if (highlightTile != null) {
     g.setColor(Color.RED);
     Rectangle rect = getTileBounds(highlightTile.x, highlightTile.y);
     g.drawRect(rect.x, rect.y, rect.width, rect.height);
   }
   if (highlightPoint != null) {
     g.setColor(Color.RED);
     g.drawLine(highlightPoint.x - 2, highlightPoint.y, highlightPoint.x + 2, highlightPoint.y);
     g.drawLine(highlightPoint.x, highlightPoint.y - 2, highlightPoint.x, highlightPoint.y + 2);
   }
   //        for (Map.Entry<Point, BufferedImage> entry: renderedTiles.entrySet()) {
   //            Point tileCoords = entry.getKey();
   //            BufferedImage tileImg = entry.getValue();
   //            Rectangle tileBounds = getTileBounds(tileCoords.x, tileCoords.y);
   //            if (tileBounds.intersects(clipBounds)) {
   //                g.drawImage(tileImg, tileBounds.x, tileBounds.y, null);
   ////                g.setColor(Color.RED);
   ////                g.drawRect(tileBounds.x, tileBounds.y, tileBounds.width, tileBounds.height);
   //            }
   //        }
 }
  public ThreeDeeView(
      Dimension dimension,
      ColourScheme colourScheme,
      BiomeScheme biomeScheme,
      CustomBiomeManager customBiomeManager,
      int rotation,
      int zoom) {
    this.dimension = dimension;
    this.colourScheme = colourScheme;
    this.biomeScheme = biomeScheme;
    this.customBiomeManager = customBiomeManager;
    this.rotation = rotation;
    this.zoom = zoom;
    scale = (int) Math.pow(2.0, Math.abs(zoom - 1));
    //        System.out.println("Zoom " + zoom + " -> scale " + scale);
    maxHeight = dimension.getMaxHeight();
    if (dimension.getTileFactory() instanceof HeightMapTileFactory) {
      waterLevel = ((HeightMapTileFactory) dimension.getTileFactory()).getWaterHeight();
    } else {
      waterLevel = maxHeight / 2;
    }
    upsideDown = dimension.getDim() < 0; // Ceiling dimension
    switch (rotation) {
      case 0:
        zSortedTiles =
            new TreeSet<>(
                (t1, t2) -> {
                  if (t1.getY() != t2.getY()) {
                    return t1.getY() - t2.getY();
                  } else {
                    return t1.getX() - t2.getX();
                  }
                });
        break;
      case 1:
        zSortedTiles =
            new TreeSet<>(
                (t1, t2) -> {
                  if (t1.getX() != t2.getX()) {
                    return t1.getX() - t2.getX();
                  } else {
                    return t2.getY() - t1.getY();
                  }
                });
        break;
      case 2:
        zSortedTiles =
            new TreeSet<>(
                (t1, t2) -> {
                  if (t1.getY() != t2.getY()) {
                    return t2.getY() - t1.getY();
                  } else {
                    return t2.getX() - t1.getX();
                  }
                });
        break;
      case 3:
        zSortedTiles =
            new TreeSet<>(
                (t1, t2) -> {
                  if (t1.getX() != t2.getX()) {
                    return t2.getX() - t1.getX();
                  } else {
                    return t1.getY() - t2.getY();
                  }
                });
        break;
      default:
        throw new IllegalArgumentException();
    }
    zSortedTiles.addAll(dimension.getTiles());
    threeDeeRenderManager =
        new ThreeDeeRenderManager(
            dimension, colourScheme, biomeScheme, customBiomeManager, rotation);

    dimension.addDimensionListener(this);
    for (Tile tile : dimension.getTiles()) {
      tile.addListener(this);
    }

    int width = dimension.getWidth() * TILE_SIZE + dimension.getHeight() * TILE_SIZE;
    int height = width / 2 + maxHeight - 1;
    //        maxX = dimension.getHighestX();
    //        maxY = dimension.getHighestY();
    maxX = maxY = 0;
    //        xOffset = 512;
    //        yOffset = 256;
    //        xOffset = yOffset = 0;
    switch (rotation) {
      case 0:
        xOffset = -getTileBounds(dimension.getLowestX(), dimension.getHighestY()).x;
        yOffset = -getTileBounds(dimension.getLowestX(), dimension.getLowestY()).y;
        break;
      case 1:
        xOffset = -getTileBounds(dimension.getHighestX(), dimension.getHighestY()).x;
        yOffset = -getTileBounds(dimension.getLowestX(), dimension.getHighestY()).y;
        break;
      case 2:
        xOffset = -getTileBounds(dimension.getHighestX(), dimension.getLowestY()).x;
        yOffset = -getTileBounds(dimension.getHighestX(), dimension.getHighestY()).y;
        break;
      case 3:
        xOffset = -getTileBounds(dimension.getLowestX(), dimension.getLowestY()).x;
        yOffset = -getTileBounds(dimension.getHighestX(), dimension.getLowestY()).y;
        break;
      default:
        throw new IllegalArgumentException();
    }
    //        System.out.println("xOffset: " + xOffset + ", yOffset: " + yOffset);
    java.awt.Dimension preferredSize = zoom(new java.awt.Dimension(width, height));
    setPreferredSize(preferredSize);
    setMinimumSize(preferredSize);
    setMaximumSize(preferredSize);
    setSize(preferredSize);

    addHierarchyListener(this);
  }