diff --git a/src/main/java/com/falsepattern/mcpatcher/internal/config/ExtraConfig.java b/src/main/java/com/falsepattern/mcpatcher/internal/config/ExtraConfig.java new file mode 100644 index 0000000..2d316c7 --- /dev/null +++ b/src/main/java/com/falsepattern/mcpatcher/internal/config/ExtraConfig.java @@ -0,0 +1,59 @@ +/* + * Right Proper MCPatcher + * + * Copyright (C) 2025 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, only version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.falsepattern.mcpatcher.internal.config; + +import com.falsepattern.lib.config.Config; +import com.falsepattern.lib.config.ConfigurationManager; +import com.falsepattern.mcpatcher.Tags; +import lombok.NoArgsConstructor; + +@Config.Comment("Additional Runtime Configurations") +@Config.LangKey +@Config(modid = Tags.MOD_ID, + category = "02_extras") +@NoArgsConstructor +public final class ExtraConfig { + //@formatter:off + @Config.Comment({"Disable this if you don't want Natural Textures configuration files from multiple resource packs to 'stack' on top of each other in order of priority.", + "If true, the total set of Natural Textures will be created using all resource packs in the chain.", + "If false, only the highest priority 'natural.properties' file will be used."}) + @Config.LangKey + @Config.Name("naturalTexturesStack") + @Config.DefaultBoolean(true) + public static boolean naturalTexturesStack; + //@formatter:on + + // region Init + static { + ConfigurationManager.selfInit(); + ModuleConfig.init(); + } + + /** + * This is here to make the static initializer run + */ + public static void init() { + + } + // endregion +} diff --git a/src/main/java/com/falsepattern/mcpatcher/internal/config/MCPatcherGuiFactory.java b/src/main/java/com/falsepattern/mcpatcher/internal/config/MCPatcherGuiFactory.java index ef00d66..8f5443f 100644 --- a/src/main/java/com/falsepattern/mcpatcher/internal/config/MCPatcherGuiFactory.java +++ b/src/main/java/com/falsepattern/mcpatcher/internal/config/MCPatcherGuiFactory.java @@ -44,6 +44,7 @@ private static Class[] getConfigClasses() { val result = new ArrayList>(); result.add(ModuleConfig.class); result.add(MixinConfig.class); + result.add(ExtraConfig.class); return result.toArray(new Class[0]); } diff --git a/src/main/java/com/falsepattern/mcpatcher/internal/config/MixinConfig.java b/src/main/java/com/falsepattern/mcpatcher/internal/config/MixinConfig.java index b3b060a..19ab8f1 100644 --- a/src/main/java/com/falsepattern/mcpatcher/internal/config/MixinConfig.java +++ b/src/main/java/com/falsepattern/mcpatcher/internal/config/MixinConfig.java @@ -49,6 +49,13 @@ public final class MixinConfig { @Config.DefaultBoolean(true) public static boolean connectedTexturesMixins; + @Config.Comment({"Disable this if you don't want natural texture mixins to land.", + "This is required for naturalTextures."}) + @Config.LangKey + @Config.Name("naturalTexturesMixins") + @Config.DefaultBoolean(true) + public static boolean naturalTexturesMixins; + @Config.Comment({"Disable this if you don't want better glass mixins to land.", "This is required for betterGlass."}) @Config.LangKey diff --git a/src/main/java/com/falsepattern/mcpatcher/internal/config/ModuleConfig.java b/src/main/java/com/falsepattern/mcpatcher/internal/config/ModuleConfig.java index c10084f..7e62653 100644 --- a/src/main/java/com/falsepattern/mcpatcher/internal/config/ModuleConfig.java +++ b/src/main/java/com/falsepattern/mcpatcher/internal/config/ModuleConfig.java @@ -41,6 +41,13 @@ public final class ModuleConfig { @Config.DefaultBoolean(true) public static boolean connectedTextures; + @Config.Comment({"Runtime toggle for natural textures.", + "Requires naturalTexturesMixins enabled."}) + @Config.LangKey + @Config.Name("naturalTextures") + @Config.DefaultBoolean(false) + public static boolean naturalTextures; + @Config.Comment({"Allow regular glass blocks, glass panes, and beacon glass to have semi-transparent textures.", "Requires betterGlassMixins enabled."}) @Config.LangKey @@ -67,6 +74,10 @@ public static boolean isConnectedTexturesEnabled() { return MixinConfig.connectedTexturesMixins && connectedTextures; } + public static boolean isNaturalTexturesEnabled() { + return MixinConfig.naturalTexturesMixins && naturalTextures; + } + public static boolean isBetterGlassEnabled() { return MixinConfig.betterGlassMixins && betterGlass; } diff --git a/src/main/java/com/falsepattern/mcpatcher/internal/mixin/Mixin.java b/src/main/java/com/falsepattern/mcpatcher/internal/mixin/Mixin.java index a4da563..1daf3bc 100644 --- a/src/main/java/com/falsepattern/mcpatcher/internal/mixin/Mixin.java +++ b/src/main/java/com/falsepattern/mcpatcher/internal/mixin/Mixin.java @@ -55,6 +55,11 @@ public enum Mixin implements IMixins { () -> MixinConfig.connectedTexturesMixins && MixinConfig.resourcePackOverlayMixins, client("ctm.TextureMapMixin_Overlay")), + NaturalTextures(Phase.EARLY, + () -> MixinConfig.naturalTexturesMixins, + client("natural.RenderBlocksMixin", + "natural.TextureMapMixin")), + RandomMobs(Phase.EARLY, () -> MixinConfig.randomMobsMixins, client("mob.EntityMixin", diff --git a/src/main/java/com/falsepattern/mcpatcher/internal/mixin/mixins/client/ctm/RenderBlocksMixin.java b/src/main/java/com/falsepattern/mcpatcher/internal/mixin/mixins/client/ctm/RenderBlocksMixin.java index bbe93e6..b89b155 100644 --- a/src/main/java/com/falsepattern/mcpatcher/internal/mixin/mixins/client/ctm/RenderBlocksMixin.java +++ b/src/main/java/com/falsepattern/mcpatcher/internal/mixin/mixins/client/ctm/RenderBlocksMixin.java @@ -25,7 +25,7 @@ import com.falsepattern.mcpatcher.internal.config.ModuleConfig; import com.falsepattern.mcpatcher.internal.modules.ctm.CTMEngine; import com.falsepattern.mcpatcher.internal.modules.ctm.PaneRenderHelper; -import com.falsepattern.mcpatcher.internal.modules.ctm.Side; +import com.falsepattern.mcpatcher.internal.modules.common.Side; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.sugar.Local; diff --git a/src/main/java/com/falsepattern/mcpatcher/internal/mixin/mixins/client/natural/RenderBlocksMixin.java b/src/main/java/com/falsepattern/mcpatcher/internal/mixin/mixins/client/natural/RenderBlocksMixin.java new file mode 100644 index 0000000..9deec97 --- /dev/null +++ b/src/main/java/com/falsepattern/mcpatcher/internal/mixin/mixins/client/natural/RenderBlocksMixin.java @@ -0,0 +1,508 @@ +/* + * Right Proper MCPatcher + * + * Copyright (C) 2025 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, only version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.falsepattern.mcpatcher.internal.mixin.mixins.client.natural; + +import com.falsepattern.mcpatcher.internal.config.ModuleConfig; +import com.falsepattern.mcpatcher.internal.modules.common.Side; +import com.falsepattern.mcpatcher.internal.modules.natural.NaturalTexturesEngine; +import com.llamalad7.mixinextras.expression.Definition; +import com.llamalad7.mixinextras.expression.Expression; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.lib.Opcodes; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.block.Block; +import net.minecraft.client.renderer.RenderBlocks; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.util.IIcon; + +@Mixin(RenderBlocks.class) +public abstract class RenderBlocksMixin { + + @Unique + private final double[] mcp$vertexUs = new double[4]; + + @Unique + private final double[] mcp$vertexVs = new double[4]; + + @Unique + private int mcp$vertexCounter = 0; + + @Unique + private void mcp$captureVertexes(int x, int y, int z, Side side, @Nullable IIcon texture, + double uA, double vA, double uB, double vB, + double uC, double vC, double uD, double vD) { + mcp$vertexUs[0] = uA; + mcp$vertexVs[0] = vA; + + mcp$vertexUs[1] = uB; + mcp$vertexVs[1] = vB; + + mcp$vertexUs[2] = uC; + mcp$vertexVs[2] = vC; + + mcp$vertexUs[3] = uD; + mcp$vertexVs[3] = vD; + + if (texture == null) return; + + NaturalTexturesEngine.applyNaturalTexture(x, y, z, side, texture, mcp$vertexUs, mcp$vertexVs); + } + + @Inject(method = { + "drawCrossedSquares", + "renderBlockCropsImpl", + }, at = @At(value = "HEAD")) + private void mcp$resetVertexCounter(CallbackInfo ci) { + mcp$vertexCounter = 0; + } + + /** Standard block render: UV Capturing Mixins */ + + // Down + @Inject(method = "renderFaceYNeg", require = 1, at = @At(value = "FIELD", + target = "Lnet/minecraft/client/renderer/RenderBlocks;enableAO:Z", + opcode = Opcodes.GETFIELD)) + private void swizzleVerticesYNeg(Block block, double x, double y, double z, IIcon texture, CallbackInfo ci, + @Local(ordinal = 3) double d3, @Local(ordinal = 4) double d4, @Local(ordinal = 5) double d5, + @Local(ordinal = 6) double d6, @Local(ordinal = 7) double d7, @Local(ordinal = 8) double d8, + @Local(ordinal = 9) double d9, @Local(ordinal = 10) double d10) { + + if (!ModuleConfig.naturalTextures) return; + mcp$captureVertexes((int) x, (int) y, (int) z, Side.YNeg, texture, d8, d10, d4, d6, d7, d9, d3, d5); + + double swap = mcp$vertexUs[0]; + mcp$vertexUs[0] = mcp$vertexUs[3]; + mcp$vertexUs[3] = swap; + + swap = mcp$vertexVs[0]; + mcp$vertexVs[0] = mcp$vertexVs[3]; + mcp$vertexVs[3] = swap; + } + + // Up + @Inject(method = "renderFaceYPos", require = 1, at = @At(value = "FIELD", + target = "Lnet/minecraft/client/renderer/RenderBlocks;enableAO:Z", + opcode = Opcodes.GETFIELD)) + private void swizzleVerticesYPos(Block block, double x, double y, double z, IIcon texture, CallbackInfo ci, + @Local(ordinal = 3) double d3, @Local(ordinal = 4) double d4, @Local(ordinal = 5) double d5, + @Local(ordinal = 6) double d6, @Local(ordinal = 7) double d7, @Local(ordinal = 8) double d8, + @Local(ordinal = 9) double d9, @Local(ordinal = 10) double d10) { + + if (!ModuleConfig.naturalTextures) return; + mcp$captureVertexes((int) x, (int) y, (int) z, Side.YPos, texture, d4, d6, d3, d5, d7, d9, d8, d10); + + double swap = mcp$vertexUs[0]; + mcp$vertexUs[0] = mcp$vertexUs[1]; + mcp$vertexUs[1] = swap; + + swap = mcp$vertexVs[0]; + mcp$vertexVs[0] = mcp$vertexVs[1]; + mcp$vertexVs[1] = swap; + } + + // North + @Inject(method = "renderFaceZNeg", require = 1, at = @At(value = "FIELD", + target = "Lnet/minecraft/client/renderer/RenderBlocks;enableAO:Z", + opcode = Opcodes.GETFIELD)) + private void swizzleVerticesZNeg(Block block, double x, double y, double z, IIcon texture, CallbackInfo ci, + @Local(ordinal = 3) double d3, @Local(ordinal = 4) double d4, @Local(ordinal = 5) double d5, + @Local(ordinal = 6) double d6, @Local(ordinal = 7) double d7, @Local(ordinal = 8) double d8, + @Local(ordinal = 9) double d9, @Local(ordinal = 10) double d10) { + + if (!ModuleConfig.naturalTextures) return; + mcp$captureVertexes((int) x, (int) y, (int) z, Side.ZNeg, texture, d3, d5, d7, d9, d4, d6, d8, d10); + + double swap = mcp$vertexUs[1]; + mcp$vertexUs[1] = mcp$vertexUs[2]; + mcp$vertexUs[2] = swap; + + swap = mcp$vertexVs[1]; + mcp$vertexVs[1] = mcp$vertexVs[2]; + mcp$vertexVs[2] = swap; + } + + // South + @Inject(method = "renderFaceZPos", require = 1, at = @At(value = "FIELD", + target = "Lnet/minecraft/client/renderer/RenderBlocks;enableAO:Z", + opcode = Opcodes.GETFIELD)) + private void swizzleVerticesZPos(Block block, double x, double y, double z, IIcon texture, CallbackInfo ci, + @Local(ordinal = 3) double d3, @Local(ordinal = 4) double d4, @Local(ordinal = 5) double d5, + @Local(ordinal = 6) double d6, @Local(ordinal = 7) double d7, @Local(ordinal = 8) double d8, + @Local(ordinal = 9) double d9, @Local(ordinal = 10) double d10) { + + if (!ModuleConfig.naturalTextures) return; + mcp$captureVertexes((int) x, (int) y, (int) z, Side.ZPos, texture, d3, d5, d4, d6, d8, d10, d7, d9); + + double swap = mcp$vertexUs[2]; + mcp$vertexUs[2] = mcp$vertexUs[3]; + mcp$vertexUs[3] = swap; + + swap = mcp$vertexVs[2]; + mcp$vertexVs[2] = mcp$vertexVs[3]; + mcp$vertexVs[3] = swap; + } + + // West + @Inject(method = "renderFaceXNeg", require = 1, at = @At(value = "FIELD", + target = "Lnet/minecraft/client/renderer/RenderBlocks;enableAO:Z", + opcode = Opcodes.GETFIELD)) + private void swizzleVerticesXNeg(Block block, double x, double y, double z, IIcon texture, CallbackInfo ci, + @Local(ordinal = 3) double d3, @Local(ordinal = 4) double d4, @Local(ordinal = 5) double d5, + @Local(ordinal = 6) double d6, @Local(ordinal = 7) double d7, @Local(ordinal = 8) double d8, + @Local(ordinal = 9) double d9, @Local(ordinal = 10) double d10) { + + if (!ModuleConfig.naturalTextures) return; + mcp$captureVertexes((int) x, (int) y, (int) z, Side.XNeg, texture, d3, d5, d7, d9, d4, d6, d8, d10); + + double swap = mcp$vertexUs[1]; + mcp$vertexUs[1] = mcp$vertexUs[2]; + mcp$vertexUs[2] = swap; + + swap = mcp$vertexVs[1]; + mcp$vertexVs[1] = mcp$vertexVs[2]; + mcp$vertexVs[2] = swap; + } + + // East + @Inject(method = "renderFaceXPos", require = 1, at = @At(value = "FIELD", + target = "Lnet/minecraft/client/renderer/RenderBlocks;enableAO:Z", + opcode = Opcodes.GETFIELD)) + private void swizzleVerticesXPos(Block block, double x, double y, double z, IIcon texture, CallbackInfo ci, + @Local(ordinal = 3) double d3, @Local(ordinal = 4) double d4, @Local(ordinal = 5) double d5, + @Local(ordinal = 6) double d6, @Local(ordinal = 7) double d7, @Local(ordinal = 8) double d8, + @Local(ordinal = 9) double d9, @Local(ordinal = 10) double d10) { + + if (!ModuleConfig.naturalTextures) return; + mcp$captureVertexes((int) x, (int) y, (int) z, Side.XPos, texture, d3, d5, d7, d9, d4, d6, d8, d10); + + double swap = mcp$vertexUs[1]; + mcp$vertexUs[1] = mcp$vertexUs[2]; + mcp$vertexUs[2] = swap; + + swap = mcp$vertexVs[1]; + mcp$vertexVs[1] = mcp$vertexVs[2]; + mcp$vertexVs[2] = swap; + } + + /** Standard block render: UV apply Mixins */ + @ModifyVariable(method = {"renderFaceXNeg", "renderFaceXPos", + "renderFaceYNeg", "renderFaceYPos", + "renderFaceZNeg", "renderFaceZPos"}, + require = 6, ordinal = 3, at = @At(value = "FIELD", + target = "Lnet/minecraft/client/renderer/RenderBlocks;enableAO:Z", + opcode = Opcodes.GETFIELD, + shift = At.Shift.AFTER)) + private double overwriteUD3(double d3) { + return ModuleConfig.naturalTextures ? mcp$vertexUs[0] : d3; + } + + @ModifyVariable(method = {"renderFaceXNeg", "renderFaceXPos", + "renderFaceYNeg", "renderFaceYPos", + "renderFaceZNeg", "renderFaceZPos"}, + require = 6, ordinal = 4, at = @At(value = "FIELD", + target = "Lnet/minecraft/client/renderer/RenderBlocks;enableAO:Z", + opcode = Opcodes.GETFIELD, + shift = At.Shift.AFTER)) + private double overwriteUD4(double d4) { + return ModuleConfig.naturalTextures ? mcp$vertexUs[1] : d4; + } + + @ModifyVariable(method = {"renderFaceXNeg", "renderFaceXPos", + "renderFaceYNeg", "renderFaceYPos", + "renderFaceZNeg", "renderFaceZPos"}, + require = 6, ordinal = 5, at = @At(value = "FIELD", + target = "Lnet/minecraft/client/renderer/RenderBlocks;enableAO:Z", + opcode = Opcodes.GETFIELD, + shift = At.Shift.AFTER)) + private double overwriteVD5(double d5) { + return ModuleConfig.naturalTextures ? mcp$vertexVs[0] : d5; + } + + @ModifyVariable(method = {"renderFaceXNeg", "renderFaceXPos", + "renderFaceYNeg", "renderFaceYPos", + "renderFaceZNeg", "renderFaceZPos"}, + require = 6, ordinal = 6, at = @At(value = "FIELD", + target = "Lnet/minecraft/client/renderer/RenderBlocks;enableAO:Z", + opcode = Opcodes.GETFIELD, + shift = At.Shift.AFTER)) + private double overwriteVD6(double d6) { + return ModuleConfig.naturalTextures ? mcp$vertexVs[1] : d6; + } + + @ModifyVariable(method = {"renderFaceXNeg", "renderFaceXPos", + "renderFaceYNeg", "renderFaceYPos", + "renderFaceZNeg", "renderFaceZPos"}, + require = 6, ordinal = 7, at = @At(value = "FIELD", + target = "Lnet/minecraft/client/renderer/RenderBlocks;enableAO:Z", + opcode = Opcodes.GETFIELD, + shift = At.Shift.AFTER)) + private double overwriteUD7(double d7) { + return ModuleConfig.naturalTextures ? mcp$vertexUs[2] : d7; + } + + @ModifyVariable(method = {"renderFaceXNeg", "renderFaceXPos", + "renderFaceYNeg", "renderFaceYPos", + "renderFaceZNeg", "renderFaceZPos"}, + require = 6, ordinal = 8, at = @At(value = "FIELD", + target = "Lnet/minecraft/client/renderer/RenderBlocks;enableAO:Z", + opcode = Opcodes.GETFIELD, + shift = At.Shift.AFTER)) + private double overwriteUD8(double d8) { + return ModuleConfig.naturalTextures ? mcp$vertexUs[3] : d8; + } + + @ModifyVariable(method = {"renderFaceXNeg", "renderFaceXPos", + "renderFaceYNeg", "renderFaceYPos", + "renderFaceZNeg", "renderFaceZPos"}, + require = 6, ordinal = 9, at = @At(value = "FIELD", + target = "Lnet/minecraft/client/renderer/RenderBlocks;enableAO:Z", + opcode = Opcodes.GETFIELD, + shift = At.Shift.AFTER)) + private double overwriteVD9(double d9) { + return ModuleConfig.naturalTextures ? mcp$vertexVs[2] : d9; + } + + @ModifyVariable(method = {"renderFaceXNeg", "renderFaceXPos", + "renderFaceYNeg", "renderFaceYPos", + "renderFaceZNeg", "renderFaceZPos"}, + require = 6, ordinal = 10, at = @At(value = "FIELD", + target = "Lnet/minecraft/client/renderer/RenderBlocks;enableAO:Z", + opcode = Opcodes.GETFIELD, + shift = At.Shift.AFTER)) + private double overwriteVD10(double d10) { + return ModuleConfig.naturalTextures ? mcp$vertexVs[3] : d10; + } + + /** Vines */ + @Expression("(? & 2) != 0") // East + @ModifyExpressionValue(method = "renderBlockVine", require = 1, at = @At(value = "MIXINEXTRAS:EXPRESSION")) + private boolean swizzleVerticesVineXPos(boolean original, Block block, int x, int y, int z, + @Local(ordinal = 0) IIcon texture, @Local Tessellator tessellator, + @Local(ordinal = 0) double minU, @Local(ordinal = 1) double minV, + @Local(ordinal = 2) double maxU, @Local(ordinal = 3) double maxV, + @Local(ordinal = 4) double offset) { + if (!original || !ModuleConfig.naturalTextures) return original; + + mcp$captureVertexes(x, y, z, Side.XPos, texture, minU, minV, maxU, maxV, minU, maxV, maxU, minV); + + // Front face + tessellator.addVertexWithUV(x + offset, y + 1, z + 1, mcp$vertexUs[0], mcp$vertexVs[0]); + tessellator.addVertexWithUV(x + offset, y + 0, z + 1, mcp$vertexUs[2], mcp$vertexVs[2]); + tessellator.addVertexWithUV(x + offset, y + 0, z + 0, mcp$vertexUs[1], mcp$vertexVs[1]); + tessellator.addVertexWithUV(x + offset, y + 1, z + 0, mcp$vertexUs[3], mcp$vertexVs[3]); + + // Back face + tessellator.addVertexWithUV(x + offset, y + 1, z + 0, mcp$vertexUs[3], mcp$vertexVs[3]); + tessellator.addVertexWithUV(x + offset, y + 0, z + 0, mcp$vertexUs[1], mcp$vertexVs[1]); + tessellator.addVertexWithUV(x + offset, y + 0, z + 1, mcp$vertexUs[2], mcp$vertexVs[2]); + tessellator.addVertexWithUV(x + offset, y + 1, z + 1, mcp$vertexUs[0], mcp$vertexVs[0]); + + return false; + } + + @Expression("(? & 8) != 0") // West + @ModifyExpressionValue(method = "renderBlockVine", require = 1, at = @At(value = "MIXINEXTRAS:EXPRESSION")) + private boolean swizzleVerticesVineXNeg(boolean original, Block block, int x, int y, int z, + @Local(ordinal = 0) IIcon texture, @Local Tessellator tessellator, + @Local(ordinal = 0) double minU, @Local(ordinal = 1) double minV, + @Local(ordinal = 2) double maxU, @Local(ordinal = 3) double maxV, + @Local(ordinal = 4) double offset) { + if (!original || !ModuleConfig.naturalTextures) return original; + + mcp$captureVertexes(x, y, z, Side.XNeg, texture, maxU, minV, minU, minV, minU, maxV, maxU, maxV); + + // Front face + tessellator.addVertexWithUV((x + 1) - offset, y + 0, z + 1, mcp$vertexUs[3], mcp$vertexVs[3]); + tessellator.addVertexWithUV((x + 1) - offset, y + 1, z + 1, mcp$vertexUs[0], mcp$vertexVs[0]); + tessellator.addVertexWithUV((x + 1) - offset, y + 1, z + 0, mcp$vertexUs[1], mcp$vertexVs[1]); + tessellator.addVertexWithUV((x + 1) - offset, y + 0, z + 0, mcp$vertexUs[2], mcp$vertexVs[2]); + + // Back face + tessellator.addVertexWithUV((x + 1) - offset, y + 0, z + 0, mcp$vertexUs[2], mcp$vertexVs[2]); + tessellator.addVertexWithUV((x + 1) - offset, y + 1, z + 0, mcp$vertexUs[1], mcp$vertexVs[1]); + tessellator.addVertexWithUV((x + 1) - offset, y + 1, z + 1, mcp$vertexUs[0], mcp$vertexVs[0]); + tessellator.addVertexWithUV((x + 1) - offset, y + 0, z + 1, mcp$vertexUs[3], mcp$vertexVs[3]); + + return false; + } + + @Expression("(? & 4) != 0") // South + @ModifyExpressionValue(method = "renderBlockVine", require = 1, at = @At(value = "MIXINEXTRAS:EXPRESSION")) + private boolean swizzleVerticesVineZPos(boolean original, Block block, int x, int y, int z, + @Local(ordinal = 0) IIcon texture, @Local Tessellator tessellator, + @Local(ordinal = 0) double minU, @Local(ordinal = 1) double minV, + @Local(ordinal = 2) double maxU, @Local(ordinal = 3) double maxV, + @Local(ordinal = 4) double offset) { + if (!original || !ModuleConfig.naturalTextures) return original; + + mcp$captureVertexes(x, y, z, Side.ZPos, texture, minU, minV, maxU, minV, maxU, maxV, minU, maxV); + + // Front face + tessellator.addVertexWithUV(x + 1, y + 0, z + offset, mcp$vertexUs[2], mcp$vertexVs[2]); + tessellator.addVertexWithUV(x + 1, y + 1, z + offset, mcp$vertexUs[1], mcp$vertexVs[1]); + tessellator.addVertexWithUV(x + 0, y + 1, z + offset, mcp$vertexUs[0], mcp$vertexVs[0]); + tessellator.addVertexWithUV(x + 0, y + 0, z + offset, mcp$vertexUs[3], mcp$vertexVs[3]); + + // Back face + tessellator.addVertexWithUV(x + 0, y + 0, z + offset, mcp$vertexUs[3], mcp$vertexVs[3]); + tessellator.addVertexWithUV(x + 0, y + 1, z + offset, mcp$vertexUs[0], mcp$vertexVs[0]); + tessellator.addVertexWithUV(x + 1, y + 1, z + offset, mcp$vertexUs[1], mcp$vertexVs[1]); + tessellator.addVertexWithUV(x + 1, y + 0, z + offset, mcp$vertexUs[2], mcp$vertexVs[2]); + + return false; + } + + @Expression("(? & 1) != 0") // North + @ModifyExpressionValue(method = "renderBlockVine", require = 1, at = @At(value = "MIXINEXTRAS:EXPRESSION")) + private boolean swizzleVerticesVineZNeg(boolean original, Block block, int x, int y, int z, + @Local(ordinal = 0) IIcon texture, @Local Tessellator tessellator, + @Local(ordinal = 0) double minU, @Local(ordinal = 1) double minV, + @Local(ordinal = 2) double maxU, @Local(ordinal = 3) double maxV, + @Local(ordinal = 4) double offset) { + if (!original || !ModuleConfig.naturalTextures) return original; + + mcp$captureVertexes(x, y, z, Side.ZNeg, texture, maxU, maxV, minU, maxV, minU, minV, maxU, minV); + + // Front face + tessellator.addVertexWithUV(x + 1, y + 1, (z + 1) - offset, mcp$vertexUs[2], mcp$vertexVs[2]); + tessellator.addVertexWithUV(x + 1, y + 0, (z + 1) - offset, mcp$vertexUs[1], mcp$vertexVs[1]); + tessellator.addVertexWithUV(x + 0, y + 0, (z + 1) - offset, mcp$vertexUs[0], mcp$vertexVs[0]); + tessellator.addVertexWithUV(x + 0, y + 1, (z + 1) - offset, mcp$vertexUs[3], mcp$vertexVs[3]); + + // Back face + tessellator.addVertexWithUV(x + 0, y + 1, (z + 1) - offset, mcp$vertexUs[3], mcp$vertexVs[3]); + tessellator.addVertexWithUV(x + 0, y + 0, (z + 1) - offset, mcp$vertexUs[0], mcp$vertexVs[0]); + tessellator.addVertexWithUV(x + 1, y + 0, (z + 1) - offset, mcp$vertexUs[1], mcp$vertexVs[1]); + tessellator.addVertexWithUV(x + 1, y + 1, (z + 1) - offset, mcp$vertexUs[2], mcp$vertexVs[2]); + + return false; + } + + @Definition(id = "blockAccess", field = "Lnet/minecraft/client/renderer/RenderBlocks;blockAccess:Lnet/minecraft/world/IBlockAccess;") + @Definition(id = "getBlock", method = "Lnet/minecraft/world/IBlockAccess;getBlock(III)Lnet/minecraft/block/Block;") + @Definition(id = "isBlockNormalCube", method = "Lnet/minecraft/block/Block;isBlockNormalCube()Z") + @Expression("this.blockAccess.getBlock(?, ?, ?).isBlockNormalCube()") // North + @ModifyExpressionValue(method = "renderBlockVine", + require = 1, + at = @At(value = "MIXINEXTRAS:EXPRESSION")) + private boolean swizzleVerticesVineYNeg(boolean original, Block block, int x, int y, int z, + @Local(ordinal = 0) IIcon texture, @Local Tessellator tessellator, + @Local(ordinal = 0) double minU, @Local(ordinal = 1) double minV, + @Local(ordinal = 2) double maxU, @Local(ordinal = 3) double maxV, + @Local(ordinal = 4) double offset) { + if (!original || !ModuleConfig.naturalTextures) return original; + + mcp$captureVertexes(x, y, z, Side.YNeg, texture, maxU, maxV, minU, maxV, minU, minV, maxU, minV); + + // Front face (bottom facing vine has no backface) + tessellator.addVertexWithUV(x + 1, (y + 1) - offset, z + 0, mcp$vertexUs[2], mcp$vertexVs[2]); + tessellator.addVertexWithUV(x + 1, (y + 1) - offset, z + 1, mcp$vertexUs[1], mcp$vertexVs[1]); + tessellator.addVertexWithUV(x + 0, (y + 1) - offset, z + 1, mcp$vertexUs[0], mcp$vertexVs[0]); + tessellator.addVertexWithUV(x + 0, (y + 1) - offset, z + 0, mcp$vertexUs[3], mcp$vertexVs[3]); + + return false; + } + + /** Crossed Squares */ + @WrapOperation( + method = "drawCrossedSquares", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;addVertexWithUV(DDDDD)V")) + private void swizzleCrossedSquareVertexUV(Tessellator instance, double x, double y, double z, double u, double v, + Operation original, @Local(argsOnly = true) IIcon texture, + @Local(ordinal = 3) double minU, @Local(ordinal = 4) double minV, + @Local(ordinal = 5) double maxU, @Local(ordinal = 6) double maxV) { + if (!ModuleConfig.naturalTextures) { + original.call(instance, x, y, z, u, v); + return; + } + + int vertIndex = mcp$vertexCounter % 4; + if (vertIndex == 0) { // First vertex, rotate UVs for the next 4 calls + + // Since the quads are diagonal, the choice of side is arbitrary + mcp$captureVertexes((int) x, (int) y, (int) z, Side.YPos, texture, minU, maxV, maxU, maxV, maxU, minV, minU, minV); + + double swap = mcp$vertexUs[0]; + mcp$vertexUs[0] = mcp$vertexUs[3]; + mcp$vertexUs[3] = mcp$vertexUs[2]; + mcp$vertexUs[2] = mcp$vertexUs[1]; + mcp$vertexUs[1] = swap; + + swap = mcp$vertexVs[0]; + mcp$vertexVs[0] = mcp$vertexVs[3]; + mcp$vertexVs[3] = mcp$vertexVs[2]; + mcp$vertexVs[2] = mcp$vertexVs[1]; + mcp$vertexVs[1] = swap; + } + + original.call(instance, x, y, z, mcp$vertexUs[vertIndex], mcp$vertexVs[vertIndex]); + mcp$vertexCounter++; + } + + /** Crops */ + @WrapOperation( + method = "renderBlockCropsImpl", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;addVertexWithUV(DDDDD)V")) + private void swizzleCropVertexUV(Tessellator instance, double x, double y, double z, double u, double v, + Operation original, @Local IIcon texture, + @Local(ordinal = 3) double minU, @Local(ordinal = 4) double minV, + @Local(ordinal = 5) double maxU, @Local(ordinal = 6) double maxV) { + if (!ModuleConfig.naturalTextures) { + original.call(instance, x, y, z, u, v); + return; + } + + if (mcp$vertexCounter % 8 == 0) { // First vertex, rotate UVs for the next 8 calls (front and back quads for each side) + + Side side = Side.values()[((mcp$vertexCounter / 8 + 2) % 4) + 2]; + + mcp$captureVertexes((int) x, (int) y, (int) z, side, texture, minU, maxV, maxU, maxV, maxU, minV, minU, minV); + + double swap = mcp$vertexUs[0]; + mcp$vertexUs[0] = mcp$vertexUs[3]; + mcp$vertexUs[3] = mcp$vertexUs[2]; + mcp$vertexUs[2] = mcp$vertexUs[1]; + mcp$vertexUs[1] = swap; + + swap = mcp$vertexVs[0]; + mcp$vertexVs[0] = mcp$vertexVs[3]; + mcp$vertexVs[3] = mcp$vertexVs[2]; + mcp$vertexVs[2] = mcp$vertexVs[1]; + mcp$vertexVs[1] = swap; + } + + int vertIndex = mcp$vertexCounter % 4; + original.call(instance, x, y, z, mcp$vertexUs[vertIndex], mcp$vertexVs[vertIndex]); + mcp$vertexCounter++; + } +} diff --git a/src/main/java/com/falsepattern/mcpatcher/internal/mixin/mixins/client/natural/TextureMapMixin.java b/src/main/java/com/falsepattern/mcpatcher/internal/mixin/mixins/client/natural/TextureMapMixin.java new file mode 100644 index 0000000..2dc70c9 --- /dev/null +++ b/src/main/java/com/falsepattern/mcpatcher/internal/mixin/mixins/client/natural/TextureMapMixin.java @@ -0,0 +1,44 @@ +/* + * Right Proper MCPatcher + * + * Copyright (C) 2025 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, only version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.falsepattern.mcpatcher.internal.mixin.mixins.client.natural; + +import com.falsepattern.mcpatcher.internal.modules.natural.NaturalTexturesEngine; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.client.renderer.texture.TextureMap; + +@Mixin(TextureMap.class) +public class TextureMapMixin { + + @Inject(method = "registerIcons", + at = @At(value = "INVOKE", + target = "Lnet/minecraft/client/renderer/entity/RenderManager;updateIcons(Lnet/minecraft/client/renderer/texture/IIconRegister;)V", + shift = At.Shift.AFTER), + require = 1) + private void updateIconsNaturalTextures(CallbackInfo ci) { + NaturalTexturesEngine.reloadNaturalTextureResources(); + } +} diff --git a/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/Axis.java b/src/main/java/com/falsepattern/mcpatcher/internal/modules/common/Axis.java similarity index 93% rename from src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/Axis.java rename to src/main/java/com/falsepattern/mcpatcher/internal/modules/common/Axis.java index 1d4a756..6337394 100644 --- a/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/Axis.java +++ b/src/main/java/com/falsepattern/mcpatcher/internal/modules/common/Axis.java @@ -20,7 +20,7 @@ * along with this program. If not, see . */ -package com.falsepattern.mcpatcher.internal.modules.ctm; +package com.falsepattern.mcpatcher.internal.modules.common; public enum Axis { Y, diff --git a/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/Side.java b/src/main/java/com/falsepattern/mcpatcher/internal/modules/common/Side.java similarity index 88% rename from src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/Side.java rename to src/main/java/com/falsepattern/mcpatcher/internal/modules/common/Side.java index 7d5ae12..f69cf56 100644 --- a/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/Side.java +++ b/src/main/java/com/falsepattern/mcpatcher/internal/modules/common/Side.java @@ -20,16 +20,16 @@ * along with this program. If not, see . */ -package com.falsepattern.mcpatcher.internal.modules.ctm; +package com.falsepattern.mcpatcher.internal.modules.common; import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.Nullable; import net.minecraftforge.common.util.ForgeDirection; -import static com.falsepattern.mcpatcher.internal.modules.ctm.Axis.X; -import static com.falsepattern.mcpatcher.internal.modules.ctm.Axis.Y; -import static com.falsepattern.mcpatcher.internal.modules.ctm.Axis.Z; +import static com.falsepattern.mcpatcher.internal.modules.common.Axis.X; +import static com.falsepattern.mcpatcher.internal.modules.common.Axis.Y; +import static com.falsepattern.mcpatcher.internal.modules.common.Axis.Z; /** diff --git a/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/CTMEngine.java b/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/CTMEngine.java index 9422312..71adad1 100644 --- a/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/CTMEngine.java +++ b/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/CTMEngine.java @@ -23,9 +23,11 @@ package com.falsepattern.mcpatcher.internal.modules.ctm; import com.falsepattern.mcpatcher.Tags; +import com.falsepattern.mcpatcher.internal.modules.common.Axis; import com.falsepattern.mcpatcher.internal.modules.common.Identity2ObjectHashMap; import com.falsepattern.mcpatcher.internal.modules.common.MCPMath; import com.falsepattern.mcpatcher.internal.modules.common.ResourceScanner; +import com.falsepattern.mcpatcher.internal.modules.common.Side; import com.falsepattern.mcpatcher.internal.modules.overlay.ResourceGenerator; import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; diff --git a/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/CTMInfo.java b/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/CTMInfo.java index aa836f2..fb991db 100644 --- a/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/CTMInfo.java +++ b/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/CTMInfo.java @@ -25,6 +25,7 @@ import com.falsepattern.mcpatcher.internal.modules.common.CommonParser; import com.falsepattern.mcpatcher.internal.modules.common.IntRange; import com.falsepattern.mcpatcher.internal.modules.common.ResourceScanner; +import com.falsepattern.mcpatcher.internal.modules.common.Side; import com.falsepattern.mcpatcher.internal.modules.common.WeightedRandom; import com.falsepattern.mcpatcher.internal.modules.overlay.ResourceGenerator; import it.unimi.dsi.fastutil.ints.IntList; diff --git a/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/CTMParser.java b/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/CTMParser.java index 9dcc871..cc92681 100644 --- a/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/CTMParser.java +++ b/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/CTMParser.java @@ -23,6 +23,7 @@ package com.falsepattern.mcpatcher.internal.modules.ctm; import com.falsepattern.mcpatcher.internal.modules.common.CommonParser; +import com.falsepattern.mcpatcher.internal.modules.common.Side; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectList; import lombok.val; diff --git a/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/PaneRenderHelper.java b/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/PaneRenderHelper.java index 71f937f..26afd1a 100644 --- a/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/PaneRenderHelper.java +++ b/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/PaneRenderHelper.java @@ -24,6 +24,7 @@ import com.falsepattern.mcpatcher.internal.Share; import com.falsepattern.mcpatcher.internal.config.ModuleConfig; +import com.falsepattern.mcpatcher.internal.modules.common.Side; import ganymedes01.etfuturum.configuration.configs.ConfigMixins; import lombok.val; diff --git a/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/Symmetry.java b/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/Symmetry.java index 63b33f8..c8adc0a 100644 --- a/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/Symmetry.java +++ b/src/main/java/com/falsepattern/mcpatcher/internal/modules/ctm/Symmetry.java @@ -22,6 +22,8 @@ package com.falsepattern.mcpatcher.internal.modules.ctm; +import com.falsepattern.mcpatcher.internal.modules.common.Side; + public enum Symmetry { None, Opposite, diff --git a/src/main/java/com/falsepattern/mcpatcher/internal/modules/natural/NaturalTexturesEngine.java b/src/main/java/com/falsepattern/mcpatcher/internal/modules/natural/NaturalTexturesEngine.java new file mode 100644 index 0000000..7dd3416 --- /dev/null +++ b/src/main/java/com/falsepattern/mcpatcher/internal/modules/natural/NaturalTexturesEngine.java @@ -0,0 +1,153 @@ +/* + * Right Proper MCPatcher + * + * Copyright (C) 2025 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, only version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.falsepattern.mcpatcher.internal.modules.natural; + +import com.falsepattern.mcpatcher.Tags; +import com.falsepattern.mcpatcher.internal.modules.common.MCPMath; +import com.falsepattern.mcpatcher.internal.modules.common.Side; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; + +import net.minecraft.util.IIcon; +import net.minecraft.util.MathHelper; + +import java.util.Map; + +public class NaturalTexturesEngine { + + static final Logger LOG = LogManager.getLogger(Tags.MOD_NAME + " NaturalTextures"); + + private static Map naturalTexturesInfo = null; + + public static void reloadNaturalTextureResources() { + LOG.debug("Reloading Natural Textures"); + + naturalTexturesInfo = NaturalTexturesParser.parseResourcePacksInOrder(); + } + + /** + * Attempts to apply UV overrides to a quad's texture, based on certain criteria. + * This method mutates the two arrays passed into it. + * @param x The X coordinate of the block + * @param y The Y coordinate of the block + * @param z The Z coordinate of the block + * @param side The side of the block being rendered + * @param texture The texture to be used + * @param vertexUs An array of size 4, containing the U coordinates of each quad vertex + * @param vertexVs An array of size 4, containing the V coordinates of each quad vertex + * For both arrays, the values adhere to the following order, relative to the texture file: + * - Top Left + * - Top Right + * - Bottom Right + * - Bottom Left + */ + public static void applyNaturalTexture(int x, int y, int z, @NotNull Side side, @NotNull IIcon texture, + double[] vertexUs, double[] vertexVs) { + // Ignore blocks rendered in inventory / player's hand + if (x == 0 && y == 0 && z == 0) return; + + String texName = texture.getIconName(); + if (!naturalTexturesInfo.containsKey(texName)) return; + NaturalTexturesInfo texInfo = naturalTexturesInfo.get(texName); + + int rand = getRandom(x, y, z, side.ordinal()); + rotateQuadUVs(texInfo.getRadiansFromRandom(rand), texture, vertexUs, vertexVs); + + int rand2 = getRandom(x, y, z, rand); + if (texInfo.getFlipFromRandom(rand2)) { + mirrorQuadUVs(vertexUs); + } + } + + /** + * Mirror the texture's U or V axis by swizzling the array's coordinates. + * This method mutates the array passed into it. + * @param vertexCoords An array of size 4, containing either the U or V coordinates of each quad vertex + * The array values should adhere to the following order, relative to the texture file: + * - Top Left + * - Top Right + * - Bottom Right + * - Bottom Left + */ + private static void mirrorQuadUVs(double[] vertexCoords) { + double swap = vertexCoords[0]; + vertexCoords[0] = vertexCoords[1]; + vertexCoords[1] = swap; + + swap = vertexCoords[2]; + vertexCoords[2] = vertexCoords[3]; + vertexCoords[3] = swap; + } + + /** + * Rotates a quad's texture clockwise by an amount in radians. This method is not intended to be used for rotating + * at angles other than 0, 90, 180 or 270 degrees, and such rotations will result in skewed UVs and in textures from + * other appearing on the corners of this quad when viewed in-game. + * This method mutates the two arrays passed into it. + * @param rotationAngle The angle, in radians, to rotate the UVs by. + * @param texture The IIcon texture to use when calculating the rotation + * @param vertexUs An array of size 4, containing the U coordinates of each quad vertex + * @param vertexVs An array of size 4, containing the V coordinates of each quad vertex + * For both arrays, the values adhere to the following order, relative to the texture file: + * - Top Left + * - Top Right + * - Bottom Right + * - Bottom Left + */ + private static void rotateQuadUVs(double rotationAngle, IIcon texture, double[] vertexUs, double[] vertexVs) { + if (rotationAngle == 0D) return; + + float lengthU = texture.getMaxU() - texture.getMinU(); + float lengthV = texture.getMaxV() - texture.getMinV(); + + float centerU = texture.getMinU() + lengthU / 2F; + float centerV = texture.getMinV() + lengthV / 2F; + + rotationAngle %= 2D * Math.PI; + + float rotSin = MathHelper.sin((float) rotationAngle); + float rotCos = MathHelper.cos((float) rotationAngle); + + double aspectU = Math.abs(rotSin * ((lengthV / lengthU) - 1D)) + 1D; + double aspectV = 1F / aspectU; + + // Rotate + for (int i = 0; i < 4; i++) { + double deltaU = (vertexUs[i] - centerU) * aspectU; + double deltaV = (vertexVs[i] - centerV) * aspectV; + + vertexUs[i] = (rotCos * deltaU) + (rotSin * deltaV) + centerU; + vertexVs[i] = (rotCos * deltaV) - (rotSin * deltaU) + centerV; + } + + } + + private static int getRandom(int x , int y, int z, int salt) { + int rand = MCPMath.intHash(salt + 37); + rand = MCPMath.intHash(rand + x); + rand = MCPMath.intHash(rand + z); + rand = MCPMath.intHash(rand + y); + return rand & Integer.MAX_VALUE; + } +} diff --git a/src/main/java/com/falsepattern/mcpatcher/internal/modules/natural/NaturalTexturesInfo.java b/src/main/java/com/falsepattern/mcpatcher/internal/modules/natural/NaturalTexturesInfo.java new file mode 100644 index 0000000..863a854 --- /dev/null +++ b/src/main/java/com/falsepattern/mcpatcher/internal/modules/natural/NaturalTexturesInfo.java @@ -0,0 +1,113 @@ +/* + * Right Proper MCPatcher + * + * Copyright (C) 2025 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, only version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.falsepattern.mcpatcher.internal.modules.natural; + +import lombok.Getter; +import lombok.experimental.Accessors; +import org.jetbrains.annotations.Nullable; + +import static com.falsepattern.mcpatcher.internal.modules.natural.NaturalTexturesEngine.LOG; + +@Getter +@Accessors(fluent = true, + chain = false) +public class NaturalTexturesInfo { + public enum UVRotation { + None, + Two, // 180 degrees rotations (total: 2) + Four, // 90 degrees rotations (total: 4) + } + + private static final UVRotation DEFAULT_ROTATION = UVRotation.None; + private static final boolean DEFAULT_FLIP_HORIZONTALLY = false; + + private final UVRotation rotation; + private final boolean flipHorizontally; + + public NaturalTexturesInfo(@Nullable String propString) { + if(propString == null) { + rotation = DEFAULT_ROTATION; + flipHorizontally = DEFAULT_FLIP_HORIZONTALLY; + return; + } + + propString = propString.trim().toUpperCase(); + + switch (propString) { + case "0": + rotation = UVRotation.None; + flipHorizontally = false; + break; + + case "2": + rotation = UVRotation.Two; + flipHorizontally = DEFAULT_FLIP_HORIZONTALLY; + break; + + case "4": + rotation = UVRotation.Four; + flipHorizontally = DEFAULT_FLIP_HORIZONTALLY; + break; + + case "F": + rotation = DEFAULT_ROTATION; + flipHorizontally = true; + break; + + case "2F": + case "F2": + rotation = UVRotation.Two; + flipHorizontally = true; + break; + + case "4F": + case "F4": + rotation = UVRotation.Four; + flipHorizontally = true; + break; + + default: + LOG.warn("Unknown pattern in natural.properties: [pattern={}]", propString); + rotation = DEFAULT_ROTATION; + flipHorizontally = DEFAULT_FLIP_HORIZONTALLY; + break; + } + } + + public final boolean getFlipFromRandom(int rand) { + return flipHorizontally && (rand & 1) == 0; + } + + public final double getRadiansFromRandom(int rand) { + switch (rotation) { + case Two: + return (rand % 2) * Math.PI; + case Four: + return (rand % 4) * Math.PI / 2D; + case None: + default: + return 0D; + } + } +} + diff --git a/src/main/java/com/falsepattern/mcpatcher/internal/modules/natural/NaturalTexturesParser.java b/src/main/java/com/falsepattern/mcpatcher/internal/modules/natural/NaturalTexturesParser.java new file mode 100644 index 0000000..4bc9abe --- /dev/null +++ b/src/main/java/com/falsepattern/mcpatcher/internal/modules/natural/NaturalTexturesParser.java @@ -0,0 +1,130 @@ +/* + * Right Proper MCPatcher + * + * Copyright (C) 2025 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, only version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.falsepattern.mcpatcher.internal.modules.natural; + +import com.falsepattern.mcpatcher.internal.config.ExtraConfig; +import com.falsepattern.mcpatcher.internal.modules.common.ResourceScanner; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.val; + +import net.minecraft.util.ResourceLocation; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Comparator; +import java.util.Map; +import java.util.regex.Pattern; + +import static com.falsepattern.mcpatcher.internal.modules.natural.NaturalTexturesEngine.LOG; + +public class NaturalTexturesParser { + private static Pattern emptyLinePattern; + private static Pattern commentLinePattern; + private static Pattern entryLinePattern; + + public static Map parseResourcePacksInOrder() { + val map = new Object2ObjectOpenHashMap(); + val packs = ResourceScanner.resourcePacks(); + + compilePatterns(); + for (val pack : packs) { + if (pack == null) { + continue; + } + val names = ResourceScanner.collectFiles(pack, "", "natural.properties", false); + names.sort(Comparator.naturalOrder()); + for (val name : names) { + // Prioritize mcpatcher path first, then optifine path for backwards compatibility + if (!(name.equals("mcpatcher/natural.properties") || name.equals("optifine/natural.properties"))) { + continue; + } + + val loc = new ResourceLocation(name); + + try (val br = new BufferedReader(new InputStreamReader(pack.getInputStream(loc)))) { + String line; + while ((line = br.readLine()) != null) { + parseLine(line, map); + } + + LOG.debug("Loaded natural textures resource {} from pack {}", name, pack.getPackName()); + + if(!ExtraConfig.naturalTexturesStack) { + return map; + } + } catch (IOException ignored) { + LOG.warn("Failed to read natural textures resource {} from pack {}. Skipping...", name, pack.getPackName()); + } + } + } + clearPatterns(); + + return map; + } + + private static void compilePatterns() { + emptyLinePattern = Pattern.compile("^\\s+$"); + commentLinePattern = Pattern.compile("(?s)^\\s*[#!].*$"); + + // Capture anything that looks like: "(some_characters)=(some_characters)", + // with optional spaces around (and inside) the two capture groups. + entryLinePattern = Pattern.compile( + "^\\s*([\\S&&[^=]]|[\\S&&[^=]][^=]*[\\S&&[^=]])\\s*=\\s*([\\S&&[^=]]|[\\S&&[^=]][^=]*[\\S&&[^=]])\\s*$"); + } + + private static void clearPatterns() { + emptyLinePattern = null; + commentLinePattern = null; + entryLinePattern = null; + } + + private static void parseLine(String line, Map map) { + // Is empty line + if (line.isEmpty() || emptyLinePattern.matcher(line).matches()) return; + + // Is a comment line + if (commentLinePattern.matcher(line).matches()) return; + + // Capture anything that looks like: "(some_characters)=(some_characters)", + // with optional spaces around (and inside) the two capture groups. + val match = entryLinePattern.matcher(line); + if (match.matches()) { + buildInfo(match.group(1), match.group(2), map); + } else { + LOG.warn("Skipping unparseable line in natural.properties: [line=\"{}\"]", line); + } + } + + private static void buildInfo(String key, String value, Map map) { + if (key.startsWith("minecraft:")) { + key = key.substring(10); + } + if(map.containsKey(key)) { + LOG.debug("Skipping pre-existing in for natural.properties: [entry={}]", key); + return; + } + + map.put(key, new NaturalTexturesInfo(value)); + } +} diff --git a/src/main/resources/assets/mcpatcher/lang/en_US.lang b/src/main/resources/assets/mcpatcher/lang/en_US.lang index f3ffcc1..2870e66 100644 --- a/src/main/resources/assets/mcpatcher/lang/en_US.lang +++ b/src/main/resources/assets/mcpatcher/lang/en_US.lang @@ -5,6 +5,8 @@ config.mcpatcher.00_modules=Modules config.mcpatcher.00_modules.tooltip=Runtime Module Toggles config.mcpatcher.00_modules.connectedTextures=Connected Textures config.mcpatcher.00_modules.connectedTextures.tooltip=Runtime toggle for connected textures.\nRequires connectedTexturesMixins enabled. +config.mcpatcher.00_modules.naturalTextures=Natural Textures +config.mcpatcher.00_modules.naturalTextures.tooltip=Runtime toggle for natural textures.\nRequires naturalTexturesMixins enabled. config.mcpatcher.00_modules.betterGlass=Better Glass config.mcpatcher.00_modules.betterGlass.tooltip=Allow regular glass blocks, glass panes, and beacon glass to have semi-transparent textures.\nRequires betterGlassMixins enabled. config.mcpatcher.00_modules.customItemTextures=Custom Item Textures @@ -18,9 +20,16 @@ config.mcpatcher.01_mixins.resourcePackOverlayMixins=RP Overlay Mixins config.mcpatcher.01_mixins.resourcePackOverlayMixins.tooltip=Resource pack overlay system. Currently used for implementing ctm_compact support.\nDisable if you're getting weird texture loading problems. config.mcpatcher.01_mixins.connectedTexturesMixins=Connected Textures Mixins config.mcpatcher.01_mixins.connectedTexturesMixins.tooltip=Disable this if you don't want connected texture mixins to land.\nThis is required for connectedTextures. +config.mcpatcher.01_mixins.naturalTexturesMixins=Natural Textures Mixins +config.mcpatcher.01_mixins.naturalTexturesMixins.tooltip=Disable this if you don't want natural texture mixins to land.\nThis is required for naturalTextures. config.mcpatcher.01_mixins.betterGlassMixins=Better Glass Mixins config.mcpatcher.01_mixins.betterGlassMixins.tooltip=Disable this if you don't want better glass mixins to land.\nThis is required for betterGlass. config.mcpatcher.01_mixins.customItemTexturesMixins=Custom Item Textures Mixins config.mcpatcher.01_mixins.customItemTexturesMixins.tooltip=Set to Disabled this if you don't want custom item texture mixins to land.\n - Weak: Will still load if some fail to apply\n - Regular: Will crash the game on failure\n - Epic: Uses ASM to rename certain methods\nThis is required for customItemTextures. config.mcpatcher.01_mixins.randomMobsMixins=Random Mobs Mixins -config.mcpatcher.01_mixins.randomMobsMixins.tooltip=Disable this if you don't want random mob mixins to land.\nThis is required for randomMobs. \ No newline at end of file +config.mcpatcher.01_mixins.randomMobsMixins.tooltip=Disable this if you don't want random mob mixins to land.\nThis is required for randomMobs. + +config.mcpatcher.02_extras=Extras +config.mcpatcher.02_extras.tooltip=Additional Runtime Configurations +config.mcpatcher.02_extras.naturalTexturesStack=Stacking Natural Textures +config.mcpatcher.02_extras.naturalTexturesStack.tooltip=Disable this if you don't want Natural Textures configuration files from multiple resource packs to 'stack' on top of each other in order of priority.\nIf true, the total set of Natural Textures will be created using all resource packs in the chain.\nIf false, only the highest priority 'natural.properties' file will be used. \ No newline at end of file diff --git a/src/main/resources/assets/minecraft/mcpatcher/natural.properties b/src/main/resources/assets/minecraft/mcpatcher/natural.properties new file mode 100644 index 0000000..aacc4bb --- /dev/null +++ b/src/main/resources/assets/minecraft/mcpatcher/natural.properties @@ -0,0 +1,131 @@ +# Default configurations for RPMCPatcher's Natural Textures +# +# How this file loads: +# +# For a valid config, the file path relative to the resource packs' root must either be: +# - 'minecraft/mcpatcher/natural.properties' or +# - 'minecraft/optifine/natural.properties' (accepted for backwards compatibility) +# The former is prioritized over the latter. +# Other mods and resource packs may contain their own Natural Textures configuration files, which will be appended +# to the full chain of configurations, based on the load order of each resource pack. +# +# If you'd like for only the highest priority file to be used, set 'naturalTexturesStack' in mcpatcher.cfg to 'false'. +# +# Configuration Format: +# +# ::= +# Omitting ':' is equivalent to the prefix 'minecraft:' +# If a file contains multiple different textures then ':' must be specified +# (For example, applying Natural Textures to IC2's rubber log top would look something like: 'ic2:blockRubWood:0=4F') +# You can add spaces before , after , and before and after the '=' without altering a configured line. +# +# When specifying the path to a texture the '.png' extension should be omitted. +# (For example, adding horizontal flip to Gregtech 5U's basalt: 'gregtech:iconsets/BASALT_STONE=F') +# +# Valid Values: +# 4 = Rotates a texture in 90 degrees increments (4 variations) +# 2 = Rotates a texture in 180 degrees increments (2 variations) +# F = Flips a texture horizontally (2 variations) +# 4F = 90 degrees + horizontal flip (8 variations) +# 2F = 180 degrees + horizontal flip (4 variations) +# 0 = Disables all variations for this texture (blocks changes to this texture from resource packs with lower priority) +# +# A line whose first visible character is '#' or '!' is considered a comment and is ignored by the configuration parser. +# + +# Organic Soils +dirt = 4 +coarse_dirt = 4 +grass_top = 4 +dirt_path_top = 4 +dirt_podzol_top = 4 +mycelium_top = 4 +mud = 2 +packed_mud = 2 + +# Minerals +stone = F +diorite = 2 +bedrock = F +deepslate = F +deepslate_top = 2 +sandstone_top = 4 +red_sandstone_top = 4 +hardened_clay = 2 +hardened_clay_stained_black = 2 +hardened_clay_stained_blue = 2 +hardened_clay_stained_brown = 2 +hardened_clay_stained_cyan = 2 +hardened_clay_stained_gray = 2 +hardened_clay_stained_green = 2 +hardened_clay_stained_light_blue = 2 +hardened_clay_stained_lime = 2 +hardened_clay_stained_magenta = 2 +hardened_clay_stained_orange = 2 +hardened_clay_stained_pink = 2 +hardened_clay_stained_purple = 2 +hardened_clay_stained_red = 2 +hardened_clay_stained_silver = 2 +hardened_clay_stained_white = 2 +hardened_clay_stained_yellow = 2 + +sand = 4 +red_sand = 4 +clay = 2 +gravel = 4F + +black_concrete_powder = 4 +blue_concrete_powder = 4 +brown_concrete_powder = 4 +cyan_concrete_powder = 4 +gray_concrete_powder = 4 +green_concrete_powder = 4 +light_blue_concrete_powder = 4 +light_gray_concrete_powder = 4 +lime_concrete_powder = 4 +magenta_concrete_powder = 4 +orange_concrete_powder = 4 +pink_concrete_powder = 4 +purple_concrete_powder = 4 +red_concrete_powder = 4 +white_concrete_powder = 4 +yellow_concrete_powder = 4 + +# Crops +farmland_dry = 2 +farmland_wet = 2 + +wheat_stage_0 = F +wheat_stage_1 = F +wheat_stage_2 = F +wheat_stage_3 = F +wheat_stage_4 = F +wheat_stage_5 = F +wheat_stage_6 = F +wheat_stage_7 = F + +carrots_stage_0 = F +carrots_stage_1 = F +carrots_stage_2 = F +carrots_stage_3 = F + +potatoes_stage_0 = F +potatoes_stage_1 = F +potatoes_stage_2 = F +potatoes_stage_3 = F + +beetroots_stage0 = F +beetroots_stage1 = F +beetroots_stage2 = F +beetroots_stage3 = F + +# Plants +cactus_top = 4 +cactus_bottom = 4F + +melon_top = 4 +pumpkin_top = 4 + +mushroom_block_skin_red = 4 +mushroom_block_skin_brown = 2 +mushroom_block_inside = 4 \ No newline at end of file