Skip to content

Commit

Permalink
Fix seams on generated item models (MC-73186) (#437)
Browse files Browse the repository at this point in the history
This PR fixes the large seams on generated item models, fixing
[MC-73186](https://bugs.mojang.com/browse/MC-73186) for these models.
Non-generated models and models which entirely replicate the item model
generation should be unaffected.
  • Loading branch information
XFactHD authored Dec 28, 2023
1 parent 3292a1c commit 77c0a7f
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
--- a/net/minecraft/client/renderer/block/model/ItemModelGenerator.java
+++ b/net/minecraft/client/renderer/block/model/ItemModelGenerator.java
@@ -39,6 +_,8 @@
@@ -32,13 +_,16 @@

Material material = p_111672_.getMaterial(s);
map.put(s, Either.left(material));
- SpriteContents spritecontents = p_111671_.apply(material).contents();
- list.addAll(this.processFrames(i, s, spritecontents));
+ TextureAtlasSprite sprite = p_111671_.apply(material);
+ // Neo: fix MC-73186 on generated item models
+ list.addAll(net.neoforged.neoforge.client.ClientHooks.fixItemModelSeams(this.processFrames(i, s, sprite.contents()), sprite));
}

map.put("particle", p_111672_.hasTexture("particle") ? Either.left(p_111672_.getMaterial("particle")) : map.get("layer0"));
BlockModel blockmodel = new BlockModel(null, list, map, false, p_111672_.getGuiLight(), p_111672_.getTransforms(), p_111672_.getOverrides());
blockmodel.name = p_111672_.name;
Expand Down
40 changes: 40 additions & 0 deletions src/main/java/net/neoforged/neoforge/client/ClientHooks.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,12 @@
import net.minecraft.client.renderer.ShaderInstance;
import net.minecraft.client.renderer.block.BlockRenderDispatcher;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.BlockElement;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.culling.Frustum;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.texture.atlas.SpriteSourceType;
import net.minecraft.client.resources.language.I18n;
import net.minecraft.client.resources.model.BakedModel;
Expand Down Expand Up @@ -1063,6 +1065,44 @@ public static <T extends BlockEntity> boolean isBlockEntityRendererVisible(Block
return renderer != null && frustum.isVisible(renderer.getRenderBoundingBox((T) blockEntity));
}

/**
* Modify the position and UVs of the edge quads of generated item models to account for sprite expansion of the
* front and back quad. Fixes <a href="https://bugs.mojang.com/browse/MC-73186">MC-73186</a> on generated item models.
*
* @param elements The generated elements, may include the front and back face
* @param sprite The texture from which the elements were generated
* @return the original elements list
*/
public static List<BlockElement> fixItemModelSeams(List<BlockElement> elements, TextureAtlasSprite sprite) {
float expand = -sprite.uvShrinkRatio();
for (BlockElement element : elements) {
// Edge elements are guaranteed to have exactly one face, anything else is either invalid or the front/back
if (element.faces.size() != 1) continue;

var faceEntry = element.faces.entrySet().iterator().next();
if (faceEntry.getKey().getAxis() == Direction.Axis.Z) continue;

// Move edge quads to account for sprite expansion of the front and back quads
element.from.x = Mth.clamp(Mth.lerp(expand, element.from.x, 8F), 0F, 16F);
element.from.y = Mth.clamp(Mth.lerp(expand, element.from.y, 8F), 0F, 16F);
element.to.x = Mth.clamp(Mth.lerp(expand, element.to.x, 8F), 0F, 16F);
element.to.y = Mth.clamp(Mth.lerp(expand, element.to.y, 8F), 0F, 16F);

float[] uv = faceEntry.getValue().uv.uvs;
// Counteract sprite expansion on edge quads to ensure alignment with pixels on the front and back quads
if (faceEntry.getKey().getAxis() == Direction.Axis.Y) {
float centerU = (uv[0] + uv[0] + uv[2] + uv[2]) / 4.0F;
uv[0] = Mth.clamp(Mth.lerp(expand, uv[0], centerU), 0F, 16F);
uv[2] = Mth.clamp(Mth.lerp(expand, uv[2], centerU), 0F, 16F);
} else {
float centerV = (uv[1] + uv[1] + uv[3] + uv[3]) / 4.0F;
uv[1] = Mth.clamp(Mth.lerp(expand, uv[1], centerV), 0F, 16F);
uv[3] = Mth.clamp(Mth.lerp(expand, uv[3], centerV), 0F, 16F);
}
}
return elements;
}

// Make sure the below method is only ever called once (by forge).
private static boolean initializedClientHooks = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Functio

if (baseLocation != null && baseSprite != null) {
// Base texture
var unbaked = UnbakedGeometryHelper.createUnbakedItemElements(0, baseSprite.contents());
var unbaked = UnbakedGeometryHelper.createUnbakedItemElements(0, baseSprite);
var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> baseSprite, modelState, modelLocation);
modelBuilder.addQuads(normalRenderTypes, quads);
}
Expand All @@ -124,7 +124,7 @@ public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Functio
if (templateSprite != null) {
// Fluid layer
var transformedState = new SimpleModelState(modelState.getRotation().compose(FLUID_TRANSFORM), modelState.isUvLocked());
var unbaked = UnbakedGeometryHelper.createUnbakedItemMaskElements(1, templateSprite.contents()); // Use template as mask
var unbaked = UnbakedGeometryHelper.createUnbakedItemMaskElements(1, templateSprite); // Use template as mask
var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> fluidSprite, transformedState, modelLocation); // Bake with fluid texture

var emissive = applyFluidLuminosity && fluid.getFluidType().getLightLevel() > 0;
Expand All @@ -140,7 +140,7 @@ public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Functio
if (sprite != null) {
// Cover/overlay
var transformedState = new SimpleModelState(modelState.getRotation().compose(COVER_TRANSFORM), modelState.isUvLocked());
var unbaked = UnbakedGeometryHelper.createUnbakedItemMaskElements(2, coverSprite.contents()); // Use cover as mask
var unbaked = UnbakedGeometryHelper.createUnbakedItemMaskElements(2, coverSprite); // Use cover as mask
var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> sprite, transformedState, modelLocation); // Bake with selected texture
modelBuilder.addQuads(normalRenderTypes, quads);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Functio
CompositeModel.Baked.Builder builder = CompositeModel.Baked.builder(context, particle, overrides, context.getTransforms());
for (int i = 0; i < textures.size(); i++) {
TextureAtlasSprite sprite = spriteGetter.apply(textures.get(i));
var unbaked = UnbakedGeometryHelper.createUnbakedItemElements(i, sprite.contents(), this.layerData.get(i));
var unbaked = UnbakedGeometryHelper.createUnbakedItemElements(i, sprite, this.layerData.get(i));
var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> sprite, modelState, modelLocation);
var renderTypeName = renderTypeNames.get(i);
var renderTypes = renderTypeName != null ? context.getRenderType(renderTypeName) : null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import net.minecraft.client.resources.model.ModelState;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.neoforge.client.ClientHooks;
import net.neoforged.neoforge.client.model.ElementsModel;
import net.neoforged.neoforge.client.model.ExtraFaceData;
import net.neoforged.neoforge.client.model.IModelBuilder;
Expand Down Expand Up @@ -112,10 +113,10 @@ public static BakedModel bake(BlockModel blockModel, ModelBaker modelBaker, Bloc
}

/**
* @see #createUnbakedItemElements(int, SpriteContents, ExtraFaceData)
* @see #createUnbakedItemElements(int, TextureAtlasSprite, ExtraFaceData)
*/
public static List<BlockElement> createUnbakedItemElements(int layerIndex, SpriteContents spriteContents) {
return createUnbakedItemElements(layerIndex, spriteContents, null);
public static List<BlockElement> createUnbakedItemElements(int layerIndex, TextureAtlasSprite sprite) {
return createUnbakedItemElements(layerIndex, sprite, null);
}

/**
Expand All @@ -124,19 +125,20 @@ public static List<BlockElement> createUnbakedItemElements(int layerIndex, Sprit
* <p>
* The {@link Direction#NORTH} and {@link Direction#SOUTH} faces take up the whole surface.
*/
public static List<BlockElement> createUnbakedItemElements(int layerIndex, SpriteContents spriteContents, @Nullable ExtraFaceData faceData) {
var elements = ITEM_MODEL_GENERATOR.processFrames(layerIndex, "layer" + layerIndex, spriteContents);
public static List<BlockElement> createUnbakedItemElements(int layerIndex, TextureAtlasSprite sprite, @Nullable ExtraFaceData faceData) {
var elements = ITEM_MODEL_GENERATOR.processFrames(layerIndex, "layer" + layerIndex, sprite.contents());
ClientHooks.fixItemModelSeams(elements, sprite);
if (faceData != null) {
elements.forEach(element -> element.setFaceData(faceData));
}
return elements;
}

/**
* @see #createUnbakedItemMaskElements(int, SpriteContents, ExtraFaceData)
* @see #createUnbakedItemMaskElements(int, TextureAtlasSprite, ExtraFaceData)
*/
public static List<BlockElement> createUnbakedItemMaskElements(int layerIndex, SpriteContents spriteContents) {
return createUnbakedItemMaskElements(layerIndex, spriteContents, null);
public static List<BlockElement> createUnbakedItemMaskElements(int layerIndex, TextureAtlasSprite sprite) {
return createUnbakedItemMaskElements(layerIndex, sprite, null);
}

/**
Expand All @@ -145,10 +147,11 @@ public static List<BlockElement> createUnbakedItemMaskElements(int layerIndex, S
* <p>
* The {@link Direction#NORTH} and {@link Direction#SOUTH} faces take up only the pixels the texture uses.
*/
public static List<BlockElement> createUnbakedItemMaskElements(int layerIndex, SpriteContents spriteContents, @Nullable ExtraFaceData faceData) {
var elements = createUnbakedItemElements(layerIndex, spriteContents, faceData);
public static List<BlockElement> createUnbakedItemMaskElements(int layerIndex, TextureAtlasSprite sprite, @Nullable ExtraFaceData faceData) {
var elements = createUnbakedItemElements(layerIndex, sprite, faceData);
elements.remove(0); // Remove north and south faces

SpriteContents spriteContents = sprite.contents();
int width = spriteContents.width(), height = spriteContents.height();
var bits = new BitSet(width * height);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
{
"parent": "item/generated",
"parent": "neoforge:item/default",
"loader": "neoforge:fluid_container",
"fluid": "minecraft:empty",
"cover_is_mask": true,
"textures": {
"layer0": "item/bucket"
"base": "item/bucket",
"fluid": "neoforge:item/mask/bucket_fluid_drip",
"cover": "neoforge:item/mask/bucket_fluid_cover_drip"
}
}

0 comments on commit 77c0a7f

Please sign in to comment.