Remove build artifacts, dev tool configs, unused dependencies, and third-party source dumps. Add proper README, update .gitignore, clean up Makefile.
385 lines
11 KiB
Java
385 lines
11 KiB
Java
package com.tiedup.remake.client.texture;
|
|
|
|
import com.mojang.blaze3d.platform.NativeImage;
|
|
import com.mojang.blaze3d.systems.RenderSystem;
|
|
import com.tiedup.remake.core.TiedUpMod;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import net.minecraft.client.renderer.texture.DynamicTexture;
|
|
import net.minecraftforge.api.distmarker.Dist;
|
|
import net.minecraftforge.api.distmarker.OnlyIn;
|
|
|
|
/**
|
|
* Handles a dynamically loaded online texture for clothes.
|
|
* Supports standard skins (64x32/64x64) and scales to proper format.
|
|
*
|
|
* Creates multiple texture variants:
|
|
* - Base texture: Original image as uploaded
|
|
* - Clothes texture: Resized for 64x64 format if needed (old 64x32 format)
|
|
* - Full-skin texture: For full-skin mode (covers entire player)
|
|
*
|
|
* Thread-safe: Texture uploads are scheduled on render thread.
|
|
*/
|
|
@OnlyIn(Dist.CLIENT)
|
|
public class DynamicOnlineTexture {
|
|
|
|
private static final int MAX_DIMENSION = 1280;
|
|
|
|
// Texture objects managed by Minecraft's texture manager
|
|
private DynamicTexture baseTexture;
|
|
private DynamicTexture clothesTexture;
|
|
private DynamicTexture fullSkinTexture;
|
|
|
|
private boolean needsResize = false;
|
|
private boolean valid = false;
|
|
|
|
private final String sourceUrl;
|
|
|
|
/**
|
|
* Create a new dynamic texture from a downloaded image.
|
|
*
|
|
* @param image The downloaded image
|
|
* @param sourceUrl The source URL (for logging)
|
|
*/
|
|
public DynamicOnlineTexture(NativeImage image, String sourceUrl) {
|
|
this.sourceUrl = sourceUrl;
|
|
if (image != null) {
|
|
this.valid = processImage(image);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process the downloaded image and create texture variants.
|
|
*
|
|
* @param image The source image
|
|
* @return true if processing succeeded
|
|
*/
|
|
private boolean processImage(NativeImage image) {
|
|
int width = image.getWidth();
|
|
int height = image.getHeight();
|
|
|
|
// Validate dimensions
|
|
if (width == 0 || height == 0) {
|
|
TiedUpMod.LOGGER.warn(
|
|
"[DynamicOnlineTexture] Invalid image dimensions: {}x{} from {}",
|
|
width,
|
|
height,
|
|
sourceUrl
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// Check for valid skin format (must be divisible by 64 width, 32 height)
|
|
if (height % 32 != 0 || width % 64 != 0) {
|
|
TiedUpMod.LOGGER.warn(
|
|
"[DynamicOnlineTexture] Invalid skin dimensions: {}x{} (must be 64x32 or 64x64 multiples) from {}",
|
|
width,
|
|
height,
|
|
sourceUrl
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// Check maximum size
|
|
if (width > MAX_DIMENSION || height > MAX_DIMENSION) {
|
|
TiedUpMod.LOGGER.warn(
|
|
"[DynamicOnlineTexture] Image too large: {}x{} (max {}) from {}",
|
|
width,
|
|
height,
|
|
MAX_DIMENSION,
|
|
sourceUrl
|
|
);
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
// Create base texture (original image)
|
|
NativeImage baseImage = copyImage(image);
|
|
|
|
// Create clothes texture (resized if needed for old 64x32 format)
|
|
NativeImage clothesImage;
|
|
if (height * 2 == width) {
|
|
// New format (64x64), use as-is
|
|
clothesImage = copyImage(image);
|
|
} else {
|
|
// Old format (64x32), needs conversion to 64x64
|
|
needsResize = true;
|
|
clothesImage = convertOldFormat(image);
|
|
}
|
|
|
|
// Create full-skin texture (square format for full coverage)
|
|
NativeImage fullSkinImage = createFullSkinImage(
|
|
clothesImage,
|
|
width
|
|
);
|
|
|
|
// Schedule texture upload on render thread
|
|
RenderSystem.recordRenderCall(() -> {
|
|
this.baseTexture = new DynamicTexture(baseImage);
|
|
this.clothesTexture = needsResize
|
|
? new DynamicTexture(clothesImage)
|
|
: this.baseTexture;
|
|
this.fullSkinTexture = new DynamicTexture(fullSkinImage);
|
|
});
|
|
|
|
return true;
|
|
} catch (Exception e) {
|
|
TiedUpMod.LOGGER.error(
|
|
"[DynamicOnlineTexture] Failed to process image from {}: {}",
|
|
sourceUrl,
|
|
e.getMessage()
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a copy of a NativeImage.
|
|
*/
|
|
private NativeImage copyImage(NativeImage source) {
|
|
NativeImage copy = new NativeImage(
|
|
source.format(),
|
|
source.getWidth(),
|
|
source.getHeight(),
|
|
false
|
|
);
|
|
copy.copyFrom(source);
|
|
return copy;
|
|
}
|
|
|
|
/**
|
|
* Convert old 64x32 format to modern 64x64 format.
|
|
* This copies the arm and leg textures to the correct locations.
|
|
*/
|
|
private NativeImage convertOldFormat(NativeImage source) {
|
|
int width = source.getWidth();
|
|
int height = source.getHeight();
|
|
int scale = width / 64;
|
|
|
|
// Create new 64x64 (scaled) image
|
|
NativeImage result = new NativeImage(
|
|
source.format(),
|
|
width,
|
|
width,
|
|
false
|
|
);
|
|
|
|
// Copy top half (head, body, arms - same as before)
|
|
for (int x = 0; x < width; x++) {
|
|
for (int y = 0; y < height; y++) {
|
|
result.setPixelRGBA(x, y, source.getPixelRGBA(x, y));
|
|
}
|
|
}
|
|
|
|
// Copy left arm from right arm location (mirrored)
|
|
// Old format has arms in row 2, col 10 (44-47, 16-32)
|
|
// New format left arm goes to row 3-4, col 8 (32-48, 48-64)
|
|
copyRegion(
|
|
source,
|
|
result,
|
|
44 * scale,
|
|
16 * scale,
|
|
32 * scale,
|
|
48 * scale,
|
|
4 * scale,
|
|
16 * scale
|
|
);
|
|
|
|
// Copy left leg from right leg location (mirrored)
|
|
// Old format has leg in row 0-1 (0-16, 16-32)
|
|
// New format left leg goes to row 3-4, col 4 (16-32, 48-64)
|
|
copyRegion(
|
|
source,
|
|
result,
|
|
0 * scale,
|
|
16 * scale,
|
|
16 * scale,
|
|
48 * scale,
|
|
16 * scale,
|
|
16 * scale
|
|
);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Copy a region from source to destination image.
|
|
*/
|
|
private void copyRegion(
|
|
NativeImage src,
|
|
NativeImage dst,
|
|
int srcX,
|
|
int srcY,
|
|
int dstX,
|
|
int dstY,
|
|
int width,
|
|
int height
|
|
) {
|
|
for (int x = 0; x < width; x++) {
|
|
for (int y = 0; y < height; y++) {
|
|
if (
|
|
srcX + x < src.getWidth() &&
|
|
srcY + y < src.getHeight() &&
|
|
dstX + x < dst.getWidth() &&
|
|
dstY + y < dst.getHeight()
|
|
) {
|
|
dst.setPixelRGBA(
|
|
dstX + x,
|
|
dstY + y,
|
|
src.getPixelRGBA(srcX + x, srcY + y)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create full-skin image (square format, opaque in key areas).
|
|
*/
|
|
private NativeImage createFullSkinImage(NativeImage source, int width) {
|
|
NativeImage fullSkin = new NativeImage(
|
|
source.format(),
|
|
width,
|
|
width,
|
|
false
|
|
);
|
|
fullSkin.copyFrom(source);
|
|
|
|
// Make key areas fully opaque for full-skin mode
|
|
int quarter = width / 4;
|
|
setAreaOpaque(fullSkin, 0, 0, quarter, quarter);
|
|
setAreaOpaque(fullSkin, 0, quarter, width, quarter * 2);
|
|
setAreaOpaque(fullSkin, quarter, quarter * 3, quarter * 3, width);
|
|
|
|
return fullSkin;
|
|
}
|
|
|
|
/**
|
|
* Make an area of the image fully opaque.
|
|
*/
|
|
private void setAreaOpaque(
|
|
NativeImage image,
|
|
int x1,
|
|
int y1,
|
|
int x2,
|
|
int y2
|
|
) {
|
|
for (int x = x1; x < x2 && x < image.getWidth(); x++) {
|
|
for (int y = y1; y < y2 && y < image.getHeight(); y++) {
|
|
int pixel = image.getPixelRGBA(x, y);
|
|
// Set alpha to 255 (fully opaque)
|
|
pixel |= 0xFF000000;
|
|
image.setPixelRGBA(x, y, pixel);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the base texture ID (original image).
|
|
*/
|
|
public int getBaseTextureId() {
|
|
return baseTexture != null ? baseTexture.getId() : -1;
|
|
}
|
|
|
|
/**
|
|
* Get the clothes texture ID (resized for modern format).
|
|
*/
|
|
public int getClothesTextureId() {
|
|
return clothesTexture != null
|
|
? clothesTexture.getId()
|
|
: getBaseTextureId();
|
|
}
|
|
|
|
/**
|
|
* Get the full-skin texture ID.
|
|
*/
|
|
public int getFullSkinTextureId() {
|
|
return fullSkinTexture != null ? fullSkinTexture.getId() : -1;
|
|
}
|
|
|
|
/**
|
|
* Bind the clothes texture for rendering.
|
|
*/
|
|
public void bindClothes() {
|
|
int id = getClothesTextureId();
|
|
if (id != -1) {
|
|
RenderSystem.setShaderTexture(0, id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bind the full-skin texture for rendering.
|
|
*/
|
|
public void bindFullSkin() {
|
|
int id = getFullSkinTextureId();
|
|
if (id != -1) {
|
|
RenderSystem.setShaderTexture(0, id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bind the base texture for rendering.
|
|
*/
|
|
public void bindBase() {
|
|
int id = getBaseTextureId();
|
|
if (id != -1) {
|
|
RenderSystem.setShaderTexture(0, id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the texture was successfully loaded.
|
|
*/
|
|
public boolean isValid() {
|
|
return valid;
|
|
}
|
|
|
|
/**
|
|
* Check if the image needed resizing (old 64x32 format).
|
|
*/
|
|
public boolean wasResized() {
|
|
return needsResize;
|
|
}
|
|
|
|
/**
|
|
* Get the source URL for debugging.
|
|
*/
|
|
public String getSourceUrl() {
|
|
return sourceUrl;
|
|
}
|
|
|
|
/**
|
|
* Release texture resources.
|
|
*/
|
|
public void close() {
|
|
if (baseTexture != null) {
|
|
baseTexture.close();
|
|
baseTexture = null;
|
|
}
|
|
if (clothesTexture != null && clothesTexture != baseTexture) {
|
|
clothesTexture.close();
|
|
clothesTexture = null;
|
|
}
|
|
if (fullSkinTexture != null) {
|
|
fullSkinTexture.close();
|
|
fullSkinTexture = null;
|
|
}
|
|
valid = false;
|
|
}
|
|
|
|
/**
|
|
* Get the DynamicTexture for clothes (for ResourceLocation registration).
|
|
*/
|
|
@Nullable
|
|
public DynamicTexture getClothesTexture() {
|
|
return clothesTexture;
|
|
}
|
|
|
|
/**
|
|
* Get the DynamicTexture for full-skin mode.
|
|
*/
|
|
@Nullable
|
|
public DynamicTexture getFullSkinTexture() {
|
|
return fullSkinTexture;
|
|
}
|
|
}
|