Initial Voronoi commit

master
Treer 2018-05-06 20:16:07 +10:00
parent f6a1cf52e4
commit c03d39eed6
8 changed files with 627 additions and 1 deletions

View File

@ -26,6 +26,7 @@ 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;
@ -330,6 +331,19 @@ public class Actions {
}
}
/**
* This is more useful for Minetest than Minecraft, as Minetest has biomes
* determined by heat and humidy, which Minecraft hasn't had since early betas
*/
@CalledOnlyBy(AmidstThread.EDT)
public void displayBiomeProfileVoronoi() {
if (gameEngineDetails.getType() != GameEngineType.MINECRAFT) {
VoronoiWindow.showDiagram(biomeAuthority.getBiomeProfileSelection());
} else {
dialogs.displayInfo("Biome profile Voronoi diagram", "Minecraft biomes are not determined by heat and humidity.");
}
}
@CalledOnlyBy(AmidstThread.EDT)
public void about() {
dialogs.displayInfo(

View File

@ -128,7 +128,8 @@ public class AmidstMenuBuilder {
if (biomeAuthority.getBiomeProfileDirectory().isValid()) {
result.add(create_Settings_BiomeProfile());
}
Menus.item(result, actions::displayGeneratorOptions, "View MapGen params", KeyEvent.VK_V);
Menus.item(result, actions::displayBiomeProfileVoronoi, "Biome Voronoi", KeyEvent.VK_V);
Menus.item(result, actions::displayGeneratorOptions, "View MapGen params", KeyEvent.VK_G);
result.addSeparator();
// @formatter:off

View File

@ -0,0 +1,5 @@
package amidst.gui.voronoi;
public class VoronoiGraph {
}

View File

@ -0,0 +1,356 @@
package amidst.gui.voronoi;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.JPanel;
import amidst.documentation.AmidstThread;
import amidst.documentation.CalledByAny;
import amidst.documentation.CalledOnlyBy;
import amidst.fragment.Fragment;
import amidst.gameengineabstraction.world.biome.IBiome;
import amidst.logging.AmidstLogger;
import amidst.minetest.world.mapgen.MinetestBiome;
import amidst.minetest.world.mapgen.MinetestBiomeProfileImpl;
import amidst.mojangapi.world.Dimension;
import amidst.mojangapi.world.coordinates.CoordinatesInWorld;
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 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_LABEL_SPACE = 1;
private static final int NODE_RADIUS = 0;
private static final int NODE_LABEL_SPACE = 0;
private static final int NODE_LABEL_FONTSIZE = 3;
private static final long serialVersionUID = 1L;
private static Stroke stroke_capButt = new BasicStroke(AXIS_WIDTH, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
private static Stroke stroke_capSquare = new BasicStroke(AXIS_WIDTH, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND);
private static AffineTransform noTransform = new AffineTransform();
public int axis_min = -40;
public int axis_max = 140;
public int graph_resolution = 1000;
private MinetestBiome[] nodes;
private int renderFlags;
private BufferedImage graph;
private int[] rgbArray;
@Override
@CalledOnlyBy(AmidstThread.EDT)
public void paintComponent(Graphics g) {
super.paintComponent(g);
createBufferedImage(graph_resolution);
this.setBorder(null);
Rectangle panelBounds = getBounds();
if (panelBounds.width > 0 && panelBounds.height > 0) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
if (GRAPHICS_DEBUG) {
g2d.setColor(Color.RED);
g2d.fillRect(0, 0, panelBounds.width, panelBounds.height);
}
int graphSize;
int desiredAxisLength = axis_max - axis_min;
AffineTransform graphTransform = new AffineTransform(g2d.getTransform());
// set up a square render area of (graphSize by graphSize) that's centered
if (panelBounds.width > panelBounds.height) {
graphSize = panelBounds.height;
graphTransform.translate((panelBounds.width - graphSize) / 2.0, 0);
} else {
graphSize = panelBounds.width;
graphTransform.translate(0, (panelBounds.height - graphSize) / 2.0);
}
// Flip the axis to y points up, and scale desiredAxisLength to full the centered render area
graphTransform.scale(graphSize / (double)desiredAxisLength, -graphSize / (double)desiredAxisLength);
graphTransform.translate(-axis_min, -axis_max);
g2d.setTransform(graphTransform);
g2d.setFont(new Font("SansSerif", Font.PLAIN, Math.round(NODE_LABEL_FONTSIZE * graphSize / (float)desiredAxisLength)));
// set graph background to white
g2d.setColor(Color.WHITE);
g2d.fillRect(axis_min, axis_min, axis_max - axis_min, axis_max - axis_min);
//drawVoronoi(g2d);
drawVoronoiGraph(rgbArray, graph_resolution, graph_resolution);
graph.setRGB(0, 0, graph_resolution, graph_resolution, rgbArray, 0, graph_resolution);
g2d.drawImage(graph, axis_min, axis_min, desiredAxisLength, desiredAxisLength, Color.WHITE, null);
// draw nodes
drawNodesOrNodeLabels(g2d);
// draw axis
if ((renderFlags & FLAG_SHOWAXIS) > 0) drawAxes(g2d);
}
}
@CalledOnlyBy(AmidstThread.EDT)
private void drawNodesOrNodeLabels(Graphics2D g2d) {
AffineTransform currentTransform = g2d.getTransform();
FontMetrics metrics = g2d.getFontMetrics(g2d.getFont());
boolean showNodes = (renderFlags & FLAG_SHOWNODES) > 0;
boolean showLabels = (renderFlags & FLAG_SHOWLABELS) > 0;
for (short i = 0; i < nodes.length; i++) {
MinetestBiome biome = nodes[i];
int x = Math.round(biome.heat_point);
int y = Math.round(biome.humidity_point);
if (perceivedBrightness(biome.getDefaultColor().getColor()) < 120) {
g2d.setColor(Color.WHITE);
} else {
g2d.setColor(Color.BLACK);
}
if (showNodes) {
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);
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(currentTransform);
}
}
}
@CalledOnlyBy(AmidstThread.EDT)
private void drawAxes(Graphics2D g2d) {
AffineTransform currentTransform = g2d.getTransform();
FontMetrics metrics = g2d.getFontMetrics(g2d.getFont());
g2d.setStroke(stroke_capSquare);
g2d.setColor(Color.BLACK);
g2d.drawLine(axis_min, 0, axis_max, 0);
g2d.drawLine(0, axis_min, 0, axis_max);
g2d.setStroke(stroke_capButt);
for (int i = axis_min; i <= axis_max; i++) {
if (i % 10 == 0 && i != 0) {
if (i % 100 == 0) {
g2d.drawLine(0, i, -TICKMARK_WIDTH_LARGE, i);
g2d.drawLine(i, 0, i, -TICKMARK_WIDTH_LARGE);
Point2D fontPosTemp = new Point(-TICKMARK_WIDTH_LARGE - TICKMARK_LABEL_SPACE, i);
Point2D fontPosHumidy = new Point(i, -TICKMARK_WIDTH_LARGE - TICKMARK_LABEL_SPACE);
g2d.setTransform(noTransform);
Point2D fontPosTransformed = currentTransform.transform(fontPosTemp, null);
g2d.drawString(
String.valueOf(i),
(float)fontPosTransformed.getX() - metrics.stringWidth(String.valueOf(i)),
(float)fontPosTransformed.getY() + metrics.getAscent() / 3
);
fontPosTransformed = currentTransform.transform(fontPosHumidy, null);
g2d.drawString(
String.valueOf(i),
(float)fontPosTransformed.getX() - metrics.stringWidth(String.valueOf(i)) / 2,
(float)fontPosTransformed.getY() + metrics.getAscent()
);
g2d.setTransform(currentTransform);
} else {
g2d.drawLine(0, i, -TICKMARK_WIDTH_SMALL, i);
g2d.drawLine(i, 0, i, -TICKMARK_WIDTH_SMALL);
}
}
}
if ((renderFlags & FLAG_SHOWLABELS) > 0) {
// label the axes
g2d.setFont(new Font("Serif", Font.PLAIN, (int)Math.round(NODE_LABEL_FONTSIZE * 1.7f * currentTransform.getScaleX())));
metrics = g2d.getFontMetrics(g2d.getFont());
Point2D fontPosHumidy = new Point(-TICKMARK_WIDTH_LARGE * 2, axis_max / 2);
Point2D fontPosTemp = new Point(axis_max / 2, -TICKMARK_WIDTH_LARGE * 2);
g2d.setTransform(noTransform);
Point2D fontPosTransformed = currentTransform.transform(fontPosTemp, null);
String value = "Temperature";
g2d.drawString(
value,
(float)fontPosTransformed.getX() - metrics.stringWidth(value),
(float)fontPosTransformed.getY() + metrics.getAscent() / 4
);
fontPosTransformed = currentTransform.transform(fontPosHumidy, null);
value = "Humidity";
drawRotatedText(
g2d,
(float)fontPosTransformed.getX() + metrics.getAscent() / 4,
(float)fontPosTransformed.getY() + metrics.stringWidth(value),
270,
value
);
g2d.setTransform(currentTransform);
}
}
public static void drawRotatedText(Graphics2D g2d, double x, double y, int angle, String text)
{
g2d.translate((float)x,(float)y);
g2d.rotate(Math.toRadians(angle));
g2d.drawString(text,0,0);
g2d.rotate(-Math.toRadians(angle));
g2d.translate(-(float)x,-(float)y);
}
/** returns the perceived brightness of col, between 0 (dark) and 255 (bight) */
@CalledByAny
public double perceivedBrightness(Color col)
{
return Math.sqrt(
.299 * Math.pow(col.getRed(), 2) +
.587 * Math.pow(col.getGreen(), 2) +
.114 * Math.pow(col.getBlue(), 2)
);
}
/** Create the graph BufferedImage if it doesn't already exist at the correct size */
private void createBufferedImage(int size) {
if (graph == null || graph.getWidth() != size) {
graph = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
rgbArray = new int[size * size];
}
}
@CalledByAny
private void drawVoronoiGraph(int[] rgbArray, int width, int height) {
if (nodes == null) return;
MinetestBiome biome_closest;
float dist_min;
int nodeCount = nodes.length;
float xScale = (axis_max - axis_min) / (float)width;
float yScale = (axis_max - axis_min) / (float)height;
int index = 0;
for (int y = 0; y < height; y++) {
float humidity = (y * yScale) + axis_min;
// TODO: break up rgbArray?
for (int x = 0; x < width; x++) {
float heat = (x * xScale) + axis_min;
dist_min = Float.MAX_VALUE;
biome_closest = null;
for (short i = 0; i < nodeCount; i++) {
MinetestBiome b = nodes[i];
float d_heat = heat - b.heat_point;
float d_humidity = humidity - b.humidity_point;
float dist = (d_heat * d_heat) + (d_humidity * d_humidity);
if (dist < dist_min) {
dist_min = dist;
biome_closest = b;
}
}
rgbArray[index++] = (biome_closest == null) ? 0x00000000 : biome_closest.getDefaultColor().getRGB();
}
}
}
private void drawVoronoi(Graphics2D g2d) {
if (nodes == null) return;
MinetestBiome biome_closest;
float dist_min;
int nodeCount = nodes.length;
for (int y = axis_min; y <= axis_max; y++) {
for (int x = axis_min; x <= axis_max; x++) {
float heat = x;
float humidity = y;
dist_min = Float.MAX_VALUE;
biome_closest = null;
for (short i = 0; i < nodeCount; i++) {
MinetestBiome b = nodes[i];
float d_heat = heat - b.heat_point;
float d_humidity = humidity - b.humidity_point;
float dist = (d_heat * d_heat) + (d_humidity * d_humidity);
if (dist < dist_min) {
dist_min = dist;
biome_closest = b;
}
}
if (biome_closest != null) {
g2d.setColor(biome_closest.getDefaultColor().getColor());
g2d.drawRect(x, y, 1, 1);
}
}
}
}
@CalledOnlyBy(AmidstThread.EDT)
public void Update(MinetestBiomeProfileImpl biomeProfile, int height, int flags) {
ArrayList<MinetestBiome> biomes = 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) {
biomes.add(mtBiome);
}
}
ArrayList<MinetestBiome> currentNodes = nodes == null ? new ArrayList<MinetestBiome>() : new ArrayList<MinetestBiome>(Arrays.asList(nodes));
if (flags != this.renderFlags || !currentNodes.equals(biomes)) {
this.renderFlags = flags;
this.nodes = biomes.toArray(new MinetestBiome[biomes.size()]);
invalidate();
repaint();
}
}
public int getRenderFlags() { return renderFlags; }
}

View File

@ -0,0 +1,190 @@
package amidst.gui.voronoi;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JCheckBox;
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.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import net.miginfocom.swing.MigLayout;
import amidst.documentation.AmidstThread;
import amidst.documentation.CalledOnlyBy;
import amidst.minetest.world.mapgen.MinetestBiomeProfileImpl;
import amidst.settings.biomeprofile.BiomeProfile;
import amidst.settings.biomeprofile.BiomeProfileSelection;
import amidst.settings.biomeprofile.BiomeProfileUpdateListener;
public class VoronoiWindow implements BiomeProfileUpdateListener, ChangeListener {
private static final int ALTITUDESLIDER_DEFAULT_LOW = -40;
private static final int ALTITUDESLIDER_DEFAULT_HIGH = 200;
private static VoronoiWindow voronoiWindow = null;
private BiomeProfileSelection biomeProfileSelection;
private final JFrame windowFrame;
private VoronoiPanel voronoiPanel;
private JSlider altitudeSlider;
private JLabel graphHeading;
private JCheckBox option_showAxis;
private JCheckBox option_showLabels;
private JCheckBox option_showNodes;
private JSpinner altitudeOffset;
/** the profile being used by Amidstest **/
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) {
JFrame result = new JFrame();
result.getContentPane().setLayout(new MigLayout());
result.setSize(width, height);
result.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
result.setVisible(false);
}
});
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
JLabel heightLabel = new JLabel("Altitude");
result.add(heightLabel, "center, wrap");
altitudeSlider = new JSlider(JSlider.VERTICAL, ALTITUDESLIDER_DEFAULT_LOW, ALTITUDESLIDER_DEFAULT_HIGH, 1);
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);
return result;
}
@Override
public void onBiomeProfileUpdate(BiomeProfile newBiomeProfile) {
UpdateSelectedBiomeProfile(newBiomeProfile);
}
@Override
public void stateChanged(ChangeEvent e) {
if (e.getSource() == altitudeOffset) {
updateHeightSlider((int)altitudeOffset.getValue());
}
updateVoronoiDiagram();
}
private void UpdateSelectedBiomeProfile(BiomeProfile newProfile) {
MinetestBiomeProfileImpl minetestProfile = (newProfile instanceof MinetestBiomeProfileImpl) ? (MinetestBiomeProfileImpl)newProfile : null;
boolean changed = (minetestProfile == null) ? (selectedProfile != null) : !minetestProfile.equals(selectedProfile);
selectedProfile = minetestProfile;
if (changed) updateVoronoiDiagram();
this.windowFrame.setTitle(selectedProfile == null ? "Biome profile Voronoi graph" : "Voronoi graph for " + selectedProfile.getName());
}
private void updateVoronoiDiagram() {
EventQueue.invokeLater(
() -> {
int altitude = getAltitudeFromDialog();
voronoiPanel.Update(selectedProfile, altitude, getOptionFlagsFromDialog());
graphHeading.setText("Biomes at altitude " + altitude);
}
);
}
@CalledOnlyBy(AmidstThread.EDT)
private void updateHeightSlider(int offset) {
int oldHeightPosition = altitudeSlider.getValue() - altitudeSlider.getMinimum();
altitudeSlider.setMinimum(ALTITUDESLIDER_DEFAULT_LOW + offset);
altitudeSlider.setMaximum(ALTITUDESLIDER_DEFAULT_HIGH + offset);
altitudeSlider.setValue(ALTITUDESLIDER_DEFAULT_LOW + offset + oldHeightPosition);
}
@CalledOnlyBy(AmidstThread.EDT)
private int getAltitudeFromDialog() {
return altitudeSlider.getValue();
}
@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);
}
@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;
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) {
BiomeProfile newProfile = null;
if (this.biomeProfileSelection != null) {
this.biomeProfileSelection.removeUpdateListener(this);
}
this.biomeProfileSelection = biomeProfileSelection;
if (biomeProfileSelection != null) {
this.biomeProfileSelection.addUpdateListener(this);
newProfile = this.biomeProfileSelection.getCurrentBiomeProfile();
}
UpdateSelectedBiomeProfile(newProfile);
this.windowFrame.setVisible(true);
}
}

View File

@ -33,6 +33,34 @@ public class MinetestBiome extends BiomeBase {
super.setIndex(index);
}
@Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (!MinetestBiome.class.isAssignableFrom(obj.getClass())) return false;
final MinetestBiome other = (MinetestBiome)obj;
if ((this.getName() == null) ? (other.getName() != null) : !this.getName().equals(other.getName())) return false;
if ((this.getDefaultColor() == null) ? (other.getDefaultColor() != null) : !this.getDefaultColor().equals(other.getDefaultColor())) return false;
return y_min == other.y_min &&
y_max == other.y_max &&
heat_point == other.heat_point &&
humidity_point == other.humidity_point &&
vertical_blend == other.vertical_blend;
}
@Override
public int hashCode() {
int hash = 3;
hash = 37 * hash + (this.getName() != null ? this.getName().hashCode() : 0);
hash = 37 * hash + (this.getDefaultColor() != null ? this.getDefaultColor().hashCode() : 0);
hash = 37 * hash + (int)y_min;
hash = 37 * hash + (int)y_max;
hash = 37 * hash + (int)Float.floatToIntBits(heat_point);
hash = 37 * hash + (int)Float.floatToIntBits(humidity_point);
hash = 37 * hash + (int)vertical_blend;
return hash;
}
public short y_min;
public short y_max;
public float heat_point;

View File

@ -159,6 +159,26 @@ public class MinetestBiomeProfileImpl implements BiomeProfile {
public boolean save(File file) {
return writeToFile(file, serialize());
}
@Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (!MinetestBiomeProfileImpl.class.isAssignableFrom(obj.getClass())) return false;
final MinetestBiomeProfileImpl other = (MinetestBiomeProfileImpl)obj;
if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) return false;
if ((this.shortcut == null) ? (other.shortcut != null) : !this.shortcut.equals(other.shortcut)) return false;
if ((this.biomeList == null) ? (other.biomeList != null) : !this.biomeList.equals(other.biomeList)) return false;
return true;
}
@Override
public int hashCode() {
int hash = 3;
hash = 37 * hash + this.name.hashCode();
hash = 37 * hash + this.shortcut.hashCode();
hash = 37 * hash + this.biomeList.hashCode();
return hash;
}
private String serialize() {
String output = "{ \"name\":\"" + name + "\", \"biomeList\":[\r\n";

View File

@ -117,4 +117,16 @@ public class BiomeColor {
private int lighten(int x) {
return Math.min(x + LIGHTEN_BRIGHTNESS, 0xFF);
}
@Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (!BiomeColor.class.isAssignableFrom(obj.getClass())) return false;
return rgb == ((BiomeColor)obj).rgb;
}
@Override
public int hashCode() {
return rgb;
}
}