package net.minecraft.client.renderer.block.model; import com.mojang.math.Quadrant; import java.util.ArrayList; import java.util.List; import java.util.Map; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.renderer.block.model.TextureSlots.Data.Builder; import net.minecraft.client.renderer.texture.SpriteContents; import net.minecraft.client.resources.model.Material; import net.minecraft.client.resources.model.ModelBaker; import net.minecraft.client.resources.model.ModelDebugName; import net.minecraft.client.resources.model.ModelState; import net.minecraft.client.resources.model.QuadCollection; import net.minecraft.client.resources.model.SpriteGetter; import net.minecraft.client.resources.model.UnbakedGeometry; import net.minecraft.client.resources.model.UnbakedModel; import net.minecraft.core.Direction; import net.minecraft.resources.ResourceLocation; import org.jetbrains.annotations.Nullable; import org.joml.Vector3f; @Environment(EnvType.CLIENT) public class ItemModelGenerator implements UnbakedModel { public static final ResourceLocation GENERATED_ITEM_MODEL_ID = ResourceLocation.withDefaultNamespace("builtin/generated"); public static final List LAYERS = List.of("layer0", "layer1", "layer2", "layer3", "layer4"); private static final float MIN_Z = 7.5F; private static final float MAX_Z = 8.5F; private static final TextureSlots.Data TEXTURE_SLOTS = new Builder().addReference("particle", "layer0").build(); private static final BlockElementFace.UVs SOUTH_FACE_UVS = new BlockElementFace.UVs(0.0F, 0.0F, 16.0F, 16.0F); private static final BlockElementFace.UVs NORTH_FACE_UVS = new BlockElementFace.UVs(16.0F, 0.0F, 0.0F, 16.0F); @Override public TextureSlots.Data textureSlots() { return TEXTURE_SLOTS; } @Override public UnbakedGeometry geometry() { return ItemModelGenerator::bake; } @Nullable @Override public UnbakedModel.GuiLight guiLight() { return UnbakedModel.GuiLight.FRONT; } private static QuadCollection bake(TextureSlots textureSlots, ModelBaker baker, ModelState modelState, ModelDebugName debugName) { return bake(textureSlots, baker.sprites(), modelState, debugName); } private static QuadCollection bake(TextureSlots textureSlots, SpriteGetter sprites, ModelState modelState, ModelDebugName debugName) { List list = new ArrayList(); for (int i = 0; i < LAYERS.size(); i++) { String string = (String)LAYERS.get(i); Material material = textureSlots.getMaterial(string); if (material == null) { break; } SpriteContents spriteContents = sprites.get(material, debugName).contents(); list.addAll(processFrames(i, string, spriteContents)); } return SimpleUnbakedGeometry.bake(list, textureSlots, sprites, modelState, debugName); } private static List processFrames(int tintIndex, String texture, SpriteContents sprite) { Map map = Map.of( Direction.SOUTH, new BlockElementFace(null, tintIndex, texture, SOUTH_FACE_UVS, Quadrant.R0), Direction.NORTH, new BlockElementFace(null, tintIndex, texture, NORTH_FACE_UVS, Quadrant.R0) ); List list = new ArrayList(); list.add(new BlockElement(new Vector3f(0.0F, 0.0F, 7.5F), new Vector3f(16.0F, 16.0F, 8.5F), map)); list.addAll(createSideElements(sprite, texture, tintIndex)); return list; } private static List createSideElements(SpriteContents sprite, String texture, int tintIndex) { float f = sprite.width(); float g = sprite.height(); List list = new ArrayList(); for (ItemModelGenerator.Span span : getSpans(sprite)) { float h = 0.0F; float i = 0.0F; float j = 0.0F; float k = 0.0F; float l = 0.0F; float m = 0.0F; float n = 0.0F; float o = 0.0F; float p = 16.0F / f; float q = 16.0F / g; float r = span.getMin(); float s = span.getMax(); float t = span.getAnchor(); ItemModelGenerator.SpanFacing spanFacing = span.getFacing(); switch (spanFacing) { case UP: l = r; h = r; j = m = s + 1.0F; n = t; i = t; k = t; o = t + 1.0F; break; case DOWN: n = t; o = t + 1.0F; l = r; h = r; j = m = s + 1.0F; i = t + 1.0F; k = t + 1.0F; break; case LEFT: l = t; h = t; j = t; m = t + 1.0F; o = r; i = r; k = n = s + 1.0F; break; case RIGHT: l = t; m = t + 1.0F; h = t + 1.0F; j = t + 1.0F; o = r; i = r; k = n = s + 1.0F; } h *= p; j *= p; i *= q; k *= q; i = 16.0F - i; k = 16.0F - k; l *= p; m *= p; n *= q; o *= q; Map map = Map.of( spanFacing.getDirection(), new BlockElementFace(null, tintIndex, texture, new BlockElementFace.UVs(l, n, m, o), Quadrant.R0) ); switch (spanFacing) { case UP: list.add(new BlockElement(new Vector3f(h, i, 7.5F), new Vector3f(j, i, 8.5F), map)); break; case DOWN: list.add(new BlockElement(new Vector3f(h, k, 7.5F), new Vector3f(j, k, 8.5F), map)); break; case LEFT: list.add(new BlockElement(new Vector3f(h, i, 7.5F), new Vector3f(h, k, 8.5F), map)); break; case RIGHT: list.add(new BlockElement(new Vector3f(j, i, 7.5F), new Vector3f(j, k, 8.5F), map)); } } return list; } private static List getSpans(SpriteContents sprite) { int i = sprite.width(); int j = sprite.height(); List list = new ArrayList(); sprite.getUniqueFrames().forEach(k -> { for (int l = 0; l < j; l++) { for (int m = 0; m < i; m++) { boolean bl = !isTransparent(sprite, k, m, l, i, j); checkTransition(ItemModelGenerator.SpanFacing.UP, list, sprite, k, m, l, i, j, bl); checkTransition(ItemModelGenerator.SpanFacing.DOWN, list, sprite, k, m, l, i, j, bl); checkTransition(ItemModelGenerator.SpanFacing.LEFT, list, sprite, k, m, l, i, j, bl); checkTransition(ItemModelGenerator.SpanFacing.RIGHT, list, sprite, k, m, l, i, j, bl); } } }); return list; } private static void checkTransition( ItemModelGenerator.SpanFacing spanFacing, List listSpans, SpriteContents contents, int frameIndex, int pixelX, int pixelY, int spriteWidth, int spriteHeight, boolean transparent ) { boolean bl = isTransparent(contents, frameIndex, pixelX + spanFacing.getXOffset(), pixelY + spanFacing.getYOffset(), spriteWidth, spriteHeight) && transparent; if (bl) { createOrExpandSpan(listSpans, spanFacing, pixelX, pixelY); } } private static void createOrExpandSpan(List listSpans, ItemModelGenerator.SpanFacing spanFacing, int pixelX, int pixelY) { ItemModelGenerator.Span span = null; for (ItemModelGenerator.Span span2 : listSpans) { if (span2.getFacing() == spanFacing) { int i = spanFacing.isHorizontal() ? pixelY : pixelX; if (span2.getAnchor() == i) { span = span2; break; } } } int j = spanFacing.isHorizontal() ? pixelY : pixelX; int k = spanFacing.isHorizontal() ? pixelX : pixelY; if (span == null) { listSpans.add(new ItemModelGenerator.Span(spanFacing, k, j)); } else { span.expand(k); } } private static boolean isTransparent(SpriteContents sprite, int frameIndex, int pixelX, int pixelY, int spriteWidth, int spriteHeight) { return pixelX >= 0 && pixelY >= 0 && pixelX < spriteWidth && pixelY < spriteHeight ? sprite.isTransparent(frameIndex, pixelX, pixelY) : true; } @Environment(EnvType.CLIENT) static class Span { private final ItemModelGenerator.SpanFacing facing; private int min; private int max; private final int anchor; public Span(ItemModelGenerator.SpanFacing facing, int minMax, int anchor) { this.facing = facing; this.min = minMax; this.max = minMax; this.anchor = anchor; } public void expand(int pos) { if (pos < this.min) { this.min = pos; } else if (pos > this.max) { this.max = pos; } } public ItemModelGenerator.SpanFacing getFacing() { return this.facing; } public int getMin() { return this.min; } public int getMax() { return this.max; } public int getAnchor() { return this.anchor; } } @Environment(EnvType.CLIENT) static enum SpanFacing { UP(Direction.UP, 0, -1), DOWN(Direction.DOWN, 0, 1), LEFT(Direction.EAST, -1, 0), RIGHT(Direction.WEST, 1, 0); private final Direction direction; private final int xOffset; private final int yOffset; private SpanFacing(final Direction direction, final int xOffset, final int yOffset) { this.direction = direction; this.xOffset = xOffset; this.yOffset = yOffset; } /** * Gets the direction of the block's facing. */ public Direction getDirection() { return this.direction; } public int getXOffset() { return this.xOffset; } public int getYOffset() { return this.yOffset; } boolean isHorizontal() { return this == DOWN || this == UP; } } }