mirror of
https://github.com/GeyserMC/Rainbow.git
synced 2025-12-19 14:59:16 +00:00
Re-use mapped geometry when the textures and geometry is the same, let GeometryRenderer return TextureHolder, fix client geometry rendering
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<byte[]> 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"schemaVersion": 1,
|
||||
"id": "rainbow-datagen",
|
||||
"version": "${version}",
|
||||
"name": "Rainbow",
|
||||
"name": "Rainbow-datagen",
|
||||
"description": "Rainbow's datagen module",
|
||||
"authors": [
|
||||
"GeyserMC contributors"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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> 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> geometryRenderer;
|
||||
private final boolean reportSuccesses;
|
||||
private final MappedGeometryCache geometryCache = new MappedGeometryCache();
|
||||
|
||||
public PackContext(GeyserMappings mappings, PackPaths paths, BedrockItemConsumer itemConsumer, AssetResolver assetResolver,
|
||||
Optional<GeometryRenderer> 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> geometryRenderer() {
|
||||
return geometryRenderer;
|
||||
}
|
||||
|
||||
public boolean reportSuccesses() {
|
||||
return reportSuccesses;
|
||||
}
|
||||
|
||||
public MappedGeometryCache geometryCache() {
|
||||
return geometryCache;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Equippable>) 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<BedrockAttachable> create(ResourceLocation bedrockIdentifier, Optional<StitchedGeometry> geometry, Consumer<TextureHolder> textureConsumer);
|
||||
Optional<BedrockAttachable> create(ResourceLocation bedrockIdentifier, Consumer<TextureHolder> textureConsumer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Supplier<StitchedGeometry>> geometry,
|
||||
public record BedrockGeometryContext(Optional<MappedGeometry> geometry,
|
||||
Optional<BedrockAnimationContext> animation, TextureHolder icon,
|
||||
boolean handheld) {
|
||||
private static final List<ResourceLocation> 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<Supplier<StitchedGeometry>> geometry;
|
||||
Optional<MappedGeometry> geometry;
|
||||
Optional<BedrockAnimationContext> 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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<TextureHolder, CompletableFuture<?>> 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<TextureHolder, CompletableFuture<?>> textureSaver) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<GeometryCacheKey, MappedGeometryInstance> 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<String, Material> textures) {
|
||||
|
||||
private GeometryCacheKey(ResolvedModel model) {
|
||||
this(model.getTopGeometry(), ((TextureSlotsAccessor) model.getTopTextureSlots()).getResolvedValues());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<TextureHolder, CompletableFuture<?>> textureSaver) {
|
||||
return CompletableFuture.allOf(
|
||||
geometry.save(serializer, geometryDirectory),
|
||||
textureSaver.apply(stitchedTextures)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package org.geysermc.rainbow.mapping.geometry;
|
||||
|
||||
import org.geysermc.rainbow.pack.geometry.BedrockGeometry;
|
||||
|
||||
public record StitchedGeometry(BedrockGeometry geometry, TextureHolder stitchedTextures) {}
|
||||
@@ -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<NativeImage>> supplier, boolean existsInResources) {
|
||||
|
||||
public Optional<byte[]> 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<NativeImage> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<byte[]> 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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<byte[]> load(AssetResolver assetResolver, ProblemReporter reporter) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> save(AssetResolver assetResolver, PackSerializer serializer, Path path, ProblemReporter reporter) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
}
|
||||
@@ -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<NativeImage> supplier;
|
||||
|
||||
public CustomTextureHolder(ResourceLocation location, Supplier<NativeImage> supplier) {
|
||||
super(location);
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<byte[]> load(AssetResolver assetResolver, ProblemReporter reporter) {
|
||||
return RainbowIO.safeIO(() -> NativeImageUtil.writeToByteArray(supplier.get()));
|
||||
}
|
||||
}
|
||||
@@ -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<byte[]> load(AssetResolver assetResolver, ProblemReporter reporter) {
|
||||
reportMissing(reporter);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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<byte[]> 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<NativeImage> 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");
|
||||
}
|
||||
}
|
||||
@@ -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<TextureHolder, CompletableFuture<?>> textureSaver) {
|
||||
List<TextureHolder> attachableTextures = new ArrayList<>();
|
||||
Optional<BedrockAttachable> createdAttachable = attachableCreator.create(identifier, attachableTextures::add);
|
||||
return CompletableFuture.allOf(
|
||||
textureSaver.apply(geometryContext.icon()),
|
||||
CompletableFuture.supplyAsync(() -> geometryContext.geometry().map(Supplier::get))
|
||||
.thenCompose(stitchedGeometry -> {
|
||||
List<TextureHolder> attachableTextures = new ArrayList<>();
|
||||
Optional<BedrockAttachable> 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())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<TextureHolder, CompletableFuture<?>> 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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user