mirror of https://github.com/Minestom/Minestom.git
352 lines
11 KiB
Java
352 lines
11 KiB
Java
package net.minestom.server.map;
|
|
|
|
import net.minestom.server.utils.thread.MinestomThread;
|
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.function.Function;
|
|
|
|
public enum MapColors {
|
|
NONE(0, 0, 0),
|
|
GRASS(127, 178, 56),
|
|
SAND(247, 233, 163),
|
|
WOOL(199, 199, 199),
|
|
FIRE(255, 0, 0),
|
|
ICE(160, 160, 255),
|
|
METAL(167, 167, 167),
|
|
PLANT(0, 124, 0),
|
|
SNOW(255, 255, 255),
|
|
CLAY(164, 168, 184),
|
|
DIRT(151, 109, 77),
|
|
STONE(112, 112, 112),
|
|
WATER(64, 64, 255),
|
|
WOOD(143, 119, 72),
|
|
QUARTZ(255, 252, 245),
|
|
COLOR_ORANGE(216, 127, 51),
|
|
COLOR_MAGENTA(178, 76, 216),
|
|
COLOR_LIGHT_BLUE(102, 153, 216),
|
|
COLOR_YELLOW(229, 229, 51),
|
|
COLOR_LIGHT_GREEN(127, 204, 25),
|
|
COLOR_PINK(242, 127, 165),
|
|
COLOR_GRAY(76, 76, 76),
|
|
COLOR_LIGHT_GRAY(153, 153, 153),
|
|
COLOR_CYAN(76, 127, 153),
|
|
COLOR_PURPLE(127, 63, 178),
|
|
COLOR_BLUE(51, 76, 178),
|
|
COLOR_BROWN(102, 76, 51),
|
|
COLOR_GREEN(102, 127, 51),
|
|
COLOR_RED(153, 51, 51),
|
|
COLOR_BLACK(25, 25, 25),
|
|
GOLD(250, 238, 77),
|
|
DIAMOND(92, 219, 213),
|
|
LAPIS(74, 128, 255),
|
|
EMERALD(0, 217, 58),
|
|
PODZOL(129, 86, 49),
|
|
NETHER(112, 2, 0),
|
|
TERRACOTTA_WHITE(209, 177, 161),
|
|
TERRACOTTA_ORANGE(159, 82, 36),
|
|
TERRACOTTA_MAGENTA(149, 87, 108),
|
|
TERRACOTTA_LIGHT_BLUE(112, 108, 138),
|
|
TERRACOTTA_YELLOW(186, 133, 36),
|
|
TERRACOTTA_LIGHT_GREEN(103, 117, 53),
|
|
TERRACOTTA_PINK(160, 77, 78),
|
|
TERRACOTTA_GRAY(57, 41, 35),
|
|
TERRACOTTA_LIGHT_GRAY(135, 107, 98),
|
|
TERRACOTTA_CYAN(87, 92, 92),
|
|
TERRACOTTA_PURPLE(122, 73, 88),
|
|
TERRACOTTA_BLUE(76, 62, 92),
|
|
TERRACOTTA_BROWN(76, 50, 35),
|
|
TERRACOTTA_GREEN(76, 82, 42),
|
|
TERRACOTTA_RED(142, 60, 46),
|
|
TERRACOTTA_BLACK(37, 22, 16),
|
|
CRIMSON_NYLIUM(189, 48, 49),
|
|
CRIMSON_STEM(148, 63, 97),
|
|
CRIMSON_HYPHAE(92, 25, 29),
|
|
WARPED_NYLIUM(22, 126, 134),
|
|
WARPED_STEM(58, 142, 140),
|
|
WARPED_HYPHAE(86, 44, 62),
|
|
WARPED_WART_BLOCK(20, 180, 133);
|
|
|
|
private final int red;
|
|
private final int green;
|
|
private final int blue;
|
|
|
|
private static final ConcurrentHashMap<Integer, PreciseMapColor> rgbMap = new ConcurrentHashMap<>();
|
|
// only used if mappingStrategy == ColorMappingStrategy.PRECISE
|
|
private static PreciseMapColor[] rgbArray = null;
|
|
|
|
private static final ColorMappingStrategy mappingStrategy;
|
|
private static final String MAPPING_ARGUMENT = "minestom.map.rgbmapping";
|
|
// only used if MAPPING_ARGUMENT is "approximate"
|
|
private static final String REDUCTION_ARGUMENT = "minestom.map.rgbreduction";
|
|
private static final int colorReduction;
|
|
|
|
static {
|
|
ColorMappingStrategy strategy;
|
|
String strategyStr = System.getProperty(MAPPING_ARGUMENT);
|
|
if (strategyStr == null) {
|
|
strategy = ColorMappingStrategy.LAZY;
|
|
} else {
|
|
try {
|
|
strategy = ColorMappingStrategy.valueOf(strategyStr.toUpperCase());
|
|
} catch (IllegalArgumentException e) {
|
|
System.err.println("Unknown color mapping strategy: " + strategyStr);
|
|
System.err.println("Defaulting to LAZY.");
|
|
strategy = ColorMappingStrategy.LAZY;
|
|
}
|
|
}
|
|
mappingStrategy = strategy;
|
|
|
|
int reduction = 10;
|
|
String reductionStr = System.getProperty(REDUCTION_ARGUMENT);
|
|
if (reductionStr != null) {
|
|
try {
|
|
reduction = Integer.parseInt(reductionStr);
|
|
} catch (NumberFormatException e) {
|
|
System.err.println("Invalid integer in reduction argument: " + reductionStr);
|
|
e.printStackTrace();
|
|
}
|
|
|
|
if (reduction < 0 || reduction >= 255) {
|
|
System.err.println("Reduction was found to be invalid: " + reduction + ". Must in 0-255, defaulting to 10.");
|
|
reduction = 10;
|
|
}
|
|
}
|
|
colorReduction = reduction;
|
|
}
|
|
|
|
MapColors(int red, int green, int blue) {
|
|
this.red = red;
|
|
this.green = green;
|
|
this.blue = blue;
|
|
}
|
|
|
|
// From the wiki: https://minecraft.gamepedia.com/Map_item_format
|
|
// Map Color ID Multiply R,G,B By = Multiplier
|
|
//Base Color ID*4 + 0 180 0.71
|
|
//Base Color ID*4 + 1 220 0.86
|
|
//Base Color ID*4 + 2 255 (same color) 1
|
|
//Base Color ID*4 + 3 135 0.53
|
|
|
|
/**
|
|
* Returns the color index with RGB multiplied by 0.53, to use on a map
|
|
*/
|
|
public byte multiply53() {
|
|
return (byte) ((ordinal() << 2) + 3);
|
|
}
|
|
|
|
/**
|
|
* Returns the color index with RGB multiplied by 0.86, to use on a map
|
|
*/
|
|
public byte multiply86() {
|
|
return (byte) ((ordinal() << 2) + 1);
|
|
}
|
|
|
|
/**
|
|
* Returns the color index with RGB multiplied by 0.71, to use on a map
|
|
*/
|
|
public byte multiply71() {
|
|
return (byte) (ordinal() << 2);
|
|
}
|
|
|
|
/**
|
|
* Returns the color index to use on a map
|
|
*/
|
|
public byte baseColor() {
|
|
return (byte) ((ordinal() << 2) + 2);
|
|
}
|
|
|
|
public int red() {
|
|
return red;
|
|
}
|
|
|
|
public int green() {
|
|
return green;
|
|
}
|
|
|
|
public int blue() {
|
|
return blue;
|
|
}
|
|
|
|
private static void fillRGBMap() {
|
|
for (MapColors base : values()) {
|
|
if (base == NONE)
|
|
continue;
|
|
for (Multiplier m : Multiplier.values()) {
|
|
PreciseMapColor preciseMapColor = new PreciseMapColor(base, m);
|
|
int rgb = preciseMapColor.toRGB();
|
|
|
|
if (mappingStrategy == ColorMappingStrategy.APPROXIMATE) {
|
|
rgb = reduceColor(rgb);
|
|
}
|
|
rgbMap.put(rgb, preciseMapColor);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void fillRGBArray() {
|
|
rgbArray = new PreciseMapColor[0xFFFFFF + 1];
|
|
MinestomThread threads = new MinestomThread(Runtime.getRuntime().availableProcessors(), "RGBMapping", true);
|
|
for (int rgb = 0; rgb <= 0xFFFFFF; rgb++) {
|
|
int finalRgb = rgb;
|
|
threads.execute(() -> rgbArray[finalRgb] = mapColor(finalRgb));
|
|
}
|
|
try {
|
|
threads.shutdown();
|
|
threads.awaitTermination(100, TimeUnit.MINUTES);
|
|
} catch (Throwable t) {
|
|
t.printStackTrace();
|
|
}
|
|
}
|
|
|
|
public static PreciseMapColor closestColor(int argb) {
|
|
int noAlpha = argb & 0xFFFFFF;
|
|
if (mappingStrategy == ColorMappingStrategy.PRECISE) {
|
|
if (rgbArray == null) {
|
|
synchronized (MapColors.class) {
|
|
if (rgbArray == null) {
|
|
fillRGBArray();
|
|
}
|
|
}
|
|
}
|
|
return rgbArray[noAlpha];
|
|
}
|
|
if (rgbMap.isEmpty()) {
|
|
synchronized (rgbMap) {
|
|
if (rgbMap.isEmpty()) {
|
|
fillRGBMap();
|
|
}
|
|
}
|
|
}
|
|
if (mappingStrategy == ColorMappingStrategy.APPROXIMATE) {
|
|
noAlpha = reduceColor(noAlpha);
|
|
}
|
|
return rgbMap.computeIfAbsent(noAlpha, MapColors::mapColor);
|
|
}
|
|
|
|
private static int reduceColor(int rgb) {
|
|
int red = (rgb >> 16) & 0xFF;
|
|
int green = (rgb >> 8) & 0xFF;
|
|
int blue = rgb & 0xFF;
|
|
|
|
red = red / colorReduction;
|
|
green = green / colorReduction;
|
|
blue = blue / colorReduction;
|
|
return (red << 16) | (green << 8) | blue;
|
|
}
|
|
|
|
private static PreciseMapColor mapColor(int rgb) {
|
|
PreciseMapColor closest = null;
|
|
int closestDistance = Integer.MAX_VALUE;
|
|
for (MapColors base : values()) {
|
|
if (base == NONE)
|
|
continue;
|
|
for (Multiplier m : Multiplier.values()) {
|
|
final int rgbKey = PreciseMapColor.toRGB(base, m);
|
|
final int redKey = (rgbKey >> 16) & 0xFF;
|
|
final int greenKey = (rgbKey >> 8) & 0xFF;
|
|
final int blueKey = rgbKey & 0xFF;
|
|
|
|
final int red = (rgb >> 16) & 0xFF;
|
|
final int green = (rgb >> 8) & 0xFF;
|
|
final int blue = rgb & 0xFF;
|
|
|
|
final int dr = redKey - red;
|
|
final int dg = greenKey - green;
|
|
final int db = blueKey - blue;
|
|
final int dist = (dr * dr + dg * dg + db * db);
|
|
if (dist < closestDistance) {
|
|
closest = new PreciseMapColor(base, m);
|
|
closestDistance = dist;
|
|
}
|
|
}
|
|
}
|
|
return closest;
|
|
}
|
|
|
|
public static class PreciseMapColor {
|
|
private final MapColors baseColor;
|
|
private final Multiplier multiplier;
|
|
|
|
PreciseMapColor(MapColors base, Multiplier multiplier) {
|
|
this.baseColor = base;
|
|
this.multiplier = multiplier;
|
|
}
|
|
|
|
public MapColors getBaseColor() {
|
|
return baseColor;
|
|
}
|
|
|
|
public Multiplier getMultiplier() {
|
|
return multiplier;
|
|
}
|
|
|
|
public byte getIndex() {
|
|
return multiplier.apply(baseColor);
|
|
}
|
|
|
|
public int toRGB() {
|
|
return toRGB(baseColor, multiplier);
|
|
}
|
|
|
|
public static int toRGB(MapColors baseColor, Multiplier multiplier) {
|
|
double r = baseColor.red();
|
|
double g = baseColor.green();
|
|
double b = baseColor.blue();
|
|
|
|
r *= multiplier.multiplier();
|
|
g *= multiplier.multiplier();
|
|
b *= multiplier.multiplier();
|
|
|
|
final int red = (int) r;
|
|
final int green = (int) g;
|
|
final int blue = (int) b;
|
|
return (red << 16) | (green << 8) | blue;
|
|
}
|
|
}
|
|
|
|
public enum Multiplier {
|
|
x1_00(MapColors::baseColor, 1.00),
|
|
x0_53(MapColors::multiply53, 0.53),
|
|
x0_71(MapColors::multiply71, 0.71),
|
|
x0_86(MapColors::multiply86, 0.86);
|
|
|
|
private final Function<MapColors, Byte> indexGetter;
|
|
private final double multiplier;
|
|
|
|
Multiplier(Function<MapColors, Byte> indexGetter, double multiplier) {
|
|
this.indexGetter = indexGetter;
|
|
this.multiplier = multiplier;
|
|
}
|
|
|
|
public double multiplier() {
|
|
return multiplier;
|
|
}
|
|
|
|
public byte apply(MapColors baseColor) {
|
|
return indexGetter.apply(baseColor);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* How does Minestom compute RGB to MapColor transitions?
|
|
*/
|
|
public enum ColorMappingStrategy {
|
|
/**
|
|
* If already computed, send the result. Otherwise, compute the closest color in a RGB Map, and add it to the map
|
|
*/
|
|
LAZY,
|
|
|
|
/**
|
|
* All colors are already in the map after the first call. Heavy hit on the memory:
|
|
* (2^24) * 4 bytes at the min (~64MB)
|
|
*/
|
|
PRECISE,
|
|
|
|
/**
|
|
* RGB components are divided by 10 before issuing a lookup (as with the PRECISE strategy), but saves on memory usage
|
|
*/
|
|
APPROXIMATE
|
|
}
|
|
}
|