minecraft-src/net/minecraft/client/renderer/texture/Stitcher.java
2025-07-04 01:41:11 +03:00

246 lines
6.7 KiB
Java

package net.minecraft.client.renderer.texture;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import org.jetbrains.annotations.Nullable;
@Environment(EnvType.CLIENT)
public class Stitcher<T extends Stitcher.Entry> {
private static final Comparator<Stitcher.Holder<?>> HOLDER_COMPARATOR = Comparator.comparing(holder -> -holder.height)
.thenComparing(holder -> -holder.width)
.thenComparing(holder -> holder.entry.name());
private final int mipLevel;
private final List<Stitcher.Holder<T>> texturesToBeStitched = new ArrayList();
private final List<Stitcher.Region<T>> storage = new ArrayList();
private int storageX;
private int storageY;
private final int maxWidth;
private final int maxHeight;
public Stitcher(int maxWidth, int maxHeight, int mipLevel) {
this.mipLevel = mipLevel;
this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
}
public int getWidth() {
return this.storageX;
}
public int getHeight() {
return this.storageY;
}
public void registerSprite(T stitcherEntry) {
Stitcher.Holder<T> holder = new Stitcher.Holder<>(stitcherEntry, this.mipLevel);
this.texturesToBeStitched.add(holder);
}
public void stitch() {
List<Stitcher.Holder<T>> list = new ArrayList(this.texturesToBeStitched);
list.sort(HOLDER_COMPARATOR);
for (Stitcher.Holder<T> holder : list) {
if (!this.addToStorage(holder)) {
throw new StitcherException(holder.entry, (Collection<Stitcher.Entry>)list.stream().map(holderx -> holderx.entry).collect(ImmutableList.toImmutableList()));
}
}
}
public void gatherSprites(Stitcher.SpriteLoader<T> loader) {
for (Stitcher.Region<T> region : this.storage) {
region.walk(loader);
}
}
static int smallestFittingMinTexel(int dimension, int mipLevel) {
return (dimension >> mipLevel) + ((dimension & (1 << mipLevel) - 1) == 0 ? 0 : 1) << mipLevel;
}
/**
* Attempts to find space for specified {@code holder}.
*
* @return {@code true} if there was space; {@code false} otherwise
*/
private boolean addToStorage(Stitcher.Holder<T> holder) {
for (Stitcher.Region<T> region : this.storage) {
if (region.add(holder)) {
return true;
}
}
return this.expand(holder);
}
/**
* Attempts to expand stitched texture in order to make space for specified {@code holder}.
*
* @return {@code true} if there was enough space to expand the texture; {@code false} otherwise
*/
private boolean expand(Stitcher.Holder<T> holder) {
int i = Mth.smallestEncompassingPowerOfTwo(this.storageX);
int j = Mth.smallestEncompassingPowerOfTwo(this.storageY);
int k = Mth.smallestEncompassingPowerOfTwo(this.storageX + holder.width);
int l = Mth.smallestEncompassingPowerOfTwo(this.storageY + holder.height);
boolean bl = k <= this.maxWidth;
boolean bl2 = l <= this.maxHeight;
if (!bl && !bl2) {
return false;
} else {
boolean bl3 = bl && i != k;
boolean bl4 = bl2 && j != l;
boolean bl5;
if (bl3 ^ bl4) {
bl5 = bl3;
} else {
bl5 = bl && i <= j;
}
Stitcher.Region<T> region;
if (bl5) {
if (this.storageY == 0) {
this.storageY = l;
}
region = new Stitcher.Region<>(this.storageX, 0, k - this.storageX, this.storageY);
this.storageX = k;
} else {
region = new Stitcher.Region<>(0, this.storageY, this.storageX, l - this.storageY);
this.storageY = l;
}
region.add(holder);
this.storage.add(region);
return true;
}
}
@Environment(EnvType.CLIENT)
public interface Entry {
int width();
int height();
ResourceLocation name();
}
@Environment(EnvType.CLIENT)
record Holder<T extends Stitcher.Entry>(T entry, int width, int height) {
public Holder(T entry, int mipLevel) {
this(entry, Stitcher.smallestFittingMinTexel(entry.width(), mipLevel), Stitcher.smallestFittingMinTexel(entry.height(), mipLevel));
}
}
@Environment(EnvType.CLIENT)
public static class Region<T extends Stitcher.Entry> {
private final int originX;
private final int originY;
private final int width;
private final int height;
@Nullable
private List<Stitcher.Region<T>> subSlots;
@Nullable
private Stitcher.Holder<T> holder;
public Region(int originX, int originY, int width, int height) {
this.originX = originX;
this.originY = originY;
this.width = width;
this.height = height;
}
public int getX() {
return this.originX;
}
public int getY() {
return this.originY;
}
public boolean add(Stitcher.Holder<T> holder) {
if (this.holder != null) {
return false;
} else {
int i = holder.width;
int j = holder.height;
if (i <= this.width && j <= this.height) {
if (i == this.width && j == this.height) {
this.holder = holder;
return true;
} else {
if (this.subSlots == null) {
this.subSlots = new ArrayList(1);
this.subSlots.add(new Stitcher.Region(this.originX, this.originY, i, j));
int k = this.width - i;
int l = this.height - j;
if (l > 0 && k > 0) {
int m = Math.max(this.height, k);
int n = Math.max(this.width, l);
if (m >= n) {
this.subSlots.add(new Stitcher.Region(this.originX, this.originY + j, i, l));
this.subSlots.add(new Stitcher.Region(this.originX + i, this.originY, k, this.height));
} else {
this.subSlots.add(new Stitcher.Region(this.originX + i, this.originY, k, j));
this.subSlots.add(new Stitcher.Region(this.originX, this.originY + j, this.width, l));
}
} else if (k == 0) {
this.subSlots.add(new Stitcher.Region(this.originX, this.originY + j, i, l));
} else if (l == 0) {
this.subSlots.add(new Stitcher.Region(this.originX + i, this.originY, k, j));
}
}
for (Stitcher.Region<T> region : this.subSlots) {
if (region.add(holder)) {
return true;
}
}
return false;
}
} else {
return false;
}
}
}
public void walk(Stitcher.SpriteLoader<T> spriteLoader) {
if (this.holder != null) {
spriteLoader.load(this.holder.entry, this.getX(), this.getY());
} else if (this.subSlots != null) {
for (Stitcher.Region<T> region : this.subSlots) {
region.walk(spriteLoader);
}
}
}
public String toString() {
return "Slot{originX="
+ this.originX
+ ", originY="
+ this.originY
+ ", width="
+ this.width
+ ", height="
+ this.height
+ ", texture="
+ this.holder
+ ", subSlots="
+ this.subSlots
+ "}";
}
}
@Environment(EnvType.CLIENT)
public interface SpriteLoader<T extends Stitcher.Entry> {
void load(T entry, int i, int j);
}
}