From 12323be01574a8f9e52aa0768fc2a92113b8d7c8 Mon Sep 17 00:00:00 2001 From: danyadev Date: Thu, 20 Nov 2025 18:04:54 +0300 Subject: [PATCH 1/2] fix texture overwrite and z-fighting for non-ctm voidstone blocks --- .gitignore | 1 + gradle.properties | 2 +- .../client/render/SubmapManagerVoidstone.java | 157 +++++++++++++----- 3 files changed, 120 insertions(+), 40 deletions(-) diff --git a/.gitignore b/.gitignore index e4380deec..046261939 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ whitelist.json src/main/resources/mixins.*.json *.bat layout.json +.DS_Store diff --git a/gradle.properties b/gradle.properties index b5bd7a022..887f20666 100644 --- a/gradle.properties +++ b/gradle.properties @@ -41,7 +41,7 @@ developmentEnvironmentUserName = Developer # Enables using modern Java syntax (up to version 17) via Jabel, while still targeting JVM 8. # See https://github.com/bsideup/jabel for details on how this works. -enableModernJavaSyntax = false +enableModernJavaSyntax = true # Enables injecting missing generics into the decompiled source code for a better coding experience. # Turns most publicly visible List, Map, etc. into proper List, Map types. diff --git a/src/main/java/team/chisel/client/render/SubmapManagerVoidstone.java b/src/main/java/team/chisel/client/render/SubmapManagerVoidstone.java index 2a1f53d97..aadd2a59c 100644 --- a/src/main/java/team/chisel/client/render/SubmapManagerVoidstone.java +++ b/src/main/java/team/chisel/client/render/SubmapManagerVoidstone.java @@ -21,91 +21,166 @@ public class SubmapManagerVoidstone extends SubmapManagerBase { // TODO there must be a better more generic way to do this... @SideOnly(Side.CLIENT) - private class RenderBlocksVoidstone extends RenderBlocksCTM { + private static class RenderBlocksVoidstone extends RenderBlocksCTM { @Override public void renderFaceXNeg(Block block, double x, double y, double z, IIcon icon) { - super.renderFaceXNeg(block, x, y, z, icon); - renderMinX += 0.005; + shiftBoundary(ForgeDirection.WEST); setOverrideBlockTexture(getBase(x, y, z, ForgeDirection.WEST.ordinal())); super.renderFaceXNeg(block, x, y, z, null); clearOverrideBlockTexture(); + resetBoundary(ForgeDirection.WEST); + + super.renderFaceXNeg(block, x, y, z, icon); } @Override public void renderFaceXPos(Block block, double x, double y, double z, IIcon icon) { - super.renderFaceXPos(block, x, y, z, icon); + shiftBoundary(ForgeDirection.EAST); setOverrideBlockTexture(getBase(x, y, z, ForgeDirection.EAST.ordinal())); - renderMaxX -= 0.005; super.renderFaceXPos(block, x, y, z, null); clearOverrideBlockTexture(); + resetBoundary(ForgeDirection.EAST); + + super.renderFaceXPos(block, x, y, z, icon); } @Override public void renderFaceYNeg(Block block, double x, double y, double z, IIcon icon) { - super.renderFaceYNeg(block, x, y, z, icon); + shiftBoundary(ForgeDirection.DOWN); setOverrideBlockTexture(getBase(x, y, z, ForgeDirection.DOWN.ordinal())); - renderMinY += 0.005; super.renderFaceYNeg(block, x, y, z, null); clearOverrideBlockTexture(); + resetBoundary(ForgeDirection.DOWN); + + super.renderFaceYNeg(block, x, y, z, icon); } @Override public void renderFaceYPos(Block block, double x, double y, double z, IIcon icon) { - super.renderFaceYPos(block, x, y, z, icon); + shiftBoundary(ForgeDirection.UP); setOverrideBlockTexture(getBase(x, y, z, ForgeDirection.UP.ordinal())); - renderMaxY -= 0.005; super.renderFaceYPos(block, x, y, z, null); clearOverrideBlockTexture(); + resetBoundary(ForgeDirection.UP); + + super.renderFaceYPos(block, x, y, z, icon); } @Override public void renderFaceZNeg(Block block, double x, double y, double z, IIcon icon) { - super.renderFaceZNeg(block, x, y, z, icon); + shiftBoundary(ForgeDirection.NORTH); setOverrideBlockTexture(getBase(x, y, z, ForgeDirection.NORTH.ordinal())); - renderMinZ += 0.005; super.renderFaceZNeg(block, x, y, z, null); clearOverrideBlockTexture(); + resetBoundary(ForgeDirection.NORTH); + + super.renderFaceZNeg(block, x, y, z, icon); } @Override public void renderFaceZPos(Block block, double x, double y, double z, IIcon icon) { - super.renderFaceZPos(block, x, y, z, icon); + shiftBoundary(ForgeDirection.SOUTH); setOverrideBlockTexture(getBase(x, y, z, ForgeDirection.SOUTH.ordinal())); - renderMaxZ -= 0.005; super.renderFaceZPos(block, x, y, z, null); clearOverrideBlockTexture(); + resetBoundary(ForgeDirection.SOUTH); + + super.renderFaceZPos(block, x, y, z, icon); } - public void reset() { - this.submap = null; + private IIcon getBase(double x, double y, double z, int side) { + return TextureType.getVIcon( + TextureType.V4, + base, + MathHelper.floor_double(x), + MathHelper.floor_double(y), + MathHelper.floor_double(z), + side); } - } - @SideOnly(Side.CLIENT) - private static ThreadLocal renderBlocksThreadLocal; + private void shiftBoundary(ForgeDirection direction) { + // When rendering a CTM overlay texture over a base block texture the base texture starts to z-fight. + // This method makes the base texture render slightly below the overlay. + // This submap check makes sure that we only apply this hack for blocks with CTM overlays + if (submap == null) { + return; + } + + float epsilon = 0.0005F; + + switch (direction) { + case WEST -> { + renderMinX += epsilon; + renderMinX += epsilon; + } + case EAST -> { + renderMinX -= epsilon; + renderMaxX -= epsilon; + } + case DOWN -> { + renderMinY += epsilon; + renderMaxY += epsilon; + } + case UP -> { + renderMinY -= epsilon; + renderMaxY -= epsilon; + } + case NORTH -> { + renderMinZ += epsilon; + renderMaxZ += epsilon; + } + case SOUTH -> { + renderMinZ -= epsilon; + renderMaxZ -= epsilon; + } + } + } - private static void initStatics() { - if (renderBlocksThreadLocal == null) { - renderBlocksThreadLocal = new ThreadLocal<>(); + private void resetBoundary(ForgeDirection direction) { + if (submap == null) { + return; + } + + float epsilon = 0.0005F; + + switch (direction) { + case WEST -> { + renderMinX -= epsilon; + renderMaxX -= epsilon; + } + case EAST -> { + renderMinX += epsilon; + renderMaxX += epsilon; + } + case DOWN -> { + renderMinY -= epsilon; + renderMaxY -= epsilon; + } + case UP -> { + renderMinY += epsilon; + renderMaxY += epsilon; + } + case NORTH -> { + renderMinZ -= epsilon; + renderMaxZ -= epsilon; + } + case SOUTH -> { + renderMinZ += epsilon; + renderMaxZ += epsilon; + } + } } } + @SideOnly(Side.CLIENT) + private static ThreadLocal renderBlocksThreadLocal; + private ISubmapManager overlay; - private TextureSubmap base; - - private IIcon getBase(double x, double y, double z, int side) { - return TextureType.getVIcon( - TextureType.V4, - base, - MathHelper.floor_double(x), - MathHelper.floor_double(y), - MathHelper.floor_double(z), - side); - } + private static TextureSubmap base; - private String texture; - private int meta; + private final String texture; + private final int meta; public SubmapManagerVoidstone(String texture, int meta) { this.texture = texture; @@ -133,18 +208,22 @@ public void registerIcons(String modName, Block block, IIconRegister register) { @Override @SideOnly(Side.CLIENT) public RenderBlocks createRenderContext(RenderBlocks rendererOld, Block block, IBlockAccess world) { - initStatics(); + if (renderBlocksThreadLocal == null) { + renderBlocksThreadLocal = ThreadLocal.withInitial(RenderBlocksVoidstone::new); + } + RenderBlocksVoidstone rb = renderBlocksThreadLocal.get(); - if (rb == null) { - rb = new RenderBlocksVoidstone(); - renderBlocksThreadLocal.set(rb); - } else rb.reset(); RenderBlocks ctx = overlay.createRenderContext(rendererOld, block, world); rb.setRenderBoundsFromBlock(block); + if (ctx instanceof RenderBlocksCTM) { rb.submap = ((RenderBlocksCTM) ctx).submap; rb.submapSmall = ((RenderBlocksCTM) ctx).submapSmall; + } else { + rb.submap = null; + rb.submapSmall = null; } + return rb; } } From 20110c2b18c50cc1a322cc3aba86799d8aece6c1 Mon Sep 17 00:00:00 2001 From: danyadev Date: Thu, 20 Nov 2025 20:29:46 +0300 Subject: [PATCH 2/2] patch CTM renderer to allow rendering an overrided block texture as if it were a CTM texture --- .../client/render/SubmapManagerVoidstone.java | 85 ------------------ .../team/chisel/ctmlib/RenderBlocksCTM.java | 90 +++++++++++++------ 2 files changed, 65 insertions(+), 110 deletions(-) diff --git a/src/main/java/team/chisel/client/render/SubmapManagerVoidstone.java b/src/main/java/team/chisel/client/render/SubmapManagerVoidstone.java index aadd2a59c..49d2d4b57 100644 --- a/src/main/java/team/chisel/client/render/SubmapManagerVoidstone.java +++ b/src/main/java/team/chisel/client/render/SubmapManagerVoidstone.java @@ -25,66 +25,54 @@ private static class RenderBlocksVoidstone extends RenderBlocksCTM { @Override public void renderFaceXNeg(Block block, double x, double y, double z, IIcon icon) { - shiftBoundary(ForgeDirection.WEST); setOverrideBlockTexture(getBase(x, y, z, ForgeDirection.WEST.ordinal())); super.renderFaceXNeg(block, x, y, z, null); clearOverrideBlockTexture(); - resetBoundary(ForgeDirection.WEST); super.renderFaceXNeg(block, x, y, z, icon); } @Override public void renderFaceXPos(Block block, double x, double y, double z, IIcon icon) { - shiftBoundary(ForgeDirection.EAST); setOverrideBlockTexture(getBase(x, y, z, ForgeDirection.EAST.ordinal())); super.renderFaceXPos(block, x, y, z, null); clearOverrideBlockTexture(); - resetBoundary(ForgeDirection.EAST); super.renderFaceXPos(block, x, y, z, icon); } @Override public void renderFaceYNeg(Block block, double x, double y, double z, IIcon icon) { - shiftBoundary(ForgeDirection.DOWN); setOverrideBlockTexture(getBase(x, y, z, ForgeDirection.DOWN.ordinal())); super.renderFaceYNeg(block, x, y, z, null); clearOverrideBlockTexture(); - resetBoundary(ForgeDirection.DOWN); super.renderFaceYNeg(block, x, y, z, icon); } @Override public void renderFaceYPos(Block block, double x, double y, double z, IIcon icon) { - shiftBoundary(ForgeDirection.UP); setOverrideBlockTexture(getBase(x, y, z, ForgeDirection.UP.ordinal())); super.renderFaceYPos(block, x, y, z, null); clearOverrideBlockTexture(); - resetBoundary(ForgeDirection.UP); super.renderFaceYPos(block, x, y, z, icon); } @Override public void renderFaceZNeg(Block block, double x, double y, double z, IIcon icon) { - shiftBoundary(ForgeDirection.NORTH); setOverrideBlockTexture(getBase(x, y, z, ForgeDirection.NORTH.ordinal())); super.renderFaceZNeg(block, x, y, z, null); clearOverrideBlockTexture(); - resetBoundary(ForgeDirection.NORTH); super.renderFaceZNeg(block, x, y, z, icon); } @Override public void renderFaceZPos(Block block, double x, double y, double z, IIcon icon) { - shiftBoundary(ForgeDirection.SOUTH); setOverrideBlockTexture(getBase(x, y, z, ForgeDirection.SOUTH.ordinal())); super.renderFaceZPos(block, x, y, z, null); clearOverrideBlockTexture(); - resetBoundary(ForgeDirection.SOUTH); super.renderFaceZPos(block, x, y, z, icon); } @@ -98,79 +86,6 @@ private IIcon getBase(double x, double y, double z, int side) { MathHelper.floor_double(z), side); } - - private void shiftBoundary(ForgeDirection direction) { - // When rendering a CTM overlay texture over a base block texture the base texture starts to z-fight. - // This method makes the base texture render slightly below the overlay. - // This submap check makes sure that we only apply this hack for blocks with CTM overlays - if (submap == null) { - return; - } - - float epsilon = 0.0005F; - - switch (direction) { - case WEST -> { - renderMinX += epsilon; - renderMinX += epsilon; - } - case EAST -> { - renderMinX -= epsilon; - renderMaxX -= epsilon; - } - case DOWN -> { - renderMinY += epsilon; - renderMaxY += epsilon; - } - case UP -> { - renderMinY -= epsilon; - renderMaxY -= epsilon; - } - case NORTH -> { - renderMinZ += epsilon; - renderMaxZ += epsilon; - } - case SOUTH -> { - renderMinZ -= epsilon; - renderMaxZ -= epsilon; - } - } - } - - private void resetBoundary(ForgeDirection direction) { - if (submap == null) { - return; - } - - float epsilon = 0.0005F; - - switch (direction) { - case WEST -> { - renderMinX -= epsilon; - renderMaxX -= epsilon; - } - case EAST -> { - renderMinX += epsilon; - renderMaxX += epsilon; - } - case DOWN -> { - renderMinY -= epsilon; - renderMaxY -= epsilon; - } - case UP -> { - renderMinY += epsilon; - renderMaxY += epsilon; - } - case NORTH -> { - renderMinZ -= epsilon; - renderMaxZ -= epsilon; - } - case SOUTH -> { - renderMinZ += epsilon; - renderMaxZ += epsilon; - } - } - } } @SideOnly(Side.CLIENT) diff --git a/src/main/java/team/chisel/ctmlib/RenderBlocksCTM.java b/src/main/java/team/chisel/ctmlib/RenderBlocksCTM.java index 29a73ed6e..c2f7b9d1a 100644 --- a/src/main/java/team/chisel/ctmlib/RenderBlocksCTM.java +++ b/src/main/java/team/chisel/ctmlib/RenderBlocksCTM.java @@ -361,6 +361,61 @@ private int avg(int... lightVals) { protected void side(Block block, SubSide side, int iconIndex) { + // If we have an overridden texture when we're supposed to render a CTM texture, + // we'll treat it as a normal block texture but still render it the CTM way: quarter of a texture at a time. + // This way has one benefit: if an overridden texture is a base block texture and a CTM texture (that is being + // rendered next) is an overlay texture above the base, we're avoiding any z-fighting issues because we + // create identical vertices and now only order of rendering dictates which texture will be rendered above + if (hasOverrideBlockTexture()) { + IIcon icon = overrideBlockTexture; + + double u0 = icon.getMinU(); + double u1 = icon.getMaxU(); + double v0 = icon.getMinV(); + double v1 = icon.getMaxV(); + + double uMid = (u0 + u1) / 2; + double vMid = (v0 + v1) / 2; + + // Map all LB-style subsides to bottom-left quarter, RB to bottom-right, RT to top-right, LT to top-left + switch (side) { + // bottom-left quarter + case XNEG_LB, XPOS_LB, YNEG_LB, YPOS_LB, ZNEG_LB, ZPOS_LB -> { + minU = u0; + maxU = uMid; + minV = vMid; + maxV = v1; + } + + // bottom-right quarter + case XNEG_RB, XPOS_RB, YNEG_RB, YPOS_RB, ZNEG_RB, ZPOS_RB -> { + minU = uMid; + maxU = u1; + minV = vMid; + maxV = v1; + } + + // top-right quarter + case XNEG_RT, XPOS_RT, YNEG_RT, YPOS_RT, ZNEG_RT, ZPOS_RT -> { + minU = uMid; + maxU = u1; + minV = v0; + maxV = vMid; + } + + // top-left quarter + case XNEG_LT, XPOS_LT, YNEG_LT, YPOS_LT, ZNEG_LT, ZPOS_LT -> { + minU = u0; + maxU = uMid; + minV = v0; + maxV = vMid; + } + } + + side.render(this); + return; + } + IIcon icon; TextureSubmap map; if (iconIndex >= 16) { @@ -378,25 +433,10 @@ protected void side(Block block, SubSide side, int iconIndex) { icon = map.getSubIcon(x, y); } - double umax = icon.getMaxU(); - double umin = icon.getMinU(); - double vmax = icon.getMaxV(); - double vmin = icon.getMinV(); - - minU = umin; - maxU = umax; - minV = vmin; - maxV = vmax; - - // uCache[0] = umin; - // uCache[1] = umax; - // uCache[2] = umax; - // uCache[3] = umin; - // - // vCache[0] = vmax; - // vCache[1] = vmax; - // vCache[2] = vmin; - // vCache[3] = vmin; + minU = icon.getMinU(); + maxU = icon.getMaxU(); + minV = icon.getMinV(); + maxV = icon.getMaxV(); side.render(this); } @@ -404,7 +444,7 @@ protected void side(Block block, SubSide side, int iconIndex) { @Override public void renderFaceXNeg(Block block, double x, double y, double z, IIcon icon) { pre(ForgeDirection.WEST); - if (!inWorld || hasOverrideBlockTexture() || submap == null) { + if (!inWorld || submap == null) { super.renderFaceXNeg(block, 0, 0, 0, icon); } else { int tex[] = ctm.getSubmapIndices(blockAccess, bx, by, bz, 4); @@ -429,7 +469,7 @@ public void renderFaceXNeg(Block block, double x, double y, double z, IIcon icon @Override public void renderFaceXPos(Block block, double x, double y, double z, IIcon icon) { pre(ForgeDirection.EAST); - if (!inWorld || hasOverrideBlockTexture() || submap == null) { + if (!inWorld || submap == null) { super.renderFaceXPos(block, 0, 0, 0, icon); } else { int tex[] = ctm.getSubmapIndices(blockAccess, bx, by, bz, 5); @@ -454,7 +494,7 @@ public void renderFaceXPos(Block block, double x, double y, double z, IIcon icon @Override public void renderFaceZNeg(Block block, double x, double y, double z, IIcon icon) { pre(ForgeDirection.NORTH); - if (!inWorld || hasOverrideBlockTexture() || submap == null) { + if (!inWorld || submap == null) { super.renderFaceZNeg(block, 0, 0, 0, icon); } else { int tex[] = ctm.getSubmapIndices(blockAccess, bx, by, bz, 2); @@ -479,7 +519,7 @@ public void renderFaceZNeg(Block block, double x, double y, double z, IIcon icon @Override public void renderFaceZPos(Block block, double x, double y, double z, IIcon icon) { pre(ForgeDirection.SOUTH); - if (!inWorld || hasOverrideBlockTexture() || submap == null) { + if (!inWorld || submap == null) { super.renderFaceZPos(block, 0, 0, 0, icon); } else { int tex[] = ctm.getSubmapIndices(blockAccess, bx, by, bz, 3); @@ -504,7 +544,7 @@ public void renderFaceZPos(Block block, double x, double y, double z, IIcon icon @Override public void renderFaceYNeg(Block block, double x, double y, double z, IIcon icon) { pre(ForgeDirection.DOWN); - if (!inWorld || hasOverrideBlockTexture() || submap == null) { + if (!inWorld || submap == null) { super.renderFaceYNeg(block, 0, 0, 0, icon); } else { int tex[] = ctm.getSubmapIndices(blockAccess, bx, by, bz, 0); @@ -529,7 +569,7 @@ public void renderFaceYNeg(Block block, double x, double y, double z, IIcon icon @Override public void renderFaceYPos(Block block, double x, double y, double z, IIcon icon) { pre(ForgeDirection.UP); - if (!inWorld || hasOverrideBlockTexture() || submap == null) { + if (!inWorld || submap == null) { super.renderFaceYPos(block, 0, 0, 0, icon); } else { int tex[] = ctm.getSubmapIndices(blockAccess, bx, by, bz, 1);