diff --git a/src/main/java/com/prupe/mcpatcher/ctm/CTMTextureGenerator.java b/src/main/java/com/prupe/mcpatcher/ctm/CTMTextureGenerator.java new file mode 100644 index 000000000..3aaff71e8 --- /dev/null +++ b/src/main/java/com/prupe/mcpatcher/ctm/CTMTextureGenerator.java @@ -0,0 +1,99 @@ +package com.prupe.mcpatcher.ctm; + +import com.prupe.mcpatcher.ctm.TileOverrideImpl.CTMCompactExpanded; +import com.prupe.mcpatcher.mal.tile.TileLoader; +import net.minecraft.util.ResourceLocation; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; + +public class CTMTextureGenerator { + + public static void generateTextures(BufferedImage[] compactIcons, CTMCompactExpanded override, + ResourceLocation propsLoc, ResourceLocation blankResource){ + BufferedImage cFull = compactIcons[0]; // Full + BufferedImage cNone = compactIcons[1]; // None + BufferedImage cVert = compactIcons[2]; // Vertical + BufferedImage cHori = compactIcons[3]; // Horizontal + BufferedImage cCorn = compactIcons[4]; // Corner + + BufferedImage[] expanded = new BufferedImage[47]; + + // Generate all 47 textures to match normal CTM + expanded[0] = cFull; + expanded[1] = generate(cFull, cHori, cFull, cHori); + expanded[2] = cHori; + expanded[3] = generate(cHori, cFull, cHori, cFull); + expanded[4] = generate(cFull, cHori, cVert, cCorn); + expanded[5] = generate(cHori, cFull, cCorn, cVert); + expanded[6] = generate(cVert, cCorn, cVert, cCorn); + expanded[7] = generate(cHori, cHori, cCorn, cCorn); + expanded[8] = generate(cCorn, cNone, cCorn, cCorn); + expanded[9] = generate(cCorn, cCorn, cCorn, cNone); + expanded[10] = generate(cNone, cCorn, cNone, cCorn); + expanded[11] = generate(cNone, cNone, cCorn, cCorn); + expanded[12] = generate(cFull, cFull, cVert, cVert); + expanded[13] = generate(cFull, cHori, cVert, cNone); + expanded[14] = generate(cHori, cHori, cNone, cNone); + expanded[15] = generate(cHori, cFull, cNone, cVert); + expanded[16] = generate(cVert, cCorn, cFull, cHori); + expanded[17] = generate(cCorn, cVert, cHori, cFull); + expanded[18] = generate(cCorn, cCorn, cHori, cHori); + expanded[19] = generate(cCorn, cVert, cCorn, cVert); + expanded[20] = generate(cNone, cCorn, cCorn, cCorn); + expanded[21] = generate(cCorn, cCorn, cNone, cCorn); + expanded[22] = generate(cCorn, cCorn, cNone, cNone); + expanded[23] = generate(cCorn, cNone, cCorn, cNone); + expanded[24] = cVert; + expanded[25] = generate(cVert, cNone, cVert, cNone); + expanded[26] = cNone; + expanded[27] = generate(cNone, cVert, cNone, cVert); + expanded[28] = generate(cVert, cCorn, cVert, cNone); + expanded[29] = generate(cHori, cHori, cNone, cCorn); + expanded[30] = generate(cVert, cNone, cVert, cCorn); + expanded[31] = generate(cHori, cHori, cCorn, cNone); + expanded[32] = generate(cNone, cNone, cNone, cCorn); + expanded[33] = generate(cNone, cNone, cCorn, cNone); + expanded[34] = generate(cCorn, cNone, cNone, cCorn); + expanded[35] = generate(cNone, cCorn, cCorn, cNone); + expanded[36] = generate(cVert, cVert, cFull, cFull); + expanded[37] = generate(cVert, cNone, cFull, cHori); + expanded[38] = generate(cNone, cNone, cHori, cHori); + expanded[39] = generate(cNone, cVert, cHori, cFull); + expanded[40] = generate(cCorn, cNone, cHori, cHori); + expanded[41] = generate(cNone, cVert, cNone, cVert); + expanded[42] = generate(cNone, cCorn, cHori, cHori); + expanded[43] = generate(cCorn, cVert, cNone, cVert); + expanded[44] = generate(cNone, cCorn, cNone, cNone); + expanded[45] = generate(cCorn, cNone, cNone, cNone); + expanded[46] = cCorn; + + for (int i = 0; i < expanded.length; i++){ + ResourceLocation resource = + TileLoader.parseTileAddress(propsLoc, String.valueOf(i), blankResource); + override.addIcon(resource, expanded[i]); + } + } + + private static BufferedImage generate(BufferedImage tl, BufferedImage tr, BufferedImage bl, BufferedImage br){ + int w = tl.getWidth(); + int hw = w/2; + BufferedImage img = new BufferedImage(tl.getWidth(), tl.getHeight(), BufferedImage.TYPE_INT_ARGB); + int numFrames = tl.getHeight() / tl.getWidth(); + Graphics2D g = img.createGraphics(); + for (int i = 0; i < numFrames; i++){ + int yOff = w * i; + copy(tl, g, 0, yOff, hw); + copy(tr, g, hw, yOff, hw); + copy(bl, g, 0, yOff + hw, hw); + copy(br, g, hw, yOff + hw, hw); + } + g.dispose(); + return img; + } + + private static void copy(BufferedImage src, Graphics2D g, int x, int y, int s){ + g.drawImage(src, x, y, x+s, y+s, x, y, x+s, y+s, null); + } + +} diff --git a/src/main/java/com/prupe/mcpatcher/ctm/CTMUtils.java b/src/main/java/com/prupe/mcpatcher/ctm/CTMUtils.java index 8a4d268c7..22ef4bdb3 100644 --- a/src/main/java/com/prupe/mcpatcher/ctm/CTMUtils.java +++ b/src/main/java/com/prupe/mcpatcher/ctm/CTMUtils.java @@ -10,12 +10,12 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.StampedLock; +import com.github.bsideup.jabel.Desugar; import net.minecraft.block.Block; import net.minecraft.util.IIcon; import net.minecraft.util.ResourceLocation; import net.minecraft.world.IBlockAccess; -import com.github.bsideup.jabel.Desugar; import com.prupe.mcpatcher.MCLogger; import com.prupe.mcpatcher.MCPatcherUtils; import com.prupe.mcpatcher.mal.block.BlockAPI; @@ -32,7 +32,7 @@ import jss.notfine.config.MCPatcherForgeConfig; public class CTMUtils { - private static final MCLogger logger = MCLogger.getLogger(MCLogger.Category.CONNECTED_TEXTURES, "CTM"); + public static final MCLogger logger = MCLogger.getLogger(MCLogger.Category.CONNECTED_TEXTURES, "CTM"); private static class Overrides { private final List all = new ArrayList<>(); diff --git a/src/main/java/com/prupe/mcpatcher/ctm/GeneratedCTMAtlasSprite.java b/src/main/java/com/prupe/mcpatcher/ctm/GeneratedCTMAtlasSprite.java new file mode 100644 index 000000000..5ba02f80b --- /dev/null +++ b/src/main/java/com/prupe/mcpatcher/ctm/GeneratedCTMAtlasSprite.java @@ -0,0 +1,77 @@ +package com.prupe.mcpatcher.ctm; + +import com.gtnewhorizons.angelica.config.AngelicaConfig; +import com.prupe.mcpatcher.mal.tile.TileLoader; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import jss.notfine.render.InterpolatedIcon; +import net.minecraft.client.resources.IResource; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.client.resources.SimpleResource; +import net.minecraft.client.resources.data.AnimationMetadataSection; +import net.minecraft.util.ResourceLocation; + +import java.awt.image.BufferedImage; + +@SideOnly(Side.CLIENT) +public class GeneratedCTMAtlasSprite extends InterpolatedIcon { + + private final TileLoader loader; + private final boolean aniFiltering; + private boolean interpolated; + + public GeneratedCTMAtlasSprite(TileLoader loader, boolean aniFiltering, String name) { + super(name); + this.loader = loader; + this.aniFiltering = aniFiltering; + } + + @Override + protected void updateAnimationInterpolated() throws IllegalArgumentException, IllegalAccessException { + if(interpolated) super.updateAnimationInterpolated(); + } + + @Override + public boolean hasCustomLoader(IResourceManager manager, ResourceLocation location) { + return true; + } + + @Override + public boolean load(IResourceManager manager, ResourceLocation location) { + BufferedImage img = loader.getImageForPath(location); + if(img == null) return true; + try { + ResourceLocation metaLoc = getMetaLoc(location); + IResource iresource = manager.getResource(metaLoc); + AnimationMetadataSection anim = (AnimationMetadataSection)iresource.getMetadata("animation"); + interpolated = anim != null && AngelicaConfig.enableNotFineFeatures && shouldInterpolate(iresource); + loadSprite(new BufferedImage[]{img, null, null, null, null}, anim, aniFiltering); + }catch (Exception exception){ + CTMUtils.logger.error("Error loading sprite for " + location); + exception.printStackTrace(); + return true; + } + return false; + } + + private static boolean shouldInterpolate(IResource resource){ + try { + if (resource instanceof SimpleResource simple) { + return simple.mcmetaJson.getAsJsonObject("animation") + .getAsJsonPrimitive("interpolate").getAsBoolean(); + } + }catch (Exception _){} + return false; + } + + /** + * Find the mcmeta file associated with 0.png + */ + private static ResourceLocation getMetaLoc(ResourceLocation location) { + String path = location.getResourcePath(); + int index = path.lastIndexOf('/'); + if(index == -1) return new ResourceLocation(location.getResourceDomain(), "0.png"); + path = path.substring(0, index) + "/0.png"; + return new ResourceLocation(location.getResourceDomain(), path); + } +} diff --git a/src/main/java/com/prupe/mcpatcher/ctm/TileOverride.java b/src/main/java/com/prupe/mcpatcher/ctm/TileOverride.java index d883038fd..3d8a1108e 100644 --- a/src/main/java/com/prupe/mcpatcher/ctm/TileOverride.java +++ b/src/main/java/com/prupe/mcpatcher/ctm/TileOverride.java @@ -5,6 +5,7 @@ import static com.prupe.mcpatcher.ctm.RenderBlockState.CONNECT_BY_MATERIAL; import static com.prupe.mcpatcher.ctm.RenderBlockState.CONNECT_BY_TILE; import static com.prupe.mcpatcher.ctm.RenderBlockState.NORMALS; +import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.BitSet; import java.util.HashSet; @@ -32,10 +33,10 @@ public abstract class TileOverride implements Comparable { private static final MCLogger logger = MCLogger.getLogger(MCLogger.Category.CONNECTED_TEXTURES, "CTM"); - private final PropertiesFile properties; + protected final PropertiesFile properties; private final String baseFilename; - private final TileLoader tileLoader; - private final int renderPass; + protected final TileLoader tileLoader; + protected final int renderPass; private final int weight; private final List matchBlocks; private final Set matchTiles; @@ -45,7 +46,7 @@ public abstract class TileOverride implements Comparable { private final BitSet biomes; private final BitSet height; - private final List tileNames = new ArrayList<>(); + protected final List tileNames = new ArrayList<>(); protected IIcon[] icons; // Index into this array is formed from these bit values: @@ -103,6 +104,7 @@ public abstract class TileOverride implements Comparable { case "sandstone", "top" -> override = new TileOverrideImpl.Top(properties, tileLoader); case "repeat", "pattern" -> override = new TileOverrideImpl.Repeat(properties, tileLoader); case "ctm_compact", "compact" -> override = new TileOverrideImpl.CTMCompact(properties, tileLoader); + case "compact_expanded" -> override = new TileOverrideImpl.CTMCompactExpanded(properties, tileLoader); default -> properties.error("unknown method \"%s\"", method); } @@ -196,7 +198,12 @@ public abstract class TileOverride implements Comparable { return tileLoader.preloadTile(resource, renderPass > RenderPassAPI.MAX_BASE_RENDER_PASS); } - private void loadIcons() { + public void addIcon(ResourceLocation resource, BufferedImage img){ + tileNames.add(resource); + tileLoader.addTile(resource, img, null, true); + } + + protected void loadIcons() { tileNames.clear(); String tileList = properties.getString("tiles", ""); ResourceLocation blankResource = RenderPassAPI.instance.getBlankResource(renderPass); @@ -221,16 +228,7 @@ public abstract class TileOverride implements Comparable { try { int from = Integer.parseInt(matcher.group(1)); int to = Integer.parseInt(matcher.group(2)); - for (int i = from; i <= to; i++) { - ResourceLocation resource = TileLoader - .parseTileAddress(properties.getResource(), String.valueOf(i), blankResource); - if (TexturePackAPI.hasResource(resource)) { - addIcon(resource); - } else { - properties.warning("could not find image %s", resource); - tileNames.add(null); - } - } + loadOverrideIcons(from, to, blankResource); } catch (NumberFormatException e) { e.printStackTrace(); } @@ -250,6 +248,19 @@ public abstract class TileOverride implements Comparable { } } + protected void loadOverrideIcons(int from, int to, ResourceLocation blankResource){ + for (int i = from; i <= to; i++) { + ResourceLocation resource = TileLoader + .parseTileAddress(properties.getResource(), String.valueOf(i), blankResource); + if (TexturePackAPI.hasResource(resource)) { + addIcon(resource); + } else { + properties.warning("could not find image %s", resource); + tileNames.add(null); + } + } + } + private List getBlockList(String property, String defaultMetadata) { List blocks = new ArrayList<>(); if (!MCPatcherUtils.isNullOrEmpty(defaultMetadata)) { diff --git a/src/main/java/com/prupe/mcpatcher/ctm/TileOverrideImpl.java b/src/main/java/com/prupe/mcpatcher/ctm/TileOverrideImpl.java index d9771c3b3..645a92c60 100644 --- a/src/main/java/com/prupe/mcpatcher/ctm/TileOverrideImpl.java +++ b/src/main/java/com/prupe/mcpatcher/ctm/TileOverrideImpl.java @@ -12,15 +12,22 @@ import static com.prupe.mcpatcher.ctm.RenderBlockState.REL_UL; import static com.prupe.mcpatcher.ctm.RenderBlockState.REL_UR; import static com.prupe.mcpatcher.ctm.RenderBlockState.TOP_FACE; +import com.prupe.mcpatcher.mal.block.RenderPassAPI; +import com.prupe.mcpatcher.mal.resource.TexturePackAPI; import net.minecraft.util.IIcon; import com.prupe.mcpatcher.mal.resource.PropertiesFile; import com.prupe.mcpatcher.mal.tile.TileLoader; import com.prupe.mcpatcher.mal.util.WeightedIndex; +import net.minecraft.util.ResourceLocation; + +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.List; public class TileOverrideImpl { - final static class CTM extends TileOverride { + static class CTM extends TileOverride { CTM(PropertiesFile properties, TileLoader tileLoader) { super(properties, tileLoader); @@ -505,4 +512,48 @@ public class TileOverrideImpl { return p; } } + + final public static class CTMCompactExpanded extends CTM { + + CTMCompactExpanded(PropertiesFile propertiesFile, TileLoader tileLoader) { + super(propertiesFile, tileLoader); + } + + @Override + String checkTileMap() { + return null; + } + + @Override + String getMethod() { + return "compact_expanded"; + } + + @Override + protected void loadOverrideIcons(int from, int to, ResourceLocation blankResource) { + List compactIcons = new ArrayList<>(); + if(from != 0){ + properties.error("expanded compact CTM requires range to start at 0, starts at %d", from); + } + if(to - from + 1 != 5){ + properties.error("expanded compact CTM requires exactly 5 icons, got range %d - %d", from, to); + } + for (int i = from; i <= to; i++) { + ResourceLocation resource = TileLoader + .parseTileAddress(properties.getResource(), String.valueOf(i), blankResource); + if (TexturePackAPI.hasResource(resource)) { + compactIcons.add(tileLoader.loadResourceImage(resource, + renderPass > RenderPassAPI.MAX_BASE_RENDER_PASS)); + } else { + // Promote from warning to error for compact CTM + properties.error("could not find image %s, required for expanded compact CTM", resource); + tileNames.add(null); + } + } + if(compactIcons.size() == 5){ + CTMTextureGenerator.generateTextures(compactIcons.toArray(new BufferedImage[0]), + this, properties.getResource(), blankResource); + } + } + } } diff --git a/src/main/java/com/prupe/mcpatcher/mal/tile/TileLoader.java b/src/main/java/com/prupe/mcpatcher/mal/tile/TileLoader.java index 03f32eff4..98ee19b89 100644 --- a/src/main/java/com/prupe/mcpatcher/mal/tile/TileLoader.java +++ b/src/main/java/com/prupe/mcpatcher/mal/tile/TileLoader.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import com.prupe.mcpatcher.ctm.GeneratedCTMAtlasSprite; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.texture.TextureMap; @@ -44,6 +45,7 @@ public class TileLoader { private TextureMap baseTextureMap; private final Map baseTexturesByName = new HashMap<>(); private final Set tilesToRegister = new HashSet<>(); + private final Set generatedTiles = new HashSet<>(); private final Map tileImages = new HashMap<>(); private final Map iconMap = new HashMap<>(); @@ -119,6 +121,10 @@ public class TileLoader { logger.fine("after registerIcons(%s) %d icons", mapName, map.size()); } + public BufferedImage getImageForPath(ResourceLocation rl) { + return tileImages.get(rl); + } + public static String getOverridePath(String prefix, String basePath, String name, String ext) { String path; if (name.endsWith(".png")) { @@ -218,6 +224,22 @@ public class TileLoader { if (tileImages.containsKey(resource)) { return true; } + return addTile(resource, loadResourceImage(resource, alternate), special, false); + } + + public boolean addTile(ResourceLocation resource, BufferedImage image, String special, boolean generated) { + tilesToRegister.add(resource); + tileImages.put(resource, image); + if(generated){ + generatedTiles.add(resource); + } + if (special != null) { + specialTextures.put(resource.toString(), special); + } + return true; + } + + public BufferedImage loadResourceImage(ResourceLocation resource, boolean alternate){ BufferedImage image = null; if (!MCPatcherForgeConfig.ConnectedTextures.debugTextures) { image = TexturePackAPI.getImage(resource); @@ -228,12 +250,7 @@ public class TileLoader { if (image == null) { image = generateDebugTexture(resource.getResourcePath(), 64, 64, alternate); } - tilesToRegister.add(resource); - tileImages.put(resource, image); - if (special != null) { - specialTextures.put(resource.toString(), special); - } - return true; + return image; } public boolean preloadTile(ResourceLocation resource, boolean alternate) { @@ -287,8 +304,14 @@ public class TileLoader { return false; } } - IIcon icon = textureMap.registerIcon(name); - map.put(name, (TextureAtlasSprite) icon); + TextureAtlasSprite icon; + if(generatedTiles.contains(resource)){ + icon = new GeneratedCTMAtlasSprite(this, textureMap.anisotropicFiltering > 1, name); + textureMap.setTextureEntry(name, icon); + }else{ + icon = (TextureAtlasSprite)textureMap.registerIcon(name); + } + map.put(name, icon); iconMap.put(name, icon); String extra = (width == height ? "" : ", " + (height / width) + " frames"); subLogger.finer("%s -> %s icon %dx%d%s", name, mapName, width, width, extra); @@ -299,6 +322,7 @@ public class TileLoader { public void finish() { tilesToRegister.clear(); tileImages.clear(); + generatedTiles.clear(); } public IIcon getIcon(String name) { diff --git a/src/main/java/jss/notfine/render/InterpolatedIcon.java b/src/main/java/jss/notfine/render/InterpolatedIcon.java index 776addc3c..c22672ed6 100644 --- a/src/main/java/jss/notfine/render/InterpolatedIcon.java +++ b/src/main/java/jss/notfine/render/InterpolatedIcon.java @@ -29,7 +29,7 @@ public class InterpolatedIcon extends TextureAtlasSprite { } } - private void updateAnimationInterpolated() throws IllegalArgumentException, IllegalAccessException { + protected void updateAnimationInterpolated() throws IllegalArgumentException, IllegalAccessException { double d0 = 1.0D - tickCounter / (double) animationMetadata.getFrameTimeSingle(frameCounter); int i = animationMetadata.getFrameIndex(frameCounter); diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/rendering/MixinRenderBlocks.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/rendering/MixinRenderBlocks.java index ae57cdd7a..74f1e8667 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/rendering/MixinRenderBlocks.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/rendering/MixinRenderBlocks.java @@ -169,7 +169,7 @@ public abstract class MixinRenderBlocks { } @ModifyExpressionValue(method = { "renderStandardBlockWithColorMultiplier" }, - at = @At(value = "FIELD", opcode = Opcodes.GETFIELD, target = "Lnet/minecraft/client/renderer/RenderBlocks;renderAllFaces:Z")) + at = @At(value = "FIELD", opcode = Opcodes.GETFIELD, target = "Lnet/minecraft/client/renderer/RenderBlocks;renderAllFaces:Z")) private boolean applyAOBrightness(boolean original, @Local(ordinal = 0) Tessellator tessellator) { ((StateAwareTessellator)tessellator).angelica$setAppliedAo(this.applyingCeleritasAO); return original;