minecraft-src/net/minecraft/client/renderer/texture/TextureAtlas.java
2025-07-04 03:45:38 +03:00

183 lines
6 KiB
Java

package net.minecraft.client.renderer.texture;
import com.mojang.blaze3d.platform.TextureUtil;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.textures.TextureFormat;
import com.mojang.logging.LogUtils;
import java.io.IOException;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@Environment(EnvType.CLIENT)
public class TextureAtlas extends AbstractTexture implements Dumpable, Tickable {
private static final Logger LOGGER = LogUtils.getLogger();
@Deprecated
public static final ResourceLocation LOCATION_BLOCKS = ResourceLocation.withDefaultNamespace("textures/atlas/blocks.png");
@Deprecated
public static final ResourceLocation LOCATION_PARTICLES = ResourceLocation.withDefaultNamespace("textures/atlas/particles.png");
private List<SpriteContents> sprites = List.of();
private List<TextureAtlasSprite.Ticker> animatedTextures = List.of();
private Map<ResourceLocation, TextureAtlasSprite> texturesByName = Map.of();
@Nullable
private TextureAtlasSprite missingSprite;
private final ResourceLocation location;
private final int maxSupportedTextureSize;
private int width;
private int height;
private int mipLevel;
public TextureAtlas(ResourceLocation location) {
this.location = location;
this.maxSupportedTextureSize = RenderSystem.getDevice().getMaxTextureSize();
}
public void upload(SpriteLoader.Preparations preparations) {
LOGGER.info("Created: {}x{}x{} {}-atlas", preparations.width(), preparations.height(), preparations.mipLevel(), this.location);
this.texture = RenderSystem.getDevice()
.createTexture(this.location::toString, TextureFormat.RGBA8, preparations.width(), preparations.height(), preparations.mipLevel() + 1);
this.width = preparations.width();
this.height = preparations.height();
this.mipLevel = preparations.mipLevel();
this.clearTextureData();
this.setFilter(false, this.mipLevel > 1);
this.texturesByName = Map.copyOf(preparations.regions());
this.missingSprite = (TextureAtlasSprite)this.texturesByName.get(MissingTextureAtlasSprite.getLocation());
if (this.missingSprite == null) {
throw new IllegalStateException("Atlas '" + this.location + "' (" + this.texturesByName.size() + " sprites) has no missing texture sprite");
} else {
List<SpriteContents> list = new ArrayList();
List<TextureAtlasSprite.Ticker> list2 = new ArrayList();
for (TextureAtlasSprite textureAtlasSprite : preparations.regions().values()) {
list.add(textureAtlasSprite.contents());
try {
textureAtlasSprite.uploadFirstFrame(this.texture);
} catch (Throwable var9) {
CrashReport crashReport = CrashReport.forThrowable(var9, "Stitching texture atlas");
CrashReportCategory crashReportCategory = crashReport.addCategory("Texture being stitched together");
crashReportCategory.setDetail("Atlas path", this.location);
crashReportCategory.setDetail("Sprite", textureAtlasSprite);
throw new ReportedException(crashReport);
}
TextureAtlasSprite.Ticker ticker = textureAtlasSprite.createTicker();
if (ticker != null) {
list2.add(ticker);
}
}
this.sprites = List.copyOf(list);
this.animatedTextures = List.copyOf(list2);
}
}
@Override
public void dumpContents(ResourceLocation resourceLocation, Path path) throws IOException {
String string = resourceLocation.toDebugFileName();
TextureUtil.writeAsPNG(path, string, this.getTexture(), this.mipLevel, i -> i);
dumpSpriteNames(path, string, this.texturesByName);
}
private static void dumpSpriteNames(Path outputDir, String outputFilename, Map<ResourceLocation, TextureAtlasSprite> sprites) {
Path path = outputDir.resolve(outputFilename + ".txt");
try {
Writer writer = Files.newBufferedWriter(path);
try {
for (Entry<ResourceLocation, TextureAtlasSprite> entry : sprites.entrySet().stream().sorted(Entry.comparingByKey()).toList()) {
TextureAtlasSprite textureAtlasSprite = (TextureAtlasSprite)entry.getValue();
writer.write(
String.format(
Locale.ROOT,
"%s\tx=%d\ty=%d\tw=%d\th=%d%n",
entry.getKey(),
textureAtlasSprite.getX(),
textureAtlasSprite.getY(),
textureAtlasSprite.contents().width(),
textureAtlasSprite.contents().height()
)
);
}
} catch (Throwable var9) {
if (writer != null) {
try {
writer.close();
} catch (Throwable var8) {
var9.addSuppressed(var8);
}
}
throw var9;
}
if (writer != null) {
writer.close();
}
} catch (IOException var10) {
LOGGER.warn("Failed to write file {}", path, var10);
}
}
public void cycleAnimationFrames() {
if (this.texture != null) {
for (TextureAtlasSprite.Ticker ticker : this.animatedTextures) {
ticker.tickAndUpload(this.texture);
}
}
}
@Override
public void tick() {
this.cycleAnimationFrames();
}
public TextureAtlasSprite getSprite(ResourceLocation name) {
TextureAtlasSprite textureAtlasSprite = (TextureAtlasSprite)this.texturesByName.getOrDefault(name, this.missingSprite);
if (textureAtlasSprite == null) {
throw new IllegalStateException("Tried to lookup sprite, but atlas is not initialized");
} else {
return textureAtlasSprite;
}
}
public void clearTextureData() {
this.sprites.forEach(SpriteContents::close);
this.animatedTextures.forEach(TextureAtlasSprite.Ticker::close);
this.sprites = List.of();
this.animatedTextures = List.of();
this.texturesByName = Map.of();
this.missingSprite = null;
}
public ResourceLocation location() {
return this.location;
}
public int maxSupportedTextureSize() {
return this.maxSupportedTextureSize;
}
int getWidth() {
return this.width;
}
int getHeight() {
return this.height;
}
}