diff --git a/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java index ff5f998..41ec572 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java @@ -1,7 +1,7 @@ package dev.spiritstudios.snapper.gui.screen; import dev.spiritstudios.snapper.Snapper; -import dev.spiritstudios.snapper.util.DynamicTexture; +import dev.spiritstudios.snapper.util.DynamicCubemapTexture; import dev.spiritstudios.snapper.util.SafeFiles; import dev.spiritstudios.snapper.util.SnapperUtil; import net.minecraft.client.MinecraftClient; @@ -13,60 +13,37 @@ import net.minecraft.screen.ScreenTexts; import net.minecraft.text.Text; import net.minecraft.util.Colors; +import net.minecraft.util.Identifier; import net.minecraft.util.Util; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.Unmodifiable; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; import java.util.stream.Stream; public class PanoramaViewerScreen extends Screen { - protected static final CubeMapRenderer PANORAMA_RENDERER = new CubeMapRenderer(Snapper.id("screenshots/panorama/panorama")); + protected static final Identifier ID = Snapper.id("screenshots/panorama"); + protected static final CubeMapRenderer PANORAMA_RENDERER = new CubeMapRenderer(ID); - protected static final RotatingCubeMapRenderer ROTATING_PANORAMA_RENDERER = new RotatingCubeMapRenderer(PANORAMA_RENDERER); - - static { - ROTATING_PANORAMA_RENDERER.registerTextures(MinecraftClient.getInstance().getTextureManager()); - } + private final RotatingCubeMapRenderer rotatingPanoramaRenderer = new RotatingCubeMapRenderer(PANORAMA_RENDERER); + private final DynamicCubemapTexture texture; private final String title; private final Screen parent; - private final List images = new ArrayList<>(); - protected PanoramaViewerScreen(String title, Screen parent) { super(Text.translatable("menu.snapper.viewer_menu")); this.title = title; this.parent = parent; this.client = MinecraftClient.getInstance(); - assert this.client != null; - - List facePaths = this.getImagePaths(); - - if (facePaths == null) { - Snapper.LOGGER.error("No panorama found"); - close(); - return; - } - - for (Path path : facePaths) { - DynamicTexture.createPanoramaFace(this.client.getTextureManager(), path) - .ifPresent(screenshotImage -> { - images.add(screenshotImage); - screenshotImage - .load() - .thenAccept(ignored -> screenshotImage.enableFiltering()); - }); - } + assert client != null; + this.texture = this.getTexture(); } - - private @Nullable @Unmodifiable List getImagePaths() { + @Nullable + private DynamicCubemapTexture getTexture() { Objects.requireNonNull(this.client); Path panoramaDir = SnapperUtil.getConfiguredScreenshotDirectory().resolve("panorama"); @@ -74,12 +51,11 @@ protected PanoramaViewerScreen(String title, Screen parent) { try (Stream stream = Files.list(panoramaDir)) { return stream - .filter(path -> { + .allMatch(path -> { if (Files.isDirectory(path)) return false; return SafeFiles.isContentType(path, "image/png", ".png"); - }) - .toList(); + }) ? DynamicCubemapTexture.createPanorama(ID, panoramaDir).orElse(null) : null; } catch (IOException | NullPointerException e) { Snapper.LOGGER.error("Failed to list the contents of directory", e); return null; @@ -90,8 +66,9 @@ protected PanoramaViewerScreen(String title, Screen parent) { public void close() { Objects.requireNonNull(this.client); - for (DynamicTexture image : images) { - image.close(); + if (texture != null) { + client.getTextureManager().destroyTexture(ID); + texture.close(); } client.setScreen(this.parent); @@ -101,6 +78,13 @@ public void close() { protected void init() { assert client != null; + if (this.texture == null) { + Snapper.LOGGER.error("No panorama found"); + close(); + return; + } + client.getTextureManager().registerTexture(ID, texture); + Path panoramaPath = Path.of(client.runDirectory.getPath(), "screenshots", "panorama"); addDrawableChild(ButtonWidget.builder(Text.translatable("button.snapper.folder"), button -> { Util.getOperatingSystem().open(panoramaPath); @@ -114,7 +98,7 @@ protected void init() { @Override public void render(DrawContext context, int mouseX, int mouseY, float delta) { - ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, true); + rotatingPanoramaRenderer.render(context, this.width, this.height, true); context.drawCenteredTextWithShadow( this.textRenderer, diff --git a/src/client/java/dev/spiritstudios/snapper/util/DynamicCubemapTexture.java b/src/client/java/dev/spiritstudios/snapper/util/DynamicCubemapTexture.java new file mode 100644 index 0000000..a10d17a --- /dev/null +++ b/src/client/java/dev/spiritstudios/snapper/util/DynamicCubemapTexture.java @@ -0,0 +1,61 @@ +package dev.spiritstudios.snapper.util; + +import dev.spiritstudios.snapper.Snapper; +import net.minecraft.client.resource.metadata.TextureResourceMetadata; +import net.minecraft.client.texture.CubemapTexture; +import net.minecraft.client.texture.NativeImage; +import net.minecraft.client.texture.TextureContents; +import net.minecraft.resource.ResourceManager; +import net.minecraft.util.Identifier; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +public class DynamicCubemapTexture extends CubemapTexture { + private static final String[] TEXTURE_SUFFIXES = new String[]{"_1.png", "_3.png", "_5.png", "_4.png", "_0.png", "_2.png"}; + private final Path path; + + public DynamicCubemapTexture(Identifier id, Path path) { + super(id); + this.path = path; + } + + @Override + public TextureContents loadContents(ResourceManager resourceManager) throws IOException { + TextureContents contents; + try (InputStream baseStream = Files.newInputStream(path.resolve("panorama" + TEXTURE_SUFFIXES[0]))) { + NativeImage baseImage = NativeImage.read(baseStream); + int width = baseImage.getWidth(); + int height = baseImage.getHeight(); + NativeImage image = new NativeImage(width, height * 6, false); + baseImage.copyRect(image, 0, 0, 0, 0, width, height, false, true); + + for (int i = 1; i < 6; i++) { + try (InputStream panoramaStream = Files.newInputStream(path.resolve("panorama" + TEXTURE_SUFFIXES[i]))) { + NativeImage panoramaImage = NativeImage.read(panoramaStream); + if (panoramaImage.getWidth() != width || panoramaImage.getHeight() != height) { + Snapper.LOGGER.error("Image dimensions of panorama '{}' sides do not match: part 0 is {}x{}, but part {} is {}x{}", getId(), width, height, i, panoramaImage.getWidth(), panoramaImage.getHeight()); + baseImage.close(); + throw new IOException(); + } + panoramaImage.copyRect(image, 0, 0, 0, i * height, width, height, false, true); + panoramaImage.close(); + } + } + + baseImage.close(); + contents = new TextureContents(image, new TextureResourceMetadata(true, false)); + } + return contents; + } + + public static Optional createPanorama(Identifier id, Path path) { + return Optional.of(new DynamicCubemapTexture( + id, + path + )); + } +} diff --git a/src/client/java/dev/spiritstudios/snapper/util/DynamicTexture.java b/src/client/java/dev/spiritstudios/snapper/util/DynamicTexture.java index b47386c..d8783f4 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/DynamicTexture.java +++ b/src/client/java/dev/spiritstudios/snapper/util/DynamicTexture.java @@ -57,20 +57,6 @@ public static Optional createScreenshot(TextureManager textureMa } } - public static Optional createPanoramaFace(TextureManager textureManager, Path path) { - try { - return Optional.of(new DynamicTexture( - textureManager, - Snapper.id( - "screenshots/panorama/" + Util.replaceInvalidChars(path.getFileName().toString(), Identifier::isPathCharacterValid) - ), - path - )); - } catch (IOException e) { - return Optional.empty(); - } - } - /* * Must be called on render thread */