diff --git a/src/main/java/ru/bclib/BCLib.java b/src/main/java/ru/bclib/BCLib.java index 7a20cc04..5c30710d 100644 --- a/src/main/java/ru/bclib/BCLib.java +++ b/src/main/java/ru/bclib/BCLib.java @@ -1,9 +1,11 @@ package ru.bclib; import net.fabricmc.api.ModInitializer; +import ru.bclib.util.Logger; public class BCLib implements ModInitializer { public static final String MOD_ID = "bclib"; + public static final Logger LOGGER = new Logger(MOD_ID); @Override public void onInitialize() {} diff --git a/src/main/java/ru/bclib/util/ColorExtractor.java b/src/main/java/ru/bclib/util/ColorExtractor.java new file mode 100644 index 00000000..9614345d --- /dev/null +++ b/src/main/java/ru/bclib/util/ColorExtractor.java @@ -0,0 +1,148 @@ +package ru.bclib.util; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Random; + +public class ColorExtractor { + private List
centers = new ArrayList<>(); + private List colors; + private Integer result; + + public ColorExtractor(List colors) { + this.colors = colors; + Random rnd = new Random(); + int size = colors.size(); + for (int i = 0; i < 4; i++) { + int color = colors.get(rnd.nextInt(size)); + this.centers.add(new Center(color)); + } + } + + public int analize() { + boolean moved = true; + while (moved) { + this.remap(); + moved = false; + for (Center center : centers) { + if (center.move()) { + moved = true; + } + } + } + List
toClear = new ArrayList<>(); + this.centers.forEach(center -> { + if (center.colors.isEmpty()) { + toClear.add(center); + } + }); + if (toClear.size() > 0) { + toClear.forEach(clear -> centers.remove(clear)); + } + this.centers.sort(Center.COMPARATOR); + + return this.getResult(); + } + + public int getResult() { + if (result == null) { + double weights = 0; + double alpha = 0; + double red = 0; + double green = 0; + double blue = 0; + for (Center center : centers) { + double weight = (double) center.colors.size() / colors.size(); + weights += weight; + alpha += center.a * weight; + red += center.r * weight; + green += center.g * weight; + blue += center.b * weight; + } ; + + int a = (int) Math.round(alpha / weights); + int r = (int) Math.round(red / weights); + int g = (int) Math.round(green / weights); + int b = (int) Math.round(blue / weights); + + this.result = a << 24 | r << 16 | g << 8 | b; + } + + return this.result; + } + + private void remap() { + this.centers.forEach(entry -> entry.colors.clear()); + this.colors.forEach(color -> { + int id = 0; + int base = centers.get(0).getColor(); + int dst = ColorUtil.colorDistance(color, base); + for (Center center : centers) { + base = center.getColor(); + int dst1 = ColorUtil.colorDistance(color, base); + if (dst1 < dst) { + dst = dst1; + id = centers.indexOf(center); + } + } + this.centers.get(id).colors.add(color); + }); + } + + private static class Center { + static final Comparator
COMPARATOR = new Comparator
() { + @Override + public int compare(Center c1, Center c2) { + return Integer.compare(c1.getColor(), c2.getColor()); + } + }; + + List colors = new ArrayList<>(); + double a, r, g, b; + + Center(int color) { + this.a = (color >> 24) & 255; + this.r = (color >> 16) & 255; + this.g = (color >> 8) & 255; + this.b = color & 255; + } + + private void update(double a, double r, double g, double b) { + this.a = a; + this.r = r; + this.g = g; + this.b = b; + } + + public int getColor() { + int a = (int) Math.round(this.a); + int r = (int) Math.round(this.r); + int g = (int) Math.round(this.g); + int b = (int) Math.round(this.b); + return a << 24 | r << 16 | g << 8 | b; + } + + public boolean move() { + double or = r; + double og = g; + double ob = b; + double a = 0, r = 0, g = 0, b = 0; + int size = this.colors.size(); + for (int col : colors) { + a += (col >> 24) & 255; + r += (col >> 16) & 255; + g += (col >> 8) & 255; + b += col & 255; + } + a /= size; + r /= size; + g /= size; + b /= size; + + this.update(a, r, g, b); + + return Math.abs(r - or) > 0.1 || Math.abs(g - og) > 0.1 || Math.abs(b - ob) > 0.1; + } + } +} diff --git a/src/main/java/ru/bclib/util/ColorUtil.java b/src/main/java/ru/bclib/util/ColorUtil.java new file mode 100644 index 00000000..1443d173 --- /dev/null +++ b/src/main/java/ru/bclib/util/ColorUtil.java @@ -0,0 +1,259 @@ +package ru.bclib.util; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.Maps; +import com.mojang.blaze3d.platform.NativeImage; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.impl.client.indigo.renderer.helper.ColorHelper; +import net.minecraft.client.Minecraft; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.util.Mth; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import ru.bclib.BCLib; + +@Environment(EnvType.CLIENT) +public class ColorUtil { + private static final float[] FLOAT_BUFFER = new float[4]; + private static final int ALPHA = 255 << 24; + + public static int color(int r, int g, int b) { + return ALPHA | (r << 16) | (g << 8) | b; + } + + public static int color(String hex) { + int r = Integer.parseInt(hex.substring(0, 2), 16); + int g = Integer.parseInt(hex.substring(2, 4), 16); + int b = Integer.parseInt(hex.substring(4, 6), 16); + return color(r, g, b); + } + + public static int[] toIntArray(int color) { + return new int[] { + (color >> 24) & 255, + (color >> 16) & 255, + (color >> 8) & 255, + color & 255 + }; + } + + public static float[] toFloatArray(int color) { + FLOAT_BUFFER[0] = ((color >> 16 & 255) / 255.0F); + FLOAT_BUFFER[1] = ((color >> 8 & 255) / 255.0F); + FLOAT_BUFFER[2] = ((color & 255) / 255.0F); + FLOAT_BUFFER[3] = ((color >> 24 & 255) / 255.0F); + + return FLOAT_BUFFER; + } + + public static float[] RGBtoHSB(int r, int g, int b, float[] hsbvals) { + float hue, saturation, brightness; + if (hsbvals == null) { + hsbvals = FLOAT_BUFFER; + } + int cmax = (r > g) ? r : g; + if (b > cmax) cmax = b; + int cmin = (r < g) ? r : g; + if (b < cmin) cmin = b; + + brightness = ((float) cmax) / 255.0F; + if (cmax != 0) + saturation = ((float) (cmax - cmin)) / ((float) cmax); + else + saturation = 0; + if (saturation == 0) + hue = 0; + else { + float redc = ((float) (cmax - r)) / ((float) (cmax - cmin)); + float greenc = ((float) (cmax - g)) / ((float) (cmax - cmin)); + float bluec = ((float) (cmax - b)) / ((float) (cmax - cmin)); + if (r == cmax) + hue = bluec - greenc; + else if (g == cmax) + hue = 2.0F + redc - bluec; + else + hue = 4.0F + greenc - redc; + hue = hue / 6.0F; + if (hue < 0) + hue = hue + 1.0F; + } + hsbvals[0] = hue; + hsbvals[1] = saturation; + hsbvals[2] = brightness; + return hsbvals; + } + + public static int HSBtoRGB(float hue, float saturation, float brightness) { + int r = 0, g = 0, b = 0; + if (saturation == 0) { + r = g = b = (int) (brightness * 255.0F + 0.5F); + } + else { + float h = (hue - (float) Math.floor(hue)) * 6.0F; + float f = h - (float) java.lang.Math.floor(h); + float p = brightness * (1.0F - saturation); + float q = brightness * (1.0F - saturation * f); + float t = brightness * (1.0F - (saturation * (1.0F - f))); + switch ((int) h) { + case 0: + r = (int) (brightness * 255.0F + 0.5F); + g = (int) (t * 255.0F + 0.5F); + b = (int) (p * 255.0F + 0.5F); + break; + case 1: + r = (int) (q * 255.0F + 0.5F); + g = (int) (brightness * 255.0F + 0.5F); + b = (int) (p * 255.0F + 0.5F); + break; + case 2: + r = (int) (p * 255.0F + 0.5F); + g = (int) (brightness * 255.0F + 0.5F); + b = (int) (t * 255.0F + 0.5F); + break; + case 3: + r = (int) (p * 255.0F + 0.5F); + g = (int) (q * 255.0F + 0.5F); + b = (int) (brightness * 255.0F + 0.5F); + break; + case 4: + r = (int) (t * 255.0F + 0.5F); + g = (int) (p * 255.0F + 0.5F); + b = (int) (brightness * 255.0F + 0.5F); + break; + case 5: + r = (int) (brightness * 255.0F + 0.5F); + g = (int) (p * 255.0F + 0.5F); + b = (int) (q * 255.0F + 0.5F); + break; + } + } + return 0xFF000000 | (r << 16) | (g << 8) | (b << 0); + } + + public static int parseHex(String hexColor) { + int len = hexColor.length(); + if (len < 6 || len > 8 || len % 2 > 0) { + return -1; + } + + int color, shift; + if (len == 6) { + color = 0xFF000000; + shift = 16; + } + else { + color = 0; + shift = 24; + } + + try { + String[] splited = hexColor.split("(?<=\\G.{2})"); + for (String digit : splited) { + color |= Integer.valueOf(digit, 16) << shift; + shift -= 8; + } + } + catch (NumberFormatException ex) { + BCLib.LOGGER.catching(ex); + return -1; + } + + return color; + } + + public static int toABGR(int color) { + int r = (color >> 16) & 255; + int g = (color >> 8) & 255; + int b = color & 255; + return 0xFF000000 | b << 16 | g << 8 | r; + } + + public static int ABGRtoARGB(int color) { + int a = (color >> 24) & 255; + int b = (color >> 16) & 255; + int g = (color >> 8) & 255; + int r = color & 255; + return a << 24 | r << 16 | g << 8 | b; + } + + public static int colorBrigtness(int color, float val) { + RGBtoHSB((color >> 16) & 255, (color >> 8) & 255, color & 255, FLOAT_BUFFER); + FLOAT_BUFFER[2] += val / 10.0F; + FLOAT_BUFFER[2] = Mth.clamp(FLOAT_BUFFER[2], 0.0F, 1.0F); + return HSBtoRGB(FLOAT_BUFFER[0], FLOAT_BUFFER[1], FLOAT_BUFFER[2]); + } + + public static int applyTint(int color, int tint) { + return colorBrigtness(ColorHelper.multiplyColor(color, tint), 1.5F); + } + + public static int colorDistance(int color1, int color2) { + int r1 = (color1 >> 16) & 255; + int g1 = (color1 >> 8) & 255; + int b1 = color1 & 255; + int r2 = (color2 >> 16) & 255; + int g2 = (color2 >> 8) & 255; + int b2 = color2 & 255; + return MHelper.sqr(r1 - r2) + MHelper.sqr(g1 - g2) + MHelper.sqr(b1 - b2); + } + + private static Map colorPalette = Maps.newHashMap(); + + public static int extractColor(Item item) { + ResourceLocation id = Registry.ITEM.getKey(item); + if (id.equals(Registry.ITEM.getDefaultKey())) return -1; + if (colorPalette.containsKey(id)) { + return colorPalette.get(id); + } + ResourceLocation texture; + if (item instanceof BlockItem) { + texture = new ResourceLocation(id.getNamespace(), "textures/block/" + id.getPath() + ".png"); + } + else { + texture = new ResourceLocation(id.getNamespace(), "textures/item/" + id.getPath() + ".png"); + } + NativeImage image = loadImage(texture, 16, 16); + List colors = new ArrayList<>(); + for (int i = 0; i < image.getWidth(); i++) { + for (int j = 0; j < 16; j++) { + int col = image.getPixelRGBA(i, j); + if (((col >> 24) & 255) > 0) { + colors.add(ABGRtoARGB(col)); + } + } + } + image.close(); + + if (colors.size() == 0) return -1; + + ColorExtractor extractor = new ColorExtractor(colors); + int color = extractor.analize(); + colorPalette.put(id, color); + + return color; + } + + public static NativeImage loadImage(ResourceLocation image, int w, int h) { + Minecraft minecraft = Minecraft.getInstance(); + ResourceManager resourceManager = minecraft.getResourceManager(); + if (resourceManager.hasResource(image)) { + try (Resource resource = resourceManager.getResource(image)) { + return NativeImage.read(resource.getInputStream()); + } + catch (IOException e) { + BCLib.LOGGER.warning("Can't load texture image: {}. Will be created empty image.", image); + BCLib.LOGGER.warning("Cause: {}.", e.getMessage()); + } + } + return new NativeImage(w, h, false); + } +} \ No newline at end of file diff --git a/src/main/java/ru/bclib/util/Logger.java b/src/main/java/ru/bclib/util/Logger.java new file mode 100644 index 00000000..52721bac --- /dev/null +++ b/src/main/java/ru/bclib/util/Logger.java @@ -0,0 +1,62 @@ +package ru.bclib.util; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; + +public final class Logger { + private static final org.apache.logging.log4j.Logger LOGGER = LogManager.getLogger(); + private final String modPref; + + public Logger(String modID) { + this.modPref = "[" + modID + "] "; + } + + public void log(Level level, String message) { + LOGGER.log(level, modPref + message); + } + + public void log(Level level, String message, Object... params) { + LOGGER.log(level, modPref + message, params); + } + + public void debug(Object message) { + this.log(Level.DEBUG, message.toString()); + } + + public void debug(Object message, Object... params) { + this.log(Level.DEBUG, message.toString(), params); + } + + public void catching(Throwable ex) { + this.error(ex.getLocalizedMessage()); + LOGGER.catching(ex); + } + + public void info(String message) { + this.log(Level.INFO, message); + } + + public void info(String message, Object... params) { + this.log(Level.INFO, message, params); + } + + public void warning(String message, Object... params) { + this.log(Level.WARN, message, params); + } + + public void warning(String message, Object obj, Exception ex) { + LOGGER.warn(modPref + message, obj, ex); + } + + public void error(String message) { + this.log(Level.ERROR, message); + } + + public void error(String message, Object obj, Exception ex) { + LOGGER.error(modPref + message, obj, ex); + } + + public void error(String message, Exception ex) { + LOGGER.error(modPref + message, ex); + } +} diff --git a/src/main/java/ru/bclib/util/MHelper.java b/src/main/java/ru/bclib/util/MHelper.java new file mode 100644 index 00000000..b7d53913 --- /dev/null +++ b/src/main/java/ru/bclib/util/MHelper.java @@ -0,0 +1,224 @@ +package ru.bclib.util; + +import java.util.Random; + +import com.mojang.math.Vector3f; + +import net.minecraft.core.Vec3i; + +public class MHelper { + private static final Vec3i[] RANDOM_OFFSETS = new Vec3i[3 * 3 * 3 - 1]; + private static final float RAD_TO_DEG = 57.295779513082320876798154814105F; + public static final float PHI = (float) (Math.PI * (3 - Math.sqrt(5))); + public static final float PI2 = (float) (Math.PI * 2); + public static final Random RANDOM = new Random(); + + public static int randRange(int min, int max, Random random) { + return min + random.nextInt(max - min + 1); + } + + public static double randRange(double min, double max, Random random) { + return min + random.nextDouble() * (max - min); + } + + public static float randRange(float min, float max, Random random) { + return min + random.nextFloat() * (max - min); + } + + public static byte setBit(byte source, int pos, boolean value) { + return value ? setBitTrue(source, pos) : setBitFalse(source, pos); + } + + public static byte setBitTrue(byte source, int pos) { + source |= 1 << pos; + return source; + } + + public static byte setBitFalse(byte source, int pos) { + source &= ~(1 << pos); + return source; + } + + public static boolean getBit(byte source, int pos) { + return ((source >> pos) & 1) == 1; + } + + public static float wrap(float x, float side) { + return x - floor(x / side) * side; + } + + public static int floor(double x) { + return x < 0 ? (int) (x - 1) : (int) x; + } + + public static int min(int a, int b) { + return a < b ? a : b; + } + + public static int min(int a, int b, int c) { + return min(a, min(b, c)); + } + + public static int max(int a, int b) { + return a > b ? a : b; + } + + public static float min(float a, float b) { + return a < b ? a : b; + } + + public static float max(float a, float b) { + return a > b ? a : b; + } + + public static float max(float a, float b, float c) { + return max(a, max(b, c)); + } + + public static int max(int a, int b, int c) { + return max(a, max(b, c)); + } + + public static boolean isEven(int num) { + return (num & 1) == 0; + } + + public static float lengthSqr(float x, float y, float z) { + return x * x + y * y + z * z; + } + + public static double lengthSqr(double x, double y, double z) { + return x * x + y * y + z * z; + } + + public static float length(float x, float y, float z) { + return (float) Math.sqrt(lengthSqr(x, y, z)); + } + + public static double length(double x, double y, double z) { + return Math.sqrt(lengthSqr(x, y, z)); + } + + public static float lengthSqr(float x, float y) { + return x * x + y * y; + } + + public static double lengthSqr(double x, double y) { + return x * x + y * y; + } + + public static float length(float x, float y) { + return (float) Math.sqrt(lengthSqr(x, y)); + } + + public static double length(double x, double y) { + return Math.sqrt(lengthSqr(x, y)); + } + + public static float dot(float x1, float y1, float z1, float x2, float y2, float z2) { + return x1 * x2 + y1 * y2 + z1 * z2; + } + + public static float dot(float x1, float y1, float x2, float y2) { + return x1 * x2 + y1 * y2; + } + + public static int getRandom(int x, int z) { + int h = x * 374761393 + z * 668265263; + h = (h ^ (h >> 13)) * 1274126177; + return h ^ (h >> 16); + } + + public static int getSeed(int seed, int x, int y) { + int h = seed + x * 374761393 + y * 668265263; + h = (h ^ (h >> 13)) * 1274126177; + return h ^ (h >> 16); + } + + public static int getSeed(int seed, int x, int y, int z) { + int h = seed + x * 374761393 + y * 668265263 + z; + h = (h ^ (h >> 13)) * 1274126177; + return h ^ (h >> 16); + } + + public static void shuffle(T[] array, Random random) { + for (int i = 0; i < array.length; i++) { + int i2 = random.nextInt(array.length); + T element = array[i]; + array[i] = array[i2]; + array[i2] = element; + } + } + + public static int sqr(int i) { + return i * i; + } + + public static float sqr(float f) { + return f * f; + } + + public static double sqr(double d) { + return d * d; + } + + public static final float radiansToDegrees(float value) { + return value * RAD_TO_DEG; + } + + public static final float degreesToRadians(float value) { + return value / RAD_TO_DEG; + } + + public static Vector3f cross(Vector3f vec1, Vector3f vec2) + { + float cx = vec1.y() * vec2.z() - vec1.z() * vec2.y(); + float cy = vec1.z() * vec2.x() - vec1.x() * vec2.z(); + float cz = vec1.x() * vec2.y() - vec1.y() * vec2.x(); + return new Vector3f(cx, cy, cz); + } + + public static Vector3f normalize(Vector3f vec) { + float length = lengthSqr(vec.x(), vec.y(), vec.z()); + if (length > 0) { + length = (float) Math.sqrt(length); + float x = vec.x() / length; + float y = vec.y() / length; + float z = vec.z() / length; + vec.set(x, y, z); + } + return vec; + } + + public static float angle(Vector3f vec1, Vector3f vec2) { + float dot = vec1.x() * vec2.x() + vec1.y() * vec2.y() + vec1.z() * vec2.z(); + float length1 = lengthSqr(vec1.x(), vec1.y(), vec1.z()); + float length2 = lengthSqr(vec2.x(), vec2.y(), vec2.z()); + return (float) Math.acos(dot / Math.sqrt(length1 * length2)); + } + + public static Vector3f randomHorizontal(Random random) { + float angleY = randRange(0, PI2, random); + float vx = (float) Math.sin(angleY); + float vz = (float) Math.cos(angleY); + return new Vector3f(vx, 0, vz); + } + + public static Vec3i[] getOffsets(Random random) { + shuffle(RANDOM_OFFSETS, random); + return RANDOM_OFFSETS; + } + + static { + int index = 0; + for (int x = -1; x <= 1; x++) { + for (int y = -1; y <= 1; y++) { + for (int z = -1; z <= 1; z++) { + if (x != 0 || y != 0 || z != 0) { + RANDOM_OFFSETS[index++] = new Vec3i(x, y, z); + } + } + } + } + } +}