diff --git a/src/main/java/com/cricketcraft/chisel/api/rendering/TextureType.java b/src/main/java/com/cricketcraft/chisel/api/rendering/TextureType.java index c4f1462b..b6cd6744 100644 --- a/src/main/java/com/cricketcraft/chisel/api/rendering/TextureType.java +++ b/src/main/java/com/cricketcraft/chisel/api/rendering/TextureType.java @@ -31,6 +31,7 @@ import team.chisel.ctmlib.ISubmapManager; import team.chisel.ctmlib.RenderBlocksCTM; import team.chisel.ctmlib.RenderBlocksColumn; import team.chisel.ctmlib.TextureSubmap; +import team.chisel.ctmlib.TextureSubmapCTM; /** * Handles all default {@link ISubmapManager} behavior @@ -197,8 +198,8 @@ public enum TextureType { IIcon baseIcon = register.registerIcon(modName + ":" + texturePath); return Triple.of( baseIcon, - new TextureSubmap(register.registerIcon(modName + ":" + texturePath + "-ctm"), 4, 4), - new TextureSubmap(baseIcon, 2, 2) + new TextureSubmapCTM(register.registerIcon(modName + ":" + texturePath + "-ctm"), 4), + new TextureSubmapCTM(baseIcon, 2) ); } diff --git a/src/main/java/com/cricketcraft/chisel/mixins/MixinFallbackResourceManager.java b/src/main/java/com/cricketcraft/chisel/mixins/MixinFallbackResourceManager.java index 3457cc0b..2a0c5b11 100644 --- a/src/main/java/com/cricketcraft/chisel/mixins/MixinFallbackResourceManager.java +++ b/src/main/java/com/cricketcraft/chisel/mixins/MixinFallbackResourceManager.java @@ -19,8 +19,8 @@ import team.chisel.ctmlib.TextureSubmapResource; /** * Provides generated resources for independently stitched CTM submap sprites. *

- * For example, a request for chisel:textures/blocks/__chisel_submap/4x4/2_1/foo.png is resolved from - * chisel:textures/blocks/foo.png and cropped to the requested cell. This lets Minecraft and other mods treat each + * For example, a request for chisel:textures/blocks/__chisel_submap/2x2/1_0/foo.png is resolved from + * chisel:textures/blocks/foo.png and cropped to its top-right tile. This lets Minecraft and other mods treat each * submap sprite like an ordinary texture resource. */ @Mixin(FallbackResourceManager.class) @@ -54,6 +54,7 @@ public abstract class MixinFallbackResourceManager { List resources = new ArrayList<>(sources.size()); for (IResource source : sources) { + // Different resource packs may provide different images for the same location, so we can't cache them resources.add(new TextureSubmapResource(source, submap, false)); } diff --git a/src/main/java/team/chisel/client/render/SubmapManagerAntiblock.java b/src/main/java/team/chisel/client/render/SubmapManagerAntiblock.java index 0ed5f3e6..9351e908 100644 --- a/src/main/java/team/chisel/client/render/SubmapManagerAntiblock.java +++ b/src/main/java/team/chisel/client/render/SubmapManagerAntiblock.java @@ -16,6 +16,7 @@ import cpw.mods.fml.relauncher.SideOnly; import team.chisel.ctmlib.Drawing; import team.chisel.ctmlib.RenderBlocksCTM; import team.chisel.ctmlib.TextureSubmap; +import team.chisel.ctmlib.TextureSubmapCTM; import team.chisel.init.ChiselBlocks; public class SubmapManagerAntiblock extends SubmapManagerBase { @@ -93,8 +94,8 @@ public class SubmapManagerAntiblock extends SubmapManagerBase { @Override @SideOnly(Side.CLIENT) public void registerIcons(String modName, Block block, IIconRegister register) { - submap = new TextureSubmap(register.registerIcon(modName + ":antiblock/" + color + "-antiBlock-ctm"), 4, 4); - submapSmall = new TextureSubmap(register.registerIcon(modName + ":antiblock/" + color + "-antiBlock"), 2, 2); + submap = new TextureSubmapCTM(register.registerIcon(modName + ":antiblock/" + color + "-antiBlock-ctm"), 4); + submapSmall = new TextureSubmapCTM(register.registerIcon(modName + ":antiblock/" + color + "-antiBlock"), 2); } @Override diff --git a/src/main/java/team/chisel/client/render/SubmapManagerCarpetFloor.java b/src/main/java/team/chisel/client/render/SubmapManagerCarpetFloor.java index 889c7ede..3320ed49 100644 --- a/src/main/java/team/chisel/client/render/SubmapManagerCarpetFloor.java +++ b/src/main/java/team/chisel/client/render/SubmapManagerCarpetFloor.java @@ -11,6 +11,7 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import team.chisel.ctmlib.RenderBlocksCTM; import team.chisel.ctmlib.TextureSubmap; +import team.chisel.ctmlib.TextureSubmapCTM; public class SubmapManagerCarpetFloor extends SubmapManagerBase { @@ -39,8 +40,8 @@ public class SubmapManagerCarpetFloor extends SubmapManagerBase { @SideOnly(Side.CLIENT) public void registerIcons(String modName, Block block, IIconRegister register) { String path = modName + ":carpet/" + color; - submap = new TextureSubmap(register.registerIcon(path + "-ctm"), 4, 4); - submapSmall = new TextureSubmap(register.registerIcon(path), 2, 2); + submap = new TextureSubmapCTM(register.registerIcon(path + "-ctm"), 4); + submapSmall = new TextureSubmapCTM(register.registerIcon(path), 2); } @Override diff --git a/src/main/java/team/chisel/client/render/SubmapManagerSlab.java b/src/main/java/team/chisel/client/render/SubmapManagerSlab.java index b4fb1ea6..2470c8c2 100644 --- a/src/main/java/team/chisel/client/render/SubmapManagerSlab.java +++ b/src/main/java/team/chisel/client/render/SubmapManagerSlab.java @@ -13,6 +13,7 @@ import cpw.mods.fml.relauncher.SideOnly; import team.chisel.ctmlib.ISubmapManager; import team.chisel.ctmlib.RenderBlocksCTM; import team.chisel.ctmlib.TextureSubmap; +import team.chisel.ctmlib.TextureSubmapCTM; public class SubmapManagerSlab implements ISubmapManager { @@ -42,8 +43,8 @@ public class SubmapManagerSlab implements ISubmapManager { @SideOnly(Side.CLIENT) public void registerIcons(String modName, Block block, IIconRegister register) { String path = modName + ":" + texture; - submap = new TextureSubmap(register.registerIcon(path + "-ctm"), 4, 4); - submapSmall = new TextureSubmap(register.registerIcon(path), 2, 2); + submap = new TextureSubmapCTM(register.registerIcon(path + "-ctm"), 4); + submapSmall = new TextureSubmapCTM(register.registerIcon(path), 2); sideTexture = register.registerIcon(path + "-side"); } diff --git a/src/main/java/team/chisel/client/render/SubmapManagerSpecialMaterial.java b/src/main/java/team/chisel/client/render/SubmapManagerSpecialMaterial.java index fda8cd60..461d010d 100644 --- a/src/main/java/team/chisel/client/render/SubmapManagerSpecialMaterial.java +++ b/src/main/java/team/chisel/client/render/SubmapManagerSpecialMaterial.java @@ -11,6 +11,7 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import team.chisel.ctmlib.RenderBlocksCTM; import team.chisel.ctmlib.TextureSubmap; +import team.chisel.ctmlib.TextureSubmapCTM; public class SubmapManagerSpecialMaterial extends SubmapManagerBase { @@ -71,8 +72,8 @@ public class SubmapManagerSpecialMaterial extends SubmapManagerBase { public void registerIcons(String modName, Block block, IIconRegister register) { String materialName = materialType.name() .toLowerCase(); - submap = new TextureSubmap(register.registerIcon(modName + ":" + materialName + "/" + color + "-ctm"), 4, 4); - submapSmall = new TextureSubmap(register.registerIcon(modName + ":" + materialName + "/" + color), 2, 2); + submap = new TextureSubmapCTM(register.registerIcon(modName + ":" + materialName + "/" + color + "-ctm"), 4); + submapSmall = new TextureSubmapCTM(register.registerIcon(modName + ":" + materialName + "/" + color), 2); } @Override diff --git a/src/main/java/team/chisel/ctmlib/SubmapManagerCTM.java b/src/main/java/team/chisel/ctmlib/SubmapManagerCTM.java index b6822fe7..de54e76a 100644 --- a/src/main/java/team/chisel/ctmlib/SubmapManagerCTM.java +++ b/src/main/java/team/chisel/ctmlib/SubmapManagerCTM.java @@ -48,8 +48,8 @@ public class SubmapManagerCTM implements ISubmapManager { @Override @SideOnly(Side.CLIENT) public void registerIcons(String modName, Block block, IIconRegister register) { - submap = new TextureSubmap(register.registerIcon(modName + ":" + textureName + "-ctm"), 4, 4); - submapSmall = new TextureSubmap(register.registerIcon(modName + ":" + textureName), 2, 2); + submap = new TextureSubmapCTM(register.registerIcon(modName + ":" + textureName + "-ctm"), 4); + submapSmall = new TextureSubmapCTM(register.registerIcon(modName + ":" + textureName), 2); } @Override diff --git a/src/main/java/team/chisel/ctmlib/TextureSubmap.java b/src/main/java/team/chisel/ctmlib/TextureSubmap.java index 0898b757..ba6ecd84 100644 --- a/src/main/java/team/chisel/ctmlib/TextureSubmap.java +++ b/src/main/java/team/chisel/ctmlib/TextureSubmap.java @@ -16,7 +16,7 @@ import cpw.mods.fml.common.eventhandler.SubscribeEvent; import lombok.experimental.Delegate; /** - * Splits a texture sheet into independently stitched sub-icons used for CTM and other texture manipulation. + * Represents a grid of individually accessible texture regions. */ public class TextureSubmap implements IIcon, ISubmap { @@ -29,17 +29,11 @@ public class TextureSubmap implements IIcon, ISubmap { protected final IIcon[][] icons; /** - * Construct a new square submap. - * * @param baseIcon The IIcon to submap. * @param width The width of the map, in icons. - * @param height The height of the map, in icons. Must equal {@code width}. + * @param height The height of the map, in icons. */ public TextureSubmap(IIcon baseIcon, int width, int height) { - if (width != height) { - throw new IllegalArgumentException("TextureSubmap must be square: " + width + "x" + height); - } - this.baseIcon = baseIcon; this.width = width; this.height = height; @@ -47,9 +41,6 @@ public class TextureSubmap implements IIcon, ISubmap { submaps.add(this); } - /** - * The large "base" icon used for the submap. - */ public IIcon getBaseIcon() { return baseIcon; } @@ -114,20 +105,138 @@ public class TextureSubmap implements IIcon, ISubmap { } } - /** - * Register the real sprites backing this submap. - */ public void registerSubIcons(TextureMap textureMap) { + registerSpriteGrid(textureMap, width, height); + } + + /** + * Splits the texture into a grid of atlas sprites and assigns sub-icons to them. + *

+ * The number of sub-icons is determined by the submap's width and height, + * while spriteColumns and spriteRows determine how many separate atlas sprites + * are used. Each sprite can contain one or more sub-icons. + *

+ * For example, a 4x4 submap backed by a 2x2 sprite grid uses four atlas sprites, + * each containing a 2x2 group of sub-icons. + */ + protected final void registerSpriteGrid(TextureMap textureMap, int spriteColumns, int spriteRows) { if (baseIcon == null || width <= 0 || height <= 0) { return; } + if (spriteColumns <= 0 || spriteRows <= 0 || width % spriteColumns != 0 || height % spriteRows != 0) { + throw new IllegalArgumentException( + "Logical submap " + width + + "x" + + height + + " cannot be backed by sprite grid " + + spriteColumns + + "x" + + spriteRows); + } ResourceLocation sourceLocation = new ResourceLocation(baseIcon.getIconName()); + IIcon[][] sprites = new IIcon[spriteColumns][spriteRows]; + + for (int spriteX = 0; spriteX < spriteColumns; spriteX++) { + for (int spriteY = 0; spriteY < spriteRows; spriteY++) { + sprites[spriteX][spriteY] = getBackingSprite( + textureMap, + sourceLocation, + spriteColumns, + spriteRows, + spriteX, + spriteY); + } + } + + int cellsPerSpriteX = width / spriteColumns; + int cellsPerSpriteY = height / spriteRows; + boolean wholeSprite = cellsPerSpriteX == 1 && cellsPerSpriteY == 1; + for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { - String name = TextureSubmapResource.getSubIconName(sourceLocation, width, height, x, y); - icons[x][y] = textureMap.registerIcon(name); + IIcon sprite = sprites[x / cellsPerSpriteX][y / cellsPerSpriteY]; + icons[x][y] = wholeSprite ? sprite + : new TextureRegion( + sprite, + cellsPerSpriteX, + cellsPerSpriteY, + x % cellsPerSpriteX, + y % cellsPerSpriteY); } } } + + private IIcon getBackingSprite(TextureMap textureMap, ResourceLocation sourceLocation, int columns, int rows, int x, + int y) { + + if (columns == 1 && rows == 1) { + return baseIcon; + } + + String name = TextureSubmapResource.getSubIconName(sourceLocation, columns, rows, x, y); + return textureMap.registerIcon(name); + } + + private static final class TextureRegion implements IIcon { + + private final IIcon parent; + private final int columns; + private final int rows; + private final int x; + private final int y; + + private TextureRegion(IIcon parent, int columns, int rows, int x, int y) { + this.parent = parent; + this.columns = columns; + this.rows = rows; + this.x = x; + this.y = y; + } + + @Override + public float getMinU() { + return getInterpolatedU(0); + } + + @Override + public float getMaxU() { + return getInterpolatedU(16); + } + + @Override + public float getInterpolatedU(double u) { + return parent.getInterpolatedU((16.0 * x + u) / columns); + } + + @Override + public float getMinV() { + return getInterpolatedV(0); + } + + @Override + public float getMaxV() { + return getInterpolatedV(16); + } + + @Override + public float getInterpolatedV(double v) { + return parent.getInterpolatedV((16.0 * y + v) / rows); + } + + @Override + public String getIconName() { + return parent.getIconName(); + } + + @Override + public int getIconWidth() { + return parent.getIconWidth() / columns; + } + + @Override + public int getIconHeight() { + return parent.getIconHeight() / rows; + } + } } diff --git a/src/main/java/team/chisel/ctmlib/TextureSubmapCTM.java b/src/main/java/team/chisel/ctmlib/TextureSubmapCTM.java new file mode 100644 index 00000000..41f2958d --- /dev/null +++ b/src/main/java/team/chisel/ctmlib/TextureSubmapCTM.java @@ -0,0 +1,27 @@ +package team.chisel.ctmlib; + +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.util.IIcon; + +/** + * A CTM submap that keeps each tile in one atlas sprite while exposing its four quadrants as sub-icons. + */ +public final class TextureSubmapCTM extends TextureSubmap { + + /** + * @param baseIcon The registered texture sheet. + * @param size The number of logical quadrants per axis: 4 for the CTM sheet, 2 for the normal texture. + */ + public TextureSubmapCTM(IIcon baseIcon, int size) { + super(baseIcon, size, size); + + if (size <= 0 || (size & 1) != 0) { + throw new IllegalArgumentException("CTM submap size must be a positive even number: " + size); + } + } + + @Override + public void registerSubIcons(TextureMap textureMap) { + registerSpriteGrid(textureMap, getWidth() / 2, getHeight() / 2); + } +} diff --git a/src/main/java/team/chisel/ctmlib/TextureSubmapResource.java b/src/main/java/team/chisel/ctmlib/TextureSubmapResource.java index eb11c40a..4137c39a 100644 --- a/src/main/java/team/chisel/ctmlib/TextureSubmapResource.java +++ b/src/main/java/team/chisel/ctmlib/TextureSubmapResource.java @@ -6,9 +6,9 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; -import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; import javax.imageio.IIOImage; @@ -35,7 +35,7 @@ public final class TextureSubmapResource implements IResource { private record SubmapCacheKey(ResourceLocation source, int columns, int rows) {} @Nullable - private static HashMap imageCache; + private static volatile ConcurrentHashMap imageCache; private static final String PREFIX = "__chisel_submap/"; @@ -103,7 +103,7 @@ public final class TextureSubmapResource implements IResource { } public static void beginTextureStitch() { - imageCache = new HashMap<>(); + imageCache = new ConcurrentHashMap<>(); } public static void endTextureStitch() { @@ -124,15 +124,19 @@ public final class TextureSubmapResource implements IResource { } private byte[] getCachedImageData(IResource resource, SubmapLocation submap) throws IOException { - if (!cacheable || imageCache == null) { + ConcurrentHashMap cache = imageCache; + if (!cacheable || cache == null) { return generateCell(resource, submap); } SubmapCacheKey key = new SubmapCacheKey(submap.source(), submap.columns(), submap.rows()); - byte[][] cells = imageCache.get(key); + byte[][] cells = cache.get(key); if (cells == null) { cells = generateSubmap(resource, submap); - imageCache.put(key, cells); + byte[][] existing = cache.putIfAbsent(key, cells); + if (existing != null) { + cells = existing; + } } return cells[submap.cellX() * submap.rows() + submap.cellY()]; @@ -176,7 +180,7 @@ public final class TextureSubmapResource implements IResource { ImageWriteParam param = writer.getDefaultWriteParam(); if (param.canWriteCompressed()) { - // disable compression so we don't spend time on compressing and decompressing to just get the image bytes + // Disable compression to avoid wasting time compressing images that will immediately be decoded again param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); param.setCompressionQuality(1.0f); }