Add biome world-distribution data and graph to Voronoi diagram

master
Treer 2018-05-12 21:56:31 +10:00
parent 97863d3ade
commit bc88396c33
12 changed files with 1003 additions and 112 deletions

View File

@ -25,8 +25,6 @@ import amidst.gui.crash.CrashWindow;
import amidst.gui.main.menu.MovePlayerPopupMenu;
import amidst.gui.main.viewer.ViewerFacade;
import amidst.gui.seedsearcher.SeedSearcherWindow;
import amidst.gui.text.TextWindow;
import amidst.gui.voronoi.VoronoiWindow;
import amidst.logging.AmidstLogger;
import amidst.minetest.world.mapgen.DefaultBiomes;
import amidst.mojangapi.world.WorldSeed;
@ -37,7 +35,6 @@ import amidst.mojangapi.world.player.Player;
import amidst.mojangapi.world.player.PlayerCoordinates;
import amidst.settings.biomeprofile.BiomeAuthority;
import amidst.settings.biomeprofile.BiomeProfile;
import amidst.settings.biomeprofile.BiomeProfileSelection;
import amidst.util.FileExtensionChecker;
@NotThreadSafe
@ -325,7 +322,7 @@ public class Actions {
generatorOptions = viewerFacade.getGeneratorOptions();
}
if (generatorOptions != null && generatorOptions.length() > 0) {
TextWindow.showMonospace("World mapgen options", generatorOptions);
dialogs.displayMonospaceText("World mapgen options", generatorOptions);
} else {
dialogs.displayInfo("World MapGen options", "There are currently no mapgen options in use");
}
@ -338,7 +335,11 @@ public class Actions {
@CalledOnlyBy(AmidstThread.EDT)
public void displayBiomeProfileVoronoi() {
if (gameEngineDetails.getType() != GameEngineType.MINECRAFT) {
VoronoiWindow.showDiagram(biomeAuthority.getBiomeProfileSelection());
// Passing a null IClimateHistogram because the default one is currently the
// 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);
} else {
dialogs.displayInfo("Biome profile Voronoi diagram", "Minecraft biomes are not determined by heat and humidity.");
}

View File

@ -13,12 +13,16 @@ import amidst.documentation.CalledOnlyBy;
import amidst.documentation.NotThreadSafe;
import amidst.gameengineabstraction.GameEngineType;
import amidst.gameengineabstraction.world.WorldTypes;
import amidst.gui.text.TextWindow;
import amidst.gui.voronoi.VoronoiWindow;
import amidst.logging.AmidstMessageBox;
import amidst.minetest.world.mapgen.IHistogram2D;
import amidst.mojangapi.RunningLauncherProfile;
import amidst.mojangapi.world.WorldSeed;
import amidst.mojangapi.world.WorldType;
import amidst.mojangapi.world.export.WorldExporterConfiguration;
import amidst.mojangapi.world.player.WorldPlayerType;
import amidst.settings.biomeprofile.BiomeProfileSelection;
@NotThreadSafe
public class MainWindowDialogs {
@ -99,6 +103,11 @@ public class MainWindowDialogs {
AmidstMessageBox.displayInfo(frame, title, message);
}
@CalledOnlyBy(AmidstThread.EDT)
public void displayMonospaceText(String title, String content) {
TextWindow.showMonospace(frame, title, content);
}
@CalledOnlyBy(AmidstThread.EDT)
public void displayError(String message) {
AmidstMessageBox.displayError(frame, "Error", message);
@ -108,6 +117,11 @@ public class MainWindowDialogs {
public void displayError(Exception e) {
AmidstMessageBox.displayError(frame, "Error", e);
}
@CalledOnlyBy(AmidstThread.EDT)
public void displayVoronoiDiagram(BiomeProfileSelection biome_profile_selection, IHistogram2D climate_histogram) {
VoronoiWindow.showDiagram(frame, biome_profile_selection, climate_histogram);
}
@CalledOnlyBy(AmidstThread.EDT)
public boolean askToConfirmSaveGameManipulation() {

View File

@ -1,6 +1,7 @@
package amidst.gui.text;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
@ -21,8 +22,8 @@ import net.miginfocom.swing.MigLayout;
public class TextWindow {
private static final TextWindow monospaceWindow = new TextWindow(Font.MONOSPACED);
public static void showMonospace(String title, String content) {
SwingUtilities.invokeLater(() -> monospaceWindow.show(title, content));
public static void showMonospace(Component parent, String title, String content) {
SwingUtilities.invokeLater(() -> monospaceWindow.show(parent, title, content));
}
private final JFrame frame;
@ -66,9 +67,10 @@ public class TextWindow {
}
@CalledOnlyBy(AmidstThread.EDT)
public void show(String title, String content) {
public void show(Component parent, String title, String content) {
this.contentTextArea.setText(content);
this.frame.setTitle(title);
this.frame.setLocationRelativeTo(parent);
this.frame.setVisible(true);
}
}

View File

@ -0,0 +1,134 @@
package amidst.gui.voronoi;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.vecmath.Point2d;
import amidst.minetest.world.mapgen.IHistogram2D;
public class FrequencyGraph {
private int width;
private int height;
private BufferedImage graph;
private IHistogram2D last_histogram2d;
int last_x_min, last_x_max, last_y_min, last_y_max;
/**
* Returns an image of the frequency distribution graph given by histogram2d
* @param histogram2d
* @param x_min - start of the x axis for the graph
* @param x_max - end of the x axis for the graph
* @param y_min - start of the y axis for the graph
* @param y_max - end of the y axis for the graph
* @param step - the increment per pixel, i.e. a step of 0.25 means 4 pixels to increment along an axis by 1
* @return an image of the frequency data
*/
public BufferedImage render(IHistogram2D histogram2d, int x_min, int x_max, int y_min, int y_max) {
int xRange = 1 + x_max - x_min; // inclusive range
int yRange = 1 + y_max - y_min; // inclusive range
float xScale = xRange / (float)width;
float yScale = yRange / (float)height;
if (graph == null || graph.getWidth() != width || graph.getHeight() != height) {
graph = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
} else {
if (histogram2d.equals(last_histogram2d) &&
x_min == last_x_min && x_max == last_x_max &&
y_min == last_y_min && y_max == last_y_max) {
// It's the same as last time
return graph;
}
}
last_histogram2d = histogram2d;
last_x_min = x_min;
last_x_max = x_max;
last_y_min = y_min;
last_y_max = y_max;
// No math here, these minDist values for band thicknesses were set by trial/eyeballing
double minDistRed = 0.0000017;
double minDistBlue = 0.000001;
double minDistBase = 0.00000001;
// the 0.01 percentile is the lowest the ClimateHistogram lookup tables store values for.
double bottomFrequency = histogram2d.frequencyAtPercentile(0.01);
Point2d mean = histogram2d.getSampleMean();
int xMean = (int)Math.round((mean.x - x_min) / xScale);
int yMean = (int)Math.round((mean.y - y_min) / yScale);
for (int y = 0; y < height; y++) {
float histogramY = (y * yScale) + y_min;
for (int x = 0; x < width; x++) {
float histogramX = (x * xScale) + x_min;
// set transparent
graph.setRGB(x, y, 0x00000000);
double distance;
double frequency = histogram2d.frequencyOfOccurance(histogramX, histogramY);
if (frequency == 0) {
// No values were ever seen in this location, color it gray to indicate
// the climate doesn't reach these values.
graph.setRGB(x, y, 0xFF707070);
} else if (frequency < (bottomFrequency + minDistBase)) {
// It is exceeding rare for the climate to ever reach these values.
// Draw a red quartile boundary line here.
if (frequency > (bottomFrequency - minDistBase)) {
// trying to make a dotted circle while avoiding sin() or cos() ;)
int absX = (int)Math.abs(x - xMean);
int absY = (int)Math.abs(y - yMean);
int travel = Math.abs((absX < absY) ? absX - (absY / 2) : absY - (absX / 2));
if(((travel / 8) & 1) > 0) {
distance = Math.abs(frequency - bottomFrequency);
graph.setRGB(x, y, 0x00FF3030 | (255 - (int)Math.round((255 * distance) / minDistBase)) << 24);
}
}
} else {
for(int percentile = 10; percentile <= 90; percentile += 5) {
if ((percentile % 25) == 0) {
distance = Math.abs(frequency - histogram2d.frequencyAtPercentile(percentile));
if (distance <= minDistRed) {
// Draw a quartile boundary line here.
graph.setRGB(x, y, 0x00FF3030 | (255 - (int)Math.round((255 * distance) / minDistRed)) << 24);
}
} else if ((percentile % 10) == 0) {
distance = Math.abs(frequency - histogram2d.frequencyAtPercentile(percentile));
if (distance <= minDistBlue) {
// Draw a 10-percentile boundary line here.
graph.setRGB(x, y, 0x003050FF | (255 - (int)Math.round((255 * distance) / minDistBlue)) << 24);
}
}
}
}
}
}
// Draw the mean, as the frequency data will be almost flat there, and random noise
// may prevent that looking point-like.
Graphics2D g2d = graph.createGraphics();
g2d.setColor(Color.RED);
g2d.fillOval(xMean - 4, yMean - 4, 9, 9);
return graph;
}
public FrequencyGraph(int width, int height) {
this.width = width;
this.height = height;
}
}

View File

@ -7,6 +7,8 @@ public class GraphNode {
float x;
float y;
double occurrenceFrequency;
@Override
public boolean equals(Object obj) {
if (obj == null) return false;

View File

@ -3,16 +3,20 @@ package amidst.gui.voronoi;
import java.awt.image.BufferedImage;
import java.util.List;
import amidst.minetest.world.mapgen.IHistogram2D;
/**
* Draws a Voronoi diagram, currently using the brute-force method
* Draws a Voronoi diagram, currently using the brute-force method.
*
*/
public class VoronoiGraph {
private BufferedImage graph;
private int[] bufferArray;
private int width;
private int height;
private IHistogram2D climateHistogram;
/** Create the graph BufferedImage if it doesn't already exist at the correct size */
private void createBufferedImage() {
if (graph == null || graph.getWidth() != width || graph.getHeight() != height) {
@ -21,10 +25,20 @@ public class VoronoiGraph {
}
}
/**
* 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.
* @param nodes
* @param x_min - start of the x axis for the graph
* @param x_max - end of the x axis for the graph
* @param y_min - start of the y axis for the graph
* @param y_max - end of the y axis for the graph
* @return an image of the Vonoroi graph
*/
public BufferedImage render(List<GraphNode> nodes, int x_min, int x_max, int y_min, int y_max) {
createBufferedImage();
GraphNode closestNode;
float closestDist;
float xScale = (x_max - x_min) / (float)width;
@ -34,21 +48,34 @@ public class VoronoiGraph {
int bufferArrayLines = bufferArray.length / width; // number of rasterlines we can write to bufferArray before it needs to be written to the BufferedImage
int bufferArrayIndex = 0;
// Convert to array for hopefully faster indexed access
int occurrenceHistogramY = Integer.MIN_VALUE;
double[] occurrenceHistogram = new double[1 + x_max - x_min];
// Convert the List to an array for hopefully faster indexed access
GraphNode[] nodeArray = nodes.toArray(new GraphNode[nodes.size()]);
int nodeCount = nodeArray.length;
// Init/zero the occurrenceFrequency data field in each node
for (short i = 0; i < nodeCount; i++) nodeArray[i].occurrenceFrequency = 0;
for (int y = 0; y < height; y++) {
float valueAtY = (y * yScale) + y_min;
if (y - bufferArrayY >= bufferArrayLines) {
// flush bufferArray into the BufferedImage.
// (I'm hoping Java has this optimized, if not, there's no need for rgbArray)
graph.setRGB(0, bufferArrayY, width, bufferArrayLines, bufferArray, 0, width);
graph.setRGB(0, bufferArrayY, width, bufferArrayLines, bufferArray, 0, width);
bufferArrayY = y;
bufferArrayIndex = 0;
}
// Populate occuranceHistogram for this value of y
if (Math.round(y * yScale) != occurrenceHistogramY && climateHistogram != null) {
int yIndex = Math.round(y * yScale);
for (int i = x_max - x_min; i >= 0; i--) occurrenceHistogram[i] = climateHistogram.frequencyOfOccurance(i + x_min, yIndex + y_min);
occurrenceHistogramY = yIndex;
}
for (int x = 0; x < width; x++) {
//float heat = (x * xScale) + axis_min;
float valueAtX = (x * xScale) + x_min;
@ -67,19 +94,31 @@ public class VoronoiGraph {
closestNode = node;
}
}
bufferArray[bufferArrayIndex++] = (closestNode == null) ? 0x00000000 : closestNode.argb;
if (closestNode != null) {
bufferArray[bufferArrayIndex++] = closestNode.argb;
closestNode.occurrenceFrequency += occurrenceHistogram[Math.round(x * xScale)];
} else {
bufferArray[bufferArrayIndex++] = 0x00000000;
}
}
}
if (bufferArrayIndex > 0) {
// flush the rest of bufferArray into the BufferedImage.
graph.setRGB(0, bufferArrayY, width, bufferArrayIndex / width, bufferArray, 0, width);
graph.setRGB(0, bufferArrayY, width, bufferArrayIndex / width, bufferArray, 0, width);
}
// Correct occurrenceFrequency for the oversampling caused by difference between
// size of BufferedImage vs histogram resolution
for (short i = 0; i < nodeCount; i++) {
nodeArray[i].occurrenceFrequency = nodeArray[i].occurrenceFrequency * xScale * yScale;
}
return graph;
}
public VoronoiGraph(int width, int height) {
public VoronoiGraph(int width, int height, IHistogram2D climate_histogram) {
this.width = width;
this.height = height;
this.climateHistogram = climate_histogram;
}
}

View File

@ -1,7 +1,9 @@
package amidst.gui.voronoi;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
@ -16,32 +18,35 @@ import java.util.ArrayList;
import java.util.List;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import amidst.documentation.AmidstThread;
import amidst.documentation.CalledByAny;
import amidst.documentation.CalledOnlyBy;
import amidst.gameengineabstraction.world.biome.IBiome;
import amidst.minetest.world.mapgen.ClimateHistogram;
import amidst.minetest.world.mapgen.IHistogram2D;
import amidst.minetest.world.mapgen.MinetestBiome;
import amidst.minetest.world.mapgen.MinetestBiomeProfileImpl;
/**
* Panel which displays a Voronoi graph of Minetest biomes
* Panel which displays a Voronoi graph of Minetest biomes
*/
public class VoronoiPanel extends JPanel {
public static final int FLAG_SHOWLABELS = 0x01;
public static final int FLAG_SHOWAXIS = 0x02;
public static final int FLAG_SHOWNODES = 0x04;
public static final int FLAG_SHOWDISTRIBUTION = 0x08;
public static final int FLAG_SHOWCOVERAGE = 0x08;
public static final boolean GRAPHICS_DEBUG = false;
private static final float AXIS_WIDTH = 0.5f;
private static final int TICKMARK_WIDTH_SMALL = 3;
private static final int TICKMARK_WIDTH_LARGE = 6;
private static final int TICKMARK_WIDTH_SMALL = 2;
private static final int TICKMARK_WIDTH_LARGE = 4;
private static final int TICKMARK_LABEL_SPACE = 1;
private static final int NODE_RADIUS = 0;
private static final int NODE_LABEL_SPACE = 0;
private static final int NODE_LABEL_SPACE = 2;
private static final int NODE_LABEL_FONTSIZE = 3;
private static final long serialVersionUID = 1L;
@ -54,18 +59,23 @@ public class VoronoiPanel extends JPanel {
public int axis_max = 140;
public int graph_resolution = 1000;
//private MinetestBiome[] nodes;
private IHistogram2D climateHistogram = null;
ArrayList<MinetestBiome> biomes = new ArrayList<MinetestBiome>();
private List<GraphNode> graphNodes = null;
private List<GraphNode> graphNodes = null;
private VoronoiGraph voronoiGraph = null;
private FrequencyGraph frequencyGraph = null;
private float frequencyGraphOpacity = 0;
private int renderFlags;
private VoronoiGraph graph = null;
@Override
@CalledOnlyBy(AmidstThread.EDT)
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (graph == null) graph = new VoronoiGraph(graph_resolution, graph_resolution);
boolean frequencyGraphWasNull = frequencyGraph == null;
if (climateHistogram == null) climateHistogram = new ClimateHistogram();
if (voronoiGraph == null) voronoiGraph = new VoronoiGraph(graph_resolution, graph_resolution, climateHistogram);
if (frequencyGraph == null) frequencyGraph = new FrequencyGraph(graph_resolution, graph_resolution);
this.setBorder(null);
Rectangle panelBounds = getBounds();
@ -102,10 +112,10 @@ public class VoronoiPanel extends JPanel {
g2d.setColor(Color.WHITE);
g2d.fillRect(axis_min, axis_min, axis_max - axis_min, axis_max - axis_min);
// Draw the filled voronoi graph
// Draw the filled voronoi graph
if (graphNodes != null) {
g2d.drawImage(
graph.render(graphNodes, axis_min, axis_max, axis_min, axis_max),
voronoiGraph.render(graphNodes, axis_min, axis_max, axis_min, axis_max),
axis_min, axis_min, desiredAxisLength, desiredAxisLength, Color.WHITE, null
);
}
@ -113,18 +123,49 @@ public class VoronoiPanel extends JPanel {
// draw nodes
drawNodesOrNodeLabels(g2d);
// overlay the frequency graph
if (frequencyGraphOpacity > 0) {
int rule = AlphaComposite.SRC_OVER;
Composite alphaComposite = AlphaComposite.getInstance(rule , frequencyGraphOpacity);
Composite originalComposite = g2d.getComposite();
g2d.setComposite(alphaComposite);
try {
if (graphNodes != null) {
g2d.drawImage(
frequencyGraph.render(climateHistogram, axis_min, axis_max, axis_min, axis_max),
axis_min, axis_min, desiredAxisLength, desiredAxisLength, null, null
);
}
} finally {
g2d.setComposite(originalComposite);
}
}
// draw axis
if ((renderFlags & FLAG_SHOWAXIS) > 0) drawAxes(g2d);
}
if (frequencyGraphWasNull && frequencyGraphOpacity <= 0) {
// 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);
});
}
}
@CalledOnlyBy(AmidstThread.EDT)
private void drawNodesOrNodeLabels(Graphics2D g2d) {
AffineTransform currentTransform = g2d.getTransform();
FontMetrics metrics = g2d.getFontMetrics(g2d.getFont());
Font font_original = g2d.getFont();
FontMetrics fontMetrics = g2d.getFontMetrics(font_original);
boolean showNodes = (renderFlags & FLAG_SHOWNODES) > 0;
boolean showLabels = (renderFlags & FLAG_SHOWLABELS) > 0;
boolean showCoverage = (renderFlags & FLAG_SHOWCOVERAGE) > 0;
int biomeIndex = 0;
for (MinetestBiome biome: biomes) {
int x = Math.round(biome.heat_point);
@ -140,18 +181,36 @@ public class VoronoiPanel extends JPanel {
g2d.fillOval(x - NODE_RADIUS, y - NODE_RADIUS, 1 + NODE_RADIUS * 2, 1 + NODE_RADIUS * 2);
}
if (showLabels) {
Point2D fontPos = new Point(x, y - (showNodes ? NODE_LABEL_SPACE : 0));
Point2D fontPosTransformed = currentTransform.transform(fontPos, null);
Point2D fontPos = new Point(x, y - (showNodes ? NODE_LABEL_SPACE : 0));
Point2D fontPosTransformed = currentTransform.transform(fontPos, null);
float fontY = (float)fontPosTransformed.getY() + (fontMetrics.getAscent() / 4f);
g2d.setTransform(noTransform);
g2d.drawString(
biome.getName(),
(float)fontPosTransformed.getX() - (metrics.stringWidth(biome.getName()) / 2),
(float)fontPosTransformed.getY() + (metrics.getAscent() / (showNodes ? 1 : -4))
);
g2d.setTransform(noTransform);
try {
if (showLabels) {
g2d.drawString(
biome.getName(),
(float)fontPosTransformed.getX() - (fontMetrics.stringWidth(biome.getName()) / 2),
fontY
);
fontY += fontMetrics.getAscent();
}
if (showCoverage) {
String value = String.format(" %.1f", graphNodes.get(biomeIndex).occurrenceFrequency * 100);
if (value.charAt(value.length() - 1) == '0') value = value.substring(0, value.length() - 2);
value += "%";
g2d.drawString(
value,
(float)fontPosTransformed.getX() - (fontMetrics.stringWidth(value) / 2),
fontY
);
}
} finally {
g2d.setTransform(currentTransform);
}
}
biomeIndex++;
}
}
@ -191,8 +250,9 @@ public class VoronoiPanel extends JPanel {
);
g2d.setTransform(currentTransform);
} else {
g2d.drawLine(0, i, -TICKMARK_WIDTH_SMALL, i);
g2d.drawLine(i, 0, i, -TICKMARK_WIDTH_SMALL);
int tickmarkWidth = (i % 50 == 0) ? (int)Math.ceil((TICKMARK_WIDTH_SMALL + TICKMARK_WIDTH_LARGE) / 2f) : TICKMARK_WIDTH_SMALL;
g2d.drawLine(0, i, -tickmarkWidth, i);
g2d.drawLine(i, 0, i, -tickmarkWidth);
}
}
}
@ -248,29 +308,48 @@ public class VoronoiPanel extends JPanel {
.114 * Math.pow(col.getBlue(), 2)
);
}
public int getRenderFlags() { return renderFlags; }
public int getRenderFlags() { return renderFlags; }
public String getDistributionData() {
StringBuilder result = new StringBuilder();
int biomeIndex = 0;
for (MinetestBiome biome: biomes) {
result.append(biome.getName());
result.append(", ");
result.append(String.format(" %.1f%%\r\n", graphNodes.get(biomeIndex).occurrenceFrequency * 100));
biomeIndex++;
}
return result.toString();
}
/**
* 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; }
@CalledOnlyBy(AmidstThread.EDT)
public void Update(MinetestBiomeProfileImpl biomeProfile, int height, int flags) {
public void Update(MinetestBiomeProfileImpl biomeProfile, int altitude, float frequency_graph_opacity, int flags) {
ArrayList<MinetestBiome> newBiomes = new ArrayList<MinetestBiome>();
if (biomeProfile != null) for (IBiome biome : biomeProfile.allBiomes()) {
MinetestBiome mtBiome = (MinetestBiome)biome;
if (height <= (mtBiome.y_max + mtBiome.vertical_blend) && height >= mtBiome.y_min) {
if (altitude <= (mtBiome.y_max + mtBiome.vertical_blend) && altitude >= mtBiome.y_min) {
newBiomes.add(mtBiome);
}
}
//ArrayList<MinetestBiome> currentBiomes = this.biomes == null ? new ArrayList<MinetestBiome>() : new ArrayList<MinetestBiome>(Arrays.asList(nodes));
if (flags != this.renderFlags || !newBiomes.equals(biomes)) {
if (flags != this.renderFlags || !newBiomes.equals(biomes) || frequencyGraphOpacity != frequency_graph_opacity) {
this.renderFlags = flags;
biomes = newBiomes;
frequencyGraphOpacity = frequency_graph_opacity;
graphNodes = new ArrayList<GraphNode>();
for(MinetestBiome biome: newBiomes) {
graphNodes.add(new GraphNode(biome.heat_point, biome.humidity_point, biome.getDefaultColor().getRGB()));
}
repaint();
}
}

View File

@ -1,22 +1,39 @@
package amidst.gui.voronoi;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Hashtable;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSlider;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import net.miginfocom.swing.MigLayout;
import amidst.ResourceLoader;
import amidst.documentation.AmidstThread;
import amidst.documentation.CalledOnlyBy;
import amidst.gui.text.TextWindow;
import amidst.minetest.world.mapgen.IHistogram2D;
import amidst.minetest.world.mapgen.MinetestBiomeProfileImpl;
import amidst.settings.biomeprofile.BiomeProfile;
import amidst.settings.biomeprofile.BiomeProfileSelection;
@ -33,27 +50,25 @@ public class VoronoiWindow implements BiomeProfileUpdateListener, ChangeListener
private final JFrame windowFrame;
private VoronoiPanel voronoiPanel;
private JSlider altitudeSlider;
private JLabel graphHeading;
private JSlider altitudeSlider;
private JSlider freqGraphSlider;
private JLabel graphHeading;
private JSpinner altitudeOffset;
private JCheckBox option_showAxis;
private JCheckBox option_showLabels;
private JCheckBox option_showNodes;
private JSpinner altitudeOffset;
private JCheckBox option_showCoverage;
private MinetestBiomeProfileImpl selectedProfile = null;
@CalledOnlyBy(AmidstThread.EDT)
private VoronoiWindow() {
this.windowFrame = createWindowFrame(800, 764);
setOptionFlagsInDialog(VoronoiPanel.FLAG_SHOWLABELS | VoronoiPanel.FLAG_SHOWAXIS | VoronoiPanel.FLAG_SHOWNODES);
}
@CalledOnlyBy(AmidstThread.EDT)
private JFrame createWindowFrame(int width, int height) {
private JFrame createWindowFrame(Component parent, int width, int height) {
JFrame result = new JFrame();
result.getContentPane().setLayout(new MigLayout());
result.getContentPane().setLayout(new MigLayout(/*"debug"/**/));
result.setSize(width, height);
result.setLocationRelativeTo(parent);
result.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
@ -61,47 +76,142 @@ public class VoronoiWindow implements BiomeProfileUpdateListener, ChangeListener
}
});
this.voronoiPanel = new VoronoiPanel();
result.add(voronoiPanel, "grow, pushx, spany 2");// the next row (which we span) will be "push", so don't do it here - the rest of this row needs to be thin
this.option_showAxis = createControl_VoronoiOption(VoronoiPanel.FLAG_SHOWAXIS);
this.option_showNodes = createControl_VoronoiOption(VoronoiPanel.FLAG_SHOWNODES);
this.option_showLabels = createControl_VoronoiOption(VoronoiPanel.FLAG_SHOWLABELS);
this.option_showCoverage = createControl_VoronoiOption(VoronoiPanel.FLAG_SHOWCOVERAGE);
JLabel heightLabel = new JLabel("Altitude");
result.add(heightLabel, "center, wrap");
altitudeSlider = new JSlider(JSlider.VERTICAL, ALTITUDESLIDER_DEFAULT_LOW, ALTITUDESLIDER_DEFAULT_HIGH, ALTITUDESLIDER_STARTING_VALUE);
altitudeSlider.addChangeListener(this);
altitudeSlider.setMajorTickSpacing(10);
altitudeSlider.setMinorTickSpacing(5);
altitudeSlider.setPaintTicks(true);
altitudeSlider.setPaintLabels(true);
result.add(altitudeSlider, "grow, pushy, wrap");
graphHeading = new JLabel();
result.add(graphHeading, "center");
JLabel offsetLabel = new JLabel("Altitude offset:");
result.add(offsetLabel, "left, wrap");
option_showAxis = new JCheckBox("Show axes");
option_showNodes = new JCheckBox("Show nodes");
option_showLabels = new JCheckBox("Show labels");
option_showAxis.addChangeListener(this);
option_showNodes.addChangeListener(this);
option_showLabels.addChangeListener(this);
result.add(option_showAxis, "center, split 3");
result.add(option_showNodes, "center");
result.add(option_showLabels, "center");
altitudeOffset = new JSpinner(new SpinnerNumberModel(0, Short.MIN_VALUE - ALTITUDESLIDER_DEFAULT_LOW, Short.MAX_VALUE - ALTITUDESLIDER_DEFAULT_HIGH, 100));
altitudeOffset.addChangeListener(this);
result.add(altitudeOffset);
// Place the controls in the window
JLabel altOffsetLablel;
result.add(this.voronoiPanel = new VoronoiPanel(), "grow, pushx, spany 2"); // the next row (which we span) will be "push", so don't do it here - the rest of this row needs to be thin
result.add(new JLabel("Altitude"), "center, wrap");
result.add(this.altitudeSlider = createControl_AltitudeSlider(), "grow, pushy, wrap");
result.add(this.graphHeading = new JLabel(), "center");
result.add(altOffsetLablel = new JLabel("Altitude offset:"), "left, wrap");
result.add(createControl_InfoButton(), "split 6");
result.add(this.freqGraphSlider = createControl_FreqGraphSlider(), "gapx push push");
result.add(this.option_showAxis, "gapx push push");
result.add(this.option_showLabels, "gapx push push");
result.add(this.option_showNodes, "gapx push push");
result.add(this.option_showCoverage, "gapx push push");
result.add(altitudeOffset = createControl_AltitudeOffset(altOffsetLablel));
return result;
}
/** Creates a slider which controls the altitude displayed in the Voronoi diagram */
private JSlider createControl_AltitudeSlider() {
JSlider result = new JSlider(
JSlider.VERTICAL,
ALTITUDESLIDER_DEFAULT_LOW,
ALTITUDESLIDER_DEFAULT_HIGH,
ALTITUDESLIDER_STARTING_VALUE
);
result.addChangeListener(this);
result.setMajorTickSpacing(10);
result.setMinorTickSpacing(5);
result.setPaintTicks(true);
result.setPaintLabels(true);
return result;
}
/** Creates a button which shows the data in text form */
private JButton createControl_InfoButton() {
JButton result = new JButton();
result.setToolTipText("Show data, legend, and notes");
Image icon = ResourceLoader.getImage("/amidst/gui/main/dataicon.png");
result.setIcon(new ImageIcon(icon));
//infoButton.setBackground(Color.WHITE);
//infoButton.setOpaque(false);
Border line = new LineBorder(Color.BLACK);
Border margin = new EmptyBorder(2, 2, 2, 2);
Border compound = new CompoundBorder(line, margin);
result.setBorder(compound);
result.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
InfoButtonClicked();
}
});
return result;
}
/** creates a slider for controlling the visibility of the frequency distribution graph */
private JSlider createControl_FreqGraphSlider() {
JSlider result = new JSlider(JSlider.HORIZONTAL, 0, 100, 0);
result.setToolTipText("<html>Hides or shows a graph of how temperature and humidity are distributed in the world.<br/>Click the \"Show data\" button on the left for more details.</html>");
result.addChangeListener(this);
result.setMajorTickSpacing(100);
result.setMinorTickSpacing(25);
result.setPaintTicks(true);
Hashtable<Integer, JLabel> labels = new Hashtable<Integer, JLabel>();
JLabel sliderLabel = new JLabel("Show distribution");
labels.put(0, new JLabel("")); // otherwise it allocates space at the end for "Show distribution"
labels.put(50, sliderLabel);
labels.put(100, new JLabel("")); // otherwise it allocates space at the end for "Show distribution"
result.setLabelTable(labels);
result.setPaintLabels(true);
result.setPreferredSize(
new Dimension(
sliderLabel.getPreferredSize().width,
result.getPreferredSize().height
)
);
return result;
}
/** Creates a CheckBox for specifying one of the VoronoiPanel option flags */
private JCheckBox createControl_VoronoiOption(int voronoi_panel_flag) {
String caption, tooltip;
switch (voronoi_panel_flag) {
case VoronoiPanel.FLAG_SHOWAXIS:
caption = "Show axes";
tooltip = "Set whether temperature and humidity axis are displayed";
break;
case VoronoiPanel.FLAG_SHOWNODES:
caption = "Show nodes";
tooltip = "Set whether temperature and humidity positions of each biome are displayed";
break;
case VoronoiPanel.FLAG_SHOWLABELS:
caption = "Show labels";
tooltip = "Set whether the biomes and axis are labelled";
break;
default:
caption = "Show %world covered";
tooltip = "Set whether to show percentage of the world covered by each biome at this altitude";
break;
}
JCheckBox result = new JCheckBox(caption);
result.setToolTipText(tooltip);
result.addChangeListener(this);
return result;
}
/**
* Creates a numeric spinner so the range provided by the AltitudeSlider can be adjusted.
* @param label - the label to try to match the width of the JSpinner to
*/
private JSpinner createControl_AltitudeOffset(JLabel label) {
JSpinner result = new JSpinner(new SpinnerNumberModel(0, Short.MIN_VALUE - ALTITUDESLIDER_DEFAULT_LOW, Short.MAX_VALUE - ALTITUDESLIDER_DEFAULT_HIGH, 100));
result.setToolTipText("<html>Allows the range of the altitude slider to be adjusted.<br/>Allowing for example, to view the biomes at Floatlands altitudes</html>");
result.addChangeListener(this);
// Drop the spinner width a little
JComponent field = ((JSpinner.DefaultEditor) result.getEditor());
Dimension prefSize = new Dimension(
(int)(label.getPreferredSize().width * 0.8f), // the 0.8 is because the scroll buttons will widen it.
field.getPreferredSize().height
);
field.setPreferredSize(prefSize);
return result;
}
@Override
public void onBiomeProfileUpdate(BiomeProfile newBiomeProfile) {
UpdateSelectedBiomeProfile(newBiomeProfile);
}
@Override
public void stateChanged(ChangeEvent e) {
if (e.getSource() == altitudeOffset) {
@ -110,6 +220,48 @@ public class VoronoiWindow implements BiomeProfileUpdateListener, ChangeListener
updateVoronoiDiagram();
}
private void InfoButtonClicked() {
StringBuilder data = new StringBuilder();
data.append("Biome profile: ");
data.append(selectedProfile.getName());
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());
data.append("\r\n");
data.append("These values are not the area the biomes cover in the Voronoi diagram,\r\n"
+ "they are the area of the Minetest world that will be covered by the biome\r\n"
+ "at the given altitude.\r\n\r\n"
+ "The surface alitudes most commonly occuring in the world will be determined\r\n"
+ "by the choice of mapgen, so these value (above) should only be considered\r\n"
+ "\"world-wide\" absolute values when biomes don't change with altitude, or the\r\n"
+ "mapgen is flat.\r\n");
data.append("\r\n\r\nDistribution legend:\r\n\r\n");
data.append("Dragging the \"Show distribution\" slider causes colored rings to appear on the\r\n"
+ "diagram. These indicate the frequently at which different temperature and\r\n"
+ "humidity combinations occur in the world:\r\n"
+ "\r\n"
+ " * Square grey outline — the technical limit of temperature and humidity.\r\n"
+ " The world does not contain temperature or humidity values in this\r\n"
+ " range.\r\n"
+ " * Red dotted ring — the practical limit of temperature and humidity. The\r\n"
+ " temperature and humidity in the world falls within this ring 99.99% of\r\n"
+ " the time.\r\n"
+ " * Red solid rings - these indicate the four quartiles. 25% of the world\r\n"
+ " has a temperature and humidity falling outside the outermost quartile\r\n"
+ " ring. The next ring has 50% of the world inside it, and 50% outside.\r\n"
+ " The innermost red ring contains 25% of the world inside it, and 25%\r\n"
+ " between it and the middle ring, etc.\r\n"
+ " * Blue solid rings - these are spaced at 10 percentile intervals. Note\r\n"
+ " that the 50 percentile ring is drawn red, but can also be considered\r\n"
+ " one of the blue rings.\r\n"
+ " * Red dot - the center of the distribution. This is the most common\r\n"
+ " temperature and humidity value\r\n.");
TextWindow.showMonospace(windowFrame, "Voronoi diagram data", data.toString());
}
private void UpdateSelectedBiomeProfile(BiomeProfile newProfile) {
MinetestBiomeProfileImpl minetestProfile = (newProfile instanceof MinetestBiomeProfileImpl) ? (MinetestBiomeProfileImpl)newProfile : null;
@ -118,14 +270,15 @@ public class VoronoiWindow implements BiomeProfileUpdateListener, ChangeListener
selectedProfile = minetestProfile;
if (changed) updateVoronoiDiagram();
this.windowFrame.setTitle(selectedProfile == null ? "Biome profile Voronoi graph" : "Voronoi graph for " + selectedProfile.getName());
this.windowFrame.setTitle(selectedProfile == null ? "Biome profile Voronoi diagram" : "Voronoi diagram for " + selectedProfile.getName());
}
private void updateVoronoiDiagram() {
EventQueue.invokeLater(
() -> {
int altitude = getAltitudeFromDialog();
voronoiPanel.Update(selectedProfile, altitude, getOptionFlagsFromDialog());
float freqGraphOpacity = freqGraphSlider.getValue() / 100f;
voronoiPanel.Update(selectedProfile, altitude, freqGraphOpacity, getOptionFlagsFromDialog());
graphHeading.setText("Biomes at altitude " + altitude);
}
);
@ -146,37 +299,31 @@ public class VoronoiWindow implements BiomeProfileUpdateListener, ChangeListener
@CalledOnlyBy(AmidstThread.EDT)
private void setOptionFlagsInDialog(int optionFlags) {
option_showAxis.setSelected((optionFlags & VoronoiPanel.FLAG_SHOWAXIS) > 0);
option_showNodes.setSelected((optionFlags & VoronoiPanel.FLAG_SHOWNODES) > 0);
option_showLabels.setSelected((optionFlags & VoronoiPanel.FLAG_SHOWLABELS) > 0);
option_showAxis.setSelected((optionFlags & VoronoiPanel.FLAG_SHOWAXIS) > 0);
option_showNodes.setSelected((optionFlags & VoronoiPanel.FLAG_SHOWNODES) > 0);
option_showLabels.setSelected((optionFlags & VoronoiPanel.FLAG_SHOWLABELS) > 0);
option_showCoverage.setSelected((optionFlags & VoronoiPanel.FLAG_SHOWCOVERAGE) > 0);
}
@CalledOnlyBy(AmidstThread.EDT)
private int getOptionFlagsFromDialog() {
int result = 0;
if (option_showAxis.isSelected()) result |= VoronoiPanel.FLAG_SHOWAXIS;
if (option_showNodes.isSelected()) result |= VoronoiPanel.FLAG_SHOWNODES;
if (option_showLabels.isSelected()) result |= VoronoiPanel.FLAG_SHOWLABELS;
if (option_showAxis.isSelected()) result |= VoronoiPanel.FLAG_SHOWAXIS;
if (option_showNodes.isSelected()) result |= VoronoiPanel.FLAG_SHOWNODES;
if (option_showLabels.isSelected()) result |= VoronoiPanel.FLAG_SHOWLABELS;
if (option_showCoverage.isSelected()) result |= VoronoiPanel.FLAG_SHOWCOVERAGE;
return result;
}
public static void showDiagram(BiomeProfileSelection biomeProfileSelection) {
if (voronoiWindow == null) {
voronoiWindow = new VoronoiWindow();
}
SwingUtilities.invokeLater(() -> voronoiWindow.show(biomeProfileSelection));
}
@CalledOnlyBy(AmidstThread.EDT)
public void show(BiomeProfileSelection biomeProfileSelection) {
private void show(BiomeProfileSelection biomeProfileSelection) {
if (this.biomeProfileSelection != null) {
this.biomeProfileSelection.removeUpdateListener(this);
}
this.biomeProfileSelection = biomeProfileSelection;
BiomeProfile newProfile = null;
if (biomeProfileSelection != null) {
this.biomeProfileSelection.addUpdateListener(this);
@ -186,4 +333,31 @@ public class VoronoiWindow implements BiomeProfileUpdateListener, ChangeListener
UpdateSelectedBiomeProfile(newProfile);
this.windowFrame.setVisible(true);
}
/** Creates and displays the Voronoi diagram window */
public static void showDiagram(Component parent, BiomeProfileSelection biome_profile_selection, IHistogram2D climate_histogram) {
if (voronoiWindow == null) {
voronoiWindow = new VoronoiWindow(parent);
}
SwingUtilities.invokeLater(() -> {
voronoiWindow.voronoiPanel.setClimateHistogram(climate_histogram);
voronoiWindow.show(biome_profile_selection);
});
}
/**
* Private constructor as this is a singleton. Invoke showDiagram() instead.
* @see showDiagram
*/
@CalledOnlyBy(AmidstThread.EDT)
private VoronoiWindow(Component parent) {
this.windowFrame = createWindowFrame(parent, 770, 760);
setOptionFlagsInDialog(
VoronoiPanel.FLAG_SHOWLABELS |
VoronoiPanel.FLAG_SHOWAXIS |
VoronoiPanel.FLAG_SHOWNODES |
VoronoiPanel.FLAG_SHOWCOVERAGE
);
}
}

View File

@ -0,0 +1,326 @@
package amidst.minetest.world.mapgen;
import javax.vecmath.Point2d;
import java.util.Arrays;
import java.util.LongSummaryStatistics;
import amidst.logging.AmidstLogger;
/**
* This class uses pre-generated data, so can only provide histogram
* information for climate noise settings similar to Minetest's default settings.
*/
public class ClimateHistogram implements IHistogram2D {
private static final float DEFAULT_SCALE = 50;
private static final float DEFAULT_OFFSET = 50;
private static final float DEFAULT_BLEND_SCALE = 50;
private static final float DEFAULT_BLEND_OFFSET = 50;
float scaleAdj = 1;
float offsetAdj = 0;
double dataSampleCount = 4293525600d * 6; // data is sum of 6 full-world samples
int dataSampleOffset = 40; // the first value in sampledHistogram_Heat is for heat of -40
// Bins: 191, range: -40 to 150 (inclusive)
int[] sampledHistogram_Heat = new int[] {0, 0, 23, 896, 5273, 18130, 35423, 56932, 85873, 144492, 227887, 330382, 441791, 575273, 785758, 1104880, 1514226, 2008173, 2580628, 3270470, 4070347, 5010230, 6233724, 7665743, 9231443, 11032009, 13054672, 15159146, 17585779, 20431953, 23539132, 26777647, 30049708, 33634225, 37557763, 41702212, 46135583, 50960488, 56066254, 61483250, 67471538, 74036663, 81075916, 88392031, 95671758, 103028198, 110423079, 118133581, 126230683, 134525547, 143089365, 151816479, 160572896, 169173433, 177480719, 185603411, 193673745, 201819880, 210125434, 218400248, 226576667, 234675120, 242660227, 250393165, 257641763, 264310278, 271052198, 277926795, 284991256, 291581211, 297501193, 303087896, 308619131, 313777446, 318058063, 322212943, 326758548, 331475102, 335722459, 338714730, 340704226, 342259956, 344228530, 346336570, 348439520, 350388503, 352166050, 353596154, 355021883, 355344601, 354627956, 353920683, 353523974, 353330547, 353024545, 352813804, 352102001, 350967293, 349073000, 346178826, 343181131, 340158957, 336577363, 332492808, 328136486, 323579081, 319083180, 314922134, 310380729, 305265626, 299550379, 293823815, 287790026, 282008407, 276140320, 269559012, 262131740, 254605986, 246951793, 239251561, 230994017, 222093705, 213071865, 204219912, 195608147, 187251811, 178964522, 170779498, 162561496, 154205050, 145761473, 137219761, 128986832, 121138771, 113558434, 106120928, 98743038, 91314126, 83938278, 77103922, 70855499, 64921287, 59244041, 53844544, 48822704, 43909928, 39348668, 35160583, 31266663, 27623387, 24258537, 21091241, 18212811, 15617700, 13237251, 11059423, 9228541, 7789154, 6607381, 5466830, 4340725, 3364753, 2610074, 2004609, 1486276, 1069920, 750199, 518992, 357847, 267761, 204042, 135284, 70635, 31506, 16563, 14437, 12363, 6219, 1714, 153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int[] sampledHistogram_Humidity = new int[] {0, 0, 31, 498, 2506, 4845, 8185, 21468, 59847, 122732, 193470, 286771, 438554, 610779, 784774, 1018080, 1351444, 1762206, 2271350, 2871968, 3617679, 4542319, 5722714, 7087672, 8612730, 10322878, 12252077, 14556399, 17083672, 19781372, 22738287, 25903300, 29414149, 33280369, 37456084, 41913668, 46847975, 52063820, 57365491, 62720169, 68287651, 74188447, 80622299, 87458209, 94297778, 101540429, 109407209, 117764134, 126033776, 134106841, 142075245, 150160062, 158481411, 167046905, 175806629, 184285718, 192691920, 201123443, 209651797, 218157100, 226149374, 234230435, 242366234, 249849182, 257017437, 264178652, 271211835, 277958877, 284189278, 290172767, 296085575, 301995199, 307977042, 313261332, 317868477, 322201082, 326388271, 330472979, 334532622, 338385357, 341846286, 344688035, 346974698, 349233925, 351053588, 352480803, 353676423, 354853005, 355571248, 355946018, 356187678, 355726165, 354284344, 352881704, 351909613, 350696269, 348847862, 346809607, 345187333, 343247921, 340859439, 338049103, 335025305, 331513640, 327718695, 323952706, 320112005, 316183516, 311617470, 306291881, 300848532, 295309226, 289332030, 282903833, 276250749, 269695133, 262942145, 255893757, 248411772, 240532829, 232446716, 224124693, 215607785, 206986511, 198415028, 190161781, 182277837, 174044450, 165348412, 156451475, 147683121, 139285547, 131231683, 123268099, 115122762, 107128906, 99350517, 91804617, 84384451, 77313619, 70743606, 64517248, 58541831, 52903662, 47555609, 42575213, 38003184, 33839429, 30012843, 26495082, 23211478, 20191059, 17507769, 14994437, 12727017, 10648031, 8847912, 7389304, 6184783, 5132563, 4108008, 3175730, 2394676, 1814862, 1395580, 1096984, 856680, 630982, 413928, 233980, 123637, 63560, 37317, 19663, 8361, 1825, 124, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
// See the code in the constructor if you wish to recalculate frequencyAtPercentileTable
/** A pre-calculation table for percentiles 1 to 100, in 1 percentile increments */
double[] frequencyAtPercentileTable = new double[] {4.288849623506293E-6, 7.849271137128854E-6, 1.0935481111330147E-5, 1.4125178764733622E-5, 1.710500033323479E-5, 1.9993831214913553E-5, 2.2662552870656077E-5, 2.534060066104869E-5, 2.802187248207058E-5, 3.080643823350056E-5, 3.33861856758062E-5, 3.6068277994214394E-5, 3.862936637606668E-5, 4.095044456557262E-5, 4.3636595467822336E-5, 4.607358640977918E-5, 4.810443669853268E-5, 5.0837165362693565E-5, 5.296404267788116E-5, 5.5325852990715865E-5, 5.7679007766327825E-5, 5.983630193800486E-5, 6.235772026254966E-5, 6.430623785962591E-5, 6.669710263491633E-5, 6.855912078302344E-5, 7.113548845931662E-5, 7.295088808887063E-5, 7.542181961574365E-5, 7.713810513240832E-5, 7.941207236464512E-5, 8.162108120760272E-5, 8.386222016118836E-5, 8.569039655815985E-5, 8.746946795713887E-5, 8.971102202243205E-5, 9.113804797595727E-5, 9.331997156194227E-5, 9.538586351908653E-5, 9.734525496179133E-5, 9.898022955022616E-5, 1.0102734313517474E-4, 1.0297431176541194E-4, 1.0459706284256709E-4, 1.0668836696809265E-4, 1.0855615168551473E-4, 1.1007621103277895E-4, 1.1237809589729694E-4, 1.1365508528516895E-4, 1.1581159959767767E-4, 1.1773346682435759E-4, 1.1928685427735908E-4, 1.2109096765312022E-4, 1.2266224460033028E-4, 1.2460344924158565E-4, 1.2639392703246802E-4, 1.2792145643612953E-4, 1.2983742624993134E-4, 1.3126086274433714E-4, 1.3318981239435993E-4, 1.3466113206470656E-4, 1.3628693214058266E-4, 1.3771262506116956E-4, 1.3927642904768147E-4, 1.4088517346529805E-4, 1.424651674401106E-4, 1.4415914818743247E-4, 1.4584085961605385E-4, 1.4707407523891278E-4, 1.48740812877974E-4, 1.4998644757789717E-4, 1.5182775373866035E-4, 1.5312561398223614E-4, 1.5476944096761824E-4, 1.564096559253218E-4, 1.5747584559976162E-4, 1.592317541086737E-4, 1.6055180720478152E-4, 1.6209298336552515E-4, 1.6335932626023207E-4, 1.6501788334796102E-4, 1.6627703968019814E-4, 1.6756546371099305E-4, 1.6888984543785755E-4, 1.7039589568960874E-4, 1.717707149296396E-4, 1.7299179342349547E-4, 1.744319494242184E-4, 1.7608213347237582E-4, 1.7744060109174724E-4, 1.7877565932783E-4, 1.8007180142527902E-4, 1.8136873249310507E-4, 1.8273003575625964E-4, 1.8385915609410723E-4, 1.8507326100357174E-4, 1.8627491892689703E-4, 1.8740872113945042E-4, 1.8879158216656317E-4, 1.9033705137062054E-4};
/** A pre-calculation table for percentiles 0 to 0.99 in 0.01 percentile increments */
double[] frequencyAtPerdimileTable = new double[] {0.0, 7.906411381395254E-8, 1.4281584607842585E-7, 2.0367198773824541E-7, 2.579481623074614E-7, 3.103739993944631E-7, 3.663211869054443E-7, 4.169483003809781E-7, 4.635227228946929E-7, 5.288508873488246E-7, 5.686991889665327E-7, 6.204826202407989E-7, 6.812762234709958E-7, 7.263347560698961E-7, 7.657426809358803E-7, 8.193734165334142E-7, 8.656717598377384E-7, 9.267166497130238E-7, 9.687699186503285E-7, 1.0100613846028922E-6, 1.040421908192894E-6, 1.0981246626722591E-6, 1.1626975068704957E-6, 1.2072828789096895E-6, 1.245628837090483E-6, 1.2925330631771625E-6, 1.3136720582621303E-6, 1.3539419032545604E-6, 1.4261585444809766E-6, 1.4613406586174258E-6, 1.5325176657436087E-6, 1.5737994074503633E-6, 1.6102602927831794E-6, 1.6548696775342068E-6, 1.6814410653583067E-6, 1.7109022657620896E-6, 1.739729245372117E-6, 1.818909640506737E-6, 1.860652162367776E-6, 1.918256413753668E-6, 1.950305933579097E-6, 2.0142046144063015E-6, 2.0414075345970236E-6, 2.078387302258539E-6, 2.1271279829810187E-6, 2.149899817465148E-6, 2.1755325291439493E-6, 2.204795837375724E-6, 2.2850671080176522E-6, 2.32281076679128E-6, 2.373852151766557E-6, 2.420014414654376E-6, 2.4519443333869577E-6, 2.4886672811746097E-6, 2.5482357788527957E-6, 2.5826040900445513E-6, 2.62377928182969E-6, 2.6443912878365593E-6, 2.6742844225227714E-6, 2.6967536393142994E-6, 2.729072454192572E-6, 2.7697985570250263E-6, 2.8357136937141744E-6, 2.886882959408015E-6, 2.9324497121704535E-6, 2.973268481768577E-6, 3.010129601199772E-6, 3.0440263924820617E-6, 3.097221433350364E-6, 3.153942913939244E-6, 3.1693172695523055E-6, 3.208017787514919E-6, 3.2466586058243742E-6, 3.2873911331250026E-6, 3.300640985971264E-6, 3.3135005090680394E-6, 3.360507790578272E-6, 3.4192720091466502E-6, 3.4414606133149838E-6, 3.480699054050278E-6, 3.5502316470557827E-6, 3.608426792734956E-6, 3.6396339028551223E-6, 3.6863827940008945E-6, 3.7040534454740007E-6, 3.7410242395545514E-6, 3.7825944022123806E-6, 3.847435398836006E-6, 3.873875563025558E-6, 3.9031983332453956E-6, 3.931204415840417E-6, 3.9598830516054155E-6, 3.975995856789808E-6, 3.998365240336164E-6, 4.039172655337482E-6, 4.0632862055249795E-6, 4.0990893986543585E-6, 4.183983009230883E-6, 4.216663091722728E-6, 4.245830038292284E-6};
// See the code in the constructor if you wish to recalculate processedHistogram_Heat
long[] processedHistogram_Heat = new long[] {0, 153, 1768, 7613, 20266, 39237, 68532, 129569, 253672, 466068, 749036, 1118894, 1652120, 2336026, 3177411, 4289864, 5747526, 7589850, 9856728, 12682921, 16136759, 20151942, 24748602, 29931873, 35920626, 43062341, 51271017, 60327682, 70390031, 81495625, 93747434, 106799416, 120743363, 135914606, 152365699, 170101021, 189361871, 209772514, 231217617, 253641954, 277358294, 302642651, 330020944, 358968983, 388063091, 417818461, 448511484, 480304585, 512482974, 545137696, 578609204, 612633066, 646964215, 681044286, 714529707, 747302721, 780388840, 814149746, 848456881, 882775746, 916166774, 948689945, 980390026, 1010742090, 1039733085, 1067743075, 1094655102, 1120797912, 1146302590, 1170887019, 1193985679, 1216640602, 1238594372, 1258144428, 1275121725, 1291945812, 1309002000, 1325954529, 1341857749, 1355308147, 1366591082, 1376374738, 1385463561, 1393347395, 1400442971, 1406379379, 1410776631, 1414661410, 1418401449, 1420937467, 1421631268, 1420937467, 1418401449, 1414661410, 1410776631, 1406379379, 1400442971, 1393347395, 1385463561, 1376374738, 1366591082, 1355308147, 1341857749, 1325954529, 1309002000, 1291945812, 1275121725, 1258144428, 1238594372, 1216640602, 1193985679, 1170887019, 1146302590, 1120797912, 1094655102, 1067743075, 1039733085, 1010742090, 980390026, 948689945, 916166774, 882775746, 848456881, 814149746, 780388840, 747302721, 714529707, 681044286, 646964215, 612633066, 578609204, 545137696, 512482974, 480304585, 448511484, 417818461, 388063091, 358968983, 330020944, 302642651, 277358294, 253641954, 231217617, 209772514, 189361871, 170101021, 152365699, 135914606, 120743363, 106799416, 93747434, 81495625, 70390031, 60327682, 51271017, 43062341, 35920626, 29931873, 24748602, 20151942, 16136759, 12682921, 9856728, 7589850, 5747526, 4289864, 3177411, 2336026, 1652120, 1118894, 749036, 466068, 253672, 129569, 68532, 39237, 20266, 7613, 1768, 153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
long[] processedHistogram_Humidity = processedHistogram_Heat;
double dataProcessedCount = dataSampleCount * 4; // we doubled the sample data by mirroring it horizontally, and doubled that by mirroring vertically
Point2d sampleMean = null;
public double FrequencyOfTemperature(float temperature) {
int index = Math.round((temperature / scaleAdj) + offsetAdj) + dataSampleOffset;
if (index >= 0 && index < processedHistogram_Heat.length) {
//return sampledHistogram_Heat[index] / dataSampleCount;
return processedHistogram_Heat[index] / dataProcessedCount;
} else {
// Our sampling range covers all non-zero values, so the answer to return is zero,
// however NaN should help highlight if range bugs are occurring elsewhere in the app.
return Double.NaN;
}
}
public double FrequencyOfHumidity(float humidity) {
int index = Math.round((humidity / scaleAdj) + offsetAdj) + dataSampleOffset;
if (index >= 0 && index < processedHistogram_Heat.length) {
//return sampledHistogram_Humidity[index] / dataSampleCount;
return processedHistogram_Heat[index] / dataProcessedCount;
} else {
// Our sampling range covers all non-zero values, so the answer to return is zero,
// however NaN should help highlight if range bugs are occurring elsewhere in the app.
return Double.NaN;
}
}
/**
* Returns a value between 0 and 1 which represents how frequently a
* block should have a climate of (temperature, humidity).
* (uses linear interpolation)
*/
@Override
public double frequencyOfOccurance(float temperature, float humidity) {
// Linearly-interpolate instead of rounding temperature and humidity
float temperature_floor = (float)Math.floor(temperature);
float humidity_floor = (float)Math.floor(humidity);
double freq_floor = FrequencyOfTemperature(temperature_floor);
double freq_ceil = FrequencyOfTemperature((float)Math.ceil(temperature));
double temperature_interp = freq_floor + (freq_ceil - freq_floor) * (temperature - temperature_floor);
freq_floor = FrequencyOfHumidity(humidity_floor);
freq_ceil = FrequencyOfHumidity((float)Math.ceil(humidity));
double humidity_interp = freq_floor + (freq_ceil - freq_floor) * (humidity - humidity_floor);
return temperature_interp * humidity_interp;
}
/**
* 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.
*/
@Override
public double frequencyAtPercentile(double percentile) {
// use the lookup-table
if (percentile >= 0.995) {
return frequencyAtPercentileTable[(int)Math.round(Math.max(1, Math.min(100, percentile))) - 1];
} else {
return frequencyAtPerdimileTable[(int)Math.round(Math.max(0, Math.min(100, percentile * 100)))];
}
}
@Override
public Point2d getSampleMean() {
// We know the true mean of the noise, so use that.
if (sampleMean == null) sampleMean = new Point2d(DEFAULT_OFFSET + offsetAdj, DEFAULT_OFFSET + offsetAdj);
return sampleMean;
}
/**
* The data from the 3 octaves of Perlin noise plus 2 octaves blending is
* *not* of a Normal Distribution, and any math that assumes it is will fail.
* This function calculates what the the mean the standard deviation
* would be if it was a normal distribution.
* (Not used, but left in for reference)
*
* Spoiler: it's ~26 (perhaps exactly 26)
*/
private double getStandardDeviation(int[] histogram) {
// Work out the Mean (we actually know it should be 50)
double total = 0;
long sampleCount = 0;
for (int i = histogram.length - 1; i >= 0; i--) {
long sample = i - dataSampleOffset;
total += (sample * histogram[i]);
sampleCount += histogram[i];
}
double mean = total / (double)sampleCount;
// Then for each number: subtract the Mean and square the result
double squaredDifferenceTotal = 0;
for (int i = 0; i < histogram.length; i++) {
long sample = i - dataSampleOffset;
double squaredDifference = (sample - mean) * (sample - mean);
squaredDifferenceTotal += squaredDifference * histogram[i];
}
// Then work out the mean of those squared differences.
double meanOfSquaredDifference = squaredDifferenceTotal / ((double)sampleCount - 1); // -1 for Bessel's correction
// Take the square root of that and we are done!
return Math.sqrt(meanOfSquaredDifference);
}
/**
* Returns the percentage of sample locations/bins that occur at or below the given frequency_of_occurance
* @see PercentileAtFrequency_Processed
*/
private double PercentileAtFrequency_Sampled(double frequency_of_occurance) {
double probabilityUnderFrequency = 0;
for (int y = sampledHistogram_Humidity.length - 1; y >= 0; y--) {
for (int x = sampledHistogram_Heat.length - 1; x >= 0; x--) {
double probability = (sampledHistogram_Heat[x] / dataSampleCount) * (sampledHistogram_Humidity[y] / dataSampleCount);
if (probability <= frequency_of_occurance) {
probabilityUnderFrequency += probability;
}
}
}
return probabilityUnderFrequency * 100;
}
/**
* Returns the percentage of sample locations/bins that occur at or below the given frequency_of_occurance,
* however it uses the processed data, rather than the real samples.
* @see PercentileAtFrequency_Sampled
*/
private double PercentileAtFrequency_Processed(double frequency_of_occurance) {
double probabilityUnderFrequency = 0;
for (int y = processedHistogram_Humidity.length - 1; y >= 0; y--) {
for (int x = processedHistogram_Heat.length - 1; x >= 0; x--) {
double probability = (processedHistogram_Heat[x] / dataProcessedCount) * (processedHistogram_Humidity[y] / dataProcessedCount);
if (probability <= frequency_of_occurance) {
probabilityUnderFrequency += probability;
}
}
}
return probabilityUnderFrequency * 100;
}
/**
* Performs a logrithmic search for a frequency_of_occurance which will split the population of
* samples along the given percentile line.
* It uses the processed data, rather than the real samples.
* It's unlikely to find an exact match - such a value might not exist, but it should get close.
* @param percentile - should be between 0 and 100
* @param upper_freq_bound - note this is an exclusive upper bound, the search alg will never try it, though it should get close enough to not matter.
* @param lower_freq_bound - note this is an exclusive lower bound, the search alg will never try it, though it should get close enough to not matter.
* @param search_depth - recurse depth, and how many calls to FrequencyAtPercentile() will be made.
*/
private double SearchForFrequencyAtPercentile(double percentile, double upper_freq_bound, double lower_freq_bound, int search_depth) {
double midPointFrequency = lower_freq_bound + ((upper_freq_bound - lower_freq_bound) / 2.0d);
double midpointPercentile = PercentileAtFrequency_Processed(midPointFrequency);
if (search_depth == 0) {
AmidstLogger.warn("Could not find exact FrequencyAtPercentile for " + percentile + ", returning " + midPointFrequency);
return midPointFrequency;
}
if (midpointPercentile < percentile) {
return SearchForFrequencyAtPercentile(percentile, upper_freq_bound, midPointFrequency, search_depth - 1);
} else if (midpointPercentile > percentile) {
return SearchForFrequencyAtPercentile(percentile, midPointFrequency, lower_freq_bound, search_depth - 1);
} else {
return midPointFrequency;
}
}
/**
* Processes the sample counts from sampledHistogram_Heat & sampledHistogram_Humidity and
* uses what we know about the climate noise algorithm (the center and symmetry) to create
* data likely to be closer to the true distrubtion.
*
* This matters if you want quartile lines to look smooth.
*
* The processed data is written to processedHistogram_Heat/processedHistogram_Humidity
*/
private void processSamples() {
int center = Math.round(DEFAULT_OFFSET + offsetAdj);
int distFromCenter = 0;
int maxDistFromCenter = Math.max(center - dataSampleOffset, Math.max(sampledHistogram_Heat.length - center - 1, sampledHistogram_Humidity.length - center - 1));
while (distFromCenter <= maxDistFromCenter) {
int indexLeft = (center + dataSampleOffset) - distFromCenter;
int indexRight = (center + dataSampleOffset) + distFromCenter;
if (indexRight > 0 && indexRight < processedHistogram_Heat.length) {
processedHistogram_Heat[indexRight] = (indexRight < sampledHistogram_Heat.length) ? sampledHistogram_Heat[indexRight] : 0;
if (indexRight < sampledHistogram_Humidity.length) processedHistogram_Heat[indexRight] += sampledHistogram_Humidity[indexRight];
if (indexLeft > 0) {
processedHistogram_Heat[indexRight] += sampledHistogram_Heat[indexLeft];
processedHistogram_Heat[indexRight] += sampledHistogram_Humidity[indexLeft];
// Mirror the processed value to the left side of processedHistogram_Heat
processedHistogram_Heat[indexLeft] = processedHistogram_Heat[indexRight];
}
}
distFromCenter++;
}
// each value in processedHistogram_Heat is now the left+right of both heat+humidy added together
dataProcessedCount = dataSampleCount * 4;
AmidstLogger.info("processedHistogram_Heat: " + Arrays.toString(processedHistogram_Heat));
}
/**
* Caches results from SearchForFrequencyAtPercentile() into frequencyAtPercentileTable and
* frequencyAtPerdimileTable.
* @see SearchForFrequencyAtPercentile
*/
private void calculatePercentileTables(int search_depth) {
LongSummaryStatistics heatStats = Arrays.stream(processedHistogram_Heat).summaryStatistics();
LongSummaryStatistics humidityStats = Arrays.stream(processedHistogram_Humidity).summaryStatistics();
frequencyAtPerdimileTable[0] = (heatStats.getMin() / (double)dataProcessedCount) * (humidityStats.getMin() / (double)dataProcessedCount);
frequencyAtPercentileTable[99] = (heatStats.getMax() / (double)dataProcessedCount) * (humidityStats.getMax() / (double)dataProcessedCount);
double lowerBound_percentile = frequencyAtPerdimileTable[0];
double lowerBound_perdimile = frequencyAtPerdimileTable[0];
for(int i = 1; i < 100; i++) {
frequencyAtPercentileTable[i - 1] = SearchForFrequencyAtPercentile(i, 1.00, lowerBound_percentile, search_depth);
frequencyAtPerdimileTable[i] = SearchForFrequencyAtPercentile(i / 100d, frequencyAtPercentileTable[0], lowerBound_perdimile, search_depth);
lowerBound_percentile = frequencyAtPercentileTable[i];
lowerBound_perdimile = frequencyAtPerdimileTable[i];
}
AmidstLogger.info("frequencyAtPercentileTable: " + Arrays.toString(frequencyAtPercentileTable));
AmidstLogger.info("frequencyAtPerdimileTable: " + Arrays.toString(frequencyAtPerdimileTable));
}
/**
* Constructor for Minetest's default climate
*/
public ClimateHistogram() {
if (dataProcessedCount == 0) {
// the samples are already processed, and the percentile tables already
// calculated, but I leave this code here in case someone wants to update
// sampledHistogram_Heat and sampledHistogram_Humidity with their own sampled
// data and recalculate.
processSamples();
calculatePercentileTables(40);
}
}
/**
* Constructor for climates which have the same frequency distribution as Minetest's default climate,
* but may have been scaled or translated.
*/
public ClimateHistogram(NoiseParams heat, NoiseParams heat_blend, NoiseParams humidity, NoiseParams humidity_blend) {
// Ensure these noise settings are close enough to Minetest's default settings
// that we can use the pre-generated data.
//
// Defaults:
// np_heat = new NoiseParams(50, 50, new Vector3f(1000, 1000, 1000), 5349, (short)3, 0.5f, 2.0f);
// np_humidity = new NoiseParams(50, 50, new Vector3f(1000, 1000, 1000), 842, (short)3, 0.5f, 2.0f);
// np_heat_blend = new NoiseParams( 0, 1.5f, new Vector3f( 8, 8, 8), 13, (short)2, 1.0f, 2.0f);
// np_humidity_blend = new NoiseParams( 0, 1.5f, new Vector3f( 8, 8, 8), 90003, (short)2, 1.0f, 2.0f);
// TODO: Check octaves, persist, and lacunarity are all the same, and that scale and offset
// is the same or can be adjusted for, and that spread is appropriate.
scaleAdj = heat.scale / DEFAULT_SCALE;
offsetAdj = DEFAULT_OFFSET - heat.offset;
float scaleAdj_blend = heat_blend.scale / DEFAULT_BLEND_SCALE;
float offsetAdj_blend = DEFAULT_BLEND_OFFSET - heat_blend.offset;
if (heat.scale != humidity.scale || heat.offset != humidity.offset ||
heat_blend.scale != humidity_blend.scale || heat_blend.offset != humidity_blend.offset ||
scaleAdj != scaleAdj_blend || offsetAdj != offsetAdj_blend ||
heat.octaves != 3 || humidity.octaves != 3 || heat_blend.octaves != 2 || humidity_blend.octaves != 2 ||
heat.persist != 0.5f || humidity.persist != 0.5f || heat_blend.persist != 1.0f || humidity_blend.persist != 1.0f ||
heat.lacunarity != 2.0f || humidity.lacunarity != 2.0f || heat_blend.lacunarity != 2.0f || humidity_blend.lacunarity != 2.0f) {
AmidstLogger.error("Non-standant climate noise in use, current ClimateHistogram instance will give wrong data.");
}
}
}

View File

@ -0,0 +1,29 @@
package amidst.minetest.world.mapgen;
import javax.vecmath.Point2d;
public interface IHistogram2D {
/**
* Returns a value between 0 and 1 which represents how frequently the
* samples fall into the bucket at (x, y).
* It's up to the implementation to decide what size its buckets are, and
* whether x and y get rounded to the nearest bucket, or bucket values are
* interpolated.
*/
double frequencyOfOccurance(float x, float y);
/**
* 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.
*/
double frequencyAtPercentile(double percentile);
/**
* get's the mean value of the distribution.
* If the histogram were a normal distribution, this would return the location of
* the peak. If the histogram had two peaks, it would return a location between them.
*/
Point2d getSampleMean();
}

View File

@ -4,13 +4,79 @@ import javax.vecmath.Vector3f;
public class NoiseParams {
/**
* 'offset' is the centre value of the noise value, often 0.
* (https://forum.minetest.net/viewtopic.php?p=227726#p227726)
*/
public float offset = 0.0f;
/**
* 'scale' is the approximate variation of the noise value either side of the centre value, often 1.
* Depending on the number of octaves and the persistence the variation is usually 1 to 2 times
* the scale value. The exact variation can be calculated from octaves and persistence.
* (To be exact, scale is the variation of octave 1, additional octaves add extra variation, see below).
* (https://forum.minetest.net/viewtopic.php?p=227726#p227726)
*/
float scale = 1.0f;
/**
* 'spread' is the size in nodes of the largest scale variation. If the noise is terrain height
* this would be the approximate size of the largest islands or seas, there will be no structure
* on a larger scale than this.
* There are 3 values for x, y, z so you can stretch or squash the shape of your structures,
* normally you would set all 3 values to be the same, even for 2D noise where the y value
* is not used).
* (https://forum.minetest.net/viewtopic.php?p=227726#p227726)
*/
public Vector3f spread = new Vector3f(250, 250, 250);
/**
* 'seed' is the magic seed number that determines the entire noise pattern.
* Just type in any random number, different for each use of noise.
* This is actually a 'seed difference', the noise actually uses 'seed' + 'world seed', to make any noise pattern world-dependant and repeatable.
* 'seed' then makes sure each use of noise in a world is using a different pattern.
* (https://forum.minetest.net/viewtopic.php?p=227726#p227726)
*/
int seed = 12345;
/**
* 'octaves' is the number of levels of detail in the noise variation.
* The largest scale variation is 'spread', that is octave 1. Each additional octave adds
* variation on a scale one half the size, so here you will have variation on scales
* of 2048, 1024, 512 nodes.
* (https://forum.minetest.net/viewtopic.php?p=227726#p227726)
*/
short octaves = 3;
/**
* 'persist' is persistence. This is how large the variation of each additional octave is relative to
* the previous octave.
* Octave 1 always outputs a variation from -1 to 1, the 'amplitude' is 1.
* With 3 octaves persist 0.5, a much used standard noise:
* Octave 2 outputs a variation from -0.5 to 0.5 (amplitude 0.5).
* Octave 3 will output -0.25 to 0.25 (amplitude 0.5 x 0.5).
* The 3 octaves are added to result in a noise variation of -1.75 to 1.75.
* 'persist' is therefore the roughness of the noise pattern, 0.33 is fairly smooth, 0.67 is rough
* and spiky, 0.5 is a medium value.
* (https://forum.minetest.net/viewtopic.php?p=227726#p227726)
*/
public float persist = 0.6f;
/**
* lacunarity is the ratio of the scales of variation of adjacent octaves.
* Standard lacunarity is 2.0, where each additional octave creates variation on a scale 1/2 that
* of the previous octave (which is why they're called octaves, it's analogous to musical octaves
* which is a doubling of frequency).
*
* Lacunarity 3.0 means that if the 'spread' (the scale of variation of octave 1) is 900 nodes,
* then the 2nd octave creates variation on a scale of 300 nodes, the 3rd octave 100 nodes etc.
* It's a way to get a wider range of detail with the same number of octaves. it also has a
* different character.
* (https://forum.minetest.net/viewtopic.php?p=228165#p228165)
*/
float lacunarity = 2.0f;
int flags = Noise.FLAG_DEFAULTS; // Static methods like compareUnsigned, divideUnsigned etc have been added to the Integer class to support the arithmetic operations for unsigned integers
public String toString(String name) {
@ -40,6 +106,17 @@ public class NoiseParams {
this(offset_, scale_, spread_, seed_, octaves_, persist_, lacunarity_, Noise.FLAG_DEFAULTS);
}
/**
*
* @param offset_
* @param scale_
* @param spread_
* @param seed_
* @param octaves_
* @param persist_ see field comment at top of this file for explanation.
* @param lacunarity_ see field comment at top of this file for explanation.
* @param flags_
*/
public NoiseParams(float offset_, float scale_, Vector3f spread_, int seed_,
short octaves_, float persist_, float lacunarity_,
int flags_)

View File

@ -6,6 +6,8 @@ import amidst.fragment.IBiomeDataOracle;
import amidst.gameengineabstraction.CoordinateSystem;
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.MapgenParams;
import amidst.minetest.world.mapgen.MinetestBiome;
import amidst.minetest.world.mapgen.MinetestBiomeProfileImpl;
@ -17,6 +19,7 @@ import amidst.settings.biomeprofile.BiomeProfileUpdateListener;
public abstract class MinetestBiomeDataOracle implements IBiomeDataOracle, BiomeProfileUpdateListener {
protected final int seed;
protected MapgenParams params;
protected ClimateHistogram climateHistogram;
/**
* Updated by onBiomeProfileUpdate event, can be null.
*/
@ -132,6 +135,17 @@ public abstract class MinetestBiomeDataOracle implements IBiomeDataOracle, Biome
return (biome_closest != null) ? biome_closest : MinetestBiome.NONE;
}
public IHistogram2D getClimateHistogram() {
if (climateHistogram == null) {
climateHistogram = new ClimateHistogram(
params.np_heat,
params.np_heat_blend,
params.np_humidity,
params.np_humidity_blend
);
}
return climateHistogram;
}
@Override
public void onBiomeProfileUpdate(BiomeProfile newBiomeProfile) {