diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java b/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java
index bec6d1dc3..18d2c6237 100644
--- a/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java
+++ b/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java
@@ -22,6 +22,7 @@ import com.volmit.iris.Iris;
import com.volmit.iris.core.IrisSettings;
import com.volmit.iris.core.gui.GuiHost;
import com.volmit.iris.core.gui.NoiseExplorerGUI;
+import com.volmit.iris.core.gui.PaletteExplorerStart;
import com.volmit.iris.core.gui.VisionGUI;
import com.volmit.iris.core.loader.IrisData;
import com.volmit.iris.core.project.IrisProject;
@@ -252,14 +253,25 @@ public class CommandStudio implements DecreeExecutor {
NoiseExplorerGUI.launch();
}
- @Decree(description = "Preview biome palette layers (External GUI)", aliases = "pal")
- public void palette(
- @Param(description = "The biome to open on", contextual = true)
- IrisBiome biome
- ) {
+ @Decree(description = "Preview the palettes around you (External GUI)", aliases = "pal", origin = DecreeOrigin.PLAYER)
+ public void palette() {
if (noGUI()) return;
+ Engine engine = engine();
+ if (engine == null) {
+ sender().sendMessage(C.RED + "Stand in an Iris world to preview its palettes!");
+ return;
+ }
+ Location location = player().getLocation();
+ IrisBiome surfaceBiome = engine.getSurfaceBiome(location.getBlockX(), location.getBlockZ());
sender().sendMessage(C.GREEN + "Opening Palette Explorer!");
- NoiseExplorerGUI.launchPalettes(biome == null ? null : biome.getLoadKey(), sender().isPlayer() ? player().getUniqueId() : null);
+ NoiseExplorerGUI.launchPalettes(new PaletteExplorerStart(
+ engine,
+ location.getWorld().getUID(),
+ player().getUniqueId(),
+ surfaceBiome == null ? null : surfaceBiome.getLoadKey(),
+ location.getX(),
+ location.getZ()
+ ));
}
@Decree(description = "Charges all spawners in the area", aliases = "zzt", origin = DecreeOrigin.PLAYER)
diff --git a/core/src/main/java/com/volmit/iris/core/gui/ExplorerStyle.java b/core/src/main/java/com/volmit/iris/core/gui/ExplorerStyle.java
new file mode 100644
index 000000000..36f84db44
--- /dev/null
+++ b/core/src/main/java/com/volmit/iris/core/gui/ExplorerStyle.java
@@ -0,0 +1,70 @@
+/*
+ * 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 javax.swing.AbstractButton;
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JToggleButton;
+
+import static com.volmit.iris.core.gui.NoiseExplorerGUI.ACCENT;
+import static com.volmit.iris.core.gui.NoiseExplorerGUI.BORDER;
+import static com.volmit.iris.core.gui.NoiseExplorerGUI.FIELD_BACKGROUND;
+import static com.volmit.iris.core.gui.NoiseExplorerGUI.ITEM_FONT;
+import static com.volmit.iris.core.gui.NoiseExplorerGUI.PRIMARY_TEXT;
+import static com.volmit.iris.core.gui.NoiseExplorerGUI.SECONDARY_TEXT;
+
+final class ExplorerStyle {
+ private static final java.awt.Color SELECTED_BACKGROUND = ACCENT.darker().darker();
+
+ private ExplorerStyle() {
+ }
+
+ static JToggleButton toggleButton(String text) {
+ JToggleButton button = new JToggleButton(text);
+ applyFrame(button);
+ button.addItemListener(event -> paintToggleState(button));
+ paintToggleState(button);
+ return button;
+ }
+
+ static JButton button(String text) {
+ JButton button = new JButton(text);
+ applyFrame(button);
+ button.setBackground(FIELD_BACKGROUND);
+ button.setForeground(PRIMARY_TEXT);
+ return button;
+ }
+
+ private static void applyFrame(AbstractButton button) {
+ button.setFocusPainted(false);
+ button.setFont(ITEM_FONT);
+ button.setContentAreaFilled(false);
+ button.setOpaque(true);
+ button.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createLineBorder(BORDER),
+ BorderFactory.createEmptyBorder(5, 9, 5, 9)
+ ));
+ }
+
+ private static void paintToggleState(JToggleButton button) {
+ button.setBackground(button.isSelected() ? SELECTED_BACKGROUND : FIELD_BACKGROUND);
+ button.setForeground(button.isSelected() ? PRIMARY_TEXT : SECONDARY_TEXT);
+ }
+}
diff --git a/core/src/main/java/com/volmit/iris/core/gui/GuiHost.java b/core/src/main/java/com/volmit/iris/core/gui/GuiHost.java
index e574db837..aac0f7df2 100644
--- a/core/src/main/java/com/volmit/iris/core/gui/GuiHost.java
+++ b/core/src/main/java/com/volmit/iris/core/gui/GuiHost.java
@@ -40,6 +40,7 @@ import java.util.ArrayList;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -84,6 +85,11 @@ public final class GuiHost {
return null;
}
+ public static Engine findEngine(UUID worldId) {
+ World world = Bukkit.getWorld(worldId);
+ return world == null ? null : engineOf(world);
+ }
+
public static void registerHotloadHook(Runnable onHotload) {
Listener listener = new HotloadListener(onHotload);
HOTLOAD_HOOKS.put(onHotload, listener);
diff --git a/core/src/main/java/com/volmit/iris/core/gui/NoiseExplorerGUI.java b/core/src/main/java/com/volmit/iris/core/gui/NoiseExplorerGUI.java
index 96a12765c..179f17de5 100644
--- a/core/src/main/java/com/volmit/iris/core/gui/NoiseExplorerGUI.java
+++ b/core/src/main/java/com/volmit/iris/core/gui/NoiseExplorerGUI.java
@@ -72,7 +72,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
-import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
@@ -105,9 +104,11 @@ public final class NoiseExplorerGUI extends JPanel {
private static final int REFINEMENT_DELAY_MILLIS = 180;
private static final long DEFAULT_SEED = 12345L;
private static final double TEXTURE_PIXELS_MAX_BLOCKS_PER_PIXEL = 0.25D;
+ private static final double PALETTE_START_BLOCKS_PER_PIXEL = 1D / 16D;
private static final long COARSE_SAMPLE_BUDGET = 4_096L;
private static final long MAX_REFINED_SAMPLE_BUDGET = 48_000L;
private static final double REFINEMENT_TARGET_MILLIS = 250D;
+ private static final double PALETTE_REFINEMENT_TARGET_MILLIS = 2_000D;
private static final double WHEEL_ZOOM_EXPONENT = 0.08D;
private static final NoiseCategory[] CATEGORY_ORDER = {
NoiseCategory.CUSTOM,
@@ -131,7 +132,8 @@ public final class NoiseExplorerGUI extends JPanel {
};
private final ExplorerMode mode;
- private final String initialPackSourceId;
+ private final PaletteExplorerStart paletteStart;
+ private final NoiseViewport initialViewport;
private volatile Engine activeEngine;
private final LongFunction customFactory;
private final String customName;
@@ -154,12 +156,11 @@ public final class NoiseExplorerGUI extends JPanel {
private final JComboBox palettePicker = new JComboBox<>(NoisePalette.values());
private final PaletteInspectorPanel paletteInspector = new PaletteInspectorPanel(new PaletteInspectorListener());
private final PaletteNavigator paletteNavigator;
- private final UUID trackedPlayerId;
private PlayerLocationTracker locationTracker;
private volatile BlockTextures blockTextures;
private List packSources = List.of();
- private NoiseViewport viewport = new NoiseViewport(0D, 0D, 1D);
+ private NoiseViewport viewport;
private NoisePalette palette;
private SourceItem selectedSource;
private NoiseProvider sampler;
@@ -179,12 +180,15 @@ public final class NoiseExplorerGUI extends JPanel {
private String errorMessage;
private NoiseExplorerGUI(ExplorerMode mode, Engine engine, String customGeneratorKey, IrisGenerator fallbackGenerator,
- String customName, long initialSeed, String initialPackSourceId, UUID trackedPlayerId) {
+ String customName, long initialSeed, PaletteExplorerStart paletteStart) {
this.mode = mode;
- this.trackedPlayerId = trackedPlayerId;
- this.paletteNavigator = new PaletteNavigator(trackedPlayerId != null, this::selectPaletteLayer);
- this.initialPackSourceId = initialPackSourceId;
+ this.paletteStart = paletteStart;
+ this.paletteNavigator = new PaletteNavigator(this::selectPaletteLayer);
this.awaitingInitialPackSource = mode == ExplorerMode.PALETTES;
+ this.initialViewport = paletteStart == null
+ ? new NoiseViewport(0D, 0D, 1D)
+ : new NoiseViewport(paletteStart.centerX(), paletteStart.centerZ(), PALETTE_START_BLOCKS_PER_PIXEL);
+ this.viewport = initialViewport;
this.activeEngine = engine;
this.customFactory = fallbackGenerator == null
? null
@@ -192,7 +196,7 @@ public final class NoiseExplorerGUI extends JPanel {
this.customName = customName;
this.seed = initialSeed;
this.seedField.setText(Long.toString(initialSeed));
- this.palette = IrisSettings.get().getGui().colorMode ? NoisePalette.TERRAIN : NoisePalette.GRAYSCALE;
+ this.palette = IrisSettings.get().getGui().colorMode && mode == ExplorerMode.NOISE ? NoisePalette.TERRAIN : NoisePalette.GRAYSCALE;
this.sourceExecutor = createExecutor("Iris Noise Source Loader");
this.catalogExecutor = createExecutor("Iris Noise Catalog Loader");
this.renderCoordinator = new NoiseRenderCoordinator(new RenderListener());
@@ -212,18 +216,15 @@ public final class NoiseExplorerGUI extends JPanel {
public static void launch() {
Engine engine = GuiHost.findActiveEngine();
EventQueue.invokeLater(() -> {
- NoiseExplorerGUI explorer = new NoiseExplorerGUI(ExplorerMode.NOISE, engine, null, null, null, DEFAULT_SEED, null, null);
+ NoiseExplorerGUI explorer = new NoiseExplorerGUI(ExplorerMode.NOISE, engine, null, null, null, DEFAULT_SEED, null);
buildFrame("Noise Explorer", explorer);
});
}
- public static void launchPalettes(String biomeKey, UUID trackedPlayerId) {
- Engine engine = GuiHost.findActiveEngine();
- String initialSourceId = biomeKey == null ? null : paletteSourceId(biomeKey, 0);
- String title = biomeKey == null ? "Palette Explorer" : "Palette Explorer: " + biomeKey;
+ public static void launchPalettes(PaletteExplorerStart start) {
EventQueue.invokeLater(() -> {
- NoiseExplorerGUI explorer = new NoiseExplorerGUI(ExplorerMode.PALETTES, engine, null, null, null, DEFAULT_SEED, initialSourceId, trackedPlayerId);
- buildFrame(title, explorer);
+ NoiseExplorerGUI explorer = new NoiseExplorerGUI(ExplorerMode.PALETTES, start.engine(), null, null, null, DEFAULT_SEED, start);
+ buildFrame("Palette Explorer", explorer);
});
}
@@ -238,7 +239,6 @@ public final class NoiseExplorerGUI extends JPanel {
fallbackGenerator,
displayName,
initialSeed,
- null,
null
);
buildFrame("Noise Explorer: " + displayName, explorer);
@@ -376,9 +376,17 @@ public final class NoiseExplorerGUI extends JPanel {
if (mode == ExplorerMode.NOISE) {
addSeedControls(controls);
+ addPaletteControls(controls);
}
- JLabel paletteTitle = new JLabel(mode == ExplorerMode.PALETTES ? "Noise view colours" : "Palette");
+ JButton resetView = createButton("Reset view");
+ resetView.addActionListener(event -> resetViewport());
+ controls.add(resetView);
+ return controls;
+ }
+
+ private void addPaletteControls(JPanel controls) {
+ JLabel paletteTitle = new JLabel("Palette");
paletteTitle.setForeground(SECONDARY_TEXT);
paletteTitle.setFont(HEADER_FONT);
controls.add(paletteTitle);
@@ -393,11 +401,6 @@ public final class NoiseExplorerGUI extends JPanel {
}
});
controls.add(palettePicker);
-
- JButton resetView = createButton("Reset view");
- resetView.addActionListener(event -> resetViewport());
- controls.add(resetView);
- return controls;
}
private void addSeedControls(JPanel controls) {
@@ -443,8 +446,8 @@ public final class NoiseExplorerGUI extends JPanel {
loadCatalog();
if (mode == ExplorerMode.PALETTES) {
loadBlockTextures();
- if (trackedPlayerId != null) {
- locationTracker = PlayerLocationTracker.follow(trackedPlayerId, paletteNavigator::showLocation);
+ if (paletteStart != null) {
+ locationTracker = PlayerLocationTracker.follow(paletteStart.playerId(), paletteStart.worldId(), paletteNavigator::showLocation);
}
}
SourceItem initial = findInitialSource();
@@ -686,7 +689,8 @@ public final class NoiseExplorerGUI extends JPanel {
}
private void selectInitialPackSource(List sources) {
- SourceItem requested = initialPackSourceId == null ? null : findSourceById(initialPackSourceId, sources);
+ String initialSourceId = paletteStart == null || paletteStart.biomeKey() == null ? null : paletteSourceId(paletteStart.biomeKey(), 0);
+ SourceItem requested = initialSourceId == null ? null : findSourceById(initialSourceId, sources);
SourceItem initial = requested == null ? sources.get(0) : requested;
if (!selectSourceInList(initial.id())) {
selectSource(initial);
@@ -938,13 +942,18 @@ public final class NoiseExplorerGUI extends JPanel {
requestRender(sampleStep);
}
- private static long refinedSampleBudget(NoiseRenderCoordinator.Result currentResult) {
+ // Smooth noise looks fine upscaled, but blocks and their textures only read correctly at full resolution.
+ private long refinedSampleBudget(NoiseRenderCoordinator.Result currentResult) {
+ boolean fullResolution = mode == ExplorerMode.PALETTES;
+ long maximumBudget = fullResolution
+ ? Math.max(COARSE_SAMPLE_BUDGET, (long) currentResult.request().width() * currentResult.request().height())
+ : MAX_REFINED_SAMPLE_BUDGET;
return NoiseRenderCoordinator.timeBoundSampleBudget(
currentResult.samples(),
currentResult.milliseconds(),
- REFINEMENT_TARGET_MILLIS,
+ fullResolution ? PALETTE_REFINEMENT_TARGET_MILLIS : REFINEMENT_TARGET_MILLIS,
COARSE_SAMPLE_BUDGET,
- MAX_REFINED_SAMPLE_BUDGET
+ maximumBudget
);
}
@@ -1039,12 +1048,12 @@ public final class NoiseExplorerGUI extends JPanel {
}
private void resetViewport() {
- viewport = new NoiseViewport(0D, 0D, 1D);
+ viewport = initialViewport;
requestPreview();
}
private void handleHotload() {
- Engine refreshedEngine = GuiHost.findActiveEngine();
+ Engine refreshedEngine = paletteStart == null ? GuiHost.findActiveEngine() : GuiHost.findEngine(paletteStart.worldId());
EventQueue.invokeLater(() -> {
if (disposed) {
return;
@@ -1088,7 +1097,7 @@ public final class NoiseExplorerGUI extends JPanel {
if (renderedImage == null) {
drawEmptyState(canvas, getWidth(), renderHeight);
} else {
- Object interpolation = renderedImage.getWidth() == getWidth()
+ Object interpolation = renderedImage.getWidth() == getWidth() || mode == ExplorerMode.PALETTES
? RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR
: RenderingHints.VALUE_INTERPOLATION_BILINEAR;
canvas.setRenderingHint(RenderingHints.KEY_INTERPOLATION, interpolation);
diff --git a/core/src/main/java/com/volmit/iris/core/gui/PaletteCatalog.java b/core/src/main/java/com/volmit/iris/core/gui/PaletteCatalog.java
index a4d19cf6a..4a1b6cb16 100644
--- a/core/src/main/java/com/volmit/iris/core/gui/PaletteCatalog.java
+++ b/core/src/main/java/com/volmit/iris/core/gui/PaletteCatalog.java
@@ -28,6 +28,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -40,7 +41,7 @@ record PaletteCatalog(List regions, List biomesOutsideRegio
Map biomes = loadBiomes(data);
List regions = new ArrayList<>();
Set biomesInRegions = new HashSet<>();
- for (String regionKey : engine.getDimension().getRegions()) {
+ for (String regionKey : regionKeysInUse(engine)) {
IrisRegion region = data.getRegionLoader().load(regionKey);
if (region == null) {
continue;
@@ -53,6 +54,16 @@ record PaletteCatalog(List regions, List biomesOutsideRegio
return new PaletteCatalog(List.copyOf(regions), outside, Map.copyOf(biomes));
}
+ // A studio focus region is generated even when the dimension's region list doesn't include it.
+ private static Set regionKeysInUse(Engine engine) {
+ Set keys = new LinkedHashSet<>();
+ if (engine.getDimension().hasFocusRegion()) {
+ keys.add(engine.getDimension().getFocusRegion());
+ }
+ keys.addAll(engine.getDimension().getRegions());
+ return keys;
+ }
+
BiomeEntry biome(String key) {
return key == null ? null : biomes.get(key);
}
diff --git a/core/src/main/java/com/volmit/iris/core/gui/PaletteExplorerStart.java b/core/src/main/java/com/volmit/iris/core/gui/PaletteExplorerStart.java
new file mode 100644
index 000000000..4ec630a51
--- /dev/null
+++ b/core/src/main/java/com/volmit/iris/core/gui/PaletteExplorerStart.java
@@ -0,0 +1,26 @@
+/*
+ * 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.engine.framework.Engine;
+
+import java.util.UUID;
+
+public record PaletteExplorerStart(Engine engine, UUID worldId, UUID playerId, String biomeKey, double centerX, double centerZ) {
+}
diff --git a/core/src/main/java/com/volmit/iris/core/gui/PaletteInspectorPanel.java b/core/src/main/java/com/volmit/iris/core/gui/PaletteInspectorPanel.java
index 62d81cf23..6e87dd690 100644
--- a/core/src/main/java/com/volmit/iris/core/gui/PaletteInspectorPanel.java
+++ b/core/src/main/java/com/volmit/iris/core/gui/PaletteInspectorPanel.java
@@ -19,12 +19,14 @@
package com.volmit.iris.core.gui;
import javax.swing.BorderFactory;
-import javax.swing.JComboBox;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
-import javax.swing.JSlider;
+import javax.swing.JToggleButton;
+import javax.swing.SwingConstants;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
@@ -59,11 +61,14 @@ final class PaletteInspectorPanel extends JPanel {
private final Listener listener;
private final JLabel titleLabel = new JLabel();
private final JLabel detailLabel = new JLabel();
- private final JComboBox viewPicker = new JComboBox<>(PaletteLayerScale.View.values());
- private final JLabel depthLabel = new JLabel();
- private final JSlider depthSlider = new JSlider(0, 0, 0);
+ private final JLabel depthLabel = new JLabel("", SwingConstants.CENTER);
+ private final JButton shallowerButton = ExplorerStyle.button("‹");
+ private final JButton deeperButton = ExplorerStyle.button("›");
+ private final JPanel depthRow = new JPanel(new BorderLayout(6, 0));
private final StatisticsView statisticsView = new StatisticsView();
- private boolean updatingControls;
+ private PaletteLayerScale.View selectedView = PaletteLayerScale.View.BLOCKS;
+ private int depth;
+ private int deepestDepth;
PaletteInspectorPanel(Listener listener) {
super(new BorderLayout());
@@ -80,19 +85,12 @@ final class PaletteInspectorPanel extends JPanel {
}
void showPreview(PaletteLayerPreview preview) {
- updatingControls = true;
- try {
- titleLabel.setText(preview.biomeKey() + " · layer " + preview.layer());
- detailLabel.setText(thicknessText(preview) + " · zoom " + formatZoom(preview.zoom())
- + " (×" + formatZoom(preview.zoom() * preview.zoom()) + " effective)");
- int deepestDepth = Math.max(0, preview.maxThickness() - 1);
- depthSlider.setMaximum(deepestDepth);
- depthSlider.setValue(preview.depth());
- depthSlider.setEnabled(deepestDepth > 0);
- depthLabel.setText(depthText(preview.depth(), deepestDepth));
- } finally {
- updatingControls = false;
- }
+ titleLabel.setText(preview.biomeKey() + " · layer " + preview.layer());
+ detailLabel.setText(thicknessText(preview) + " · zoom " + formatZoom(preview.zoom())
+ + " (×" + formatZoom(preview.zoom() * preview.zoom()) + " effective)");
+ depth = preview.depth();
+ deepestDepth = Math.max(0, preview.maxThickness() - 1);
+ refreshDepthControls();
statisticsView.show(preview);
setVisible(true);
}
@@ -106,7 +104,7 @@ final class PaletteInspectorPanel extends JPanel {
}
PaletteLayerScale.View selectedView() {
- return (PaletteLayerScale.View) viewPicker.getSelectedItem();
+ return selectedView;
}
private JPanel buildControls() {
@@ -123,26 +121,58 @@ final class PaletteInspectorPanel extends JPanel {
controls.add(titleLabel);
controls.add(detailLabel);
- controls.add(sectionLabel("View"));
- viewPicker.setBackground(FIELD_BACKGROUND);
- viewPicker.setForeground(PRIMARY_TEXT);
- viewPicker.addActionListener(event -> listener.onViewChanged(selectedView()));
- controls.add(viewPicker);
-
- depthLabel.setForeground(SECONDARY_TEXT);
- depthLabel.setFont(HEADER_FONT);
- controls.add(depthLabel);
- depthSlider.setOpaque(false);
- depthSlider.addChangeListener(event -> {
- if (!updatingControls && !depthSlider.getValueIsAdjusting()) {
- listener.onDepthChanged(depthSlider.getValue());
- }
- });
- depthSlider.addChangeListener(event -> depthLabel.setText(depthText(depthSlider.getValue(), depthSlider.getMaximum())));
- controls.add(depthSlider);
+ controls.add(sectionLabel("Show"));
+ controls.add(buildViewButtons());
+ controls.add(sectionLabel("Depth"));
+ controls.add(buildDepthRow());
return controls;
}
+ private JPanel buildViewButtons() {
+ JPanel row = new JPanel(new GridLayout(1, 0, 4, 0));
+ row.setOpaque(false);
+ ButtonGroup group = new ButtonGroup();
+ for (PaletteLayerScale.View view : PaletteLayerScale.View.values()) {
+ JToggleButton button = ExplorerStyle.toggleButton(view.toString());
+ button.setSelected(view == selectedView);
+ button.addActionListener(event -> {
+ selectedView = view;
+ listener.onViewChanged(view);
+ });
+ group.add(button);
+ row.add(button);
+ }
+ return row;
+ }
+
+ private JPanel buildDepthRow() {
+ depthRow.setOpaque(false);
+ depthLabel.setForeground(PRIMARY_TEXT);
+ depthLabel.setFont(ITEM_FONT);
+ shallowerButton.addActionListener(event -> changeDepth(-1));
+ deeperButton.addActionListener(event -> changeDepth(1));
+ depthRow.add(shallowerButton, BorderLayout.WEST);
+ depthRow.add(depthLabel, BorderLayout.CENTER);
+ depthRow.add(deeperButton, BorderLayout.EAST);
+ return depthRow;
+ }
+
+ private void changeDepth(int step) {
+ int nextDepth = Math.clamp(depth + step, 0, deepestDepth);
+ if (nextDepth == depth) {
+ return;
+ }
+ depth = nextDepth;
+ refreshDepthControls();
+ listener.onDepthChanged(depth);
+ }
+
+ private void refreshDepthControls() {
+ depthLabel.setText(depthText(depth, deepestDepth));
+ shallowerButton.setEnabled(depth > 0);
+ deeperButton.setEnabled(depth < deepestDepth);
+ }
+
private static JLabel sectionLabel(String text) {
JLabel label = new JLabel(text);
label.setForeground(SECONDARY_TEXT);
@@ -159,9 +189,9 @@ final class PaletteInspectorPanel extends JPanel {
private static String depthText(int depth, int deepestDepth) {
if (deepestDepth == 0) {
- return "Depth: top block only";
+ return "Top block only";
}
- return "Depth " + depth + (depth == 0 ? " (top)" : "") + " of " + deepestDepth;
+ return depth == 0 ? "Top block" : depth + (depth == 1 ? " block down" : " blocks down");
}
private static String formatZoom(double zoom) {
diff --git a/core/src/main/java/com/volmit/iris/core/gui/PaletteLayerPreview.java b/core/src/main/java/com/volmit/iris/core/gui/PaletteLayerPreview.java
index 0ce1191c6..46acd59eb 100644
--- a/core/src/main/java/com/volmit/iris/core/gui/PaletteLayerPreview.java
+++ b/core/src/main/java/com/volmit/iris/core/gui/PaletteLayerPreview.java
@@ -38,6 +38,7 @@ final class PaletteLayerPreview {
static final double STATISTICS_SPACING_BLOCKS = 37D;
private static final int MISSING_BLOCK_COLOR = 0x5A5F6B;
private static final double FAR_FROM_WEIGHT_FACTOR = 2D;
+ private static final int BLOCK_CACHE_SIZE = 1 << 16;
private final String biomeKey;
private final IrisBiome biome;
@@ -50,6 +51,7 @@ final class PaletteLayerPreview {
private final int[] colorByEntry;
private final BlockTexture[] textureByEntry;
private final List entries;
+ private final SampledBlock[] blockCache = new SampledBlock[BLOCK_CACHE_SIZE];
private final long[] histogram;
private final double absentShare;
private final double outOfRangeShare;
@@ -90,11 +92,30 @@ final class PaletteLayerPreview {
return new PaletteLayerPreview(biomeKey, biome, layer, depth, terrainRandom, data, textures);
}
+ // The world picks one block per whole coordinate, and zoomed in, many pixels fall on the same block.
double sample(double x, double z) {
- if (biome.getLayerThickness(layer, x, z, random, data) <= depth) {
+ int blockX = (int) Math.floor(x);
+ int blockZ = (int) Math.floor(z);
+ int cacheSlot = blockCacheSlot(blockX, blockZ);
+ SampledBlock cached = blockCache[cacheSlot];
+ if (cached != null && cached.x() == blockX && cached.z() == blockZ) {
+ return cached.noise();
+ }
+ double noise = sampleBlock(blockX, blockZ);
+ blockCache[cacheSlot] = new SampledBlock(blockX, blockZ, noise);
+ return noise;
+ }
+
+ private double sampleBlock(int blockX, int blockZ) {
+ if (biome.getLayerThickness(layer, blockX, blockZ, random, data) <= depth) {
return Double.NaN;
}
- return biome.getLayerNoise(layer, x, z, depth, random, data);
+ return biome.getLayerNoise(layer, blockX, blockZ, depth, random, data);
+ }
+
+ private static int blockCacheSlot(int blockX, int blockZ) {
+ long hash = blockX * 0x9E3779B97F4A7C15L ^ blockZ * 0xC2B2AE3D27D4EB4FL;
+ return (int) ((hash ^ (hash >>> 29)) & (BLOCK_CACHE_SIZE - 1));
}
int colorForNoise(double noise) {
@@ -291,6 +312,9 @@ final class PaletteLayerPreview {
}
}
+ private record SampledBlock(int x, int z, double noise) {
+ }
+
private record Statistics(long[] histogram, double[] sharesByEntry, double absentShare, double outOfRangeShare) {
}
}
diff --git a/core/src/main/java/com/volmit/iris/core/gui/PaletteLayerScale.java b/core/src/main/java/com/volmit/iris/core/gui/PaletteLayerScale.java
index a9d715847..12a0cfffd 100644
--- a/core/src/main/java/com/volmit/iris/core/gui/PaletteLayerScale.java
+++ b/core/src/main/java/com/volmit/iris/core/gui/PaletteLayerScale.java
@@ -87,7 +87,7 @@ final class PaletteLayerScale implements NoiseColorScale {
enum View {
BLOCKS("Blocks"),
NOISE("Noise"),
- BLOCKS_SHADED_BY_NOISE("Blocks + noise");
+ BLOCKS_SHADED_BY_NOISE("Both");
private final String label;
diff --git a/core/src/main/java/com/volmit/iris/core/gui/PaletteNavigator.java b/core/src/main/java/com/volmit/iris/core/gui/PaletteNavigator.java
index 51112f2cc..df3d2808e 100644
--- a/core/src/main/java/com/volmit/iris/core/gui/PaletteNavigator.java
+++ b/core/src/main/java/com/volmit/iris/core/gui/PaletteNavigator.java
@@ -50,7 +50,6 @@ import java.awt.GridLayout;
import java.awt.Rectangle;
import java.util.Enumeration;
import java.util.HashSet;
-import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -71,11 +70,9 @@ final class PaletteNavigator extends JPanel {
private static final String DETAIL_COLOR = "#8890A0";
private final Listener listener;
- private final boolean showsPlayerLocation;
private final CardLayout cards = new CardLayout();
private final JPanel cardPanel = new JPanel(cards);
private final JLabel regionLabel = new JLabel();
- private final JLabel positionLabel = new JLabel();
private final DefaultListModel hereRows = new DefaultListModel<>();
private final JList hereList = new JList<>(hereRows);
private final JTextField searchField = new JTextField();
@@ -88,9 +85,8 @@ final class PaletteNavigator extends JPanel {
private int selectedLayer;
private boolean updatingSelection;
- PaletteNavigator(boolean showsPlayerLocation, Listener listener) {
+ PaletteNavigator(Listener listener) {
super(new BorderLayout());
- this.showsPlayerLocation = showsPlayerLocation;
this.listener = listener;
setBackground(SIDEBAR_BACKGROUND);
cardPanel.add(buildHereCard(), HERE_CARD);
@@ -132,32 +128,17 @@ final class PaletteNavigator extends JPanel {
JToggleButton overview = createTab("Overview", OVERVIEW_CARD, group);
bar.add(here);
bar.add(overview);
- (showsPlayerLocation ? here : overview).doClick();
+ here.doClick();
return bar;
}
private JToggleButton createTab(String text, String card, ButtonGroup group) {
- JToggleButton tab = new JToggleButton(text);
- tab.setFocusPainted(false);
- tab.setFont(ITEM_FONT);
- tab.setBorder(BorderFactory.createCompoundBorder(
- BorderFactory.createLineBorder(BORDER),
- BorderFactory.createEmptyBorder(5, 9, 5, 9)
- ));
- tab.addItemListener(event -> styleTab(tab));
+ JToggleButton tab = ExplorerStyle.toggleButton(text);
tab.addActionListener(event -> cards.show(cardPanel, card));
- styleTab(tab);
group.add(tab);
return tab;
}
- private static void styleTab(JToggleButton tab) {
- tab.setBackground(tab.isSelected() ? ACCENT.darker().darker() : FIELD_BACKGROUND);
- tab.setForeground(tab.isSelected() ? PRIMARY_TEXT : SECONDARY_TEXT);
- tab.setContentAreaFilled(false);
- tab.setOpaque(true);
- }
-
private JPanel buildHereCard() {
JPanel card = new JPanel(new BorderLayout());
card.setBackground(SIDEBAR_BACKGROUND);
@@ -167,11 +148,9 @@ final class PaletteNavigator extends JPanel {
BorderFactory.createMatteBorder(1, 0, 1, 0, BORDER),
BorderFactory.createEmptyBorder(9, 12, 9, 12)
));
- for (JLabel label : List.of(regionLabel, positionLabel)) {
- label.setForeground(PRIMARY_TEXT);
- label.setFont(ITEM_FONT);
- location.add(label);
- }
+ regionLabel.setForeground(PRIMARY_TEXT);
+ regionLabel.setFont(ITEM_FONT);
+ location.add(regionLabel);
hereList.setBackground(SIDEBAR_BACKGROUND);
hereList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
@@ -254,17 +233,13 @@ final class PaletteNavigator extends JPanel {
updatingSelection = true;
try {
hereRows.clear();
- if (!showsPlayerLocation) {
- showHereMessage("Run /iris std pal in-game", "to see where you are standing.");
- return;
- }
if (location == null || catalog == null) {
- showHereMessage("Finding where you are...", " ");
+ showHereMessage("Finding where you are...");
return;
}
switch (location.status()) {
- case OFFLINE -> showHereMessage("You are offline.", " ");
- case OUTSIDE_IRIS_WORLD -> showHereMessage("You are not in an Iris world.", " ");
+ case OFFLINE -> showHereMessage("You are offline.");
+ case IN_OTHER_WORLD -> showHereMessage("You left the world this window shows.");
case IN_REGION -> showHereRows(location);
}
selectInHereList();
@@ -273,18 +248,16 @@ final class PaletteNavigator extends JPanel {
}
}
- private void showHereMessage(String firstLine, String secondLine) {
- regionLabel.setText(firstLine);
- positionLabel.setText(secondLine);
+ private void showHereMessage(String message) {
+ regionLabel.setText(message);
}
private void showHereRows(PlayerLocation here) {
RegionEntry region = catalog.region(here.regionKey());
regionLabel.setText(labeled("Region", region == null ? here.regionKey() : region.name(), here.regionKey()));
- positionLabel.setText(here.isBelowSurfaceBiome() ? "Underground, below the surface biome" : "On the surface");
- addBiomeLayers("Surface biome", here.surfaceBiomeKey());
+ addBiomeLayers("Surface biome here", here.surfaceBiomeKey());
if (here.isBelowSurfaceBiome()) {
- addBiomeLayers("Biome at your position", here.biomeAtPositionKey());
+ addBiomeLayers("Underground biome at your height", here.biomeAtPositionKey());
}
if (region == null) {
return;
diff --git a/core/src/main/java/com/volmit/iris/core/gui/PlayerLocationTracker.java b/core/src/main/java/com/volmit/iris/core/gui/PlayerLocationTracker.java
index 00eb1cc88..f9f99ffd6 100644
--- a/core/src/main/java/com/volmit/iris/core/gui/PlayerLocationTracker.java
+++ b/core/src/main/java/com/volmit/iris/core/gui/PlayerLocationTracker.java
@@ -36,17 +36,19 @@ final class PlayerLocationTracker {
private static final long CHECK_INTERVAL_TICKS = 10L;
private final UUID playerId;
+ private final UUID worldId;
private final Listener listener;
private BukkitTask task;
private PlayerLocation lastLocation;
- private PlayerLocationTracker(UUID playerId, Listener listener) {
+ private PlayerLocationTracker(UUID playerId, UUID worldId, Listener listener) {
this.playerId = playerId;
+ this.worldId = worldId;
this.listener = listener;
}
- static PlayerLocationTracker follow(UUID playerId, Listener listener) {
- PlayerLocationTracker tracker = new PlayerLocationTracker(playerId, listener);
+ static PlayerLocationTracker follow(UUID playerId, UUID worldId, Listener listener) {
+ PlayerLocationTracker tracker = new PlayerLocationTracker(playerId, worldId, listener);
tracker.task = Bukkit.getScheduler().runTaskTimer(Iris.instance, tracker::checkLocation, 0L, CHECK_INTERVAL_TICKS);
return tracker;
}
@@ -71,16 +73,19 @@ final class PlayerLocationTracker {
if (player == null) {
return PlayerLocation.OFFLINE;
}
+ if (player.getWorld() == null || !player.getWorld().getUID().equals(worldId)) {
+ return PlayerLocation.IN_OTHER_WORLD;
+ }
Engine engine = engineAt(player.getLocation());
if (engine == null) {
- return PlayerLocation.OUTSIDE_IRIS_WORLD;
+ return PlayerLocation.IN_OTHER_WORLD;
}
Location position = player.getLocation();
IrisRegion region = engine.getRegion(position);
String surfaceBiome = keyOf(engine.getSurfaceBiome(position.getBlockX(), position.getBlockZ()));
String biomeAtPosition = keyOf(engine.getBiomeOrMantle(position));
if (region == null || region.getLoadKey() == null || surfaceBiome == null) {
- return PlayerLocation.OUTSIDE_IRIS_WORLD;
+ return PlayerLocation.IN_OTHER_WORLD;
}
return new PlayerLocation(Status.IN_REGION, region.getLoadKey(), surfaceBiome, biomeAtPosition);
}
@@ -99,14 +104,14 @@ final class PlayerLocationTracker {
enum Status {
OFFLINE,
- OUTSIDE_IRIS_WORLD,
+ IN_OTHER_WORLD,
IN_REGION
}
// Biomes are 3D: underground, the biome at the player's position can differ from the surface biome above it.
record PlayerLocation(Status status, String regionKey, String surfaceBiomeKey, String biomeAtPositionKey) {
static final PlayerLocation OFFLINE = new PlayerLocation(Status.OFFLINE, null, null, null);
- static final PlayerLocation OUTSIDE_IRIS_WORLD = new PlayerLocation(Status.OUTSIDE_IRIS_WORLD, null, null, null);
+ static final PlayerLocation IN_OTHER_WORLD = new PlayerLocation(Status.IN_OTHER_WORLD, null, null, null);
boolean isBelowSurfaceBiome() {
return biomeAtPositionKey != null && !biomeAtPositionKey.equals(surfaceBiomeKey);