package ar.com.companeros.identity; import java.util.Arrays; /** Compositor de píxeles compartido por el renderizador y las imágenes de presentación. */ public final class HostSkinAtlas { private HostSkinAtlas() { } // La piel de jugador conserva sus coordenadas originales dentro del atlas 128×128. public static int[] compose(int[] skinArgb, int height, int stage) { if (skinArgb == null || skinArgb.length != 64 * height || (height != 32 && height != 64) || stage < 0 || stage > 4) throw new IllegalArgumentException("Skin o etapa inválida"); int[] skin = new int[64 * 64]; System.arraycopy(skinArgb, 0, skin, 0, skinArgb.length); if (height == 32) { mirrorLimb(skin, 40, 16, 32, 48); mirrorLimb(skin, 0, 16, 16, 48); boolean opaqueHat = true; for (int y = 0; y < 16; y++) for (int x = 32; x < 64; x++) if ((skin[y * 64 + x] >>> 24) < 128) opaqueHat = false; if (opaqueHat) for (int y = 0; y < 16; y++) for (int x = 32; x < 64; x++) skin[y * 64 + x] &= 0xFFFFFF; } else if (slim(skin)) { widenArm(skin, 40, 16); widenArm(skin, 40, 32); widenArm(skin, 32, 48); widenArm(skin, 48, 48); } // La segunda capa se proyecta sobre el mismo cuerpo: conserva pelo, gorros y ropa. flatten(skin, 32, 0, 0, 0, 32, 16); if (height == 64) { flatten(skin, 16, 32, 16, 16, 24, 16); flatten(skin, 40, 32, 40, 16, 16, 16); flatten(skin, 0, 32, 0, 16, 16, 16); flatten(skin, 48, 48, 32, 48, 16, 16); flatten(skin, 0, 48, 16, 48, 16, 16); } int[] atlas = new int[128 * 128]; int skinTone = averageFace(skin); Arrays.fill(atlas, skinTone); for (int y = 0; y < 64; y++) System.arraycopy(skin, y * 64, atlas, y * 128, 64); // La boca y los dientes ocupan UV independientes; nunca pintan cavidades sobre la cara. fill(atlas, 96, 96, 16, 16, 0xFF271A18); fill(atlas, 112, 96, 16, 16, 0xFFD6CDB7); metal(atlas, 64, 64, 0xFF26212B, 0xFF56314F); metal(atlas, 96, 64, 0xFF25AAB4, 0xFF98F2E8); metal(atlas, 64, 96, 0xFF737F88, 0xFFCFD7D8); metal(atlas, 64, 32, 0xFF94512C, 0xFFCE8653); if (stage > 0) { // Heridas propias sobre los UV originales: bordes oscuros, tejido rojo y pequeñas escurridas. scar(atlas, 14, 10, stage + 1, 1, stage); // mejilla, conserva ojos y pelo scar(atlas, 44, 23, 3 + stage, 1, stage); scar(atlas, 37, 54, 2 + stage, -1, stage); scar(atlas, 24, 23, stage + 1, 1, stage); if (stage >= 2) { scar(atlas, 10, 13, 2, 1, stage); scar(atlas, 5, 25, 3, 1, stage); } if (stage >= 3) { scar(atlas, 26, 21, 5, -1, stage); scar(atlas, 22, 55, 4, 1, stage); } } // PIEL EXPUESTA: net propio para antebrazos y cuello, sin alterar ropa ni skin original. // Una tapa de cubo intermedia no puede reutilizar el hombro vestido de vanilla. exposedArm(atlas, 40, 16, 64); exposedArm(atlas, 32, 48, 80); for (int y = 0; y < 8; y++) for (int x = 0; x < 16; x++) atlas[(12 + y) * 128 + 64 + x] = atlas[(28 + y % 4) * 128 + 44 + x % 4]; return atlas; } // NET 4×6×4: paredes del antebrazo, tapa de codo y cap original de la mano en la muñeca. private static void exposedArm(int[] atlas, int sourceU, int sourceV, int targetU) { for (int side = 0; side < 4; side++) copyPatch(atlas, sourceU + side * 4, sourceV + 10, targetU + side * 4, 4, 4, 6); copyPatch(atlas, sourceU + 4, sourceV + 12, targetU + 4, 0, 4, 4); copyPatch(atlas, sourceU + 8, sourceV, targetU + 8, 0, 4, 4); } private static void copyPatch(int[] atlas, int sx, int sy, int dx, int dy, int width, int height) { for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) atlas[(dy + y) * 128 + dx + x] = atlas[(sy + y) * 128 + sx + x]; } private static void scar(int[] atlas, int x, int y, int length, int slope, int stage) { for (int i = 0; i < length; i++) { int px = x + (i / 3) * slope, py = y + i; int center = py * 128 + px; atlas[center] = (i & 1) == 0 ? 0xFF9B2524 : 0xFF651717; if (stage >= 2 && px > 0) atlas[center - 1] = blend(atlas[center - 1], 0xFF4F2020, .65); if (stage >= 3) atlas[center + 1] = blend(atlas[center + 1], 0xFFCA5544, .65); } } private static void metal(int[] atlas, int u, int v, int base, int edge) { for (int y = 0; y < 32; y++) for (int x = 0; x < 32; x++) { double highlight = x % 8 == 0 || y % 8 == 0 ? .65 : (x + y) % 11 == 0 ? .22 : .06; atlas[(v + y) * 128 + u + x] = blend(base, edge, highlight); } } private static boolean slim(int[] skin) { for (int y = 20; y < 32; y++) if ((skin[y * 64 + 54] >>> 24) != 0 || (skin[y * 64 + 55] >>> 24) != 0) return false; return (skin[20 * 64 + 44] >>> 24) != 0; } private static void widenArm(int[] skin, int u, int v) { int[] before = skin.clone(); copyFace(before, skin, u + 4, v, 3, 4, u + 4, v, 4, 4, false); copyFace(before, skin, u + 7, v, 3, 4, u + 8, v, 4, 4, false); copyFace(before, skin, u, v + 4, 4, 12, u, v + 4, 4, 12, false); copyFace(before, skin, u + 4, v + 4, 3, 12, u + 4, v + 4, 4, 12, false); copyFace(before, skin, u + 7, v + 4, 4, 12, u + 8, v + 4, 4, 12, false); copyFace(before, skin, u + 11, v + 4, 3, 12, u + 12, v + 4, 4, 12, false); } private static void mirrorLimb(int[] skin, int fromU, int fromV, int toU, int toV) { int[] before = skin.clone(); for (int side = 0; side < 4; side++) copyFace(before, skin, fromU + side * 4, fromV + 4, 4, 12, toU + side * 4, toV + 4, 4, 12, true); copyFace(before, skin, fromU + 4, fromV, 4, 4, toU + 4, toV, 4, 4, true); copyFace(before, skin, fromU + 8, fromV, 4, 4, toU + 8, toV, 4, 4, true); } private static void copyFace(int[] source, int[] destination, int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh, boolean mirror) { for (int y = 0; y < dh; y++) for (int x = 0; x < dw; x++) { int sampleX = x * sw / dw; if (mirror) sampleX = sw - sampleX - 1; destination[(dy + y) * 64 + dx + x] = source[(sy + y * sh / dh) * 64 + sx + sampleX]; } } private static void flatten(int[] skin, int sx, int sy, int dx, int dy, int width, int height) { int[] before = skin.clone(); for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) { int top = before[(sy + y) * 64 + sx + x]; int bottom = before[(dy + y) * 64 + dx + x]; if ((top >>> 24) != 0) skin[(dy + y) * 64 + dx + x] = blend(bottom, top, (top >>> 24) / 255.0); } } private static int averageFace(int[] skin) { long red = 0, green = 0, blue = 0; int count = 0; for (int y = 12; y < 15; y++) for (int x = 9; x < 15; x++) { int pixel = skin[y * 64 + x]; if ((pixel >>> 24) == 0) continue; red += (pixel >>> 16) & 255; green += (pixel >>> 8) & 255; blue += pixel & 255; count++; } return count == 0 ? 0xFFC58E73 : 0xFF000000 | (int)(red / count) << 16 | (int)(green / count) << 8 | (int)(blue / count); } private static int blend(int bottom, int top, double amount) { int r = (int)(((bottom >>> 16) & 255) * (1 - amount) + ((top >>> 16) & 255) * amount); int g = (int)(((bottom >>> 8) & 255) * (1 - amount) + ((top >>> 8) & 255) * amount); int b = (int)((bottom & 255) * (1 - amount) + (top & 255) * amount); return 0xFF000000 | r << 16 | g << 8 | b; } private static void fill(int[] image, int x, int y, int width, int height, int color) { for (int row = y; row < y + height; row++) Arrays.fill(image, row * 128 + x, row * 128 + x + width, color); } }