mirror of
https://github.com/GeyserMC/Rainbow.git
synced 2025-12-19 14:59:16 +00:00
Work on improving model generation/stitching of multiple textures
This commit is contained in:
@@ -8,20 +8,25 @@ import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.client.resources.model.ResolvedModel;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.world.item.equipment.EquipmentAsset;
|
||||
import org.geysermc.rainbow.client.accessor.ResolvedModelAccessor;
|
||||
import org.geysermc.rainbow.client.mixin.EntityRenderDispatcherAccessor;
|
||||
import org.geysermc.rainbow.mapping.AssetResolver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Optional;
|
||||
|
||||
public class MinecraftAssetResolver implements AssetResolver {
|
||||
private final ModelManager modelManager;
|
||||
private final EquipmentAssetManager equipmentAssetManager;
|
||||
private final ResourceManager resourceManager;
|
||||
|
||||
public MinecraftAssetResolver(Minecraft minecraft) {
|
||||
modelManager = minecraft.getModelManager();
|
||||
equipmentAssetManager = ((EntityRenderDispatcherAccessor) minecraft.getEntityRenderDispatcher()).getEquipmentAssets();
|
||||
resourceManager = minecraft.getResourceManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -38,4 +43,9 @@ public class MinecraftAssetResolver implements AssetResolver {
|
||||
public Optional<EquipmentClientInfo> getEquipmentInfo(ResourceKey<EquipmentAsset> key) {
|
||||
return Optional.of(equipmentAssetManager.get(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openAsset(ResourceLocation location) throws IOException {
|
||||
return resourceManager.open(location);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import org.geysermc.rainbow.mapping.PackSerializer;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
@@ -24,11 +23,9 @@ import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class MinecraftPackSerializer implements PackSerializer {
|
||||
private final HolderLookup.Provider registries;
|
||||
private final ResourceManager resourceManager;
|
||||
|
||||
public MinecraftPackSerializer(Minecraft minecraft) {
|
||||
registries = Objects.requireNonNull(minecraft.level).registryAccess();
|
||||
resourceManager = minecraft.getResourceManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,13 +41,12 @@ public class MinecraftPackSerializer implements PackSerializer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> saveTexture(ResourceLocation texture, Path path) {
|
||||
public CompletableFuture<?> saveTexture(byte[] texture, Path path) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
ResourceLocation texturePath = texture.withPath(p -> "textures/" + p + ".png");
|
||||
try (InputStream inputTexture = resourceManager.open(texturePath)) {
|
||||
try {
|
||||
CodecUtil.ensureDirectoryExists(path.getParent());
|
||||
try (OutputStream outputTexture = new FileOutputStream(path.toFile())) {
|
||||
IOUtils.copy(inputTexture, outputTexture);
|
||||
outputTexture.write(texture);
|
||||
}
|
||||
} catch (IOException exception) {
|
||||
// TODO log
|
||||
|
||||
@@ -88,8 +88,8 @@ public final class PackManager {
|
||||
|
||||
Set<BedrockItem> bedrockItems = pack.getBedrockItems();
|
||||
long attachables = bedrockItems.stream().filter(item -> item.attachable().isPresent()).count();
|
||||
long geometries = bedrockItems.stream().filter(item -> item.geometry().isPresent()).count();
|
||||
long animations = bedrockItems.stream().filter(item -> item.animation().isPresent()).count();
|
||||
long geometries = bedrockItems.stream().filter(item -> item.geometry().geometry().isPresent()).count();
|
||||
long animations = bedrockItems.stream().filter(item -> item.geometry().animation().isPresent()).count();
|
||||
|
||||
return """
|
||||
-- PACK GENERATION REPORT --
|
||||
|
||||
@@ -1,14 +1,33 @@
|
||||
package org.geysermc.rainbow.client;
|
||||
|
||||
import com.mojang.blaze3d.platform.NativeImage;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
|
||||
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.command.v2.ArgumentTypeRegistry;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.texture.SpriteContents;
|
||||
import net.minecraft.client.renderer.texture.SpriteLoader;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.metadata.animation.FrameSize;
|
||||
import net.minecraft.commands.synchronization.SingletonArgumentInfo;
|
||||
import net.minecraft.data.AtlasIds;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.geysermc.rainbow.Rainbow;
|
||||
import org.geysermc.rainbow.client.command.CommandSuggestionsArgumentType;
|
||||
import org.geysermc.rainbow.client.command.PackGeneratorCommand;
|
||||
import org.geysermc.rainbow.client.mapper.PackMapper;
|
||||
import org.geysermc.rainbow.mixin.SpriteContentsAccessor;
|
||||
import org.geysermc.rainbow.mixin.SpriteLoaderAccessor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class RainbowClient implements ClientModInitializer {
|
||||
|
||||
@@ -18,10 +37,53 @@ public class RainbowClient implements ClientModInitializer {
|
||||
// TODO export language overrides
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
ClientCommandRegistrationCallback.EVENT.register((dispatcher, buildContext) -> PackGeneratorCommand.register(dispatcher, packManager, packMapper));
|
||||
ClientCommandRegistrationCallback.EVENT.register((dispatcher, buildContext) -> {
|
||||
PackGeneratorCommand.register(dispatcher, packManager, packMapper);
|
||||
|
||||
dispatcher.register(
|
||||
ClientCommandManager.literal("DEBUGTEST")
|
||||
.then(ClientCommandManager.argument("textures", StringArgumentType.greedyString())
|
||||
.executes(context -> {
|
||||
SpriteLoader spriteLoader = new SpriteLoader(AtlasIds.BLOCKS, 1024, 16, 16);
|
||||
List<SpriteContents> sprites = Arrays.stream(StringArgumentType.getString(context, "textures").split(" "))
|
||||
.map(ResourceLocation::tryParse)
|
||||
.map(RainbowClient::readSpriteContents)
|
||||
.toList();
|
||||
SpriteLoader.Preparations preparations = ((SpriteLoaderAccessor) spriteLoader).invokeStitch(sprites, 0, Util.backgroundExecutor());
|
||||
|
||||
try (NativeImage stitched = stitchTextureAtlas(preparations)) {
|
||||
stitched.writeToFile(FabricLoader.getInstance().getGameDir().resolve("test.png"));
|
||||
} catch (IOException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
return 0;
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
ClientTickEvents.START_CLIENT_TICK.register(packMapper::tick);
|
||||
|
||||
ArgumentTypeRegistry.registerArgumentType(Rainbow.getModdedLocation("command_suggestions"),
|
||||
CommandSuggestionsArgumentType.class, SingletonArgumentInfo.contextFree(CommandSuggestionsArgumentType::new));
|
||||
}
|
||||
|
||||
private static SpriteContents readSpriteContents(ResourceLocation location) {
|
||||
try (InputStream textureStream = Minecraft.getInstance().getResourceManager().open(location.withPath(path -> "textures/" + path + ".png"))) {
|
||||
NativeImage texture = NativeImage.read(textureStream);
|
||||
return new SpriteContents(location, new FrameSize(texture.getWidth(), texture.getHeight()), texture);
|
||||
} catch (IOException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static NativeImage stitchTextureAtlas(SpriteLoader.Preparations preparations) {
|
||||
NativeImage stitched = new NativeImage(preparations.width(), preparations.height(), false);
|
||||
for (TextureAtlasSprite sprite : preparations.regions().values()) {
|
||||
try (SpriteContents contents = sprite.contents()) {
|
||||
((SpriteContentsAccessor) contents).getOriginalImage().copyRect(stitched, 0, 0,
|
||||
sprite.getX(), sprite.getY(), contents.width(), contents.height(), false, false);
|
||||
}
|
||||
}
|
||||
return stitched;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,16 @@ public class Rainbow {
|
||||
return ResourceLocation.fromNamespaceAndPath(MOD_ID, path);
|
||||
}
|
||||
|
||||
// TODO rename remove file
|
||||
public static String fileSafeResourceLocation(ResourceLocation location) {
|
||||
return location.toString().replace(':', '.').replace('/', '_');
|
||||
}
|
||||
|
||||
public static ResourceLocation decorateResourceLocation(ResourceLocation location, String type, String extension) {
|
||||
return location.withPath(path -> type + "/" + path + "." + extension);
|
||||
}
|
||||
|
||||
public static ResourceLocation decorateTextureLocation(ResourceLocation location) {
|
||||
return decorateResourceLocation(location, "textures", "png");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.geysermc.rainbow.image;
|
||||
|
||||
import com.mojang.blaze3d.platform.NativeImage;
|
||||
import org.geysermc.rainbow.mixin.NativeImageAccessor;
|
||||
import org.lwjgl.stb.STBImage;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Pipe;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
|
||||
public class NativeImageUtil {
|
||||
|
||||
// Adjusted NativeImage#writeToFile
|
||||
@SuppressWarnings("DataFlowIssue")
|
||||
public static byte[] writeToByteArray(NativeImage image) throws IOException {
|
||||
if (!image.format().supportedByStb()) {
|
||||
throw new UnsupportedOperationException("Don't know how to write format " + image.format());
|
||||
} else {
|
||||
((NativeImageAccessor) (Object) image).invokeCheckAllocated();
|
||||
Pipe pipe = Pipe.open();
|
||||
try (WritableByteChannel outputChannel = pipe.sink()) {
|
||||
if (!((NativeImageAccessor) (Object) image).invokeWriteToChannel(outputChannel)) {
|
||||
throw new IOException("Could not write image to pipe: " + STBImage.stbi_failure_reason());
|
||||
}
|
||||
}
|
||||
|
||||
try (ReadableByteChannel inputChannel = pipe.source()) {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
ByteBuffer buffer = ByteBuffer.allocate(4096);
|
||||
while (inputChannel.read(buffer) != -1) {
|
||||
buffer.flip();
|
||||
while (buffer.hasRemaining()) {
|
||||
bytes.write(buffer.get());
|
||||
}
|
||||
buffer.clear();
|
||||
}
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.equipment.EquipmentAsset;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface AssetResolver {
|
||||
@@ -16,4 +18,6 @@ public interface AssetResolver {
|
||||
Optional<ClientItem> getClientItem(ResourceLocation location);
|
||||
|
||||
Optional<EquipmentClientInfo> getEquipmentInfo(ResourceKey<EquipmentAsset> key);
|
||||
|
||||
InputStream openAsset(ResourceLocation location) throws IOException;
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@ import net.minecraft.client.renderer.item.properties.select.Charge;
|
||||
import net.minecraft.client.renderer.item.properties.select.ContextDimension;
|
||||
import net.minecraft.client.renderer.item.properties.select.DisplayContext;
|
||||
import net.minecraft.client.renderer.item.properties.select.TrimMaterialProperty;
|
||||
import net.minecraft.client.resources.model.Material;
|
||||
import net.minecraft.client.resources.model.ResolvedModel;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
@@ -37,11 +35,8 @@ import net.minecraft.world.item.component.ItemAttributeModifiers;
|
||||
import net.minecraft.world.item.equipment.trim.TrimMaterial;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.geysermc.rainbow.mapping.animation.AnimationMapper;
|
||||
import org.geysermc.rainbow.mapping.animation.BedrockAnimationContext;
|
||||
import org.geysermc.rainbow.mapping.attachable.AttachableMapper;
|
||||
import org.geysermc.rainbow.mapping.geometry.BedrockGeometryContext;
|
||||
import org.geysermc.rainbow.mapping.geometry.GeometryMapper;
|
||||
import org.geysermc.rainbow.definition.GeyserBaseDefinition;
|
||||
import org.geysermc.rainbow.definition.GeyserItemDefinition;
|
||||
import org.geysermc.rainbow.definition.GeyserLegacyDefinition;
|
||||
@@ -52,9 +47,7 @@ import org.geysermc.rainbow.definition.predicate.GeyserPredicate;
|
||||
import org.geysermc.rainbow.definition.predicate.GeyserRangeDispatchPredicate;
|
||||
import org.geysermc.rainbow.mixin.LateBoundIdMapperAccessor;
|
||||
import org.geysermc.rainbow.mixin.RangeSelectItemModelAccessor;
|
||||
import org.geysermc.rainbow.mixin.TextureSlotsAccessor;
|
||||
import org.geysermc.rainbow.pack.BedrockItem;
|
||||
import org.geysermc.rainbow.pack.BedrockTextures;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -62,9 +55,6 @@ import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class BedrockItemMapper {
|
||||
private static final List<ResourceLocation> HANDHELD_MODELS = Stream.of("item/handheld", "item/handheld_rod", "item/handheld_mace")
|
||||
.map(ResourceLocation::withDefaultNamespace)
|
||||
.toList();
|
||||
private static final List<ResourceLocation> TRIMMABLE_ARMOR_TAGS = Stream.of("is_armor", "trimmable_armors")
|
||||
.map(ResourceLocation::withDefaultNamespace)
|
||||
.toList();
|
||||
@@ -117,7 +107,7 @@ public class BedrockItemMapper {
|
||||
case ConditionalItemModel.Unbaked conditional -> mapConditionalModel(conditional, context.child("condition model "));
|
||||
case RangeSelectItemModel.Unbaked rangeSelect -> mapRangeSelectModel(rangeSelect, context.child("range select model "));
|
||||
case SelectItemModel.Unbaked select -> mapSelectModel(select, context.child("select model "));
|
||||
default -> context.reporter.report(() -> "unsupported item model " + getModelId(model));
|
||||
default -> context.report("unsupported item model " + getModelId(model));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,10 +116,6 @@ public class BedrockItemMapper {
|
||||
|
||||
context.packContext().assetResolver().getResolvedModel(itemModelLocation)
|
||||
.ifPresentOrElse(itemModel -> {
|
||||
ResolvedModel parentModel = itemModel.parent();
|
||||
// debugName() returns the resource location of the model as a string
|
||||
boolean handheld = parentModel != null && HANDHELD_MODELS.contains(ResourceLocation.parse(parentModel.debugName()));
|
||||
|
||||
ResourceLocation bedrockIdentifier;
|
||||
if (itemModelLocation.getNamespace().equals(ResourceLocation.DEFAULT_NAMESPACE)) {
|
||||
bedrockIdentifier = ResourceLocation.fromNamespaceAndPath("geyser_mc", itemModelLocation.getPath());
|
||||
@@ -137,29 +123,13 @@ public class BedrockItemMapper {
|
||||
bedrockIdentifier = itemModelLocation;
|
||||
}
|
||||
|
||||
Material layer0Texture = itemModel.getTopTextureSlots().getMaterial("layer0");
|
||||
Optional<ResourceLocation> texture;
|
||||
Optional<ResolvedModel> customGeometry;
|
||||
if (layer0Texture != null) {
|
||||
texture = Optional.of(layer0Texture.texture());
|
||||
customGeometry = Optional.empty();
|
||||
} else {
|
||||
// We can't stitch multiple textures together yet, so we just grab the first one we see
|
||||
// This will only work properly for models with just one texture
|
||||
texture = ((TextureSlotsAccessor) itemModel.getTopTextureSlots()).getResolvedValues().values().stream()
|
||||
.map(Material::texture)
|
||||
.findAny();
|
||||
// Unknown texture (doesn't use layer0), so we immediately assume the geometry is custom
|
||||
// This check should probably be done differently
|
||||
customGeometry = Optional.of(itemModel);
|
||||
}
|
||||
|
||||
texture.ifPresentOrElse(itemTexture -> {
|
||||
BedrockGeometryContext geometry = BedrockGeometryContext.create(itemModel);
|
||||
if (context.packContext.reportSuccesses()) {
|
||||
// Not a problem, but just report to get the model printed in the report file
|
||||
context.reporter.report(() -> "creating mapping for block model " + itemModelLocation);
|
||||
context.create(bedrockIdentifier, itemTexture, handheld, customGeometry);
|
||||
}, () -> context.reporter.report(() -> "not mapping block model " + itemModelLocation + " because it has no texture"));
|
||||
}, () -> context.reporter.report(() -> "missing block model " + itemModelLocation));
|
||||
context.report("creating mapping for block model " + itemModelLocation);
|
||||
}
|
||||
context.create(bedrockIdentifier, geometry);
|
||||
}, () -> context.report("missing block model " + itemModelLocation));
|
||||
}
|
||||
|
||||
private static void mapConditionalModel(ConditionalItemModel.Unbaked model, MappingContext context) {
|
||||
@@ -176,7 +146,7 @@ public class BedrockItemMapper {
|
||||
ItemModel.Unbaked onFalse = model.onFalse();
|
||||
|
||||
if (predicateProperty == null) {
|
||||
context.reporter.report(() -> "unsupported conditional model property " + property + ", only mapping on_false");
|
||||
context.report("unsupported conditional model property " + property + ", only mapping on_false");
|
||||
mapItem(onFalse, context.child("condition on_false (unsupported property)"));
|
||||
return;
|
||||
}
|
||||
@@ -197,7 +167,7 @@ public class BedrockItemMapper {
|
||||
};
|
||||
|
||||
if (predicateProperty == null) {
|
||||
context.reporter.report(() -> "unsupported range dispatch model property " + property + ", only mapping fallback, if it is present");
|
||||
context.report("unsupported range dispatch model property " + property + ", only mapping fallback, if it is present");
|
||||
} else {
|
||||
for (RangeSelectItemModel.Entry entry : model.entries()) {
|
||||
mapItem(entry.model(), context.with(new GeyserRangeDispatchPredicate(predicateProperty, entry.threshold(), model.scale()), "threshold " + entry.threshold()));
|
||||
@@ -223,7 +193,7 @@ public class BedrockItemMapper {
|
||||
|
||||
if (dataConstructor == null) {
|
||||
if (unbakedSwitch.property() instanceof DisplayContext) {
|
||||
context.reporter.report(() -> "unsupported select model property display_context, only mapping \"gui\" case, if it exists");
|
||||
context.report("unsupported select model property display_context, only mapping \"gui\" case, if it exists");
|
||||
for (SelectItemModel.SwitchCase<?> switchCase : cases) {
|
||||
if (switchCase.values().contains(ItemDisplayContext.GUI)) {
|
||||
mapItem(switchCase.model(), context.child("select GUI display_context case (unsupported property) "));
|
||||
@@ -231,7 +201,7 @@ public class BedrockItemMapper {
|
||||
}
|
||||
}
|
||||
}
|
||||
context.reporter.report(() -> "unsupported select model property " + unbakedSwitch.property() + ", only mapping fallback, if present");
|
||||
context.report("unsupported select model property " + unbakedSwitch.property() + ", only mapping fallback, if present");
|
||||
model.fallback().ifPresent(fallback -> mapItem(fallback, context.child("select fallback case (unsupported property) ")));
|
||||
return;
|
||||
}
|
||||
@@ -255,17 +225,11 @@ public class BedrockItemMapper {
|
||||
return new MappingContext(predicateStack, stack, reporter.forChild(() -> childName), definitionCreator, packContext);
|
||||
}
|
||||
|
||||
public void create(ResourceLocation bedrockIdentifier, ResourceLocation texture, boolean displayHandheld,
|
||||
Optional<ResolvedModel> customModel) {
|
||||
List<ResourceLocation> tags;
|
||||
if (stack.is(ItemTags.TRIMMABLE_ARMOR)) {
|
||||
tags = TRIMMABLE_ARMOR_TAGS;
|
||||
} else {
|
||||
tags = List.of();
|
||||
}
|
||||
public void create(ResourceLocation bedrockIdentifier, BedrockGeometryContext geometry) {
|
||||
List<ResourceLocation> tags = stack.is(ItemTags.TRIMMABLE_ARMOR) ? TRIMMABLE_ARMOR_TAGS : List.of();
|
||||
|
||||
GeyserBaseDefinition base = new GeyserBaseDefinition(bedrockIdentifier, Optional.ofNullable(stack.getHoverName().tryCollapseToString()), predicateStack,
|
||||
new GeyserBaseDefinition.BedrockOptions(Optional.empty(), true, displayHandheld, calculateProtectionValue(stack), tags),
|
||||
new GeyserBaseDefinition.BedrockOptions(Optional.empty(), true, geometry.handheld(), calculateProtectionValue(stack), tags),
|
||||
stack.getComponentsPatch());
|
||||
try {
|
||||
packContext.mappings().map(stack.getItemHolder(), definitionCreator.apply(base));
|
||||
@@ -274,26 +238,13 @@ public class BedrockItemMapper {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO Should probably get a better way to get geometry texture
|
||||
String safeIdentifier = base.textureName();
|
||||
String bone = "bone";
|
||||
ResourceLocation geometryTexture = texture;
|
||||
Optional<BedrockGeometryContext> bedrockGeometry = customModel.flatMap(model -> GeometryMapper.mapGeometry(safeIdentifier, bone, model, geometryTexture));
|
||||
Optional<BedrockAnimationContext> bedrockAnimation = customModel.map(model -> AnimationMapper.mapAnimation(safeIdentifier, bone, model.getTopTransforms()));
|
||||
// TODO move attachable mapping somewhere else for cleaner code?
|
||||
packContext.itemConsumer().accept(new BedrockItem(bedrockIdentifier, base.textureName(), geometry,
|
||||
AttachableMapper.mapItem(stack.getComponentsPatch(), bedrockIdentifier, geometry, packContext.assetResolver(), packContext.additionalTextureConsumer())));
|
||||
}
|
||||
|
||||
boolean exportTexture = true;
|
||||
if (customModel.isPresent()) {
|
||||
texture = texture.withPath(path -> path + "_icon");
|
||||
// FIXME Bit of a hack, preferably render geometry at a later stage
|
||||
exportTexture = !packContext.geometryRenderer().render(stack, packContext.paths().packRoot().resolve(BedrockTextures.TEXTURES_FOLDER + texture.getPath() + ".png"));
|
||||
packContext.additionalTextureConsumer().accept(geometryTexture);
|
||||
}
|
||||
|
||||
packContext.itemConsumer().accept(new BedrockItem(bedrockIdentifier, base.textureName(), texture, exportTexture,
|
||||
AttachableMapper.mapItem(stack.getComponentsPatch(), bedrockIdentifier, bedrockGeometry, bedrockAnimation,
|
||||
packContext.assetResolver(),
|
||||
packContext.additionalTextureConsumer()),
|
||||
bedrockGeometry.map(BedrockGeometryContext::geometry), bedrockAnimation.map(BedrockAnimationContext::animation)));
|
||||
public void report(String problem) {
|
||||
reporter.report(() -> problem);
|
||||
}
|
||||
|
||||
private static int calculateProtectionValue(ItemStack stack) {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package org.geysermc.rainbow.mapping;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.geysermc.rainbow.mapping.geometry.GeometryRenderer;
|
||||
import org.geysermc.rainbow.definition.GeyserMappings;
|
||||
import org.geysermc.rainbow.mapping.geometry.TextureHolder;
|
||||
import org.geysermc.rainbow.pack.PackPaths;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public record PackContext(GeyserMappings mappings, PackPaths paths, BedrockItemConsumer itemConsumer, AssetResolver assetResolver,
|
||||
GeometryRenderer geometryRenderer, Consumer<ResourceLocation> additionalTextureConsumer,
|
||||
GeometryRenderer geometryRenderer, Consumer<TextureHolder> additionalTextureConsumer,
|
||||
boolean reportSuccesses) {
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.geysermc.rainbow.mapping;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -10,5 +9,5 @@ public interface PackSerializer {
|
||||
|
||||
<T> CompletableFuture<?> saveJson(Codec<T> codec, T object, Path path);
|
||||
|
||||
CompletableFuture<?> saveTexture(ResourceLocation texture, Path path);
|
||||
CompletableFuture<?> saveTexture(byte[] texture, Path path);
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import net.minecraft.world.item.equipment.Equippable;
|
||||
import org.geysermc.rainbow.mapping.AssetResolver;
|
||||
import org.geysermc.rainbow.mapping.animation.BedrockAnimationContext;
|
||||
import org.geysermc.rainbow.mapping.geometry.BedrockGeometryContext;
|
||||
import org.geysermc.rainbow.mapping.geometry.TextureHolder;
|
||||
import org.geysermc.rainbow.pack.attachable.BedrockAttachable;
|
||||
|
||||
import java.util.List;
|
||||
@@ -19,12 +19,12 @@ import java.util.function.Consumer;
|
||||
|
||||
public class AttachableMapper {
|
||||
|
||||
public static Optional<BedrockAttachable> mapItem(DataComponentPatch components, ResourceLocation bedrockIdentifier, Optional<BedrockGeometryContext> customGeometry,
|
||||
Optional<BedrockAnimationContext> customAnimation, AssetResolver assetResolver, Consumer<ResourceLocation> textureConsumer) {
|
||||
public static Optional<BedrockAttachable> mapItem(DataComponentPatch components, ResourceLocation bedrockIdentifier, BedrockGeometryContext geometryContext,
|
||||
AssetResolver assetResolver, Consumer<TextureHolder> textureConsumer) {
|
||||
// Crazy optional statement
|
||||
// Unfortunately we can't have both equippables and custom models, so we prefer the latter :(
|
||||
return customGeometry
|
||||
.map(geometry -> BedrockAttachable.geometry(bedrockIdentifier, geometry.geometry().definitions().getFirst(), geometry.texture().getPath()))
|
||||
return geometryContext.geometry()
|
||||
.map(geometry -> BedrockAttachable.geometry(bedrockIdentifier, geometry.definitions().getFirst(), geometryContext.texture().location().getPath()))
|
||||
.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)))
|
||||
@@ -33,14 +33,14 @@ public class AttachableMapper {
|
||||
.mapSecond(info -> info.getLayers(getLayer(assetInfo.getFirst()))))
|
||||
.filter(assetInfo -> !assetInfo.getSecond().isEmpty())
|
||||
.map(assetInfo -> {
|
||||
ResourceLocation texture = getTexture(assetInfo.getSecond(), getLayer(assetInfo.getFirst()));
|
||||
textureConsumer.accept(texture);
|
||||
return BedrockAttachable.equipment(bedrockIdentifier, assetInfo.getFirst(), texture.getPath());
|
||||
ResourceLocation equipmentTexture = getTexture(assetInfo.getSecond(), getLayer(assetInfo.getFirst()));
|
||||
textureConsumer.accept(new TextureHolder(equipmentTexture));
|
||||
return BedrockAttachable.equipment(bedrockIdentifier, assetInfo.getFirst(), equipmentTexture.getPath());
|
||||
}))
|
||||
.map(attachable -> {
|
||||
customAnimation.ifPresent(context -> {
|
||||
attachable.withAnimation("first_person", context.firstPerson());
|
||||
attachable.withAnimation("third_person", context.thirdPerson());
|
||||
geometryContext.animation().ifPresent(animation -> {
|
||||
attachable.withAnimation("first_person", animation.firstPerson());
|
||||
attachable.withAnimation("third_person", animation.thirdPerson());
|
||||
attachable.withScript("animate", "first_person", "context.is_first_person == 1.0");
|
||||
attachable.withScript("animate", "third_person", "context.is_first_person == 0.0");
|
||||
});
|
||||
|
||||
@@ -1,6 +1,53 @@
|
||||
package org.geysermc.rainbow.mapping.geometry;
|
||||
|
||||
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.resources.ResourceLocation;
|
||||
import org.geysermc.rainbow.Rainbow;
|
||||
import org.geysermc.rainbow.mapping.animation.AnimationMapper;
|
||||
import org.geysermc.rainbow.mapping.animation.BedrockAnimationContext;
|
||||
import org.geysermc.rainbow.pack.geometry.BedrockGeometry;
|
||||
|
||||
public record BedrockGeometryContext(BedrockGeometry geometry, ResourceLocation texture) {}
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public record BedrockGeometryContext(Optional<BedrockGeometry> geometry, Optional<BedrockAnimationContext> animation, TextureHolder texture,
|
||||
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(ResolvedModel model) {
|
||||
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<BedrockGeometry> geometry;
|
||||
Optional<BedrockAnimationContext> animation;
|
||||
TextureHolder texture;
|
||||
|
||||
if (layer0Texture != null) {
|
||||
geometry = Optional.empty();
|
||||
animation = Optional.empty();
|
||||
texture = new TextureHolder(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.fileSafeResourceLocation(modelLocation);
|
||||
|
||||
StitchedTextures stitchedTextures = StitchedTextures.stitchModelTextures(textures);
|
||||
geometry = GeometryMapper.mapGeometry(safeIdentifier, "bone", model, stitchedTextures);
|
||||
animation = Optional.of(AnimationMapper.mapAnimation(safeIdentifier, "bone", model.getTopTransforms()));
|
||||
texture = new TextureHolder(modelLocation.withSuffix("_stitched"), Optional.of(stitchedTextures.stitched()));
|
||||
// TODO geometry rendering
|
||||
}
|
||||
|
||||
return new BedrockGeometryContext(geometry, animation, texture, handheld);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +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 net.minecraft.resources.ResourceLocation;
|
||||
import org.geysermc.rainbow.mixin.FaceBakeryAccessor;
|
||||
import org.geysermc.rainbow.pack.geometry.BedrockGeometry;
|
||||
import org.joml.Vector2f;
|
||||
import org.joml.Vector3f;
|
||||
@@ -19,7 +19,7 @@ import java.util.Optional;
|
||||
public class GeometryMapper {
|
||||
private static final Vector3fc CENTRE_OFFSET = new Vector3f(8.0F, 0.0F, 8.0F);
|
||||
|
||||
public static Optional<BedrockGeometryContext> mapGeometry(String identifier, String boneName, ResolvedModel model, ResourceLocation texture) {
|
||||
public static Optional<BedrockGeometry> mapGeometry(String identifier, String boneName, ResolvedModel model, StitchedTextures textures) {
|
||||
UnbakedGeometry top = model.getTopGeometry();
|
||||
if (top == UnbakedGeometry.EMPTY) {
|
||||
return Optional.empty();
|
||||
@@ -31,9 +31,8 @@ public class GeometryMapper {
|
||||
builder.withVisibleBoundsHeight(4.0F);
|
||||
builder.withVisibleBoundsOffset(new Vector3f(0.0F, 0.75F, 0.0F));
|
||||
|
||||
// TODO proper texture size
|
||||
builder.withTextureWidth(16);
|
||||
builder.withTextureHeight(16);
|
||||
builder.withTextureWidth(textures.width());
|
||||
builder.withTextureHeight(textures.height());
|
||||
|
||||
BedrockGeometry.Bone.Builder bone = BedrockGeometry.bone(boneName);
|
||||
|
||||
@@ -43,7 +42,7 @@ public class GeometryMapper {
|
||||
SimpleUnbakedGeometry geometry = (SimpleUnbakedGeometry) top;
|
||||
for (BlockElement element : geometry.elements()) {
|
||||
// TODO the origin here is wrong, some models seem to be mirrored weirdly in blockbench
|
||||
BedrockGeometry.Cube cube = mapBlockElement(element).build();
|
||||
BedrockGeometry.Cube cube = mapBlockElement(element, textures).build();
|
||||
bone.withCube(cube);
|
||||
min.min(cube.origin());
|
||||
max.max(cube.origin().add(cube.size(), new Vector3f()));
|
||||
@@ -55,35 +54,36 @@ public class GeometryMapper {
|
||||
|
||||
// Bind to the bone of the current item slot
|
||||
bone.withBinding("q.item_slot_to_bone_name(context.item_slot)");
|
||||
return Optional.of(new BedrockGeometryContext(builder.withBone(bone).build(), texture));
|
||||
return Optional.of(builder.withBone(bone).build());
|
||||
}
|
||||
|
||||
private static BedrockGeometry.Cube.Builder mapBlockElement(BlockElement element) {
|
||||
private static BedrockGeometry.Cube.Builder mapBlockElement(BlockElement element, StitchedTextures textures) {
|
||||
// The centre of the model is back by 8 in the X and Z direction on Java, so move the origin of the cube and the pivot like that
|
||||
BedrockGeometry.Cube.Builder builder = BedrockGeometry.cube(element.from().sub(CENTRE_OFFSET, new Vector3f()), element.to().sub(element.from(), new Vector3f()));
|
||||
|
||||
for (Map.Entry<Direction, BlockElementFace> faceEntry : element.faces().entrySet()) {
|
||||
// TODO texture key
|
||||
Direction direction = faceEntry.getKey();
|
||||
BlockElementFace face = faceEntry.getValue();
|
||||
|
||||
Vector2f uvOrigin;
|
||||
Vector2f uvSize;
|
||||
BlockElementFace.UVs uvs = face.uvs();
|
||||
if (uvs != null) {
|
||||
// Up and down faces are special
|
||||
if (direction.getAxis() == Direction.Axis.Y) {
|
||||
uvOrigin = new Vector2f(uvs.maxU(), uvs.maxV());
|
||||
uvSize = new Vector2f(uvs.minU() - uvs.maxU(), uvs.minV() - uvs.maxV());
|
||||
} else {
|
||||
uvOrigin = new Vector2f(uvs.minU(), uvs.minV());
|
||||
uvSize = new Vector2f(uvs.maxU() - uvs.minU(), uvs.maxV() - uvs.minV());
|
||||
}
|
||||
} else {
|
||||
uvOrigin = new Vector2f();
|
||||
uvSize = new Vector2f();
|
||||
if (uvs == null) {
|
||||
// Java defaults to a set of UV values determined by the position of the face if no UV values were specified
|
||||
uvs = FaceBakeryAccessor.invokeDefaultFaceUV(element.from(), element.to(), direction);
|
||||
}
|
||||
|
||||
// Up and down faces are special and have their UVs flipped
|
||||
if (direction.getAxis() == Direction.Axis.Y) {
|
||||
uvOrigin = new Vector2f(uvs.maxU(), uvs.maxV());
|
||||
uvSize = new Vector2f(uvs.minU() - uvs.maxU(), uvs.minV() - uvs.maxV());
|
||||
} else {
|
||||
uvOrigin = new Vector2f(uvs.minU(), uvs.minV());
|
||||
uvSize = new Vector2f(uvs.maxU() - uvs.minU(), uvs.maxV() - uvs.minV());
|
||||
}
|
||||
|
||||
// If the texture was stitched (which it should have been, unless it doesn't exist), offset the UVs by the texture's starting UV
|
||||
textures.getSprite(face.texture()).ifPresent(sprite -> uvOrigin.add(sprite.getX(), sprite.getY()));
|
||||
builder.withFace(direction, uvOrigin, uvSize, face.rotation());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.geysermc.rainbow.mapping.geometry;
|
||||
|
||||
import com.mojang.blaze3d.platform.NativeImage;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.block.model.TextureSlots;
|
||||
import net.minecraft.client.renderer.texture.SpriteContents;
|
||||
import net.minecraft.client.renderer.texture.SpriteLoader;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.metadata.animation.FrameSize;
|
||||
import net.minecraft.client.resources.model.Material;
|
||||
import net.minecraft.data.AtlasIds;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.geysermc.rainbow.mixin.SpriteContentsAccessor;
|
||||
import org.geysermc.rainbow.mixin.SpriteLoaderAccessor;
|
||||
import org.geysermc.rainbow.mixin.TextureSlotsAccessor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public record StitchedTextures(Map<String, TextureAtlasSprite> sprites, Supplier<NativeImage> stitched, int width, int height) {
|
||||
|
||||
public Optional<TextureAtlasSprite> getSprite(String key) {
|
||||
if (TextureSlotsAccessor.invokeIsTextureReference(key)) {
|
||||
key = key.substring(1);
|
||||
}
|
||||
return Optional.ofNullable(sprites.get(key));
|
||||
}
|
||||
|
||||
public static StitchedTextures stitchModelTextures(TextureSlots textures) {
|
||||
Map<String, Material> materials = ((TextureSlotsAccessor) textures).getResolvedValues();
|
||||
SpriteLoader.Preparations preparations = prepareStitching(materials.values().stream().map(Material::texture));
|
||||
|
||||
Map<String, TextureAtlasSprite> sprites = new HashMap<>();
|
||||
for (Map.Entry<String, Material> material : materials.entrySet()) {
|
||||
sprites.put(material.getKey(), preparations.getSprite(material.getValue().texture()));
|
||||
}
|
||||
return new StitchedTextures(Map.copyOf(sprites), () -> stitchTextureAtlas(preparations), preparations.width(), preparations.height());
|
||||
}
|
||||
|
||||
private static SpriteLoader.Preparations prepareStitching(Stream<ResourceLocation> textures) {
|
||||
// Atlas ID doesn't matter much here, but BLOCKS is the most appropriate
|
||||
// Not sure if 1024 should be the max supported texture size, but it seems to work
|
||||
SpriteLoader spriteLoader = new SpriteLoader(AtlasIds.BLOCKS, 1024, 16, 16);
|
||||
List<SpriteContents> sprites = textures.map(StitchedTextures::readSpriteContents).toList();
|
||||
return ((SpriteLoaderAccessor) spriteLoader).invokeStitch(sprites, 0, Util.backgroundExecutor());
|
||||
}
|
||||
|
||||
private static SpriteContents readSpriteContents(ResourceLocation location) {
|
||||
// TODO decorate path util
|
||||
// TODO don't use ResourceManager
|
||||
// TODO IO is on main thread here?
|
||||
try (InputStream textureStream = Minecraft.getInstance().getResourceManager().open(location.withPath(path -> "textures/" + path + ".png"))) {
|
||||
NativeImage texture = NativeImage.read(textureStream);
|
||||
return new SpriteContents(location, new FrameSize(texture.getWidth(), texture.getHeight()), texture);
|
||||
} catch (IOException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static NativeImage stitchTextureAtlas(SpriteLoader.Preparations preparations) {
|
||||
NativeImage stitched = new NativeImage(preparations.width(), preparations.height(), false);
|
||||
for (TextureAtlasSprite sprite : preparations.regions().values()) {
|
||||
try (SpriteContents contents = sprite.contents()) {
|
||||
((SpriteContentsAccessor) contents).getOriginalImage().copyRect(stitched, 0, 0,
|
||||
sprite.getX(), sprite.getY(), contents.width(), contents.height(), false, false);
|
||||
}
|
||||
}
|
||||
return stitched;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.geysermc.rainbow.mapping.geometry;
|
||||
|
||||
import com.mojang.blaze3d.platform.NativeImage;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public record TextureHolder(ResourceLocation location, Optional<Supplier<NativeImage>> supplier) {
|
||||
|
||||
public TextureHolder(ResourceLocation location) {
|
||||
this(location, Optional.empty());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.geysermc.rainbow.mixin;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockElementFace;
|
||||
import net.minecraft.client.renderer.block.model.FaceBakery;
|
||||
import net.minecraft.core.Direction;
|
||||
import org.joml.Vector3fc;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(FaceBakery.class)
|
||||
public interface FaceBakeryAccessor {
|
||||
|
||||
@Invoker
|
||||
static BlockElementFace.UVs invokeDefaultFaceUV(Vector3fc posFrom, Vector3fc posTo, Direction facing) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.geysermc.rainbow.mixin;
|
||||
|
||||
import com.mojang.blaze3d.platform.NativeImage;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
|
||||
@Mixin(NativeImage.class)
|
||||
public interface NativeImageAccessor {
|
||||
|
||||
@Invoker
|
||||
void invokeCheckAllocated();
|
||||
|
||||
@Invoker
|
||||
boolean invokeWriteToChannel(WritableByteChannel channel) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.geysermc.rainbow.mixin;
|
||||
|
||||
import com.mojang.blaze3d.platform.NativeImage;
|
||||
import net.minecraft.client.renderer.texture.SpriteContents;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(SpriteContents.class)
|
||||
public interface SpriteContentsAccessor {
|
||||
|
||||
@Accessor
|
||||
NativeImage getOriginalImage();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.geysermc.rainbow.mixin;
|
||||
|
||||
import net.minecraft.client.renderer.texture.SpriteContents;
|
||||
import net.minecraft.client.renderer.texture.SpriteLoader;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@Mixin(SpriteLoader.class)
|
||||
public interface SpriteLoaderAccessor {
|
||||
|
||||
@Invoker
|
||||
SpriteLoader.Preparations invokeStitch(List<SpriteContents> contents, int mipLevel, Executor executor);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import net.minecraft.client.renderer.block.model.TextureSlots;
|
||||
import net.minecraft.client.resources.model.Material;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -12,4 +13,9 @@ public interface TextureSlotsAccessor {
|
||||
|
||||
@Accessor
|
||||
Map<String, Material> getResolvedValues();
|
||||
|
||||
@Invoker
|
||||
static boolean invokeIsTextureReference(String name) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,8 @@ package org.geysermc.rainbow.pack;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.geysermc.rainbow.Rainbow;
|
||||
import org.geysermc.rainbow.mapping.PackSerializer;
|
||||
import org.geysermc.rainbow.pack.animation.BedrockAnimation;
|
||||
import org.geysermc.rainbow.mapping.geometry.BedrockGeometryContext;
|
||||
import org.geysermc.rainbow.pack.attachable.BedrockAttachable;
|
||||
import org.geysermc.rainbow.pack.geometry.BedrockGeometry;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
@@ -13,15 +12,14 @@ import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public record BedrockItem(ResourceLocation identifier, String textureName, ResourceLocation texture, boolean exportTexture, Optional<BedrockAttachable> attachable,
|
||||
Optional<BedrockGeometry> geometry, Optional<BedrockAnimation> animation) {
|
||||
public record BedrockItem(ResourceLocation identifier, String textureName, BedrockGeometryContext geometry, Optional<BedrockAttachable> attachable) {
|
||||
|
||||
public List<CompletableFuture<?>> save(PackSerializer serializer, Path attachableDirectory, Path geometryDirectory, Path animationDirectory) {
|
||||
return Stream.concat(
|
||||
attachable.stream().map(present -> present.save(serializer, attachableDirectory)),
|
||||
Stream.concat(
|
||||
geometry.stream().map(present -> present.save(serializer, geometryDirectory)),
|
||||
animation.stream().map(present -> present.save(serializer, animationDirectory, Rainbow.fileSafeResourceLocation(identifier)))
|
||||
geometry.geometry().stream().map(present -> present.save(serializer, geometryDirectory)),
|
||||
geometry.animation().stream().map(context -> context.animation().save(serializer, animationDirectory, Rainbow.fileSafeResourceLocation(identifier)))
|
||||
)
|
||||
).toList();
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.component.CustomModelData;
|
||||
import org.geysermc.rainbow.CodecUtil;
|
||||
import org.geysermc.rainbow.PackConstants;
|
||||
import org.geysermc.rainbow.Rainbow;
|
||||
import org.geysermc.rainbow.image.NativeImageUtil;
|
||||
import org.geysermc.rainbow.mapping.AssetResolver;
|
||||
import org.geysermc.rainbow.mapping.BedrockItemMapper;
|
||||
import org.geysermc.rainbow.mapping.PackContext;
|
||||
@@ -19,9 +21,11 @@ import org.geysermc.rainbow.mapping.PackSerializer;
|
||||
import org.geysermc.rainbow.mapping.geometry.GeometryRenderer;
|
||||
import org.geysermc.rainbow.mapping.geometry.NoopGeometryRenderer;
|
||||
import org.geysermc.rainbow.definition.GeyserMappings;
|
||||
import org.geysermc.rainbow.mapping.geometry.TextureHolder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
@@ -41,7 +45,7 @@ public class BedrockPack {
|
||||
|
||||
private final BedrockTextures.Builder itemTextures = BedrockTextures.builder();
|
||||
private final Set<BedrockItem> bedrockItems = new HashSet<>();
|
||||
private final Set<ResourceLocation> texturesToExport = new HashSet<>();
|
||||
private final Set<TextureHolder> texturesToExport = new HashSet<>();
|
||||
private final Set<ResourceLocation> modelsMapped = new HashSet<>();
|
||||
private final IntSet customModelDataMapped = new IntOpenHashSet();
|
||||
|
||||
@@ -59,9 +63,7 @@ public class BedrockPack {
|
||||
// Not reading existing item mappings/texture atlas for now since that doesn't work all that well yet
|
||||
this.context = new PackContext(new GeyserMappings(), paths, item -> {
|
||||
itemTextures.withItemTexture(item);
|
||||
if (item.exportTexture()) {
|
||||
texturesToExport.add(item.texture());
|
||||
}
|
||||
texturesToExport.add(item.geometry().texture());
|
||||
bedrockItems.add(item);
|
||||
}, assetResolver, geometryRenderer, texturesToExport::add, reportSuccesses);
|
||||
this.reporter = reporter;
|
||||
@@ -130,8 +132,27 @@ public class BedrockPack {
|
||||
futures.addAll(item.save(serializer, paths.attachables(), paths.geometry(), paths.animation()));
|
||||
}
|
||||
|
||||
for (ResourceLocation texture : texturesToExport) {
|
||||
futures.add(serializer.saveTexture(texture, paths.packRoot().resolve(BedrockTextures.TEXTURES_FOLDER + texture.getPath() + ".png")));
|
||||
for (TextureHolder texture : texturesToExport) {
|
||||
ResourceLocation textureLocation = Rainbow.decorateTextureLocation(texture.location());
|
||||
texture.supplier()
|
||||
.flatMap(image -> {
|
||||
try {
|
||||
return Optional.of(NativeImageUtil.writeToByteArray(image.get()));
|
||||
} catch (IOException exception) {
|
||||
// TODO log
|
||||
return Optional.empty();
|
||||
}
|
||||
})
|
||||
.or(() -> {
|
||||
try (InputStream textureStream = context.assetResolver().openAsset(textureLocation)) {
|
||||
return Optional.of(textureStream.readAllBytes());
|
||||
} catch (IOException exception) {
|
||||
// TODO log
|
||||
return Optional.empty();
|
||||
}
|
||||
})
|
||||
.map(bytes -> serializer.saveTexture(bytes, paths.packRoot().resolve(textureLocation.getPath())))
|
||||
.ifPresent(futures::add);
|
||||
}
|
||||
|
||||
if (paths.zipOutput().isPresent()) {
|
||||
|
||||
@@ -33,7 +33,7 @@ public record BedrockTextures(Map<String, String> textures) {
|
||||
private final Map<String, String> textures = new HashMap<>();
|
||||
|
||||
public Builder withItemTexture(BedrockItem item) {
|
||||
return withTexture(item.textureName(), TEXTURES_FOLDER + item.texture().getPath());
|
||||
return withTexture(item.textureName(), TEXTURES_FOLDER + item.geometry().texture().location().getPath());
|
||||
}
|
||||
|
||||
public Builder withTexture(String name, String texture) {
|
||||
|
||||
@@ -5,10 +5,16 @@
|
||||
"compatibilityLevel": "JAVA_21",
|
||||
"client": [
|
||||
"LateBoundIdMapperAccessor",
|
||||
"NativeImageAccessor",
|
||||
"RangeSelectItemModelAccessor",
|
||||
"SpriteContentsAccessor",
|
||||
"SpriteLoaderAccessor",
|
||||
"TextureSlotsAccessor"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
},
|
||||
"mixins": [
|
||||
"FaceBakeryAccessor"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user