diff --git a/client/src/main/java/org/geysermc/rainbow/client/render/MinecraftGeometryRenderer.java b/client/src/main/java/org/geysermc/rainbow/client/render/MinecraftGeometryRenderer.java index b86ba75..848e5f7 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/render/MinecraftGeometryRenderer.java +++ b/client/src/main/java/org/geysermc/rainbow/client/render/MinecraftGeometryRenderer.java @@ -1,24 +1,9 @@ package org.geysermc.rainbow.client.render; -import com.mojang.blaze3d.buffers.GpuBuffer; -import com.mojang.blaze3d.platform.NativeImage; -import com.mojang.blaze3d.systems.CommandEncoder; -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.textures.GpuTexture; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.navigation.ScreenRectangle; -import net.minecraft.client.gui.render.pip.OversizedItemRenderer; -import net.minecraft.client.gui.render.state.GuiItemRenderState; -import net.minecraft.client.gui.render.state.GuiRenderState; -import net.minecraft.client.gui.render.state.pip.OversizedItemRenderState; -import net.minecraft.client.renderer.item.TrackingItemStackRenderState; -import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; import org.geysermc.rainbow.mapping.geometry.GeometryRenderer; -import org.geysermc.rainbow.client.mixin.PictureInPictureRendererAccessor; -import org.joml.Matrix3x2fStack; - -import java.util.Objects; +import org.geysermc.rainbow.mapping.texture.TextureHolder; // TODO maybe just use this even for normal 2D items, not sure, could be useful for composite models and stuff // TODO output in a size bedrock likes @@ -26,49 +11,7 @@ public class MinecraftGeometryRenderer implements GeometryRenderer { public static final MinecraftGeometryRenderer INSTANCE = new MinecraftGeometryRenderer(); @Override - public NativeImage render(ItemStack stack) { - TrackingItemStackRenderState itemRenderState = new TrackingItemStackRenderState(); - Minecraft.getInstance().getItemModelResolver().updateForTopItem(itemRenderState, stack, ItemDisplayContext.GUI, null, null, 0); - itemRenderState.setOversizedInGui(true); - - GuiItemRenderState guiItemRenderState = new GuiItemRenderState("geometry_render", new Matrix3x2fStack(16), itemRenderState, 0, 0, null); - ScreenRectangle sizeBounds = guiItemRenderState.oversizedItemBounds(); - Objects.requireNonNull(sizeBounds); - OversizedItemRenderState oversizedRenderState = new OversizedItemRenderState(guiItemRenderState, sizeBounds.left(), sizeBounds.top(), sizeBounds.right() + 4, sizeBounds.bottom() + 4); - - try (OversizedItemRenderer itemRenderer = new OversizedItemRenderer(Minecraft.getInstance().renderBuffers().bufferSource())) { - //noinspection DataFlowIssue - ((PictureInPictureCopyRenderer) itemRenderer).rainbow$allowTextureCopy(); - itemRenderer.prepare(oversizedRenderState, new GuiRenderState(), 4); - return writeToImage(((PictureInPictureRendererAccessor) itemRenderer).getTexture()); - } - } - - // Simplified TextureUtil#writeAsPNG with some modifications to just write to a NativeImage, flip the image and just generate it at full size - private static NativeImage writeToImage(GpuTexture texture) { - RenderSystem.assertOnRenderThread(); - int width = texture.getWidth(0); - int height = texture.getHeight(0); - int bufferSize = texture.getFormat().pixelSize() * width * height; - - GpuBuffer buffer = RenderSystem.getDevice().createBuffer(() -> "Texture output buffer", GpuBuffer.USAGE_COPY_DST | GpuBuffer.USAGE_MAP_READ, bufferSize); - CommandEncoder commandEncoder = RenderSystem.getDevice().createCommandEncoder(); - - NativeImage image = new NativeImage(width, height, false); - Runnable writer = () -> { - try (GpuBuffer.MappedView mappedView = commandEncoder.mapBuffer(buffer, true, false)) { - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - int colour = mappedView.data().getInt((x + y * width) * texture.getFormat().pixelSize()); - image.setPixelABGR(x, height - y - 1, colour); - } - } - } - - buffer.close(); - }; - commandEncoder.copyTextureToBuffer(texture, buffer, 0, writer, 0); - - return image; + public TextureHolder render(ResourceLocation location, ItemStack stack) { + return new RenderedTextureHolder(location, stack); } } diff --git a/client/src/main/java/org/geysermc/rainbow/client/render/RenderedTextureHolder.java b/client/src/main/java/org/geysermc/rainbow/client/render/RenderedTextureHolder.java new file mode 100644 index 0000000..3fe0a8e --- /dev/null +++ b/client/src/main/java/org/geysermc/rainbow/client/render/RenderedTextureHolder.java @@ -0,0 +1,95 @@ +package org.geysermc.rainbow.client.render; + +import com.mojang.blaze3d.buffers.GpuBuffer; +import com.mojang.blaze3d.platform.NativeImage; +import com.mojang.blaze3d.systems.CommandEncoder; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.textures.GpuTexture; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.client.gui.render.pip.OversizedItemRenderer; +import net.minecraft.client.gui.render.state.GuiItemRenderState; +import net.minecraft.client.gui.render.state.GuiRenderState; +import net.minecraft.client.gui.render.state.pip.OversizedItemRenderState; +import net.minecraft.client.renderer.item.TrackingItemStackRenderState; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.ProblemReporter; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.ItemStack; +import org.geysermc.rainbow.RainbowIO; +import org.geysermc.rainbow.client.mixin.PictureInPictureRendererAccessor; +import org.geysermc.rainbow.image.NativeImageUtil; +import org.geysermc.rainbow.mapping.AssetResolver; +import org.geysermc.rainbow.mapping.PackSerializer; +import org.geysermc.rainbow.mapping.texture.TextureHolder; +import org.joml.Matrix3x2fStack; + +import java.nio.file.Path; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public class RenderedTextureHolder extends TextureHolder { + private final ItemStack stackToRender; + + public RenderedTextureHolder(ResourceLocation location, ItemStack stackToRender) { + super(location); + this.stackToRender = stackToRender; + } + + @Override + public Optional load(AssetResolver assetResolver, ProblemReporter reporter) { + throw new UnsupportedOperationException("Rendered texture does not support loading"); + } + + @Override + public CompletableFuture save(AssetResolver assetResolver, PackSerializer serializer, Path path, ProblemReporter reporter) { + TrackingItemStackRenderState itemRenderState = new TrackingItemStackRenderState(); + Minecraft.getInstance().getItemModelResolver().updateForTopItem(itemRenderState, stackToRender, ItemDisplayContext.GUI, null, null, 0); + itemRenderState.setOversizedInGui(true); + + GuiItemRenderState guiItemRenderState = new GuiItemRenderState("geometry_render", new Matrix3x2fStack(16), itemRenderState, 0, 0, null); + ScreenRectangle sizeBounds = guiItemRenderState.oversizedItemBounds(); + Objects.requireNonNull(sizeBounds); + OversizedItemRenderState oversizedRenderState = new OversizedItemRenderState(guiItemRenderState, sizeBounds.left(), sizeBounds.top(), sizeBounds.right() + 4, sizeBounds.bottom() + 4); + + try (OversizedItemRenderer itemRenderer = new OversizedItemRenderer(Minecraft.getInstance().renderBuffers().bufferSource())) { + //noinspection DataFlowIssue + ((PictureInPictureCopyRenderer) itemRenderer).rainbow$allowTextureCopy(); + itemRenderer.prepare(oversizedRenderState, new GuiRenderState(), 4); + writeAsPNG(serializer, path, ((PictureInPictureRendererAccessor) itemRenderer).getTexture()); + } + return CompletableFuture.completedFuture(null); + } + + // Simplified TextureUtil#writeAsPNG with some modifications to flip the image and just generate it at full size + private static void writeAsPNG(PackSerializer serializer, Path path, GpuTexture texture) { + RenderSystem.assertOnRenderThread(); + int width = texture.getWidth(0); + int height = texture.getHeight(0); + int bufferSize = texture.getFormat().pixelSize() * width * height; + + GpuBuffer buffer = RenderSystem.getDevice().createBuffer(() -> "Texture output buffer", GpuBuffer.USAGE_COPY_DST | GpuBuffer.USAGE_MAP_READ, bufferSize); + CommandEncoder commandEncoder = RenderSystem.getDevice().createCommandEncoder(); + + Runnable writer = () -> { + try (GpuBuffer.MappedView mappedView = commandEncoder.mapBuffer(buffer, true, false)) { + RainbowIO.safeIO(() -> { + try (NativeImage image = new NativeImage(width, height, false)) { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int colour = mappedView.data().getInt((x + y * width) * texture.getFormat().pixelSize()); + image.setPixelABGR(x, height - y - 1, colour); + } + } + + serializer.saveTexture(NativeImageUtil.writeToByteArray(image), path); + } + }); + } + + buffer.close(); + }; + commandEncoder.copyTextureToBuffer(texture, buffer, 0, writer, 0); + } +} diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java index b6496d2..de390b3 100644 --- a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java @@ -1,6 +1,7 @@ package org.geysermc.rainbow.datagen; import com.google.common.hash.HashCode; +import com.google.common.hash.Hashing; import com.mojang.serialization.Codec; import net.fabricmc.fabric.api.client.datagen.v1.provider.FabricModelProvider; import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; @@ -125,7 +126,7 @@ public abstract class RainbowModelProvider extends FabricModelProvider { public CompletableFuture saveTexture(byte[] texture, Path path) { return CompletableFuture.runAsync(() -> { try { - output.writeIfNeeded(path, texture, HashCode.fromBytes(texture)); + output.writeIfNeeded(path, texture, Hashing.sha1().hashBytes(texture)); } catch (IOException exception) { LOGGER.error("Failed to save texture to {}", path, exception); } diff --git a/datagen/src/main/resources/fabric.mod.json b/datagen/src/main/resources/fabric.mod.json index 14354a6..e1629a6 100644 --- a/datagen/src/main/resources/fabric.mod.json +++ b/datagen/src/main/resources/fabric.mod.json @@ -2,7 +2,7 @@ "schemaVersion": 1, "id": "rainbow-datagen", "version": "${version}", - "name": "Rainbow", + "name": "Rainbow-datagen", "description": "Rainbow's datagen module", "authors": [ "GeyserMC contributors" diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java index 8d7747a..153140f 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java @@ -128,7 +128,7 @@ public class BedrockItemMapper { bedrockIdentifier = itemModelLocation; } - BedrockGeometryContext geometry = BedrockGeometryContext.create(bedrockIdentifier, context.stack, itemModel, context.packContext); + BedrockGeometryContext geometry = BedrockGeometryContext.create(bedrockIdentifier, itemModel, context.stack, context.packContext); if (context.packContext.reportSuccesses()) { // Not a problem, but just report to get the model printed in the report file context.report("creating mapping for block model " + itemModelLocation); diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java index 96ad637..5507b02 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java @@ -2,9 +2,55 @@ package org.geysermc.rainbow.mapping; import org.geysermc.rainbow.mapping.geometry.GeometryRenderer; import org.geysermc.rainbow.definition.GeyserMappings; +import org.geysermc.rainbow.mapping.geometry.MappedGeometryCache; import org.geysermc.rainbow.pack.PackPaths; import java.util.Optional; -public record PackContext(GeyserMappings mappings, PackPaths paths, BedrockItemConsumer itemConsumer, AssetResolver assetResolver, - Optional geometryRenderer, boolean reportSuccesses) {} +public final class PackContext { + private final GeyserMappings mappings; + private final PackPaths paths; + private final BedrockItemConsumer itemConsumer; + private final AssetResolver assetResolver; + private final Optional geometryRenderer; + private final boolean reportSuccesses; + private final MappedGeometryCache geometryCache = new MappedGeometryCache(); + + public PackContext(GeyserMappings mappings, PackPaths paths, BedrockItemConsumer itemConsumer, AssetResolver assetResolver, + Optional geometryRenderer, boolean reportSuccesses) { + this.mappings = mappings; + this.paths = paths; + this.itemConsumer = itemConsumer; + this.assetResolver = assetResolver; + this.geometryRenderer = geometryRenderer; + this.reportSuccesses = reportSuccesses; + } + + public GeyserMappings mappings() { + return mappings; + } + + public PackPaths paths() { + return paths; + } + + public BedrockItemConsumer itemConsumer() { + return itemConsumer; + } + + public AssetResolver assetResolver() { + return assetResolver; + } + + public Optional geometryRenderer() { + return geometryRenderer; + } + + public boolean reportSuccesses() { + return reportSuccesses; + } + + public MappedGeometryCache geometryCache() { + return geometryCache; + } +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java index 7b9579b..adc493a 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java @@ -10,8 +10,8 @@ import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.item.equipment.Equippable; import org.geysermc.rainbow.mapping.AssetResolver; import org.geysermc.rainbow.mapping.geometry.BedrockGeometryContext; -import org.geysermc.rainbow.mapping.geometry.StitchedGeometry; -import org.geysermc.rainbow.mapping.geometry.TextureHolder; +import org.geysermc.rainbow.mapping.geometry.MappedGeometry; +import org.geysermc.rainbow.mapping.texture.TextureHolder; import org.geysermc.rainbow.pack.attachable.BedrockAttachable; import java.util.List; @@ -23,8 +23,8 @@ public class AttachableMapper { public static AttachableCreator mapItem(AssetResolver assetResolver, BedrockGeometryContext geometryContext, DataComponentPatch components) { // Crazy optional statement // Unfortunately we can't have both equippables and custom models, so we prefer the latter :( - return (bedrockIdentifier, stitchedGeometry, textureConsumer) -> stitchedGeometry - .map(stitched -> BedrockAttachable.geometry(bedrockIdentifier, stitched.geometry().definitions().getFirst(), stitched.stitchedTextures().location().getPath())) + return (bedrockIdentifier, textureConsumer) -> geometryContext.geometry() + .map(geometry -> BedrockAttachable.geometry(bedrockIdentifier, geometry)) .or(() -> Optional.ofNullable(components.get(DataComponents.EQUIPPABLE)) .flatMap(optional -> (Optional) optional) .flatMap(equippable -> equippable.assetId().flatMap(assetResolver::getEquipmentInfo).map(info -> Pair.of(equippable.slot(), info))) @@ -34,7 +34,7 @@ public class AttachableMapper { .filter(assetInfo -> !assetInfo.getSecond().isEmpty()) .map(assetInfo -> { ResourceLocation equipmentTexture = getTexture(assetInfo.getSecond(), getLayer(assetInfo.getFirst())); - textureConsumer.accept(TextureHolder.createFromResources(equipmentTexture)); + textureConsumer.accept(TextureHolder.createBuiltIn(equipmentTexture)); return BedrockAttachable.equipment(bedrockIdentifier, assetInfo.getFirst(), equipmentTexture.getPath()); })) .map(attachable -> { @@ -59,6 +59,6 @@ public class AttachableMapper { @FunctionalInterface public interface AttachableCreator { - Optional create(ResourceLocation bedrockIdentifier, Optional geometry, Consumer textureConsumer); + Optional create(ResourceLocation bedrockIdentifier, Consumer textureConsumer); } } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java index 25730ff..1b57233 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java @@ -1,6 +1,5 @@ package org.geysermc.rainbow.mapping.geometry; -import com.google.common.base.Suppliers; import net.minecraft.client.renderer.block.model.TextureSlots; import net.minecraft.client.resources.model.Material; import net.minecraft.client.resources.model.ResolvedModel; @@ -10,51 +9,41 @@ import org.geysermc.rainbow.Rainbow; import org.geysermc.rainbow.mapping.PackContext; import org.geysermc.rainbow.mapping.animation.AnimationMapper; import org.geysermc.rainbow.mapping.animation.BedrockAnimationContext; -import org.geysermc.rainbow.pack.geometry.BedrockGeometry; +import org.geysermc.rainbow.mapping.texture.TextureHolder; import java.util.List; import java.util.Optional; -import java.util.function.Supplier; import java.util.stream.Stream; -public record BedrockGeometryContext(Optional> geometry, +public record BedrockGeometryContext(Optional geometry, Optional animation, TextureHolder icon, boolean handheld) { private static final List HANDHELD_MODELS = Stream.of("item/handheld", "item/handheld_rod", "item/handheld_mace") .map(ResourceLocation::withDefaultNamespace) .toList(); - public static BedrockGeometryContext create(ResourceLocation bedrockIdentifier, ItemStack stackToRender, ResolvedModel model, PackContext context) { + public static BedrockGeometryContext create(ResourceLocation bedrockIdentifier, ResolvedModel model, ItemStack stackToRender, PackContext context) { ResolvedModel parentModel = model.parent(); // debugName() returns the resource location of the model as a string boolean handheld = parentModel != null && HANDHELD_MODELS.contains(ResourceLocation.parse(parentModel.debugName())); TextureSlots textures = model.getTopTextureSlots(); Material layer0Texture = textures.getMaterial("layer0"); - Optional> geometry; + Optional geometry; Optional animation; TextureHolder icon; if (layer0Texture != null) { geometry = Optional.empty(); animation = Optional.empty(); - icon = TextureHolder.createFromResources(layer0Texture.texture()); + icon = TextureHolder.createBuiltIn(layer0Texture.texture()); } else { // Unknown model (doesn't use layer0), so we immediately assume the geometry is custom // This check should probably be done differently (actually check if the model is 2D or 3D) - ResourceLocation modelLocation = ResourceLocation.parse(model.debugName()); - String safeIdentifier = Rainbow.safeResourceLocation(bedrockIdentifier); - - geometry = Optional.of(Suppliers.memoize(() -> { - StitchedTextures stitchedTextures = StitchedTextures.stitchModelTextures(textures, context); - BedrockGeometry mappedGeometry = GeometryMapper.mapGeometry(safeIdentifier, "bone", model, stitchedTextures); - return new StitchedGeometry(mappedGeometry, TextureHolder.createProvided(modelLocation.withSuffix("_stitched"), stitchedTextures.stitched())); - })); - - animation = Optional.of(AnimationMapper.mapAnimation(safeIdentifier, "bone", model.getTopTransforms())); - icon = context.geometryRenderer().isPresent() ? TextureHolder.createProvided(modelLocation, () -> context.geometryRenderer().orElseThrow().render(stackToRender)) - : TextureHolder.createNonExistent(modelLocation); + geometry = Optional.of(context.geometryCache().mapGeometry(bedrockIdentifier, model, stackToRender, context)); + animation = Optional.of(AnimationMapper.mapAnimation(Rainbow.safeResourceLocation(bedrockIdentifier), "bone", model.getTopTransforms())); + icon = geometry.get().icon(); } return new BedrockGeometryContext(geometry, animation, icon, handheld); diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java index 4ae3d2e..2095477 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java @@ -7,6 +7,7 @@ import net.minecraft.client.renderer.block.model.SimpleUnbakedGeometry; import net.minecraft.client.resources.model.ResolvedModel; import net.minecraft.client.resources.model.UnbakedGeometry; import net.minecraft.core.Direction; +import org.geysermc.rainbow.mapping.texture.StitchedTextures; import org.geysermc.rainbow.mixin.FaceBakeryAccessor; import org.geysermc.rainbow.pack.geometry.BedrockGeometry; import org.joml.Vector2f; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryRenderer.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryRenderer.java index 4607641..d2713b8 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryRenderer.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryRenderer.java @@ -1,9 +1,10 @@ package org.geysermc.rainbow.mapping.geometry; -import com.mojang.blaze3d.platform.NativeImage; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; +import org.geysermc.rainbow.mapping.texture.TextureHolder; public interface GeometryRenderer { - NativeImage render(ItemStack stack); + TextureHolder render(ResourceLocation location, ItemStack stack); } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/MappedGeometry.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/MappedGeometry.java new file mode 100644 index 0000000..9c3e48d --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/MappedGeometry.java @@ -0,0 +1,34 @@ +package org.geysermc.rainbow.mapping.geometry; + +import org.geysermc.rainbow.mapping.PackSerializer; +import org.geysermc.rainbow.mapping.texture.TextureHolder; + +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +public interface MappedGeometry { + + String identifier(); + + TextureHolder stitchedTextures(); + + TextureHolder icon(); + + CompletableFuture save(PackSerializer serializer, Path geometryDirectory, Function> textureSaver); + + default CachedGeometry cachedCopy() { + if (this instanceof CachedGeometry cached) { + return cached; + } + return new CachedGeometry(identifier(), TextureHolder.createCopy(stitchedTextures()), TextureHolder.createCopy(icon())); + } + + record CachedGeometry(String identifier, TextureHolder stitchedTextures, TextureHolder icon) implements MappedGeometry { + + @Override + public CompletableFuture save(PackSerializer serializer, Path geometryDirectory, Function> textureSaver) { + return CompletableFuture.completedFuture(null); + } + } +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/MappedGeometryCache.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/MappedGeometryCache.java new file mode 100644 index 0000000..6814669 --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/MappedGeometryCache.java @@ -0,0 +1,49 @@ +package org.geysermc.rainbow.mapping.geometry; + +import com.google.common.base.Suppliers; +import net.minecraft.client.renderer.block.model.TextureSlots; +import net.minecraft.client.resources.model.Material; +import net.minecraft.client.resources.model.ResolvedModel; +import net.minecraft.client.resources.model.UnbakedGeometry; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import org.geysermc.rainbow.Rainbow; +import org.geysermc.rainbow.mapping.PackContext; +import org.geysermc.rainbow.mapping.texture.StitchedTextures; +import org.geysermc.rainbow.mapping.texture.TextureHolder; +import org.geysermc.rainbow.mixin.TextureSlotsAccessor; +import org.geysermc.rainbow.pack.geometry.BedrockGeometry; + +import java.util.HashMap; +import java.util.Map; + +public class MappedGeometryCache { + private final Map cachedGeometry = new HashMap<>(); + + public MappedGeometry mapGeometry(ResourceLocation bedrockIdentifier, ResolvedModel model, ItemStack stackToRender, PackContext context) { + GeometryCacheKey cacheKey = new GeometryCacheKey(model); + MappedGeometry cached = cachedGeometry.get(cacheKey); + if (cached != null) { + return cached.cachedCopy(); + } + + ResourceLocation modelLocation = ResourceLocation.parse(model.debugName()); + ResourceLocation stitchedTexturesLocation = modelLocation.withSuffix("_stitched"); + String safeIdentifier = Rainbow.safeResourceLocation(bedrockIdentifier); + + StitchedTextures stitchedTextures = StitchedTextures.stitchModelTextures(model.getTopTextureSlots(), context); + BedrockGeometry geometry = GeometryMapper.mapGeometry(safeIdentifier, "bone", model, stitchedTextures); + TextureHolder icon = context.geometryRenderer().isPresent() ? context.geometryRenderer().orElseThrow().render(modelLocation, stackToRender) + : TextureHolder.createNonExistent(modelLocation); + MappedGeometryInstance instance = new MappedGeometryInstance(geometry, TextureHolder.createCustom(stitchedTexturesLocation, stitchedTextures.stitched()), icon); + cachedGeometry.put(cacheKey, instance); + return instance; + } + + private record GeometryCacheKey(UnbakedGeometry geometry, Map textures) { + + private GeometryCacheKey(ResolvedModel model) { + this(model.getTopGeometry(), ((TextureSlotsAccessor) model.getTopTextureSlots()).getResolvedValues()); + } + } +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/MappedGeometryInstance.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/MappedGeometryInstance.java new file mode 100644 index 0000000..956ea5a --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/MappedGeometryInstance.java @@ -0,0 +1,25 @@ +package org.geysermc.rainbow.mapping.geometry; + +import org.geysermc.rainbow.mapping.PackSerializer; +import org.geysermc.rainbow.mapping.texture.TextureHolder; +import org.geysermc.rainbow.pack.geometry.BedrockGeometry; + +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +public record MappedGeometryInstance(BedrockGeometry geometry, TextureHolder stitchedTextures, TextureHolder icon) implements MappedGeometry { + + @Override + public String identifier() { + return geometry.definitions().getFirst().info().identifier(); + } + + @Override + public CompletableFuture save(PackSerializer serializer, Path geometryDirectory, Function> textureSaver) { + return CompletableFuture.allOf( + geometry.save(serializer, geometryDirectory), + textureSaver.apply(stitchedTextures) + ); + } +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedGeometry.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedGeometry.java deleted file mode 100644 index 2ee4f48..0000000 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedGeometry.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.geysermc.rainbow.mapping.geometry; - -import org.geysermc.rainbow.pack.geometry.BedrockGeometry; - -public record StitchedGeometry(BedrockGeometry geometry, TextureHolder stitchedTextures) {} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/TextureHolder.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/TextureHolder.java deleted file mode 100644 index c209741..0000000 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/TextureHolder.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.geysermc.rainbow.mapping.geometry; - -import com.mojang.blaze3d.platform.NativeImage; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.ProblemReporter; -import org.geysermc.rainbow.Rainbow; -import org.geysermc.rainbow.RainbowIO; -import org.geysermc.rainbow.image.NativeImageUtil; -import org.geysermc.rainbow.mapping.AssetResolver; - -import java.io.InputStream; -import java.util.Optional; -import java.util.function.Supplier; - -public record TextureHolder(ResourceLocation location, Optional> supplier, boolean existsInResources) { - - public Optional load(AssetResolver assetResolver, ProblemReporter reporter) { - if (existsInResources) { - return RainbowIO.safeIO(() -> { - try (InputStream texture = assetResolver.openAsset(Rainbow.decorateTextureLocation(location))) { - return texture.readAllBytes(); - } - }); - } else if (supplier.isPresent()) { - return RainbowIO.safeIO(() -> NativeImageUtil.writeToByteArray(supplier.get().get())); - } - reporter.report(() -> "missing texture for " + location + "; please provide it manually"); - return Optional.empty(); - } - - public static TextureHolder createProvided(ResourceLocation location, Supplier supplier) { - return new TextureHolder(location, Optional.of(supplier), false); - } - - public static TextureHolder createFromResources(ResourceLocation location) { - return new TextureHolder(location, Optional.empty(), true); - } - - public static TextureHolder createNonExistent(ResourceLocation location) { - return new TextureHolder(location, Optional.empty(), false); - } -} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/BuiltInTextureHolder.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/BuiltInTextureHolder.java new file mode 100644 index 0000000..b25f067 --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/BuiltInTextureHolder.java @@ -0,0 +1,32 @@ +package org.geysermc.rainbow.mapping.texture; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.ProblemReporter; +import org.geysermc.rainbow.Rainbow; +import org.geysermc.rainbow.RainbowIO; +import org.geysermc.rainbow.mapping.AssetResolver; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.Optional; + +public class BuiltInTextureHolder extends TextureHolder { + private final ResourceLocation source; + + public BuiltInTextureHolder(ResourceLocation location, ResourceLocation source) { + super(location); + this.source = source; + } + + @Override + public Optional load(AssetResolver assetResolver, ProblemReporter reporter) { + return RainbowIO.safeIO(() -> { + try (InputStream texture = assetResolver.openAsset(Rainbow.decorateTextureLocation(source))) { + return texture.readAllBytes(); + } catch (FileNotFoundException | NullPointerException exception) { + reportMissing(reporter); + return null; + } + }); + } +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/CopyTextureHolder.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/CopyTextureHolder.java new file mode 100644 index 0000000..284aab1 --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/CopyTextureHolder.java @@ -0,0 +1,27 @@ +package org.geysermc.rainbow.mapping.texture; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.ProblemReporter; +import org.geysermc.rainbow.mapping.AssetResolver; +import org.geysermc.rainbow.mapping.PackSerializer; + +import java.nio.file.Path; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public class CopyTextureHolder extends TextureHolder { + + public CopyTextureHolder(ResourceLocation location) { + super(location); + } + + @Override + public Optional load(AssetResolver assetResolver, ProblemReporter reporter) { + return Optional.empty(); + } + + @Override + public CompletableFuture save(AssetResolver assetResolver, PackSerializer serializer, Path path, ProblemReporter reporter) { + return CompletableFuture.completedFuture(null); + } +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/CustomTextureHolder.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/CustomTextureHolder.java new file mode 100644 index 0000000..5ebe0dd --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/CustomTextureHolder.java @@ -0,0 +1,25 @@ +package org.geysermc.rainbow.mapping.texture; + +import com.mojang.blaze3d.platform.NativeImage; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.ProblemReporter; +import org.geysermc.rainbow.RainbowIO; +import org.geysermc.rainbow.image.NativeImageUtil; +import org.geysermc.rainbow.mapping.AssetResolver; + +import java.util.Optional; +import java.util.function.Supplier; + +public class CustomTextureHolder extends TextureHolder { + private final Supplier supplier; + + public CustomTextureHolder(ResourceLocation location, Supplier supplier) { + super(location); + this.supplier = supplier; + } + + @Override + public Optional load(AssetResolver assetResolver, ProblemReporter reporter) { + return RainbowIO.safeIO(() -> NativeImageUtil.writeToByteArray(supplier.get())); + } +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/MissingTextureHolder.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/MissingTextureHolder.java new file mode 100644 index 0000000..75154f6 --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/MissingTextureHolder.java @@ -0,0 +1,20 @@ +package org.geysermc.rainbow.mapping.texture; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.ProblemReporter; +import org.geysermc.rainbow.mapping.AssetResolver; + +import java.util.Optional; + +public class MissingTextureHolder extends TextureHolder { + + public MissingTextureHolder(ResourceLocation location) { + super(location); + } + + @Override + public Optional load(AssetResolver assetResolver, ProblemReporter reporter) { + reportMissing(reporter); + return Optional.empty(); + } +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/StitchedTextures.java similarity index 98% rename from rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/StitchedTextures.java index 1ce8e85..fc2aa20 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/StitchedTextures.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.mapping.geometry; +package org.geysermc.rainbow.mapping.texture; import com.mojang.blaze3d.platform.NativeImage; import net.minecraft.Util; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/TextureHolder.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/TextureHolder.java new file mode 100644 index 0000000..e7c821f --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/TextureHolder.java @@ -0,0 +1,56 @@ +package org.geysermc.rainbow.mapping.texture; + +import com.mojang.blaze3d.platform.NativeImage; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.ProblemReporter; +import org.geysermc.rainbow.mapping.AssetResolver; +import org.geysermc.rainbow.mapping.PackSerializer; + +import java.nio.file.Path; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + +public abstract class TextureHolder { + protected final ResourceLocation location; + + public TextureHolder(ResourceLocation location) { + this.location = location; + } + + public abstract Optional load(AssetResolver assetResolver, ProblemReporter reporter); + + public CompletableFuture save(AssetResolver assetResolver, PackSerializer serializer, Path path, ProblemReporter reporter) { + return load(assetResolver, reporter) + .map(bytes -> serializer.saveTexture(bytes, path)) + .orElse(CompletableFuture.completedFuture(null)); + } + + public static TextureHolder createCustom(ResourceLocation location, Supplier supplier) { + return new CustomTextureHolder(location, supplier); + } + + public static TextureHolder createBuiltIn(ResourceLocation location, ResourceLocation source) { + return new BuiltInTextureHolder(location, source); + } + + public static TextureHolder createBuiltIn(ResourceLocation location) { + return createBuiltIn(location, location); + } + + public static TextureHolder createNonExistent(ResourceLocation location) { + return new MissingTextureHolder(location); + } + + public static TextureHolder createCopy(TextureHolder original) { + return new CopyTextureHolder(original.location); + } + + public ResourceLocation location() { + return location; + } + + protected void reportMissing(ProblemReporter reporter) { + reporter.report(() -> "missing texture for " + location + "; please provide it manually"); + } +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java index 5481ac0..d3866a7 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java @@ -5,8 +5,7 @@ import org.geysermc.rainbow.Rainbow; import org.geysermc.rainbow.mapping.PackSerializer; import org.geysermc.rainbow.mapping.attachable.AttachableMapper; import org.geysermc.rainbow.mapping.geometry.BedrockGeometryContext; -import org.geysermc.rainbow.mapping.geometry.StitchedGeometry; -import org.geysermc.rainbow.mapping.geometry.TextureHolder; +import org.geysermc.rainbow.mapping.texture.TextureHolder; import org.geysermc.rainbow.pack.attachable.BedrockAttachable; import java.nio.file.Path; @@ -15,26 +14,19 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.Function; -import java.util.function.Supplier; public record BedrockItem(ResourceLocation identifier, String textureName, BedrockGeometryContext geometryContext, AttachableMapper.AttachableCreator attachableCreator) { public CompletableFuture save(PackSerializer serializer, Path attachableDirectory, Path geometryDirectory, Path animationDirectory, Function> textureSaver) { + List attachableTextures = new ArrayList<>(); + Optional createdAttachable = attachableCreator.create(identifier, attachableTextures::add); return CompletableFuture.allOf( textureSaver.apply(geometryContext.icon()), - CompletableFuture.supplyAsync(() -> geometryContext.geometry().map(Supplier::get)) - .thenCompose(stitchedGeometry -> { - List attachableTextures = new ArrayList<>(); - Optional createdAttachable = attachableCreator.create(identifier, stitchedGeometry, attachableTextures::add); - return CompletableFuture.allOf( - createdAttachable.map(attachable -> attachable.save(serializer, attachableDirectory)).orElse(noop()), - CompletableFuture.allOf(attachableTextures.stream().map(textureSaver).toArray(CompletableFuture[]::new)), - stitchedGeometry.map(StitchedGeometry::geometry).map(geometry -> geometry.save(serializer, geometryDirectory)).orElse(noop()), - stitchedGeometry.map(StitchedGeometry::stitchedTextures).map(textureSaver).orElse(noop()), - geometryContext.animation().map(context -> context.animation().save(serializer, animationDirectory, Rainbow.safeResourceLocation(identifier))).orElse(noop()) - ); - }) + createdAttachable.map(attachable -> attachable.save(serializer, attachableDirectory)).orElse(noop()), + CompletableFuture.allOf(attachableTextures.stream().map(textureSaver).toArray(CompletableFuture[]::new)), + geometryContext.geometry().map(geometry -> geometry.save(serializer, geometryDirectory, textureSaver)).orElse(noop()), + geometryContext.animation().map(context -> context.animation().save(serializer, animationDirectory, Rainbow.safeResourceLocation(identifier))).orElse(noop()) ); } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java index 9a38d69..00b7394 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java @@ -20,7 +20,7 @@ import org.geysermc.rainbow.mapping.PackContext; import org.geysermc.rainbow.mapping.PackSerializer; import org.geysermc.rainbow.mapping.geometry.GeometryRenderer; import org.geysermc.rainbow.definition.GeyserMappings; -import org.geysermc.rainbow.mapping.geometry.TextureHolder; +import org.geysermc.rainbow.mapping.texture.TextureHolder; import org.jetbrains.annotations.NotNull; import java.nio.file.Path; @@ -127,9 +127,7 @@ public class BedrockPack { Function> textureSaver = texture -> { ResourceLocation textureLocation = Rainbow.decorateTextureLocation(texture.location()); - return texture.load(context.assetResolver(), reporter) - .map(bytes -> serializer.saveTexture(bytes, paths.packRoot().resolve(textureLocation.getPath()))) - .orElse(CompletableFuture.completedFuture(null)); + return texture.save(context.assetResolver(), serializer, paths.packRoot().resolve(textureLocation.getPath()), reporter); }; for (BedrockItem item : bedrockItems) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java index 1178962..84406cb 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java @@ -12,9 +12,9 @@ import net.minecraft.world.entity.EquipmentSlot; import org.geysermc.rainbow.PackConstants; import org.geysermc.rainbow.Rainbow; import org.geysermc.rainbow.mapping.PackSerializer; +import org.geysermc.rainbow.mapping.geometry.MappedGeometry; import org.geysermc.rainbow.pack.BedrockTextures; import org.geysermc.rainbow.pack.BedrockVersion; -import org.geysermc.rainbow.pack.geometry.BedrockGeometry; import org.jetbrains.annotations.NotNull; import java.nio.file.Path; @@ -62,13 +62,13 @@ public record BedrockAttachable(BedrockVersion formatVersion, AttachableInfo inf .withRenderController(VanillaRenderControllers.ARMOR); } - public static BedrockAttachable.Builder geometry(ResourceLocation identifier, BedrockGeometry.GeometryDefinition geometry, String texture) { + public static BedrockAttachable.Builder geometry(ResourceLocation identifier, MappedGeometry geometry) { return builder(identifier) .withMaterial(DisplaySlot.DEFAULT, VanillaMaterials.ENTITY) .withMaterial(DisplaySlot.ENCHANTED, VanillaMaterials.ENTITY_ALPHATEST_GLINT) - .withTexture(DisplaySlot.DEFAULT, texture) + .withTexture(DisplaySlot.DEFAULT, geometry.stitchedTextures().location().getPath()) .withTexture(DisplaySlot.ENCHANTED, VanillaTextures.ENCHANTED_ITEM_GLINT) - .withGeometry(DisplaySlot.DEFAULT, geometry.info().identifier()) + .withGeometry(DisplaySlot.DEFAULT, geometry.identifier()) .withRenderController(VanillaRenderControllers.ITEM_DEFAULT); }