mirror of
https://github.com/GTNewHorizons/Chisel.git
synced 2026-10-26 14:24:34 +00:00
Preserve custom mipmaps and anisotropic filtering for CTM sprites
This commit is contained in:
1 parent
ec607a41ed
commit
68d65d23ed
5 files changed
+128
-98
No files matched your search
+6
-1
@@ -69,7 +69,7 @@ enableModernJavaSyntax = jabel
|
||||
|
||||
# Enables injecting missing generics into the decompiled source code for a better coding experience.
|
||||
# Turns most publicly visible List, Map, etc. into proper List<E>, Map<K, V> types.
|
||||
enableGenericInjection = false
|
||||
enableGenericInjection = true
|
||||
|
||||
# Generate a class with a String field for the mod version named as defined below.
|
||||
# If generateGradleTokenClass is empty or not missing, no such class will be generated.
|
||||
@@ -186,6 +186,11 @@ modrinthRelations =
|
||||
# Leave this empty if you don't want to publish on CurseForge.
|
||||
curseForgeProjectId =
|
||||
|
||||
# Comma-separated supported CurseForge environments: client, server (case-insensitive).
|
||||
# Use client or server for mods that only support one environment. Defaults to client,server.
|
||||
# These tags describe where the mod can run, not whether installation is required on both sides.
|
||||
curseForgeEnvironments = client,server
|
||||
|
||||
# The project's relations on CurseForge. You can use this to refer to other projects on CurseForge.
|
||||
# Syntax: type1:name1;type2:name2;...
|
||||
# Where type can be one of [requiredDependency, embeddedLibrary, optionalDependency, tool, incompatible],
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#This file is generated by updateDaemonJvm
|
||||
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/1630f7ebef05444cb27a2709ea0249b3/redirect
|
||||
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
|
||||
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/1630f7ebef05444cb27a2709ea0249b3/redirect
|
||||
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
|
||||
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/d4fd992c9557644e637ebe98263e0ae7/redirect
|
||||
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/faa12903720d410b387cc69ccafb1a74/redirect
|
||||
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/1630f7ebef05444cb27a2709ea0249b3/redirect
|
||||
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
|
||||
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/a4c09dd2e2d7079373d30e524bbc2829/redirect
|
||||
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/8e1d9ee5d0f13e442218f6884a306da1/redirect
|
||||
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/539ea72837c6244075f8ee037babc29e/redirect
|
||||
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/876dbbf00bf4ef6190f7da074cee903a/redirect
|
||||
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/539ea72837c6244075f8ee037babc29e/redirect
|
||||
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/876dbbf00bf4ef6190f7da074cee903a/redirect
|
||||
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/9064f22734b42ac03e85df04b632e167/redirect
|
||||
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/4d0277c49991306ac78048580fe34bcf/redirect
|
||||
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/539ea72837c6244075f8ee037babc29e/redirect
|
||||
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/876dbbf00bf4ef6190f7da074cee903a/redirect
|
||||
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/a9fa3b809e484a1e1ac4308a5dda661a/redirect
|
||||
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/aa99913fa6d767d899ffc96082da01ca/redirect
|
||||
toolchainVersion=25
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ pluginManagement {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'com.gtnewhorizons.gtnhsettingsconvention' version '2.0.24'
|
||||
id 'com.gtnewhorizons.gtnhsettingsconvention' version '2.0.32'
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package team.chisel.ctmlib;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -15,14 +16,16 @@ import net.minecraft.client.renderer.texture.TextureMap;
|
||||
import net.minecraft.client.resources.IResource;
|
||||
import net.minecraft.client.resources.IResourceManager;
|
||||
import net.minecraft.client.resources.data.AnimationMetadataSection;
|
||||
import net.minecraft.client.resources.data.TextureMetadataSection;
|
||||
import net.minecraft.util.IIcon;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraftforge.client.event.TextureStitchEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.github.bsideup.jabel.Desugar;
|
||||
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
@@ -35,17 +38,13 @@ import team.chisel.Chisel;
|
||||
*/
|
||||
public class TextureSubmap implements IIcon, ISubmap {
|
||||
|
||||
private static List<TextureSubmap> submaps = Lists.newArrayList();
|
||||
private static TextureSubmap dummy = new TextureSubmap(null, 0, 0);
|
||||
static {
|
||||
MinecraftForge.EVENT_BUS.register(dummy);
|
||||
}
|
||||
private static final List<TextureSubmap> submaps = new ArrayList<>();
|
||||
|
||||
private int width, height;
|
||||
@Delegate
|
||||
private IIcon baseIcon;
|
||||
|
||||
protected IIcon[][] icons;
|
||||
private final IIcon baseIcon;
|
||||
private final int width;
|
||||
private final int height;
|
||||
protected final IIcon[][] icons;
|
||||
|
||||
/**
|
||||
* Construct a new square submap.
|
||||
@@ -110,23 +109,26 @@ public class TextureSubmap implements IIcon, ISubmap {
|
||||
|
||||
/* ==== Internal Stitching Logic ==== */
|
||||
|
||||
/**
|
||||
* Registers each submap cell as an independent atlas sprite so filtered sampling cannot bleed into adjacent
|
||||
* cells of the source texture sheet.
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public final void onTextureStitchPre(TextureStitchEvent.Pre event) {
|
||||
if (event.map.getTextureType() != 0) return;
|
||||
TextureSubmapSprite.clearSourceCache();
|
||||
for (TextureSubmap ts : submaps) {
|
||||
ts.registerSubIcons(event.map);
|
||||
}
|
||||
submaps.clear();
|
||||
static {
|
||||
MinecraftForge.EVENT_BUS.register(new StitchHandler());
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public final void onTextureStitchPost(TextureStitchEvent.Post event) {
|
||||
TextureSubmapSprite.clearSourceCache();
|
||||
private static final class StitchHandler {
|
||||
|
||||
@SubscribeEvent
|
||||
public void onTextureStitchPre(TextureStitchEvent.Pre event) {
|
||||
if (event.map.getTextureType() != 0) return;
|
||||
TextureSubmapSprite.clearSourceCache();
|
||||
for (TextureSubmap submap : submaps) {
|
||||
submap.registerSubIcons(event.map);
|
||||
}
|
||||
submaps.clear();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onTextureStitchPost(TextureStitchEvent.Post event) {
|
||||
TextureSubmapSprite.clearSourceCache();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,19 +144,11 @@ public class TextureSubmap implements IIcon, ISubmap {
|
||||
for (int y = 0; y < height; y++) {
|
||||
String name = getSubIconName(sourceIcon, width, height, x, y);
|
||||
TextureAtlasSprite registered = textureMap.getTextureExtry(name);
|
||||
|
||||
if (!(registered instanceof TextureSubmapSprite)) {
|
||||
TextureSubmapSprite sprite = new TextureSubmapSprite(name, sourceIcon, width, height, x, y);
|
||||
if (textureMap.setTextureEntry(name, sprite)) {
|
||||
registered = sprite;
|
||||
} else {
|
||||
registered = textureMap.getTextureExtry(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (registered instanceof TextureSubmapSprite) {
|
||||
icons[x][y] = registered;
|
||||
if (registered == null) {
|
||||
registered = new TextureSubmapSprite(name, sourceIcon, width, height, x, y);
|
||||
textureMap.setTextureEntry(name, registered);
|
||||
}
|
||||
icons[x][y] = registered;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,20 +167,24 @@ public class TextureSubmap implements IIcon, ISubmap {
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
private static class TextureSubmapSprite extends TextureAtlasSprite {
|
||||
private static final class TextureSubmapSprite extends TextureAtlasSprite {
|
||||
|
||||
private static final Map<ResourceLocation, SourceData> sourceCache = new ConcurrentHashMap<>();
|
||||
@Desugar
|
||||
private record SourceTexture(BufferedImage[] images, AnimationMetadataSection animation) {}
|
||||
|
||||
private final ResourceLocation sourceIcon;
|
||||
private static final Map<ResourceLocation, SourceTexture> sourceCache = new ConcurrentHashMap<>();
|
||||
|
||||
private final ResourceLocation sourceLocation;
|
||||
private final int columns;
|
||||
private final int rows;
|
||||
private final int cellX;
|
||||
private final int cellY;
|
||||
|
||||
private TextureSubmapSprite(String name, ResourceLocation sourceIcon, int columns, int rows, int cellX,
|
||||
private TextureSubmapSprite(String name, ResourceLocation sourceLocation, int columns, int rows, int cellX,
|
||||
int cellY) {
|
||||
|
||||
super(name);
|
||||
this.sourceIcon = sourceIcon;
|
||||
this.sourceLocation = sourceLocation;
|
||||
this.columns = columns;
|
||||
this.rows = rows;
|
||||
this.cellX = cellX;
|
||||
@@ -200,55 +198,97 @@ public class TextureSubmap implements IIcon, ISubmap {
|
||||
|
||||
@Override
|
||||
public boolean load(IResourceManager manager, ResourceLocation location) {
|
||||
ResourceLocation source = getSourceResource();
|
||||
|
||||
try {
|
||||
SourceData sourceData = getSourceData(manager, source);
|
||||
BufferedImage subImage = extractSubImage(sourceData.image, sourceData.animation != null);
|
||||
int mipmapLevels = Minecraft.getMinecraft().gameSettings.mipmapLevels;
|
||||
boolean useAnisotropicFiltering = Minecraft.getMinecraft().gameSettings.anisotropicFiltering > 1;
|
||||
|
||||
// The source animation metadata can be reused: frame indices and timings stay the same,
|
||||
// only the pixels of each frame are cropped to this submap cell.
|
||||
BufferedImage[] images = new BufferedImage[Minecraft.getMinecraft().gameSettings.mipmapLevels + 1];
|
||||
images[0] = subImage;
|
||||
loadSprite(images, sourceData.animation, false);
|
||||
SourceTexture sourceTexture = getSourceTexture(manager, sourceLocation, mipmapLevels);
|
||||
BufferedImage[] images = new BufferedImage[sourceTexture.images().length];
|
||||
boolean animated = sourceTexture.animation() != null;
|
||||
|
||||
for (int level = 0; level < images.length; level++) {
|
||||
BufferedImage sourceImage = sourceTexture.images()[level];
|
||||
if (sourceImage == null) continue;
|
||||
// A mip level can be too small to split into the submap grid
|
||||
if (sourceImage.getWidth() < columns) continue;
|
||||
images[level] = extractSubImage(sourceImage, animated);
|
||||
}
|
||||
|
||||
loadSprite(images, sourceTexture.animation(), useAnisotropicFiltering);
|
||||
return false;
|
||||
} catch (IOException | RuntimeException e) {
|
||||
Chisel.logger.warn("Unable to create submap sprite {} from {}", getIconName(), source, e);
|
||||
Chisel.logger.warn("Unable to create submap sprite {} from {}", getIconName(), sourceLocation, e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static SourceData getSourceData(IResourceManager manager, ResourceLocation source) throws IOException {
|
||||
SourceData sourceData = sourceCache.get(source);
|
||||
if (sourceData != null) {
|
||||
return sourceData;
|
||||
private static SourceTexture getSourceTexture(IResourceManager manager, ResourceLocation sourceLocation,
|
||||
int mipmapLevels) throws IOException {
|
||||
|
||||
SourceTexture cached = sourceCache.get(sourceLocation);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
IResource resource = manager.getResource(source);
|
||||
ResourceLocation location = getSourceResource(sourceLocation, 0);
|
||||
IResource resource = manager.getResource(location);
|
||||
|
||||
BufferedImage[] images = new BufferedImage[mipmapLevels + 1];
|
||||
images[0] = readImage(resource, location);
|
||||
|
||||
TextureMetadataSection textureMetadata = (TextureMetadataSection) resource.getMetadata("texture");
|
||||
if (textureMetadata != null && !textureMetadata.getListMipmaps()
|
||||
.isEmpty()) {
|
||||
|
||||
int width = images[0].getWidth();
|
||||
int height = images[0].getHeight();
|
||||
|
||||
if (MathHelper.roundUpToPowerOfTwo(width) != width
|
||||
|| MathHelper.roundUpToPowerOfTwo(height) != height) {
|
||||
throw new RuntimeException("Unable to load extra miplevels, source texture is not power of two");
|
||||
}
|
||||
|
||||
for (int mipLevel : textureMetadata.getListMipmaps()) {
|
||||
if (mipLevel <= 0 || mipLevel >= images.length || images[mipLevel] != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ResourceLocation mipLocation = getSourceResource(sourceLocation, mipLevel);
|
||||
try {
|
||||
images[mipLevel] = readImage(manager.getResource(mipLocation), mipLocation);
|
||||
} catch (IOException e) {
|
||||
Chisel.logger.warn("Unable to load mip level {} from {}", mipLevel, mipLocation, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnimationMetadataSection animation = (AnimationMetadataSection) resource.getMetadata("animation");
|
||||
SourceTexture sourceTexture = new SourceTexture(images, animation);
|
||||
|
||||
SourceTexture existing = sourceCache.putIfAbsent(sourceLocation, sourceTexture);
|
||||
return existing != null ? existing : sourceTexture;
|
||||
}
|
||||
|
||||
private static BufferedImage readImage(IResource resource, ResourceLocation location) throws IOException {
|
||||
BufferedImage image;
|
||||
try (InputStream stream = resource.getInputStream()) {
|
||||
image = ImageIO.read(stream);
|
||||
}
|
||||
|
||||
if (image == null) {
|
||||
throw new IOException("ImageIO could not decode " + source);
|
||||
throw new IOException("ImageIO could not decode " + location);
|
||||
}
|
||||
|
||||
AnimationMetadataSection animation = (AnimationMetadataSection) resource.getMetadata("animation");
|
||||
sourceData = new SourceData(image, animation);
|
||||
|
||||
SourceData cached = sourceCache.putIfAbsent(source, sourceData);
|
||||
return cached == null ? sourceData : cached;
|
||||
return image;
|
||||
}
|
||||
|
||||
private static void clearSourceCache() {
|
||||
sourceCache.clear();
|
||||
}
|
||||
|
||||
private ResourceLocation getSourceResource() {
|
||||
return new ResourceLocation(
|
||||
sourceIcon.getResourceDomain(),
|
||||
"textures/blocks/" + sourceIcon.getResourcePath() + ".png");
|
||||
private static ResourceLocation getSourceResource(ResourceLocation sourceLocation, int mipLevel) {
|
||||
String domain = sourceLocation.getResourceDomain();
|
||||
String path = sourceLocation.getResourcePath();
|
||||
if (mipLevel == 0) return new ResourceLocation(domain, "textures/blocks/" + path + ".png");
|
||||
return new ResourceLocation(domain, "textures/blocks/mipmaps/" + path + "." + mipLevel + ".png");
|
||||
}
|
||||
|
||||
private BufferedImage extractSubImage(BufferedImage source, boolean animated) {
|
||||
@@ -272,12 +312,8 @@ public class TextureSubmap implements IIcon, ISubmap {
|
||||
|
||||
int cellWidth = frameSize / columns;
|
||||
int cellHeight = frameSize / rows;
|
||||
if (cellWidth != cellHeight) {
|
||||
throw new IllegalArgumentException(
|
||||
"Submap cell is not square: " + cellWidth + "x" + cellHeight + " (" + columns + "x" + rows + ")");
|
||||
}
|
||||
|
||||
int frameCount = animated ? sourceHeight / frameSize : 1;
|
||||
|
||||
BufferedImage result = new BufferedImage(cellWidth, cellHeight * frameCount, BufferedImage.TYPE_INT_ARGB);
|
||||
int[] pixels = new int[cellWidth * cellHeight];
|
||||
|
||||
@@ -290,16 +326,5 @@ public class TextureSubmap implements IIcon, ISubmap {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static class SourceData {
|
||||
|
||||
private final BufferedImage image;
|
||||
private final AnimationMetadataSection animation;
|
||||
|
||||
private SourceData(BufferedImage image, AnimationMetadataSection animation) {
|
||||
this.image = image;
|
||||
this.animation = animation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user