diff --git a/core/src/main/java/com/volmit/iris/core/gui/HotloadComparison.java b/core/src/main/java/com/volmit/iris/core/gui/HotloadComparison.java deleted file mode 100644 index 9df922a78..000000000 --- a/core/src/main/java/com/volmit/iris/core/gui/HotloadComparison.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.core.gui; - -import com.volmit.iris.core.gui.components.MapStyle; -import com.volmit.iris.core.gui.components.RenderType; - -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.awt.image.DataBufferInt; -import java.util.Locale; - -/** - * Keeps the picture of the map from just before a hotload, and shows it next to the new one: either split by a - * draggable divider, or with everything that stayed the same faded out. - */ -final class HotloadComparison { - private static final double DEFAULT_SWIPE_FRACTION = 0.5D; - private static final int DIVIDER_GRAB_DISTANCE = 8; - private static final int DIVIDER_HANDLE_RADIUS = 9; - private static final int UNCHANGED_FADE_TARGET = 0x1E1F22; - private static final double UNCHANGED_FADE = 0.78D; - private static final Color DIVIDER = new Color(255, 255, 255, 220); - - private BufferedImage before; - private Scene beforeScene; - private Mode mode = Mode.OFF; - private double swipeFraction = DEFAULT_SWIPE_FRACTION; - private BufferedImage changes; - private ChangesKey changesKey; - private double changedShare; - - enum Mode { - OFF, - SWIPE, - CHANGES; - - Mode next() { - Mode[] modes = values(); - return modes[(ordinal() + 1) % modes.length]; - } - } - - /** - * What a map picture shows, so the before picture is only laid over a now picture of the same kind and zoom. - */ - record Scene(RenderType type, MapStyle style, double blocksPerPixel, double centerX, double centerZ) { - boolean looksLike(Scene other) { - return type == other.type && style.equals(other.style); - } - } - - boolean hasBefore() { - return before != null; - } - - Mode mode() { - return mode; - } - - void setMode(Mode nextMode) { - mode = nextMode; - } - - void capture(BufferedImage layer, Scene scene) { - BufferedImage copy = new BufferedImage(layer.getWidth(), layer.getHeight(), BufferedImage.TYPE_INT_RGB); - copy.setAccelerationPriority(0F); - Graphics2D graphics = copy.createGraphics(); - try { - graphics.drawImage(layer, 0, 0, null); - } finally { - graphics.dispose(); - } - before = copy; - beforeScene = scene; - changes = null; - changesKey = null; - } - - void forget() { - before = null; - beforeScene = null; - changes = null; - changesKey = null; - mode = Mode.OFF; - } - - boolean isShowing() { - return mode != Mode.OFF && before != null; - } - - boolean isNearDivider(int screenX, int width) { - return isShowing() && mode == Mode.SWIPE - && Math.abs(screenX - dividerX(width)) <= StudioTheme.scale(DIVIDER_GRAB_DISTANCE); - } - - void moveDivider(int screenX, int width) { - swipeFraction = Math.max(0D, Math.min(1D, screenX / (double) Math.max(1, width))); - } - - /** - * Draws the comparison in place of the plain map picture. - * - * @param nowVersion changes whenever the now picture is redrawn, so the change mask is only rebuilt when needed - * @param nowComplete whether every tile of the now picture has been drawn; changes are only counted then - */ - void paint(Graphics2D canvas, BufferedImage now, long nowVersion, Scene nowScene, boolean nowComplete) { - canvas.drawImage(now, 0, 0, null); - if (!beforeScene.looksLike(nowScene)) { - drawCaption(canvas, now.getWidth(), "Before and after needs the view and layers the hotload had"); - return; - } - if (mode == Mode.SWIPE) { - paintSwipe(canvas, now.getWidth(), now.getHeight(), nowScene); - return; - } - if (beforeScene.blocksPerPixel() != nowScene.blocksPerPixel()) { - drawCaption(canvas, now.getWidth(), "Changes needs the zoom the hotload had"); - return; - } - if (!nowComplete) { - drawCaption(canvas, now.getWidth(), "Comparing once the map has finished drawing"); - return; - } - int offsetX = (int) Math.round(beforeScreenX(now.getWidth(), nowScene)); - int offsetZ = (int) Math.round(beforeScreenZ(now.getHeight(), nowScene)); - canvas.drawImage(changesImage(now, nowVersion, offsetX, offsetZ), 0, 0, null); - drawCaption(canvas, now.getWidth(), changesCaption()); - } - - private void paintSwipe(Graphics2D canvas, int width, int height, Scene now) { - int dividerX = dividerX(width); - double scale = beforeScene.blocksPerPixel() / now.blocksPerPixel(); - Graphics2D beforeSide = (Graphics2D) canvas.create(); - try { - beforeSide.clipRect(0, 0, dividerX, height); - beforeSide.drawImage(before, - (int) Math.round(beforeScreenX(width, now)), - (int) Math.round(beforeScreenZ(height, now)), - (int) Math.round(before.getWidth() * scale), - (int) Math.round(before.getHeight() * scale), - null); - } finally { - beforeSide.dispose(); - } - canvas.setColor(DIVIDER); - canvas.setStroke(new BasicStroke(StudioTheme.scale(2))); - canvas.drawLine(dividerX, 0, dividerX, height); - int radius = StudioTheme.scale(DIVIDER_HANDLE_RADIUS); - canvas.fillOval(dividerX - radius, height / 2 - radius, radius * 2, radius * 2); - canvas.setColor(StudioTheme.EDITOR_BACKGROUND); - int gripHalfHeight = radius / 2; - int gripGap = Math.max(1, radius / 4); - canvas.drawLine(dividerX - gripGap, height / 2 - gripHalfHeight, dividerX - gripGap, height / 2 + gripHalfHeight); - canvas.drawLine(dividerX + gripGap, height / 2 - gripHalfHeight, dividerX + gripGap, height / 2 + gripHalfHeight); - drawSideLabel(canvas, "Before", dividerX - StudioTheme.scale(12), 1D); - drawSideLabel(canvas, "Now", dividerX + StudioTheme.scale(12), 0D); - } - - private int dividerX(int width) { - return (int) Math.round(width * swipeFraction); - } - - // Where the before picture's top left corner lands on screen now, after any panning, zooming or resizing since. - private double beforeScreenX(int width, Scene now) { - double beforeLeftWorld = beforeScene.centerX() - before.getWidth() * 0.5D * beforeScene.blocksPerPixel(); - return width * 0.5D + (beforeLeftWorld - now.centerX()) / now.blocksPerPixel(); - } - - private double beforeScreenZ(int height, Scene now) { - double beforeTopWorld = beforeScene.centerZ() - before.getHeight() * 0.5D * beforeScene.blocksPerPixel(); - return height * 0.5D + (beforeTopWorld - now.centerZ()) / now.blocksPerPixel(); - } - - private BufferedImage changesImage(BufferedImage now, long nowVersion, int offsetX, int offsetZ) { - ChangesKey key = new ChangesKey(nowVersion, offsetX, offsetZ, now.getWidth(), now.getHeight()); - if (changes != null && key.equals(changesKey)) { - return changes; - } - int width = now.getWidth(); - int height = now.getHeight(); - int[] nowPixels = pixels(now); - int[] beforePixels = pixels(before); - BufferedImage mask = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); - mask.setAccelerationPriority(0F); - int[] maskPixels = pixels(mask); - long compared = 0L; - long changed = 0L; - for (int y = 0; y < height; y++) { - int beforeY = y - offsetZ; - boolean rowInBefore = beforeY >= 0 && beforeY < before.getHeight(); - for (int x = 0; x < width; x++) { - int index = y * width + x; - int nowColor = nowPixels[index] & 0xFFFFFF; - int beforeX = x - offsetX; - boolean inBefore = rowInBefore && beforeX >= 0 && beforeX < before.getWidth(); - boolean differs = inBefore && (beforePixels[beforeY * before.getWidth() + beforeX] & 0xFFFFFF) != nowColor; - if (inBefore) { - compared++; - } - if (differs) { - changed++; - maskPixels[index] = nowColor; - } else { - maskPixels[index] = fade(nowColor); - } - } - } - changedShare = compared == 0L ? 0D : changed / (double) compared; - changes = mask; - changesKey = key; - return mask; - } - - private String changesCaption() { - if (changedShare == 0D) { - return "Nothing on screen changed in the hotload"; - } - double percent = changedShare * 100D; - String amount = percent < 0.1D ? "<0.1" : String.format(Locale.ROOT, percent < 10D ? "%.1f" : "%.0f", percent); - return amount + "% of the map on screen changed in the hotload"; - } - - private static int[] pixels(BufferedImage image) { - return ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); - } - - private static int fade(int color) { - int red = (int) (((color >> 16) & 0xFF) * (1D - UNCHANGED_FADE) + ((UNCHANGED_FADE_TARGET >> 16) & 0xFF) * UNCHANGED_FADE); - int green = (int) (((color >> 8) & 0xFF) * (1D - UNCHANGED_FADE) + ((UNCHANGED_FADE_TARGET >> 8) & 0xFF) * UNCHANGED_FADE); - int blue = (int) ((color & 0xFF) * (1D - UNCHANGED_FADE) + (UNCHANGED_FADE_TARGET & 0xFF) * UNCHANGED_FADE); - return (red << 16) | (green << 8) | blue; - } - - private static void drawSideLabel(Graphics2D canvas, String text, int anchorX, double pushX) { - canvas.setFont(StudioTheme.boldFont()); - int padding = StudioTheme.scale(8); - int width = canvas.getFontMetrics().stringWidth(text) + padding * 2; - int height = canvas.getFontMetrics().getHeight() + padding; - int x = (int) Math.round(anchorX - width * pushX); - int y = StudioTheme.scale(12); - StudioOverlay.drawCard(canvas, x, y, width, height); - canvas.setColor(StudioTheme.TEXT); - canvas.drawString(text, x + padding, y + padding / 2 + canvas.getFontMetrics().getAscent()); - } - - private static void drawCaption(Graphics2D canvas, int width, String text) { - canvas.setFont(StudioTheme.font()); - int padding = StudioTheme.scale(10); - int captionWidth = canvas.getFontMetrics().stringWidth(text) + padding * 2; - int height = canvas.getFontMetrics().getHeight() + padding; - int x = (width - captionWidth) / 2; - int y = StudioTheme.scale(12); - StudioOverlay.drawCard(canvas, x, y, captionWidth, height); - canvas.setColor(StudioTheme.TEXT); - canvas.drawString(text, x + padding, y + padding / 2 + canvas.getFontMetrics().getAscent()); - } - - private record ChangesKey(long nowVersion, int offsetX, int offsetZ, int width, int height) { - } -} diff --git a/core/src/main/java/com/volmit/iris/core/gui/MapCanvas.java b/core/src/main/java/com/volmit/iris/core/gui/MapCanvas.java index 4b82ae514..d78dd54ab 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/MapCanvas.java +++ b/core/src/main/java/com/volmit/iris/core/gui/MapCanvas.java @@ -33,7 +33,6 @@ import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; import javax.swing.Timer; import java.awt.Color; -import java.awt.Cursor; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; @@ -87,7 +86,6 @@ final class MapCanvas extends JPanel { private final Timer notificationTimer; private final Timer legendTimer; private final Map notifications = new LinkedHashMap<>(); - private final HotloadComparison comparison = new HotloadComparison(); private Engine engine; private IrisRenderer renderer; @@ -95,7 +93,6 @@ final class MapCanvas extends JPanel { private VisionRenderController.Frame renderFrame; private BufferedImage tileLayer; private boolean tileLayerStale = true; - private long tileLayerVersion; private MapStyle style = MapStyle.PLAIN; private RenderType currentType = RenderType.BIOME; private String viewLabel = MapView.BIOMES.label(); @@ -105,7 +102,6 @@ final class MapCanvas extends JPanel { private MapHoverProbe.Details hoverDetails; private Point cursor; private Point dragOrigin; - private boolean draggingDivider; private double centerX; private double centerZ; private long contentRevision = 1L; @@ -184,14 +180,6 @@ final class MapCanvas extends JPanel { return style; } - HotloadComparison.Mode comparisonMode() { - return comparison.hasBefore() ? comparison.mode() : HotloadComparison.Mode.OFF; - } - - boolean hasBeforePicture() { - return comparison.hasBefore(); - } - void start() { refreshMarkers(); requestRender(); @@ -238,27 +226,14 @@ final class MapCanvas extends JPanel { notifyUser("No active Iris engine"); return; } - boolean captured = captureBeforePicture(); renderer = new IrisRenderer(engine); overlay = new VisionOverlay(engine, openerId); contentRevision++; requestRender(); - notifyUser(captured && comparison.mode() == HotloadComparison.Mode.OFF - ? "Refreshing ยท press C to compare with before" - : "Refreshing"); + notifyUser("Refreshing"); listener.onTogglesChanged(); } - // Only a finished picture is worth comparing against; a half drawn one would show its gaps as changes. - private boolean captureBeforePicture() { - if (tileLayer == null || controller.progress(renderFrame).completion() < 1D) { - comparison.forget(); - return false; - } - comparison.capture(currentTileLayer(), currentScene()); - return true; - } - void toggleShading() { applyStyle(style.withShading(!style.shading())); notifyUser(style.shading() ? "Terrain shading on" : "Terrain shading off"); @@ -297,13 +272,11 @@ final class MapCanvas extends JPanel { } /** - * Esc steps back one layer of emphasis: the highlight first, then the comparison, then the help. + * Esc steps back one layer of emphasis: the highlight first, then the help. */ void dismissEmphasis() { if (style.highlight() != null) { clearHighlight(); - } else if (comparisonMode() != HotloadComparison.Mode.OFF) { - showComparison(HotloadComparison.Mode.OFF); } else if (help) { toggleHelp(); } @@ -314,26 +287,6 @@ final class MapCanvas extends JPanel { notifyUser("Highlight cleared"); } - void cycleComparison() { - showComparison(comparisonMode().next()); - } - - void showComparison(HotloadComparison.Mode mode) { - if (!comparison.hasBefore()) { - notifyUser("Refresh or hotload the pack first, then compare with before"); - listener.onTogglesChanged(); - return; - } - comparison.setMode(mode); - notifyUser(switch (mode) { - case OFF -> "Comparison off"; - case SWIPE -> "Drag the divider to compare before and now"; - case CHANGES -> "Showing what changed in the hotload"; - }); - repaint(); - listener.onTogglesChanged(); - } - private void applyStyle(MapStyle next) { boolean heightsWereNeeded = needsHeights(); style = next; @@ -440,9 +393,6 @@ final class MapCanvas extends JPanel { public void mouseMoved(MouseEvent event) { cursor = event.getPoint(); setDetailedHover(event.isShiftDown()); - setCursor(comparison.isNearDivider(event.getX(), getWidth()) - ? Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR) - : Cursor.getDefaultCursor()); showQuickHover(); hoverTimer.restart(); refreshStatus(); @@ -451,11 +401,6 @@ final class MapCanvas extends JPanel { @Override public void mouseDragged(MouseEvent event) { cursor = event.getPoint(); - if (draggingDivider) { - comparison.moveDivider(event.getX(), getWidth()); - repaint(); - return; - } if (dragOrigin == null) { return; } @@ -480,8 +425,6 @@ final class MapCanvas extends JPanel { cursor = event.getPoint(); if (event.isPopupTrigger()) { showContextMenu(event); - } else if (SwingUtilities.isLeftMouseButton(event) && comparison.isNearDivider(event.getX(), getWidth())) { - draggingDivider = true; } else if (SwingUtilities.isLeftMouseButton(event)) { dragOrigin = event.getPoint(); } @@ -490,7 +433,6 @@ final class MapCanvas extends JPanel { @Override public void mouseReleased(MouseEvent event) { dragOrigin = null; - draggingDivider = false; if (event.isPopupTrigger()) { showContextMenu(event); } @@ -628,17 +570,7 @@ final class MapCanvas extends JPanel { // The display server gets one finished picture per paint: handing it every tile separately made the map lag. private void renderTiles(Graphics2D canvas) { - BufferedImage layer = currentTileLayer(); - if (!comparison.isShowing()) { - canvas.drawImage(layer, 0, 0, null); - return; - } - boolean complete = controller.progress(renderFrame).completion() >= 1D; - comparison.paint(canvas, layer, tileLayerVersion, currentScene(), complete); - } - - private HotloadComparison.Scene currentScene() { - return new HotloadComparison.Scene(currentType, style, blocksPerPixel, centerX, centerZ); + canvas.drawImage(currentTileLayer(), 0, 0, null); } private BufferedImage currentTileLayer() { @@ -657,7 +589,6 @@ final class MapCanvas extends JPanel { layer.dispose(); } tileLayerStale = false; - tileLayerVersion++; } return tileLayer; } @@ -833,7 +764,7 @@ final class MapCanvas extends JPanel { private void renderHover(Graphics2D canvas) { QuickHover quick = quickHover; - if (quick == null || draggingDivider) { + if (quick == null) { return; } MapHoverProbe.Details details = hoverDetails; @@ -846,7 +777,7 @@ final class MapCanvas extends JPanel { private void renderHelp(Graphics2D canvas) { List keys = List.of("Drag", "Wheel / + -", "\\", "Double-click", "Right-click", "Ctrl+Click", "Alt+Click", - "Shift", "H", "Esc", "S", "B", "C", "R", "F", "G", "E", "M", "1 to 0, P", "/"); + "Shift", "H", "Esc", "S", "B", "R", "F", "G", "E", "M", "1 to 0, P", "/"); List descriptions = List.of( "Move the map", "Zoom in and out", @@ -860,7 +791,6 @@ final class MapCanvas extends JPanel { "Clear the highlight", "Terrain shading", "Region borders", - "Compare with before the last hotload", "Redraw the map", "Follow your player", "Chunk and region grid", diff --git a/core/src/main/java/com/volmit/iris/core/gui/MapWindow.java b/core/src/main/java/com/volmit/iris/core/gui/MapWindow.java index 8bdbe61c5..77ba0c503 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/MapWindow.java +++ b/core/src/main/java/com/volmit/iris/core/gui/MapWindow.java @@ -73,10 +73,6 @@ public final class MapWindow implements MapCanvas.Listener { private final JToggleButton followToggle = StudioToolbar.toggle("Follow", "Keep your player in the middle (F)"); private final JToggleButton shadingToggle = StudioToolbar.toggle("Shading", "Shade hills and valleys on every view (S)"); private final JToggleButton bordersToggle = StudioToolbar.toggle("Borders", "Outline the regions on every view (B)"); - private final JToggleButton swipeToggle = StudioToolbar.toggle("Before / after", - "Split the map between before and after the last hotload or refresh (C)"); - private final JToggleButton changesToggle = StudioToolbar.toggle("Changes", - "Fade out everything the last hotload or refresh left the same (C twice)"); private final MapLegendPanel legendPanel; private NoiseExplorerGUI paletteExplorer; @@ -150,18 +146,11 @@ public final class MapWindow implements MapCanvas.Listener { mapControls.add(toolbarSeparator()); mapControls.add(shadingToggle); mapControls.add(bordersToggle); - mapControls.add(toolbarSeparator()); - mapControls.add(swipeToggle); - mapControls.add(changesToggle); gridToggle.addActionListener(event -> canvas.toggleGrid()); entitiesToggle.addActionListener(event -> canvas.toggleEntities()); followToggle.addActionListener(event -> canvas.toggleFollow()); shadingToggle.addActionListener(event -> canvas.toggleShading()); bordersToggle.addActionListener(event -> canvas.toggleRegionBorders()); - swipeToggle.addActionListener(event -> canvas.showComparison( - swipeToggle.isSelected() ? HotloadComparison.Mode.SWIPE : HotloadComparison.Mode.OFF)); - changesToggle.addActionListener(event -> canvas.showComparison( - changesToggle.isSelected() ? HotloadComparison.Mode.CHANGES : HotloadComparison.Mode.OFF)); mapControls.forEach(toolbar::add); paletteControls.add(StudioToolbar.button("Reset view", "Go back to the starting position and zoom (R)", this::resetPaletteView)); @@ -297,8 +286,6 @@ public final class MapWindow implements MapCanvas.Listener { followToggle.setSelected(canvas.isFollowing()); shadingToggle.setSelected(canvas.style().shading()); bordersToggle.setSelected(canvas.style().regionBorders()); - swipeToggle.setSelected(canvas.comparisonMode() == HotloadComparison.Mode.SWIPE); - changesToggle.setSelected(canvas.comparisonMode() == HotloadComparison.Mode.CHANGES); } @Override @@ -340,7 +327,6 @@ public final class MapWindow implements MapCanvas.Listener { bindMap(rootPane, KeyEvent.VK_ESCAPE, 0, "dismiss", canvas::dismissEmphasis); bindMap(rootPane, KeyEvent.VK_S, 0, "shading", canvas::toggleShading); bindMap(rootPane, KeyEvent.VK_B, 0, "borders", canvas::toggleRegionBorders); - bindMap(rootPane, KeyEvent.VK_C, 0, "compare", canvas::cycleComparison); bindMap(rootPane, KeyEvent.VK_EQUALS, 0, "zoom-in", canvas::zoomIn); bindMap(rootPane, KeyEvent.VK_EQUALS, KeyEvent.SHIFT_DOWN_MASK, "zoom-in-plus", canvas::zoomIn); bindMap(rootPane, KeyEvent.VK_ADD, 0, "zoom-in-keypad", canvas::zoomIn);