minecraft-src/net/minecraft/client/renderer/block/model/ItemModelGenerator.java
2025-07-04 03:45:38 +03:00

310 lines
8.9 KiB
Java

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<String> 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<BlockElement> 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<BlockElement> processFrames(int tintIndex, String texture, SpriteContents sprite) {
Map<Direction, BlockElementFace> 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<BlockElement> 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<BlockElement> createSideElements(SpriteContents sprite, String texture, int tintIndex) {
float f = sprite.width();
float g = sprite.height();
List<BlockElement> 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<Direction, BlockElementFace> 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<ItemModelGenerator.Span> getSpans(SpriteContents sprite) {
int i = sprite.width();
int j = sprite.height();
List<ItemModelGenerator.Span> 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<ItemModelGenerator.Span> 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<ItemModelGenerator.Span> 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;
}
}
}