mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-22 08:07:42 +00:00
Give procedural objects their own tab
Variant picker, grouped by type.
This commit is contained in:
1 parent
8d12019246
commit
dc7615219b
12 files changed
+420
-116
No files matched your search
@@ -40,6 +40,8 @@ enum MapView {
|
||||
Group.BLOCKS, null, KeyEvent.VK_P),
|
||||
OBJECTS("Objects", "Every object in the pack in 3D, with the blocks it is made of",
|
||||
Group.BLOCKS, null, KeyEvent.VK_O),
|
||||
PROCEDURAL("Procedural", "Trees, rock formations and more that Iris generates from settings, every variant in 3D",
|
||||
Group.BLOCKS, null, KeyEvent.VK_T),
|
||||
NOISE("Noise", "The pack's noise generators and Iris' built-in noise styles",
|
||||
Group.GENERATORS, null, KeyEvent.VK_N);
|
||||
|
||||
@@ -70,7 +72,7 @@ enum MapView {
|
||||
}
|
||||
|
||||
/**
|
||||
* The tile renderer's mode for this view, or null for the palette, object and noise views, which draw themselves.
|
||||
* The tile renderer's mode for this view, or null for the palette, object, procedural and noise views, which draw themselves.
|
||||
*/
|
||||
RenderType renderType() {
|
||||
return renderType;
|
||||
|
||||
@@ -46,7 +46,9 @@ import java.awt.event.KeyEvent;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
/**
|
||||
@@ -56,7 +58,6 @@ import java.util.function.IntConsumer;
|
||||
public final class MapWindow implements MapCanvas.Listener {
|
||||
private static final String MAP_CARD = "map";
|
||||
private static final String PALETTES_CARD = "palettes";
|
||||
private static final String OBJECTS_CARD = "objects";
|
||||
private static final String NOISE_CARD = "noise";
|
||||
private static final int SIDEBAR_WIDTH = 270;
|
||||
private static final int SIDEBAR_MINIMUM_WIDTH = 200;
|
||||
@@ -91,7 +92,7 @@ public final class MapWindow implements MapCanvas.Listener {
|
||||
private final MapLegendPanel legendPanel;
|
||||
|
||||
private NoiseExplorerGUI paletteExplorer;
|
||||
private ObjectExplorer objectExplorer;
|
||||
private final Map<MapView, ObjectExplorer> objectExplorers = new EnumMap<>(MapView.class);
|
||||
private NoiseExplorerGUI noiseExplorer;
|
||||
private JComponent noiseSidebar;
|
||||
private MapView currentView;
|
||||
@@ -204,8 +205,8 @@ public final class MapWindow implements MapCanvas.Listener {
|
||||
showMap(previousView, view);
|
||||
} else if (view == MapView.PALETTES) {
|
||||
showPalettes(previousView);
|
||||
} else if (view == MapView.OBJECTS) {
|
||||
showObjects();
|
||||
} else if (view == MapView.OBJECTS || view == MapView.PROCEDURAL) {
|
||||
showObjectExplorer(view);
|
||||
} else {
|
||||
showNoise();
|
||||
}
|
||||
@@ -246,20 +247,18 @@ public final class MapWindow implements MapCanvas.Listener {
|
||||
}
|
||||
}
|
||||
|
||||
private void showObjects() {
|
||||
boolean firstOpen = objectExplorer == null;
|
||||
if (firstOpen) {
|
||||
createObjectExplorer();
|
||||
}
|
||||
private void showObjectExplorer(MapView view) {
|
||||
boolean firstOpen = !objectExplorers.containsKey(view);
|
||||
ObjectExplorer explorer = objectExplorers.computeIfAbsent(view, this::createObjectExplorer);
|
||||
canvas.setActive(false);
|
||||
showControls(explorerControls);
|
||||
useSidebar(viewListAbove(objectExplorer.browser()));
|
||||
useSidebar(viewListAbove(explorer.browser()));
|
||||
inspectorSlot.removeAll();
|
||||
inspectorSlot.add(objectExplorer.overview(), BorderLayout.CENTER);
|
||||
showCard(OBJECTS_CARD);
|
||||
inspectorSlot.add(explorer.overview(), BorderLayout.CENTER);
|
||||
showCard(view.name());
|
||||
EventQueue.invokeLater(() -> viewList.select(currentView));
|
||||
if (firstOpen) {
|
||||
objectExplorer.open();
|
||||
explorer.open();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,11 +272,13 @@ public final class MapWindow implements MapCanvas.Listener {
|
||||
});
|
||||
}
|
||||
|
||||
private void createObjectExplorer() {
|
||||
objectExplorer = new ObjectExplorer(start.engine(), start.worldId());
|
||||
keepClearOfIsland(objectExplorer.viewport()::setCoveredLeftWidth);
|
||||
content.add(objectExplorer.view(), OBJECTS_CARD);
|
||||
statusBars.add(objectExplorer.statusBar(), OBJECTS_CARD);
|
||||
private ObjectExplorer createObjectExplorer(MapView view) {
|
||||
ObjectSource source = view == MapView.PROCEDURAL ? new ProceduralObjectSource() : new PackObjectSource();
|
||||
ObjectExplorer explorer = new ObjectExplorer(start.engine(), start.worldId(), source);
|
||||
keepClearOfIsland(explorer::setCoveredLeftWidth);
|
||||
content.add(explorer.view(), view.name());
|
||||
statusBars.add(explorer.statusBar(), view.name());
|
||||
return explorer;
|
||||
}
|
||||
|
||||
private void showNoise() {
|
||||
@@ -391,7 +392,8 @@ public final class MapWindow implements MapCanvas.Listener {
|
||||
}
|
||||
|
||||
private void resetExplorerView() {
|
||||
if (currentView == MapView.OBJECTS) {
|
||||
ObjectExplorer objectExplorer = objectExplorers.get(currentView);
|
||||
if (objectExplorer != null) {
|
||||
objectExplorer.resetCamera();
|
||||
return;
|
||||
}
|
||||
@@ -487,9 +489,7 @@ public final class MapWindow implements MapCanvas.Listener {
|
||||
if (paletteExplorer != null) {
|
||||
paletteExplorer.close();
|
||||
}
|
||||
if (objectExplorer != null) {
|
||||
objectExplorer.close();
|
||||
}
|
||||
objectExplorers.values().forEach(ObjectExplorer::close);
|
||||
if (noiseExplorer != null) {
|
||||
noiseExplorer.close();
|
||||
}
|
||||
|
||||
@@ -51,15 +51,21 @@ final class ObjectBrowser extends JPanel {
|
||||
private final JTextField searchField = new JTextField();
|
||||
private final DefaultTreeModel treeModel = new DefaultTreeModel(new DefaultMutableTreeNode());
|
||||
private final JTree tree = new JTree(treeModel);
|
||||
private final JLabel countLabel = new JLabel("Loading objects...");
|
||||
private final JLabel countLabel = new JLabel();
|
||||
private final String noun;
|
||||
private List<String> objectKeys = List.of();
|
||||
private String chosenKey;
|
||||
private boolean updatingSelection;
|
||||
|
||||
ObjectBrowser(Consumer<String> onObjectChosen) {
|
||||
/**
|
||||
* @param noun what one entry is called in the browser's texts, such as "object"
|
||||
*/
|
||||
ObjectBrowser(String noun, Consumer<String> onObjectChosen) {
|
||||
super(new BorderLayout());
|
||||
this.noun = noun;
|
||||
this.onObjectChosen = onObjectChosen;
|
||||
StudioToolbar.makeSearchField(searchField, "Search objects...");
|
||||
countLabel.setText("Loading " + noun + "s...");
|
||||
StudioToolbar.makeSearchField(searchField, "Search " + noun + "s...");
|
||||
searchField.getDocument().addDocumentListener(new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent event) {
|
||||
@@ -97,7 +103,7 @@ final class ObjectBrowser extends JPanel {
|
||||
|
||||
void showObjects(List<String> keys) {
|
||||
objectKeys = keys.stream().sorted(Comparator.comparing(key -> key.toLowerCase(Locale.ROOT))).toList();
|
||||
countLabel.setText(objectKeys.size() == 1 ? "1 object" : objectKeys.size() + " objects");
|
||||
countLabel.setText(objectKeys.size() == 1 ? "1 " + noun : objectKeys.size() + " " + noun + "s");
|
||||
rebuildTree();
|
||||
}
|
||||
|
||||
@@ -128,7 +134,7 @@ final class ObjectBrowser extends JPanel {
|
||||
}
|
||||
}
|
||||
if (root.getChildCount() == 0) {
|
||||
root.add(new DefaultMutableTreeNode(Entry.note("No matching objects")));
|
||||
root.add(new DefaultMutableTreeNode(Entry.note("No matching " + noun + "s")));
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
@@ -19,10 +19,8 @@
|
||||
package com.volmit.iris.core.gui;
|
||||
|
||||
import com.volmit.iris.core.gui.BlockTextures.BlockTexture;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisObject;
|
||||
import com.volmit.iris.engine.object.procedural.ProceduralObjectCatalog;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.util.BlockVector;
|
||||
@@ -31,8 +29,6 @@ import javax.swing.JComponent;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -45,8 +41,8 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* The map window's Objects view: every object in the pack in a folder tree, the chosen one in 3D, and what it is made
|
||||
* of. Inspired by the Procedural Objects workbench.
|
||||
* The map window's Objects and Procedural views: everything its source offers in a folder tree, the chosen one in 3D,
|
||||
* and what it is made of. Inspired by the Procedural Objects workbench.
|
||||
*/
|
||||
final class ObjectExplorer {
|
||||
private static final NamespacedKey TINT_BIOME = NamespacedKey.minecraft("plains");
|
||||
@@ -55,10 +51,12 @@ final class ObjectExplorer {
|
||||
|
||||
private final Engine startEngine;
|
||||
private final UUID worldId;
|
||||
private final ObjectBrowser browser = new ObjectBrowser(this::showObject);
|
||||
private final ObjectSource source;
|
||||
private final ObjectBrowser browser;
|
||||
private final ObjectViewport viewport = new ObjectViewport();
|
||||
private final ObjectLayerCut layerCut = new ObjectLayerCut(shownLayers -> showLayerCut());
|
||||
private final JComponent viewWithLayerCut = layerCut.floatingOver(viewport);
|
||||
private final ObjectVariantPicker variantPicker = new ObjectVariantPicker(this::showVariant);
|
||||
private final JComponent viewWithOverlays = variantPicker.floatingOver(layerCut.floatingOver(viewport));
|
||||
private final ObjectOverviewPanel overview = new ObjectOverviewPanel(this::showSeeThrough);
|
||||
private final StudioStatusBar statusBar = new StudioStatusBar();
|
||||
private final ExecutorService loader = Executors.newSingleThreadExecutor(runnable -> {
|
||||
@@ -74,33 +72,39 @@ final class ObjectExplorer {
|
||||
private volatile BlockTextures textures;
|
||||
private volatile BiomeTintColors tintColors;
|
||||
private String shownKey;
|
||||
private List<IrisObject> shownVariants = List.of();
|
||||
private LoadedObject shown;
|
||||
private ObjectMesh wholeMesh;
|
||||
private Set<String> seeThroughTypes = Set.of();
|
||||
|
||||
ObjectExplorer(Engine engine, UUID worldId) {
|
||||
ObjectExplorer(Engine engine, UUID worldId, ObjectSource source) {
|
||||
this.startEngine = engine;
|
||||
this.worldId = worldId;
|
||||
this.source = source;
|
||||
this.browser = new ObjectBrowser(source.noun(), this::showObject);
|
||||
GuiHost.interruptOnPluginShutdown(loader);
|
||||
GuiHost.registerHotloadHook(hotloadHook);
|
||||
statusBar.showPosition(CAMERA_HINT);
|
||||
viewport.bindKey(KeyEvent.VK_PAGE_UP, "show-layer-above", () -> layerCut.stepBy(1));
|
||||
viewport.bindKey(KeyEvent.VK_PAGE_DOWN, "hide-top-layer", () -> layerCut.stepBy(-1));
|
||||
viewport.bindKey(KeyEvent.VK_OPEN_BRACKET, "previous-variant", () -> variantPicker.stepBy(-1));
|
||||
viewport.bindKey(KeyEvent.VK_CLOSE_BRACKET, "next-variant", () -> variantPicker.stepBy(1));
|
||||
}
|
||||
|
||||
JComponent browser() {
|
||||
return browser;
|
||||
}
|
||||
|
||||
ObjectViewport viewport() {
|
||||
return viewport;
|
||||
}
|
||||
|
||||
/**
|
||||
* The 3D view with the slider that cuts away its top layers.
|
||||
* The 3D view with the slider that cuts away its top layers and the bar that steps through variants.
|
||||
*/
|
||||
JComponent view() {
|
||||
return viewWithLayerCut;
|
||||
return viewWithOverlays;
|
||||
}
|
||||
|
||||
void setCoveredLeftWidth(int width) {
|
||||
viewport.setCoveredLeftWidth(width);
|
||||
variantPicker.setCoveredLeftWidth(width);
|
||||
}
|
||||
|
||||
JComponent overview() {
|
||||
@@ -143,10 +147,7 @@ final class ObjectExplorer {
|
||||
}
|
||||
|
||||
private List<String> objectKeys() {
|
||||
IrisData data = engine().getData();
|
||||
List<String> keys = new ArrayList<>(Arrays.asList(data.getObjectLoader().getPossibleKeys()));
|
||||
keys.addAll(data.getProceduralObjects().variantKeys());
|
||||
return keys;
|
||||
return source.keys(engine().getData());
|
||||
}
|
||||
|
||||
private void showCatalog(List<String> keys) {
|
||||
@@ -154,9 +155,10 @@ final class ObjectExplorer {
|
||||
browser.showObjects(keys);
|
||||
String key = shownKey != null && keys.contains(shownKey) ? shownKey : browser.firstObject();
|
||||
if (key == null) {
|
||||
viewport.showMessage("This pack has no objects");
|
||||
viewport.showMessage("This pack has no " + source.noun() + "s");
|
||||
overview.clear();
|
||||
layerCut.showHeight(1);
|
||||
variantPicker.showVariants(0);
|
||||
return;
|
||||
}
|
||||
browser.select(key);
|
||||
@@ -165,31 +167,54 @@ final class ObjectExplorer {
|
||||
|
||||
private void showObject(String key) {
|
||||
shownKey = key;
|
||||
shown = null;
|
||||
wholeMesh = null;
|
||||
meshRevision.incrementAndGet();
|
||||
long revision = objectRevision.incrementAndGet();
|
||||
viewport.showMessage("Loading " + key + "...");
|
||||
statusBar.showActivity("Loading " + key + "...");
|
||||
shownVariants = List.of();
|
||||
variantPicker.showVariants(0);
|
||||
long revision = startLoading(key);
|
||||
loader.execute(() -> {
|
||||
if (revision != objectRevision.get()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
LoadedObject loaded = load(key);
|
||||
EventQueue.invokeLater(() -> showLoaded(revision, loaded));
|
||||
List<IrisObject> variants = source.variants(engine().getData(), key);
|
||||
if (variants.isEmpty()) {
|
||||
throw new IllegalStateException("Nothing could be loaded for " + key);
|
||||
}
|
||||
LoadedObject loaded = load(variants.get(0));
|
||||
EventQueue.invokeLater(() -> {
|
||||
if (revision == objectRevision.get()) {
|
||||
shownVariants = variants;
|
||||
variantPicker.showVariants(variants.size());
|
||||
}
|
||||
showLoaded(revision, loaded);
|
||||
});
|
||||
} catch (RuntimeException error) {
|
||||
EventQueue.invokeLater(() -> showFailure(revision, key, error));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private LoadedObject load(String key) {
|
||||
IrisObject object = objectFor(key);
|
||||
if (object == null) {
|
||||
throw new IllegalStateException(ProceduralObjectCatalog.isVariantKey(key)
|
||||
? "This variant has no blocks" : "The object file could not be read");
|
||||
}
|
||||
private void showVariant(int index) {
|
||||
IrisObject variant = shownVariants.get(index);
|
||||
long revision = startLoading(variant.getLoadKey());
|
||||
loader.execute(() -> {
|
||||
if (revision != objectRevision.get()) {
|
||||
return;
|
||||
}
|
||||
LoadedObject loaded = load(variant);
|
||||
EventQueue.invokeLater(() -> showLoaded(revision, loaded));
|
||||
});
|
||||
}
|
||||
|
||||
private long startLoading(String key) {
|
||||
shown = null;
|
||||
wholeMesh = null;
|
||||
meshRevision.incrementAndGet();
|
||||
viewport.showMessage("Loading " + key + "...");
|
||||
statusBar.showActivity("Loading " + key + "...");
|
||||
return objectRevision.incrementAndGet();
|
||||
}
|
||||
|
||||
private LoadedObject load(IrisObject object) {
|
||||
Map<BlockVector, BlockData> blocks = new HashMap<>();
|
||||
object.getBlocks().forEach((position, block) -> {
|
||||
if (!block.getMaterial().isAir()) {
|
||||
@@ -197,17 +222,10 @@ final class ObjectExplorer {
|
||||
}
|
||||
});
|
||||
ObjectMesh mesh = ObjectMesh.build(blocks, this::modelOf);
|
||||
ObjectSummary summary = ObjectSummary.of(key, mesh.bounds(), blocks.values(), this::iconOf);
|
||||
ObjectSummary summary = ObjectSummary.of(object.getLoadKey(), mesh.bounds(), blocks.values(), this::iconOf);
|
||||
return new LoadedObject(blocks, mesh, summary);
|
||||
}
|
||||
|
||||
private IrisObject objectFor(String key) {
|
||||
IrisData data = engine().getData();
|
||||
if (ProceduralObjectCatalog.isVariantKey(key)) {
|
||||
return data.getProceduralObjects().variant(key);
|
||||
}
|
||||
return data.getObjectLoader().load(key);
|
||||
}
|
||||
|
||||
private void showLoaded(long revision, LoadedObject loaded) {
|
||||
if (revision != objectRevision.get()) {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.volmit.iris.core.gui;
|
||||
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.engine.object.IrisObject;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Where an object explorer gets what it shows: the pack's object files, or the objects Iris generates from settings.
|
||||
* Both are called on the explorer's loader thread only.
|
||||
*/
|
||||
interface ObjectSource {
|
||||
/** What one entry is called, such as "object", for the browser's texts. */
|
||||
String noun();
|
||||
|
||||
/** The key of every entry, where a slash separates folders. */
|
||||
List<String> keys(IrisData data);
|
||||
|
||||
/** The objects of the entry, one for an object file and one per variant for a procedural object; empty when none load. */
|
||||
List<IrisObject> variants(IrisData data, String key);
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.volmit.iris.core.gui;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingConstants;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.RoundRectangle2D;
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
/**
|
||||
* A small bar floating at the bottom of the 3D view that steps through the variants of a procedural object. It hides
|
||||
* itself for anything with a single variant.
|
||||
*/
|
||||
final class ObjectVariantPicker extends JPanel {
|
||||
private static final long serialVersionUID = -2716301839488217455L;
|
||||
private static final int BOTTOM_MARGIN = 14;
|
||||
private static final int LABEL_WIDTH = 110;
|
||||
private static final String SHORTCUT_HINT = " · [ and ]";
|
||||
|
||||
private final JLabel shownVariant = new JLabel(" ", SwingConstants.CENTER);
|
||||
private final IntConsumer onVariantChosen;
|
||||
private int variantCount;
|
||||
private int shownIndex;
|
||||
private int coveredLeftWidth;
|
||||
|
||||
/**
|
||||
* @param onVariantChosen told the index of the variant to show whenever the user steps to another one
|
||||
*/
|
||||
ObjectVariantPicker(IntConsumer onVariantChosen) {
|
||||
super(new BorderLayout());
|
||||
this.onVariantChosen = onVariantChosen;
|
||||
setOpaque(false);
|
||||
setBorder(StudioTheme.emptyBorder(3, 4, 3, 4));
|
||||
shownVariant.setFont(StudioTheme.smallFont());
|
||||
shownVariant.setForeground(StudioTheme.TEXT);
|
||||
shownVariant.setPreferredSize(new Dimension(StudioTheme.scale(LABEL_WIDTH), shownVariant.getPreferredSize().height));
|
||||
add(StudioToolbar.button("‹", "Previous variant" + SHORTCUT_HINT, () -> stepBy(-1)), BorderLayout.WEST);
|
||||
add(shownVariant, BorderLayout.CENTER);
|
||||
add(StudioToolbar.button("›", "Next variant" + SHORTCUT_HINT, () -> stepBy(1)), BorderLayout.EAST);
|
||||
showVariants(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* The view with this bar floating over its bottom edge, centred on the part the left island leaves uncovered.
|
||||
*/
|
||||
JComponent floatingOver(JComponent view) {
|
||||
ObjectVariantPicker picker = this;
|
||||
JPanel stack = new JPanel(null) {
|
||||
private static final long serialVersionUID = 1609842937565014832L;
|
||||
|
||||
// The bar overlaps the view, so the view may not repaint without the bar on top of it.
|
||||
@Override
|
||||
public boolean isOptimizedDrawingEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doLayout() {
|
||||
view.setBounds(0, 0, getWidth(), getHeight());
|
||||
Dimension size = picker.getPreferredSize();
|
||||
int centerX = coveredLeftWidth + (getWidth() - coveredLeftWidth) / 2;
|
||||
picker.setBounds(centerX - size.width / 2, getHeight() - size.height - StudioTheme.scale(BOTTOM_MARGIN), size.width, size.height);
|
||||
}
|
||||
};
|
||||
stack.add(picker);
|
||||
stack.add(view);
|
||||
return stack;
|
||||
}
|
||||
|
||||
void setCoveredLeftWidth(int width) {
|
||||
coveredLeftWidth = width;
|
||||
if (getParent() != null) {
|
||||
getParent().revalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts again at the first of {@code count} variants without telling anyone, since the first is what loads anyway.
|
||||
*/
|
||||
void showVariants(int count) {
|
||||
variantCount = count;
|
||||
shownIndex = 0;
|
||||
showLabel();
|
||||
setVisible(count > 1);
|
||||
}
|
||||
|
||||
void stepBy(int step) {
|
||||
if (variantCount < 2) {
|
||||
return;
|
||||
}
|
||||
shownIndex = Math.floorMod(shownIndex + step, variantCount);
|
||||
showLabel();
|
||||
onVariantChosen.accept(shownIndex);
|
||||
}
|
||||
|
||||
private void showLabel() {
|
||||
shownVariant.setText("Variant " + (shownIndex + 1) + " of " + variantCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics graphics) {
|
||||
Graphics2D canvas = (Graphics2D) graphics.create();
|
||||
try {
|
||||
canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
double arc = StudioTheme.scale(StudioTheme.OVERLAY_ARC);
|
||||
RoundRectangle2D shape = new RoundRectangle2D.Double(0.5, 0.5, getWidth() - 1D, getHeight() - 1D, arc, arc);
|
||||
canvas.setColor(StudioTheme.OVERLAY_BACKGROUND);
|
||||
canvas.fill(shape);
|
||||
canvas.setColor(StudioTheme.OVERLAY_BORDER);
|
||||
canvas.draw(shape);
|
||||
} finally {
|
||||
canvas.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.volmit.iris.core.gui;
|
||||
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.engine.object.IrisObject;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The pack's object files, by their load keys.
|
||||
*/
|
||||
final class PackObjectSource implements ObjectSource {
|
||||
@Override
|
||||
public String noun() {
|
||||
return "object";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> keys(IrisData data) {
|
||||
return Arrays.asList(data.getObjectLoader().getPossibleKeys());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IrisObject> variants(IrisData data, String key) {
|
||||
IrisObject object = data.getObjectLoader().load(key);
|
||||
return object == null ? List.of() : List.of(object);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.volmit.iris.core.gui;
|
||||
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.engine.object.IrisObject;
|
||||
import com.volmit.iris.engine.object.procedural.ResolvedProceduralPlacement;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The objects the pack's procedural settings generate, one entry per definition in a folder per type.
|
||||
*/
|
||||
final class ProceduralObjectSource implements ObjectSource {
|
||||
private static final Map<String, String> FOLDER_BY_KIND = Map.of(
|
||||
"tree", "Trees",
|
||||
"formation", "Formations",
|
||||
"crystal", "Crystals",
|
||||
"coral", "Coral",
|
||||
"fungus", "Fungi",
|
||||
"ruin", "Ruins");
|
||||
|
||||
private Map<String, ResolvedProceduralPlacement> definitionsByKey = Map.of();
|
||||
|
||||
@Override
|
||||
public String noun() {
|
||||
return "procedural object";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> keys(IrisData data) {
|
||||
Map<String, ResolvedProceduralPlacement> byKey = new LinkedHashMap<>();
|
||||
for (ResolvedProceduralPlacement definition : data.getProceduralObjects().definitions()) {
|
||||
byKey.put(FOLDER_BY_KIND.getOrDefault(definition.kind(), definition.kind()) + "/" + definition.name(), definition);
|
||||
}
|
||||
definitionsByKey = byKey;
|
||||
return List.copyOf(byKey.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IrisObject> variants(IrisData data, String key) {
|
||||
ResolvedProceduralPlacement definition = definitionsByKey.get(key);
|
||||
return definition == null ? List.of() : definition.variants();
|
||||
}
|
||||
}
|
||||
+23
-37
@@ -20,7 +20,6 @@ package com.volmit.iris.engine.object.procedural;
|
||||
import com.google.gson.Gson;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.engine.object.IrisObject;
|
||||
import com.volmit.iris.engine.object.annotations.Snippet;
|
||||
import com.volmit.iris.engine.object.procedural.coral.Coral;
|
||||
import com.volmit.iris.engine.object.procedural.coral.ResolvedCoral;
|
||||
@@ -41,15 +40,14 @@ import java.io.Reader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* The procedural objects of one pack: those its regions and biomes declare, and those saved on their own as snippets,
|
||||
* such as {@code snippet/procedural-tree/oak.json}. A definition several places share is baked once, and every variant
|
||||
* can be found again by its key.
|
||||
* such as {@code snippet/procedural-tree/oak.json}. A definition several places share is baked once.
|
||||
*/
|
||||
public final class ProceduralObjectCatalog {
|
||||
private static final Gson DEFINITION_JSON = new Gson();
|
||||
@@ -57,7 +55,6 @@ public final class ProceduralObjectCatalog {
|
||||
|
||||
private final IrisData data;
|
||||
private final KMap<String, ResolvedProceduralPlacement> byDefinition = new KMap<>();
|
||||
private final KMap<String, ResolvedProceduralPlacement> byVariantKey = new KMap<>();
|
||||
|
||||
public ProceduralObjectCatalog(IrisData data) {
|
||||
this.data = data;
|
||||
@@ -74,7 +71,7 @@ public final class ProceduralObjectCatalog {
|
||||
|
||||
private ResolvedProceduralPlacement placementOf(Object definition) {
|
||||
String identity = definition.getClass().getName() + DEFINITION_JSON.toJson(definition);
|
||||
return byDefinition.computeIfAbsent(identity, ignored -> register(resolvedFor(definition)));
|
||||
return byDefinition.computeIfAbsent(identity, ignored -> resolvedFor(definition));
|
||||
}
|
||||
|
||||
private ResolvedProceduralPlacement resolvedFor(Object definition) {
|
||||
@@ -89,22 +86,29 @@ public final class ProceduralObjectCatalog {
|
||||
};
|
||||
}
|
||||
|
||||
private ResolvedProceduralPlacement register(ResolvedProceduralPlacement placement) {
|
||||
for (String key : placement.variantKeys()) {
|
||||
byVariantKey.putIfAbsent(key, placement);
|
||||
/**
|
||||
* Every procedural object the pack defines, in snippets or in regions and biomes, whether or not its dimensions
|
||||
* reach them. Where two definitions share a type and name, the first one found stands for both.
|
||||
*/
|
||||
public List<ResolvedProceduralPlacement> definitions() {
|
||||
Map<String, ResolvedProceduralPlacement> byTypeAndName = new LinkedHashMap<>();
|
||||
for (Class<?> type : DEFINITION_TYPES) {
|
||||
snippetDefinitions(type).forEach(definition -> addDefinition(byTypeAndName, placementOf(definition)));
|
||||
}
|
||||
return placement;
|
||||
data.getRegionLoader().streamAll().forEach(region -> addDefinitions(byTypeAndName, region.getProceduralObjects()));
|
||||
data.getBiomeLoader().streamAll().forEach(biome -> addDefinitions(byTypeAndName, biome.getProceduralObjects()));
|
||||
return List.copyOf(byTypeAndName.values());
|
||||
}
|
||||
|
||||
/** The key of every variant the pack defines, in snippets or in regions and biomes, whether or not its dimensions reach them. */
|
||||
public List<String> variantKeys() {
|
||||
Set<String> keys = new LinkedHashSet<>();
|
||||
for (Class<?> type : DEFINITION_TYPES) {
|
||||
snippetDefinitions(type).forEach(definition -> keys.addAll(placementOf(definition).variantKeys()));
|
||||
private void addDefinitions(Map<String, ResolvedProceduralPlacement> byTypeAndName, ProceduralObjectSettings settings) {
|
||||
if (settings == null) {
|
||||
return;
|
||||
}
|
||||
data.getRegionLoader().streamAll().forEach(region -> addVariantKeys(keys, region.getProceduralObjects()));
|
||||
data.getBiomeLoader().streamAll().forEach(biome -> addVariantKeys(keys, biome.getProceduralObjects()));
|
||||
return List.copyOf(keys);
|
||||
settings.resolved(data).forEach(placement -> addDefinition(byTypeAndName, placement));
|
||||
}
|
||||
|
||||
private static void addDefinition(Map<String, ResolvedProceduralPlacement> byTypeAndName, ResolvedProceduralPlacement placement) {
|
||||
byTypeAndName.putIfAbsent(placement.kind() + "/" + placement.name(), placement);
|
||||
}
|
||||
|
||||
private List<Object> snippetDefinitions(Class<?> type) {
|
||||
@@ -136,22 +140,4 @@ public final class ProceduralObjectCatalog {
|
||||
}
|
||||
}
|
||||
|
||||
private void addVariantKeys(Set<String> keys, ProceduralObjectSettings settings) {
|
||||
if (settings == null) {
|
||||
return;
|
||||
}
|
||||
for (ResolvedProceduralPlacement placement : settings.resolved(data)) {
|
||||
keys.addAll(placement.variantKeys());
|
||||
}
|
||||
}
|
||||
|
||||
/** The baked variant loaded under {@code key}; null when no definition resolved so far has it, or it baked no blocks. */
|
||||
public IrisObject variant(String key) {
|
||||
ResolvedProceduralPlacement placement = byVariantKey.get(key);
|
||||
return placement == null ? null : placement.variant(key);
|
||||
}
|
||||
|
||||
public static boolean isVariantKey(String key) {
|
||||
return key.startsWith("procedural/");
|
||||
}
|
||||
}
|
||||
+5
-9
@@ -68,6 +68,11 @@ public abstract class ResolvedProceduralPlacement {
|
||||
return shape.name();
|
||||
}
|
||||
|
||||
/** The type of object, as the variant keys name it: tree, ruin, formation, coral, fungus or crystal. */
|
||||
public String kind() {
|
||||
return shape.kind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the world generator can place it. A place mode this version does not know, such as v4's CEILING_HANG,
|
||||
* reads as null, and placing such an object on the surface would put it in the wrong place.
|
||||
@@ -130,15 +135,6 @@ public abstract class ResolvedProceduralPlacement {
|
||||
return baked.get(rng.i(baked.size()));
|
||||
}
|
||||
|
||||
/** The baked variant loaded under {@code key}; null when it is not one of these, or baked no blocks. */
|
||||
public IrisObject variant(String key) {
|
||||
for (IrisObject variant : variants()) {
|
||||
if (variant.getLoadKey().equals(key)) {
|
||||
return variant;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Bakes every variant; the default draws variant {@code i} from {@code seed + i * stride}. */
|
||||
protected List<IrisObject> bake(ObjectBuilder blocks) {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<!-- Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.79289 13.7929L2.20711 13.2071C1.81658 12.8166 1.81658 12.1834 2.20711 11.7929L8.79289 5.20711C9.18342 4.81658 9.81658 4.81658 10.2071 5.20711L10.7929 5.79289C11.1834 6.18342 11.1834 6.81658 10.7929 7.20711L4.20711 13.7929C3.81658 14.1834 3.18342 14.1834 2.79289 13.7929Z" stroke="#6C707E" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.5 8.5L7.5 6.5" stroke="#6C707E" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.5 2.5V1.5" stroke="#6C707E" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.5 5.5H14.5M11.5 9.5L12.5 10.5M6.5 4.5L5.5 3.5" stroke="#6C707E" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 889 B |
@@ -0,0 +1,7 @@
|
||||
<!-- Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.79289 13.7929L2.20711 13.2071C1.81658 12.8166 1.81658 12.1834 2.20711 11.7929L8.79289 5.20711C9.18342 4.81658 9.81658 4.81658 10.2071 5.20711L10.7929 5.79289C11.1834 6.18342 11.1834 6.81658 10.7929 7.20711L4.20711 13.7929C3.81658 14.1834 3.18342 14.1834 2.79289 13.7929Z" stroke="#CED0D6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.5 8.5L7.5 6.5" stroke="#CED0D6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.5 2.5V1.5" stroke="#CED0D6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.5 5.5H14.5M11.5 9.5L12.5 10.5M6.5 4.5L5.5 3.5" stroke="#CED0D6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 889 B |
Reference in new issue
Block a user