diff --git a/src/main/java/amidst/gui/main/Actions.java b/src/main/java/amidst/gui/main/Actions.java index b47d1ec2..11832b44 100644 --- a/src/main/java/amidst/gui/main/Actions.java +++ b/src/main/java/amidst/gui/main/Actions.java @@ -27,6 +27,7 @@ import amidst.gui.main.viewer.ViewerFacade; import amidst.gui.seedsearcher.SeedSearcherWindow; import amidst.logging.AmidstLogger; import amidst.minetest.world.mapgen.DefaultBiomes; +import amidst.minetest.world.mapgen.MapgenRelay; import amidst.mojangapi.world.WorldSeed; import amidst.mojangapi.world.WorldType; import amidst.mojangapi.world.coordinates.CoordinatesInWorld; @@ -47,6 +48,7 @@ public class Actions { private final BiomeAuthority biomeAuthority; private final GameEngineDetails gameEngineDetails; private final AmidstVersion amidstVersion; + private final MapgenRelay mapgenRelay; @CalledOnlyBy(AmidstThread.EDT) public Actions( @@ -66,6 +68,8 @@ public class Actions { this.biomeAuthority = biomeAuthority; this.gameEngineDetails = gameEngineDetails; this.amidstVersion = amidstVersion; + + this.mapgenRelay = new MapgenRelay(worldSwitcher); } @CalledOnlyBy(AmidstThread.EDT) @@ -339,7 +343,7 @@ public class Actions { // only one implemented anyway. // It might be worth passing this value once Amidstest is reading game profiles, // as then it will at least know if the current world DOESN'T use the default climate settings. - dialogs.displayVoronoiDiagram(biomeAuthority.getBiomeProfileSelection(), null); + dialogs.displayVoronoiDiagram(biomeAuthority.getBiomeProfileSelection(), mapgenRelay, null); } else { dialogs.displayInfo("Biome profile Voronoi diagram", "Minecraft biomes are not determined by heat and humidity."); } diff --git a/src/main/java/amidst/gui/main/MainWindowDialogs.java b/src/main/java/amidst/gui/main/MainWindowDialogs.java index 3839a21c..ab6126d0 100644 --- a/src/main/java/amidst/gui/main/MainWindowDialogs.java +++ b/src/main/java/amidst/gui/main/MainWindowDialogs.java @@ -17,6 +17,7 @@ import amidst.gui.text.TextWindow; import amidst.gui.voronoi.VoronoiWindow; import amidst.logging.AmidstMessageBox; import amidst.minetest.world.mapgen.IHistogram2D; +import amidst.minetest.world.mapgen.MapgenRelay; import amidst.mojangapi.RunningLauncherProfile; import amidst.mojangapi.world.WorldSeed; import amidst.mojangapi.world.WorldType; @@ -119,8 +120,8 @@ public class MainWindowDialogs { } @CalledOnlyBy(AmidstThread.EDT) - public void displayVoronoiDiagram(BiomeProfileSelection biome_profile_selection, IHistogram2D climate_histogram) { - VoronoiWindow.showDiagram(frame, biome_profile_selection, climate_histogram); + public void displayVoronoiDiagram(BiomeProfileSelection biome_profile_selection, MapgenRelay mapgen, IHistogram2D climate_histogram) { + VoronoiWindow.showDiagram(frame, biome_profile_selection, mapgen, climate_histogram); } @CalledOnlyBy(AmidstThread.EDT) diff --git a/src/main/java/amidst/gui/main/WorldSwitchedListener.java b/src/main/java/amidst/gui/main/WorldSwitchedListener.java new file mode 100644 index 00000000..51600f4d --- /dev/null +++ b/src/main/java/amidst/gui/main/WorldSwitchedListener.java @@ -0,0 +1,7 @@ +package amidst.gui.main; + +import amidst.gui.main.viewer.ViewerFacade; + +public interface WorldSwitchedListener { + void onWorldSwitched(ViewerFacade viewerFacade); +} diff --git a/src/main/java/amidst/gui/main/WorldSwitcher.java b/src/main/java/amidst/gui/main/WorldSwitcher.java index e587a019..6489bd2c 100644 --- a/src/main/java/amidst/gui/main/WorldSwitcher.java +++ b/src/main/java/amidst/gui/main/WorldSwitcher.java @@ -4,10 +4,12 @@ import java.awt.BorderLayout; import java.awt.Container; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import javax.swing.JFrame; +import javax.swing.SwingUtilities; import amidst.dependency.injection.Factory1; import amidst.documentation.AmidstThread; @@ -25,7 +27,6 @@ import amidst.mojangapi.world.WorldType; import amidst.mojangapi.world.player.MovablePlayerList; import amidst.mojangapi.world.player.WorldPlayerType; import amidst.parsing.FormatException; -import amidst.settings.biomeprofile.BiomeProfile; import amidst.settings.biomeprofile.BiomeProfileSelection; import amidst.threading.ThreadMaster; @@ -40,6 +41,7 @@ public class WorldSwitcher { private final AtomicReference viewerFacadeReference; private final MainWindowDialogs dialogs; private final Supplier menuBarSupplier; + private final ArrayList listeners = new ArrayList(); @CalledOnlyBy(AmidstThread.EDT) public WorldSwitcher( @@ -62,6 +64,14 @@ public class WorldSwitcher { this.dialogs = dialogs; this.menuBarSupplier = menuBarSupplier; } + + public ViewerFacade addWorldSwitchedListener(WorldSwitchedListener listener) { + listeners.add(listener); + return viewerFacadeReference.get(); + } + public void removeWorldSwitchedListener(WorldSwitchedListener listener) { + listeners.remove(listener); + } @CalledOnlyBy(AmidstThread.EDT) public void displayWorld(WorldSeed worldSeed, WorldType worldType, BiomeProfileSelection biomeProfileSelection) { @@ -131,6 +141,12 @@ public class WorldSwitcher { threadMaster.setOnRepaintTick(viewerFacade.getOnRepainterTick()); threadMaster.setOnFragmentLoadTick(viewerFacade.getOnFragmentLoaderTick()); viewerFacadeReference.set(viewerFacade); + + SwingUtilities.invokeLater(() -> { + for (WorldSwitchedListener listener: listeners) { + listener.onWorldSwitched(viewerFacade); + } + }); } @CalledOnlyBy(AmidstThread.EDT) diff --git a/src/main/java/amidst/gui/main/viewer/ViewerFacade.java b/src/main/java/amidst/gui/main/viewer/ViewerFacade.java index dde7ea5b..fc9a573b 100644 --- a/src/main/java/amidst/gui/main/viewer/ViewerFacade.java +++ b/src/main/java/amidst/gui/main/viewer/ViewerFacade.java @@ -10,6 +10,7 @@ import amidst.documentation.AmidstThread; import amidst.documentation.CalledOnlyBy; import amidst.documentation.NotThreadSafe; import amidst.fragment.FragmentGraph; +import amidst.fragment.IBiomeDataOracle; import amidst.fragment.layer.LayerManager; import amidst.fragment.layer.LayerReloader; import amidst.mojangapi.world.Dimension; @@ -138,6 +139,11 @@ public class ViewerFacade { worldIconSelection.select(worldIcon); } + @CalledOnlyBy(AmidstThread.EDT) + public IBiomeDataOracle getBiomeDataOracle() { + return world.getBiomeDataOracle(); + } + @CalledOnlyBy(AmidstThread.EDT) public WorldSeed getWorldSeed() { return world.getWorldSeed(); diff --git a/src/main/java/amidst/gui/voronoi/VoronoiGraph.java b/src/main/java/amidst/gui/voronoi/VoronoiGraph.java index 8d11c6e3..17e92f9a 100644 --- a/src/main/java/amidst/gui/voronoi/VoronoiGraph.java +++ b/src/main/java/amidst/gui/voronoi/VoronoiGraph.java @@ -25,6 +25,10 @@ public class VoronoiGraph { } } + public void setHistogram(IHistogram2D histogram) { + climateHistogram = histogram; + } + /** * Returns an image of the Vonoroi graph, and if a climateHistogram has been provided then * it calculates and sets the occurrenceFrequency field in each of the GraphNodes. diff --git a/src/main/java/amidst/gui/voronoi/VoronoiPanel.java b/src/main/java/amidst/gui/voronoi/VoronoiPanel.java index af03ec7a..325cc2aa 100644 --- a/src/main/java/amidst/gui/voronoi/VoronoiPanel.java +++ b/src/main/java/amidst/gui/voronoi/VoronoiPanel.java @@ -23,9 +23,11 @@ import javax.swing.SwingUtilities; import amidst.documentation.AmidstThread; import amidst.documentation.CalledByAny; import amidst.documentation.CalledOnlyBy; +import amidst.fragment.IBiomeDataOracle; import amidst.gameengineabstraction.world.biome.IBiome; import amidst.minetest.world.mapgen.ClimateHistogram; import amidst.minetest.world.mapgen.IHistogram2D; +import amidst.minetest.world.mapgen.IHistogram2DTransformationProvider; import amidst.minetest.world.mapgen.MinetestBiome; import amidst.minetest.world.mapgen.MinetestBiomeProfileImpl; @@ -60,6 +62,9 @@ public class VoronoiPanel extends JPanel { public int graph_resolution = 1000; private IHistogram2D climateHistogram = null; + private IHistogram2D adjustedClimateHistogram = null; + private float adjustedClimateHistogramArgument = Float.NaN; + private IHistogram2DTransformationProvider mapgenClimateHistogramAdjustment = null; ArrayList biomes = new ArrayList(); private List graphNodes = null; private VoronoiGraph voronoiGraph = null; @@ -73,8 +78,12 @@ public class VoronoiPanel extends JPanel { super.paintComponent(g); boolean frequencyGraphWasNull = frequencyGraph == null; - if (climateHistogram == null) climateHistogram = new ClimateHistogram(); - if (voronoiGraph == null) voronoiGraph = new VoronoiGraph(graph_resolution, graph_resolution, climateHistogram); + if (adjustedClimateHistogram == null) { + if (climateHistogram == null) climateHistogram = new ClimateHistogram(); + adjustedClimateHistogram = (mapgenClimateHistogramAdjustment == null) ? climateHistogram : mapgenClimateHistogramAdjustment.getTransformedHistogram(climateHistogram, adjustedClimateHistogramArgument); + if (voronoiGraph != null) voronoiGraph.setHistogram(adjustedClimateHistogram); + } + if (voronoiGraph == null) voronoiGraph = new VoronoiGraph(graph_resolution, graph_resolution, adjustedClimateHistogram); if (frequencyGraph == null) frequencyGraph = new FrequencyGraph(graph_resolution, graph_resolution); this.setBorder(null); @@ -132,7 +141,7 @@ public class VoronoiPanel extends JPanel { try { if (graphNodes != null) { g2d.drawImage( - frequencyGraph.render(climateHistogram, axis_min, axis_max, axis_min, axis_max), + frequencyGraph.render(adjustedClimateHistogram, axis_min, axis_max, axis_min, axis_max), axis_min, axis_min, desiredAxisLength, desiredAxisLength, null, null ); } @@ -150,7 +159,7 @@ public class VoronoiPanel extends JPanel { // In order to prevent a delay when the user enables the frequencyGraph, // Make it pre-render after the UI has finished updating. SwingUtilities.invokeLater(() -> { - frequencyGraph.render(climateHistogram, axis_min, axis_max, axis_min, axis_max); + frequencyGraph.render(adjustedClimateHistogram, axis_min, axis_max, axis_min, axis_max); }); } } @@ -328,10 +337,13 @@ public class VoronoiPanel extends JPanel { * If the world doesn't use Minetest's default Heat&Humidity algorithm, then pass * a histogram of it here. */ - public void setClimateHistogram(IHistogram2D climate_histogram) { this.climateHistogram = climate_histogram; } + public void setClimateHistogram(IHistogram2D climate_histogram) { + this.climateHistogram = climate_histogram; + this.adjustedClimateHistogram = null; // force adjustedClimateHistogram to be recalculated with the new climateHistogram + } @CalledOnlyBy(AmidstThread.EDT) - public void Update(MinetestBiomeProfileImpl biomeProfile, int altitude, float frequency_graph_opacity, int flags) { + public void Update(MinetestBiomeProfileImpl biomeProfile, IBiomeDataOracle mapgen, int altitude, float frequency_graph_opacity, int flags) { ArrayList newBiomes = new ArrayList(); if (biomeProfile != null) for (IBiome biome : biomeProfile.allBiomes()) { @@ -350,7 +362,23 @@ public class VoronoiPanel extends JPanel { if (!alreadyOccupied) newBiomes.add(mtBiome); } } - + + IHistogram2DTransformationProvider oldAjustment = mapgenClimateHistogramAdjustment; + float oldArgument = adjustedClimateHistogramArgument; + + if (mapgen instanceof IHistogram2DTransformationProvider) { + mapgenClimateHistogramAdjustment = (IHistogram2DTransformationProvider)mapgen; + adjustedClimateHistogramArgument = altitude; + } else { + mapgenClimateHistogramAdjustment = null; + adjustedClimateHistogramArgument = Float.NaN; + } + boolean histogramAdjusted = oldAjustment != mapgenClimateHistogramAdjustment || oldArgument != adjustedClimateHistogramArgument; + if (histogramAdjusted) { + // something changed, force adjustedClimateHistogram to be recalculated. + this.adjustedClimateHistogram = null; + } + if (flags != this.renderFlags || !newBiomes.equals(biomes) || frequencyGraphOpacity != frequency_graph_opacity) { this.renderFlags = flags; biomes = newBiomes; @@ -360,6 +388,8 @@ public class VoronoiPanel extends JPanel { graphNodes.add(new GraphNode(biome.heat_point, biome.humidity_point, biome.getDefaultColor().getRGB())); } + repaint(); + } else if (histogramAdjusted) { repaint(); } } diff --git a/src/main/java/amidst/gui/voronoi/VoronoiWindow.java b/src/main/java/amidst/gui/voronoi/VoronoiWindow.java index b82ee490..6932d749 100644 --- a/src/main/java/amidst/gui/voronoi/VoronoiWindow.java +++ b/src/main/java/amidst/gui/voronoi/VoronoiWindow.java @@ -5,8 +5,11 @@ import java.awt.Component; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Image; +import java.awt.KeyEventDispatcher; +import java.awt.KeyboardFocusManager; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.Hashtable; @@ -32,14 +35,19 @@ import net.miginfocom.swing.MigLayout; import amidst.ResourceLoader; import amidst.documentation.AmidstThread; import amidst.documentation.CalledOnlyBy; +import amidst.fragment.IBiomeDataOracle; import amidst.gui.text.TextWindow; import amidst.minetest.world.mapgen.IHistogram2D; +import amidst.minetest.world.mapgen.IHistogram2DTransformationProvider; +import amidst.minetest.world.mapgen.MapgenRelay; +import amidst.minetest.world.mapgen.MapgenUpdatedListener; import amidst.minetest.world.mapgen.MinetestBiomeProfileImpl; +import amidst.minetest.world.oracle.MinetestBiomeDataOracle; import amidst.settings.biomeprofile.BiomeProfile; import amidst.settings.biomeprofile.BiomeProfileSelection; import amidst.settings.biomeprofile.BiomeProfileUpdateListener; -public class VoronoiWindow implements BiomeProfileUpdateListener, ChangeListener { +public class VoronoiWindow implements BiomeProfileUpdateListener, MapgenUpdatedListener, ChangeListener, KeyEventDispatcher { private static final int ALTITUDESLIDER_DEFAULT_LOW = -40; private static final int ALTITUDESLIDER_DEFAULT_HIGH = 200; @@ -47,6 +55,7 @@ public class VoronoiWindow implements BiomeProfileUpdateListener, ChangeListener private static VoronoiWindow voronoiWindow = null; private BiomeProfileSelection biomeProfileSelection; + private MapgenRelay mapgenRelay; private final JFrame windowFrame; private VoronoiPanel voronoiPanel; @@ -60,6 +69,7 @@ public class VoronoiWindow implements BiomeProfileUpdateListener, ChangeListener private JCheckBox option_showCoverage; private MinetestBiomeProfileImpl selectedProfile = null; + private MinetestBiomeDataOracle selectedMapgen = null; @CalledOnlyBy(AmidstThread.EDT) @@ -212,6 +222,22 @@ public class VoronoiWindow implements BiomeProfileUpdateListener, ChangeListener UpdateSelectedBiomeProfile(newBiomeProfile); } + @Override + public void onMapgenUpdated(IBiomeDataOracle biomeDataOracle) { + UpdateSelectedMapgen(biomeDataOracle); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent e) { + if (e.getID() == KeyEvent.VK_F5) { + // TODO: Reload the biome from disk and update the Voronoi diagram + // (Use case: biome-editing using a text editor, since Amidst doesn't provide + // a Voronoi editor. Auto update when file updates would be even better) + } + // return false to pass the KeyEvent to the next KeyEventDispatcher in the chain + return false; + } + @Override public void stateChanged(ChangeEvent e) { if (e.getSource() == altitudeOffset) { @@ -223,7 +249,12 @@ public class VoronoiWindow implements BiomeProfileUpdateListener, ChangeListener private void InfoButtonClicked() { StringBuilder data = new StringBuilder(); data.append("Biome profile: "); - data.append(selectedProfile.getName()); + data.append(selectedProfile.getName()); + if (selectedMapgen instanceof IHistogram2DTransformationProvider) { + data.append(" with "); + data.append(selectedMapgen.getName()); + data.append(" mapgen"); + } data.append("\r\n\r\n"); data.append("At altitude " + getAltitudeFromDialog() + ", the world is composed of the following biomes:\r\n\r\n"); data.append(voronoiPanel.getDistributionData()); @@ -273,7 +304,27 @@ public class VoronoiWindow implements BiomeProfileUpdateListener, ChangeListener selectedProfile = minetestProfile; if (changed) updateVoronoiDiagram(); - this.windowFrame.setTitle(selectedProfile == null ? "Biome profile Voronoi diagram" : "Voronoi diagram for " + selectedProfile.getName()); + this.windowFrame.setTitle(getTitle(selectedProfile, selectedMapgen)); + } + + private void UpdateSelectedMapgen(IBiomeDataOracle newMapgen) { + + MinetestBiomeDataOracle minetestMapgen = (newMapgen instanceof MinetestBiomeDataOracle) ? (MinetestBiomeDataOracle)newMapgen : null; + + boolean changed = (minetestMapgen == null) ? (selectedMapgen != null) : (selectedMapgen == null || (minetestMapgen.getClass() != selectedMapgen.getClass())); + selectedMapgen = minetestMapgen; + + if (changed) updateVoronoiDiagram(); + this.windowFrame.setTitle(getTitle(selectedProfile, selectedMapgen)); + } + + private String getTitle(MinetestBiomeProfileImpl biome_profile, MinetestBiomeDataOracle mapgen) { + String result = biome_profile == null ? "Biome profile Voronoi diagram" : "Voronoi diagram for " + biome_profile.getName(); + + if (mapgen instanceof IHistogram2DTransformationProvider) { + result += " with " + mapgen.getName() + " mapgen"; + } + return result; } private void updateVoronoiDiagram() { @@ -281,7 +332,7 @@ public class VoronoiWindow implements BiomeProfileUpdateListener, ChangeListener () -> { int altitude = getAltitudeFromDialog(); float freqGraphOpacity = freqGraphSlider.getValue() / 100f; - voronoiPanel.Update(selectedProfile, altitude, freqGraphOpacity, getOptionFlagsFromDialog()); + voronoiPanel.Update(selectedProfile, selectedMapgen, altitude, freqGraphOpacity, getOptionFlagsFromDialog()); graphHeading.setText("Biomes at altitude " + altitude); } ); @@ -319,33 +370,44 @@ public class VoronoiWindow implements BiomeProfileUpdateListener, ChangeListener } @CalledOnlyBy(AmidstThread.EDT) - private void show(BiomeProfileSelection biomeProfileSelection) { + private void show(BiomeProfileSelection biomeProfileSelection, MapgenRelay mapgen_relay) { if (this.biomeProfileSelection != null) { this.biomeProfileSelection.removeUpdateListener(this); } + if (this.mapgenRelay != null) { + this.mapgenRelay.removeMapgenUpdatedListener(this); + } this.biomeProfileSelection = biomeProfileSelection; + this.mapgenRelay = mapgen_relay; BiomeProfile newProfile = null; if (biomeProfileSelection != null) { this.biomeProfileSelection.addUpdateListener(this); newProfile = this.biomeProfileSelection.getCurrentBiomeProfile(); } + IBiomeDataOracle newMapgen = null; + if (mapgen_relay != null) { + this.mapgenRelay.addMapgenUpdatedListener(this); + newMapgen = this.mapgenRelay.getBiomeDataOracle(); + } + UpdateSelectedBiomeProfile(newProfile); + UpdateSelectedMapgen(newMapgen); this.windowFrame.setVisible(true); } /** Creates and displays the Voronoi diagram window */ - public static void showDiagram(Component parent, BiomeProfileSelection biome_profile_selection, IHistogram2D climate_histogram) { + public static void showDiagram(Component parent, BiomeProfileSelection biome_profile_selection, MapgenRelay mapgen, IHistogram2D climate_histogram) { if (voronoiWindow == null) { voronoiWindow = new VoronoiWindow(parent); } SwingUtilities.invokeLater(() -> { voronoiWindow.voronoiPanel.setClimateHistogram(climate_histogram); - voronoiWindow.show(biome_profile_selection); + voronoiWindow.show(biome_profile_selection, mapgen); }); } @@ -362,5 +424,7 @@ public class VoronoiWindow implements BiomeProfileUpdateListener, ChangeListener VoronoiPanel.FLAG_SHOWNODES | VoronoiPanel.FLAG_SHOWCOVERAGE ); + + //KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this); } } diff --git a/src/main/java/amidst/minetest/world/mapgen/IHistogram2DTransformationProvider.java b/src/main/java/amidst/minetest/world/mapgen/IHistogram2DTransformationProvider.java new file mode 100644 index 00000000..db7ff4d9 --- /dev/null +++ b/src/main/java/amidst/minetest/world/mapgen/IHistogram2DTransformationProvider.java @@ -0,0 +1,11 @@ +package amidst.minetest.world.mapgen; + +public interface IHistogram2DTransformationProvider { + /** + * Returns an adjusted histogram (scaled, translated, etc.) + * @param source is the histogram to be transformed + * @param argument is used if the transformation requires an argument (such + * as altitude, for a climate histogram). + */ + IHistogram2D getTransformedHistogram(IHistogram2D source, float argument); +} diff --git a/src/main/java/amidst/minetest/world/mapgen/MapgenRelay.java b/src/main/java/amidst/minetest/world/mapgen/MapgenRelay.java new file mode 100644 index 00000000..8f8a0bee --- /dev/null +++ b/src/main/java/amidst/minetest/world/mapgen/MapgenRelay.java @@ -0,0 +1,45 @@ +package amidst.minetest.world.mapgen; + +import java.util.ArrayList; + +import amidst.fragment.IBiomeDataOracle; +import amidst.gui.main.WorldSwitchedListener; +import amidst.gui.main.WorldSwitcher; +import amidst.gui.main.viewer.ViewerFacade; + +/** + * Adapts the WorldSwitcher to provides notifications for when the mapgen has changed + */ +public class MapgenRelay implements WorldSwitchedListener { + + private IBiomeDataOracle biomeDataOracle; + private final ArrayList listeners = new ArrayList(); + + @Override + public void onWorldSwitched(ViewerFacade viewerFacade) { + + biomeDataOracle = null; + if (viewerFacade != null) biomeDataOracle = viewerFacade.getBiomeDataOracle(); + + for (MapgenUpdatedListener listener: listeners) { + listener.onMapgenUpdated(biomeDataOracle); + } + } + + public void addMapgenUpdatedListener(MapgenUpdatedListener listener) { + listeners.add(listener); + } + public void removeMapgenUpdatedListener(MapgenUpdatedListener listener) { + listeners.remove(listener); + } + + public IBiomeDataOracle getBiomeDataOracle() { return biomeDataOracle; } + + public MapgenRelay(WorldSwitcher worldSwitcher) { + + ViewerFacade currentViewerFacade = worldSwitcher.addWorldSwitchedListener(this); + + // Force a switched event to bring the instance's state up to date. + onWorldSwitched(currentViewerFacade); + } +} diff --git a/src/main/java/amidst/minetest/world/mapgen/MapgenUpdatedListener.java b/src/main/java/amidst/minetest/world/mapgen/MapgenUpdatedListener.java new file mode 100644 index 00000000..47f45bdf --- /dev/null +++ b/src/main/java/amidst/minetest/world/mapgen/MapgenUpdatedListener.java @@ -0,0 +1,7 @@ +package amidst.minetest.world.mapgen; + +import amidst.fragment.IBiomeDataOracle; + +public interface MapgenUpdatedListener { + void onMapgenUpdated(IBiomeDataOracle biomeDataOracle); +} diff --git a/src/main/java/amidst/minetest/world/oracle/BiomeDataOracleValleys.java b/src/main/java/amidst/minetest/world/oracle/BiomeDataOracleValleys.java index 4842022d..c13bd41f 100644 --- a/src/main/java/amidst/minetest/world/oracle/BiomeDataOracleValleys.java +++ b/src/main/java/amidst/minetest/world/oracle/BiomeDataOracleValleys.java @@ -1,9 +1,12 @@ package amidst.minetest.world.oracle; +import javax.vecmath.Point2d; + import amidst.documentation.Immutable; import amidst.logging.AmidstLogger; import amidst.logging.AmidstMessageBox; -import amidst.minetest.world.mapgen.Constants; +import amidst.minetest.world.mapgen.IHistogram2D; +import amidst.minetest.world.mapgen.IHistogram2DTransformationProvider; import amidst.minetest.world.mapgen.InvalidNoiseParamsException; import amidst.minetest.world.mapgen.MapgenParams; import amidst.minetest.world.mapgen.MapgenValleysParams; @@ -14,15 +17,15 @@ import amidst.mojangapi.world.coordinates.Resolution; import amidst.settings.biomeprofile.BiomeProfileSelection; @Immutable -public class BiomeDataOracleValleys extends MinetestBiomeDataOracle { +public class BiomeDataOracleValleys extends MinetestBiomeDataOracle implements IHistogram2DTransformationProvider { private final MapgenValleysParams valleysParams; - // Raising this reduces the rate of evaporation + /** Raising this reduces the rate of evaporation */ static final float evaporation = 300.0f; static final float humidity_dropoff = 4.0f; - // Constant to convert altitude chill to heat + /** Constant to convert altitude chill to heat */ static final float alt_to_heat = 20.0f; - // Humidity reduction by altitude + /** Humidity reduction by altitude */ static final float alt_to_humid = 10.0f; @@ -66,7 +69,77 @@ public class BiomeDataOracleValleys extends MinetestBiomeDataOracle { boolean isRiver; }; - + /** + * Provides the climate histogram for a Valleys map at the given altitude + * (Valleys mapgen adjusts the climate with altitude and around rivers) + */ + class ValleysClimateHistogram implements IHistogram2D { + IHistogram2D sourceHistogram; + float altitude; + Point2d sampleMean = null; + + @Override + public double frequencyOfOccurance(float temperature, float humidity) { + + if (altitude > 0.0f) { + if (use_altitude_dry) humidity += alt_to_humid * altitude / altitude_chill; + if (use_altitude_chill) temperature += alt_to_heat * altitude / altitude_chill; + } + + // TODO Crazy math to add river humidity distribution to sourceHistogram + + // Average heat was increased to balance against altitude chill. + if (use_altitude_chill) temperature -= 5.0f; + // Humidity was scaled down to balance the effect of rivers. + if (humid_rivers) humidity /= 0.8f; + + // Now that we've performed the opposite of every transformation Valleys mapgen + // makes to the humidity and temperature, we can obtain the frequencyOfOccurance. + double result = sourceHistogram.frequencyOfOccurance(temperature, humidity); + + // results outside the sampling range return NaN rather than zero, but the + // sampling range covers all non-zero values, so we can treat NaN as zero. + return Double.isNaN(result) ? 0 : result; + } + + @Override + /** + * Returns the "FrequencyOfOccurance" value at which 'percentile' amount of + * samples will fall beneath. + * So if percentile was 10, then a value between 0 and 1 would be returned such + * that 10% of results from FrequencyOfOccurance() would fall below it. + */ + public double frequencyAtPercentile(double percentile) { + // TODO Auto-generated method stub + return sourceHistogram.frequencyAtPercentile(percentile); + } + + @Override + public Point2d getSampleMean() { + if (sampleMean == null) { + sampleMean = new Point2d(sourceHistogram.getSampleMean()); + + // Average heat was increased to balance against altitude chill. + if (use_altitude_chill) sampleMean.x += 5.0f; + // Humidity was scaled down to balance the effect of rivers. + if (humid_rivers) sampleMean.y *= 0.8f; + + // TODO Crazy math to add river humidity distribution to sourceHistogram + + if (altitude > 0.0f) { + if (use_altitude_dry) sampleMean.y -= alt_to_humid * altitude / altitude_chill; + if (use_altitude_chill) sampleMean.x -= alt_to_heat * altitude / altitude_chill; + } + } + return sampleMean; + } + + public ValleysClimateHistogram(IHistogram2D source_histogram, float altitude) { + this.sourceHistogram = source_histogram; + this.altitude = altitude; + } + } + /** * @param mapgenCarpathianParams * @param biomeProfileSelection - if null then a default biomeprofile will be used @@ -115,6 +188,11 @@ public class BiomeDataOracleValleys extends MinetestBiomeDataOracle { use_altitude_dry = (valleysParams.spflags & MapgenValleysParams.FLAG_VALLEYS_ALT_DRY) > 0; vary_driver_depth = (valleysParams.spflags & MapgenValleysParams.FLAG_VALLEYS_VARY_RIVER_DEPTH) > 0; } + + @Override + public IHistogram2D getTransformedHistogram(IHistogram2D climate_histogram, float altitude) { + return new ValleysClimateHistogram(climate_histogram, altitude); + } float terrainLevelAtPoint(int x, int z) { @@ -143,6 +221,11 @@ public class BiomeDataOracleValleys extends MinetestBiomeDataOracle { // Note that tempTerrainNoise.slope, tempTerrainNoise.rivers, and // tempTerrainNoise.valley have now been updated with new values. + // + // If there is a river then terrain_height is the height of the bottom of the + // river, otherwise it's the height above the water-table. + // tempTerrainNoise.rivers is the water table (and the height of rivers), it's + // greater than terrain_height if there is a river. // Ground height ignoring riverbeds float t_alt = Math.max(tempTerrainNoise.rivers, terrain_height); @@ -150,6 +233,7 @@ public class BiomeDataOracleValleys extends MinetestBiomeDataOracle { if (humid_rivers) { float river_y = tempTerrainNoise.rivers; if (vary_driver_depth) { + // get a heat value that includes altitude chill float heat = (use_altitude_chill && (terrain_height > 0.0f || river_y > 0.0f)) ? tempTerrainNoise.heat - alt_to_heat * Math.max(terrain_height, river_y) / altitude_chill : tempTerrainNoise.heat; @@ -159,8 +243,11 @@ public class BiomeDataOracleValleys extends MinetestBiomeDataOracle { river_y += delta * Math.max(t_evap, 0.08f); } } - float water_depth = (t_alt - river_y) / humidity_dropoff; - tempTerrainNoise.humidity *= 1.0f + Math.pow(0.5f, Math.max(water_depth, 1.0f)); + float water_depth = (t_alt - river_y) / humidity_dropoff; // depth of water-table from surface, not depth of the river + double humidityScale = 1.0f + Math.pow(0.5f, Math.max(water_depth, 1.0f)); + tempTerrainNoise.humidity *= humidityScale; + //AmidstLogger.info("*" + humidityScale); + } if (use_altitude_dry) { if (t_alt > 0.0f) tempTerrainNoise.humidity -= alt_to_humid * t_alt / altitude_chill; diff --git a/src/main/java/amidst/minetest/world/oracle/MinetestBiomeDataOracle.java b/src/main/java/amidst/minetest/world/oracle/MinetestBiomeDataOracle.java index 8ca2b9c1..7dcfba47 100644 --- a/src/main/java/amidst/minetest/world/oracle/MinetestBiomeDataOracle.java +++ b/src/main/java/amidst/minetest/world/oracle/MinetestBiomeDataOracle.java @@ -8,6 +8,7 @@ import amidst.gameengineabstraction.world.biome.IBiome; import amidst.logging.AmidstLogger; import amidst.minetest.world.mapgen.ClimateHistogram; import amidst.minetest.world.mapgen.IHistogram2D; +import amidst.minetest.world.mapgen.IHistogram2DTransformationProvider; import amidst.minetest.world.mapgen.MapgenParams; import amidst.minetest.world.mapgen.MinetestBiome; import amidst.minetest.world.mapgen.MinetestBiomeProfileImpl; @@ -147,6 +148,21 @@ public abstract class MinetestBiomeDataOracle implements IBiomeDataOracle, Biome return climateHistogram; } + /** + * Feel free to override this with the name of the mapgen in subclasses. + * (Currently it's only needed by Oracles that implement IHistogram2DTransformationProvider) + * Normally you could get a name from the WorldType, but in some places + * the worldType hasn't been stored. + */ + public String getName() { + String result = this.getClass().getName(); + int pos = result.lastIndexOf("DataOracle"); + if (pos >= 0 && (pos + 10) < result.length()) { + result = result.substring(pos + 10); + } + return result; + } + @Override public void onBiomeProfileUpdate(BiomeProfile newBiomeProfile) { this.biomeProfile = newBiomeProfile;