mirror of
https://github.com/GeyserMC/Rainbow.git
synced 2025-12-19 14:59:16 +00:00
Sort Geyser mappings, async texture stitching, fix datagen
This commit is contained in:
@@ -87,9 +87,9 @@ 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().geometry().isPresent()).count();
|
||||
long animations = bedrockItems.stream().filter(item -> item.geometry().animation().isPresent()).count();
|
||||
//long attachables = bedrockItems.stream().filter(item -> item.attachableCreator().isPresent()).count();
|
||||
long geometries = bedrockItems.stream().filter(item -> item.geometryContext().geometry().isPresent()).count();
|
||||
long animations = bedrockItems.stream().filter(item -> item.geometryContext().animation().isPresent()).count();
|
||||
|
||||
return """
|
||||
-- PACK GENERATION REPORT --
|
||||
@@ -98,15 +98,15 @@ public final class PackManager {
|
||||
Generated pack: %s
|
||||
Mappings written: %d
|
||||
Item texture atlas size: %d
|
||||
Attachables tried to export: %d
|
||||
Attachables tried to export: FIXME
|
||||
Geometry files tried to export: %d
|
||||
Animations tried to export: %d
|
||||
Textures tried to export: %d
|
||||
Textures tried to export: FIXME
|
||||
|
||||
-- MAPPING TREE REPORT --
|
||||
%s
|
||||
""".formatted(randomSummaryComment(), pack.name(), pack.getMappings(), pack.getItemTextureAtlasSize(),
|
||||
attachables, geometries, animations, pack.getAdditionalExportedTextures(), problems);
|
||||
geometries, animations, problems);
|
||||
}
|
||||
|
||||
private static String randomSummaryComment() {
|
||||
|
||||
@@ -70,7 +70,7 @@ public abstract class RainbowModelProvider extends FabricModelProvider {
|
||||
CompletableFuture<BedrockPack> bedrockPack = ClientPackLoader.openClientResources()
|
||||
.thenCompose(resourceManager -> registries.thenApply(registries -> {
|
||||
try (resourceManager) {
|
||||
BedrockPack pack = createBedrockPack(outputRoot, new Serializer(output, resourceManager, registries),
|
||||
BedrockPack pack = createBedrockPack(outputRoot, new Serializer(output, registries),
|
||||
new DatagenResolver(resourceManager, equipmentInfos, itemInfos, models)).build();
|
||||
|
||||
for (Item item : itemInfos.keySet()) {
|
||||
@@ -105,7 +105,7 @@ public abstract class RainbowModelProvider extends FabricModelProvider {
|
||||
this.models = models;
|
||||
}
|
||||
|
||||
private record Serializer(CachedOutput output, ResourceManager resourceManager, HolderLookup.Provider registries) implements PackSerializer {
|
||||
private record Serializer(CachedOutput output, HolderLookup.Provider registries) implements PackSerializer {
|
||||
|
||||
@Override
|
||||
public <T> CompletableFuture<?> saveJson(Codec<T> codec, T object, Path path) {
|
||||
@@ -113,12 +113,10 @@ public abstract class RainbowModelProvider extends FabricModelProvider {
|
||||
}
|
||||
|
||||
@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)) {
|
||||
byte[] textureBytes = inputTexture.readAllBytes();
|
||||
output.writeIfNeeded(path, textureBytes, HashCode.fromBytes(textureBytes));
|
||||
try {
|
||||
output.writeIfNeeded(path, texture, HashCode.fromBytes(texture));
|
||||
} catch (IOException exception) {
|
||||
LOGGER.error("Failed to save file to {}", path, exception);
|
||||
}
|
||||
@@ -181,5 +179,10 @@ public abstract class RainbowModelProvider extends FabricModelProvider {
|
||||
public Optional<EquipmentClientInfo> getEquipmentInfo(ResourceKey<EquipmentAsset> key) {
|
||||
return Optional.ofNullable(equipmentInfos.get(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openAsset(ResourceLocation location) throws IOException {
|
||||
return resourceManager.open(location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,12 @@ package org.geysermc.rainbow.definition;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public record GeyserGroupDefinition(Optional<ResourceLocation> model, List<GeyserMapping> definitions) implements GeyserMapping {
|
||||
@@ -18,7 +21,9 @@ public record GeyserGroupDefinition(Optional<ResourceLocation> model, List<Geyse
|
||||
);
|
||||
|
||||
public GeyserGroupDefinition with(GeyserMapping mapping) {
|
||||
return new GeyserGroupDefinition(model, Stream.concat(definitions.stream(), Stream.of(mapping)).toList());
|
||||
return new GeyserGroupDefinition(model, Stream.concat(definitions.stream(), Stream.of(mapping))
|
||||
.sorted(Comparator.comparing(Function.identity()))
|
||||
.toList());
|
||||
}
|
||||
|
||||
public boolean isFor(Optional<ResourceLocation> model) {
|
||||
@@ -53,4 +58,26 @@ public record GeyserGroupDefinition(Optional<ResourceLocation> model, List<Geyse
|
||||
public Type type() {
|
||||
return Type.GROUP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull GeyserMapping other) {
|
||||
if (other instanceof GeyserGroupDefinition(Optional<ResourceLocation> otherModel, List<GeyserMapping> otherDefinitions)) {
|
||||
if (model.isPresent() && otherModel.isPresent()) {
|
||||
return model.get().compareTo(otherModel.get());
|
||||
} else if (model.isPresent()) {
|
||||
return 1; // Groups with models are always greater than groups without
|
||||
} else if (otherModel.isPresent()) {
|
||||
return -1;
|
||||
} else if (definitions.isEmpty() && otherDefinitions.isEmpty()) {
|
||||
return 0;
|
||||
} else if (definitions.isEmpty()) {
|
||||
return -1; // Groups with definitions are always greater than groups without
|
||||
} else if (otherDefinitions.isEmpty()) {
|
||||
return 1;
|
||||
}
|
||||
// Compare the first definition as a last resort
|
||||
return definitions.getFirst().compareTo(otherDefinitions.getFirst());
|
||||
}
|
||||
return 1; // Groups are always greater than individual mappings
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.geysermc.rainbow.definition;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -9,4 +10,12 @@ public interface GeyserItemDefinition extends GeyserMapping {
|
||||
GeyserBaseDefinition base();
|
||||
|
||||
boolean conflictsWith(Optional<ResourceLocation> parentModel, GeyserItemDefinition other);
|
||||
|
||||
@Override
|
||||
default int compareTo(@NotNull GeyserMapping other) {
|
||||
if (other instanceof GeyserItemDefinition itemDefinition) {
|
||||
return base().bedrockIdentifier().compareTo(itemDefinition.base().bedrockIdentifier());
|
||||
}
|
||||
return -1; // Groups are always greater than individual mappings
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.util.StringRepresentable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface GeyserMapping {
|
||||
public interface GeyserMapping extends Comparable<GeyserMapping> {
|
||||
|
||||
Codec<GeyserMapping> CODEC = Codec.lazyInitialized(() -> Type.CODEC.dispatch(GeyserMapping::type, Type::codec));
|
||||
// Not perfect since we're not checking single definitions in groups without a model... but good enough
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.geysermc.rainbow.CodecUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -26,7 +27,10 @@ public class GeyserMappings {
|
||||
).apply(instance, (format, mappings) -> new GeyserMappings(mappings))
|
||||
);
|
||||
|
||||
private final Multimap<Holder<Item>, GeyserMapping> mappings = MultimapBuilder.hashKeys().hashSetValues().build();
|
||||
private final Multimap<Holder<Item>, GeyserMapping> mappings = MultimapBuilder
|
||||
.hashKeys()
|
||||
.<GeyserMapping>treeSetValues(Comparator.comparing(mapping -> mapping))
|
||||
.build();
|
||||
|
||||
public GeyserMappings() {}
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ public class BedrockItemMapper {
|
||||
bedrockIdentifier = itemModelLocation;
|
||||
}
|
||||
|
||||
BedrockGeometryContext geometry = BedrockGeometryContext.create(itemModel);
|
||||
BedrockGeometryContext geometry = BedrockGeometryContext.create(bedrockIdentifier, itemModel, 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);
|
||||
@@ -238,9 +238,8 @@ public class BedrockItemMapper {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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())));
|
||||
AttachableMapper.mapItem(packContext.assetResolver(), geometry, stack.getComponentsPatch())));
|
||||
}
|
||||
|
||||
public void report(String problem) {
|
||||
|
||||
@@ -2,12 +2,7 @@ package org.geysermc.rainbow.mapping;
|
||||
|
||||
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<TextureHolder> additionalTextureConsumer,
|
||||
boolean reportSuccesses) {
|
||||
}
|
||||
GeometryRenderer geometryRenderer, boolean reportSuccesses) {}
|
||||
|
||||
@@ -19,12 +19,12 @@ import java.util.function.Consumer;
|
||||
|
||||
public class AttachableMapper {
|
||||
|
||||
public static Optional<BedrockAttachable> mapItem(DataComponentPatch components, ResourceLocation bedrockIdentifier, BedrockGeometryContext geometryContext,
|
||||
AssetResolver assetResolver, Consumer<TextureHolder> textureConsumer) {
|
||||
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 geometryContext.geometry()
|
||||
.map(geometry -> BedrockAttachable.geometry(bedrockIdentifier, geometry.definitions().getFirst(), geometryContext.texture().location().getPath()))
|
||||
return (bedrockIdentifier, stitchedGeometry, textureConsumer) -> stitchedGeometry
|
||||
.map(BedrockGeometryContext.StitchedGeometry::geometry)
|
||||
.map(geometry -> BedrockAttachable.geometry(bedrockIdentifier, geometry.definitions().getFirst(), geometryContext.icon().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)))
|
||||
@@ -55,4 +55,10 @@ public class AttachableMapper {
|
||||
private static ResourceLocation getTexture(List<EquipmentClientInfo.Layer> info, EquipmentClientInfo.LayerType layer) {
|
||||
return info.getFirst().textureId().withPath(path -> "entity/equipment/" + layer.getSerializedName() + "/" + path);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface AttachableCreator {
|
||||
|
||||
Optional<BedrockAttachable> create(ResourceLocation bedrockIdentifier, Optional<BedrockGeometryContext.StitchedGeometry> geometry, Consumer<TextureHolder> textureConsumer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +1,62 @@
|
||||
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.resources.ResourceLocation;
|
||||
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 java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public record BedrockGeometryContext(Optional<BedrockGeometry> geometry, Optional<BedrockAnimationContext> animation, TextureHolder texture,
|
||||
public record BedrockGeometryContext(Optional<Supplier<StitchedGeometry>> 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(ResolvedModel model) {
|
||||
public static BedrockGeometryContext create(ResourceLocation bedrockIdentifier, ResolvedModel model, 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<BedrockGeometry> geometry;
|
||||
Optional<Supplier<StitchedGeometry>> geometry;
|
||||
Optional<BedrockAnimationContext> animation;
|
||||
TextureHolder texture;
|
||||
TextureHolder icon;
|
||||
|
||||
if (layer0Texture != null) {
|
||||
geometry = Optional.empty();
|
||||
animation = Optional.empty();
|
||||
texture = new TextureHolder(layer0Texture.texture());
|
||||
icon = 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);
|
||||
String safeIdentifier = Rainbow.fileSafeResourceLocation(bedrockIdentifier);
|
||||
|
||||
geometry = Optional.of(Suppliers.memoize(() -> {
|
||||
StitchedTextures stitchedTextures = StitchedTextures.stitchModelTextures(textures, context);
|
||||
BedrockGeometry mappedGeometry = GeometryMapper.mapGeometry(safeIdentifier, "bone", model, stitchedTextures);
|
||||
return new StitchedGeometry(mappedGeometry, new TextureHolder(modelLocation.withSuffix("_stitched"), stitchedTextures.stitched()));
|
||||
}));
|
||||
|
||||
animation = Optional.of(AnimationMapper.mapAnimation(safeIdentifier, "bone", model.getTopTransforms()));
|
||||
texture = new TextureHolder(modelLocation.withSuffix("_stitched"), Optional.of(stitchedTextures.stitched()));
|
||||
// TODO geometry rendering
|
||||
icon = new TextureHolder(modelLocation); // TODO
|
||||
}
|
||||
|
||||
return new BedrockGeometryContext(geometry, animation, texture, handheld);
|
||||
return new BedrockGeometryContext(geometry, animation, icon, handheld);
|
||||
}
|
||||
|
||||
public record StitchedGeometry(BedrockGeometry geometry, TextureHolder stitchedTextures) {}
|
||||
}
|
||||
|
||||
@@ -14,15 +14,14 @@ import org.joml.Vector3f;
|
||||
import org.joml.Vector3fc;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class GeometryMapper {
|
||||
private static final Vector3fc CENTRE_OFFSET = new Vector3f(8.0F, 0.0F, 8.0F);
|
||||
|
||||
public static Optional<BedrockGeometry> mapGeometry(String identifier, String boneName, ResolvedModel model, StitchedTextures textures) {
|
||||
public static BedrockGeometry mapGeometry(String identifier, String boneName, ResolvedModel model, StitchedTextures textures) {
|
||||
UnbakedGeometry top = model.getTopGeometry();
|
||||
if (top == UnbakedGeometry.EMPTY) {
|
||||
return Optional.empty();
|
||||
return BedrockGeometry.EMPTY;
|
||||
}
|
||||
|
||||
BedrockGeometry.Builder builder = BedrockGeometry.builder(identifier);
|
||||
@@ -54,7 +53,7 @@ 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(builder.withBone(bone).build());
|
||||
return builder.withBone(bone).build();
|
||||
}
|
||||
|
||||
private static BedrockGeometry.Cube.Builder mapBlockElement(BlockElement element, StitchedTextures textures) {
|
||||
|
||||
@@ -11,6 +11,8 @@ 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.Rainbow;
|
||||
import org.geysermc.rainbow.mapping.PackContext;
|
||||
import org.geysermc.rainbow.mixin.SpriteContentsAccessor;
|
||||
import org.geysermc.rainbow.mixin.SpriteLoaderAccessor;
|
||||
import org.geysermc.rainbow.mixin.TextureSlotsAccessor;
|
||||
@@ -33,9 +35,9 @@ public record StitchedTextures(Map<String, TextureAtlasSprite> sprites, Supplier
|
||||
return Optional.ofNullable(sprites.get(key));
|
||||
}
|
||||
|
||||
public static StitchedTextures stitchModelTextures(TextureSlots textures) {
|
||||
public static StitchedTextures stitchModelTextures(TextureSlots textures, PackContext context) {
|
||||
Map<String, Material> materials = ((TextureSlotsAccessor) textures).getResolvedValues();
|
||||
SpriteLoader.Preparations preparations = prepareStitching(materials.values().stream().map(Material::texture));
|
||||
SpriteLoader.Preparations preparations = prepareStitching(materials.values().stream().map(Material::texture), context);
|
||||
|
||||
Map<String, TextureAtlasSprite> sprites = new HashMap<>();
|
||||
for (Map.Entry<String, Material> material : materials.entrySet()) {
|
||||
@@ -44,19 +46,19 @@ public record StitchedTextures(Map<String, TextureAtlasSprite> sprites, Supplier
|
||||
return new StitchedTextures(Map.copyOf(sprites), () -> stitchTextureAtlas(preparations), preparations.width(), preparations.height());
|
||||
}
|
||||
|
||||
private static SpriteLoader.Preparations prepareStitching(Stream<ResourceLocation> textures) {
|
||||
private static SpriteLoader.Preparations prepareStitching(Stream<ResourceLocation> textures, PackContext context) {
|
||||
// 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.distinct().map(StitchedTextures::readSpriteContents).toList();
|
||||
List<SpriteContents> sprites = textures.distinct().map(texture -> readSpriteContents(texture, context)).toList();
|
||||
return ((SpriteLoaderAccessor) spriteLoader).invokeStitch(sprites, 0, Util.backgroundExecutor());
|
||||
}
|
||||
|
||||
private static SpriteContents readSpriteContents(ResourceLocation location) {
|
||||
private static SpriteContents readSpriteContents(ResourceLocation location, PackContext context) {
|
||||
// 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"))) {
|
||||
try (InputStream textureStream = context.assetResolver().openAsset(Rainbow.decorateTextureLocation(location))) {
|
||||
NativeImage texture = NativeImage.read(textureStream);
|
||||
return new SpriteContents(location, new FrameSize(texture.getWidth(), texture.getHeight()), texture);
|
||||
} catch (IOException exception) {
|
||||
|
||||
@@ -8,6 +8,10 @@ import java.util.function.Supplier;
|
||||
|
||||
public record TextureHolder(ResourceLocation location, Optional<Supplier<NativeImage>> supplier) {
|
||||
|
||||
public TextureHolder(ResourceLocation location, Supplier<NativeImage> supplier) {
|
||||
this(location, Optional.of(supplier));
|
||||
}
|
||||
|
||||
public TextureHolder(ResourceLocation location) {
|
||||
this(location, Optional.empty());
|
||||
}
|
||||
|
||||
@@ -3,24 +3,39 @@ 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.mapping.attachable.AttachableMapper;
|
||||
import org.geysermc.rainbow.mapping.geometry.BedrockGeometryContext;
|
||||
import org.geysermc.rainbow.mapping.geometry.TextureHolder;
|
||||
import org.geysermc.rainbow.pack.attachable.BedrockAttachable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public record BedrockItem(ResourceLocation identifier, String textureName, BedrockGeometryContext geometry, Optional<BedrockAttachable> attachable) {
|
||||
public record BedrockItem(ResourceLocation identifier, String textureName, BedrockGeometryContext geometryContext, AttachableMapper.AttachableCreator attachableCreator) {
|
||||
|
||||
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.geometry().stream().map(present -> present.save(serializer, geometryDirectory)),
|
||||
geometry.animation().stream().map(context -> context.animation().save(serializer, animationDirectory, Rainbow.fileSafeResourceLocation(identifier)))
|
||||
)
|
||||
).toList();
|
||||
public CompletableFuture<?> save(PackSerializer serializer, Path attachableDirectory, Path geometryDirectory, Path animationDirectory,
|
||||
Function<TextureHolder, CompletableFuture<?>> textureSaver) {
|
||||
return 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(
|
||||
textureSaver.apply(geometryContext.icon()),
|
||||
createdAttachable.map(attachable -> attachable.save(serializer, attachableDirectory)).orElse(noop()),
|
||||
CompletableFuture.allOf(attachableTextures.stream().map(textureSaver).toArray(CompletableFuture[]::new)),
|
||||
stitchedGeometry.map(BedrockGeometryContext.StitchedGeometry::geometry).map(geometry -> geometry.save(serializer, geometryDirectory)).orElse(noop()),
|
||||
stitchedGeometry.map(BedrockGeometryContext.StitchedGeometry::stitchedTextures).map(textureSaver).orElse(noop()),
|
||||
geometryContext.animation().map(context -> context.animation().save(serializer, animationDirectory, Rainbow.fileSafeResourceLocation(identifier))).orElse(noop())
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private static <T> CompletableFuture<T> noop() {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
public class BedrockPack {
|
||||
@@ -45,7 +46,6 @@ public class BedrockPack {
|
||||
|
||||
private final BedrockTextures.Builder itemTextures = BedrockTextures.builder();
|
||||
private final Set<BedrockItem> bedrockItems = new HashSet<>();
|
||||
private final Set<TextureHolder> texturesToExport = new HashSet<>();
|
||||
private final Set<ResourceLocation> modelsMapped = new HashSet<>();
|
||||
private final IntSet customModelDataMapped = new IntOpenHashSet();
|
||||
|
||||
@@ -63,9 +63,8 @@ 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);
|
||||
texturesToExport.add(item.geometry().texture());
|
||||
bedrockItems.add(item);
|
||||
}, assetResolver, geometryRenderer, texturesToExport::add, reportSuccesses);
|
||||
}, assetResolver, geometryRenderer, reportSuccesses);
|
||||
this.reporter = reporter;
|
||||
}
|
||||
|
||||
@@ -128,13 +127,10 @@ public class BedrockPack {
|
||||
futures.add(serializer.saveJson(GeyserMappings.CODEC, context.mappings(), paths.mappings()));
|
||||
futures.add(serializer.saveJson(PackManifest.CODEC, manifest, paths.manifest()));
|
||||
futures.add(serializer.saveJson(BedrockTextureAtlas.CODEC, BedrockTextureAtlas.itemAtlas(name, itemTextures), paths.itemAtlas()));
|
||||
for (BedrockItem item : bedrockItems) {
|
||||
futures.addAll(item.save(serializer, paths.attachables(), paths.geometry(), paths.animation()));
|
||||
}
|
||||
|
||||
for (TextureHolder texture : texturesToExport) {
|
||||
Function<TextureHolder, CompletableFuture<?>> textureSaver = texture -> {
|
||||
ResourceLocation textureLocation = Rainbow.decorateTextureLocation(texture.location());
|
||||
texture.supplier()
|
||||
return texture.supplier()
|
||||
.flatMap(image -> {
|
||||
try {
|
||||
return Optional.of(NativeImageUtil.writeToByteArray(image.get()));
|
||||
@@ -152,7 +148,11 @@ public class BedrockPack {
|
||||
}
|
||||
})
|
||||
.map(bytes -> serializer.saveTexture(bytes, paths.packRoot().resolve(textureLocation.getPath())))
|
||||
.ifPresent(futures::add);
|
||||
.orElse(CompletableFuture.completedFuture(null));
|
||||
};
|
||||
|
||||
for (BedrockItem item : bedrockItems) {
|
||||
futures.add(item.save(serializer, paths.attachables(), paths.geometry(), paths.animation(), textureSaver));
|
||||
}
|
||||
|
||||
if (paths.zipOutput().isPresent()) {
|
||||
@@ -178,10 +178,6 @@ public class BedrockPack {
|
||||
return itemTextures.build().size();
|
||||
}
|
||||
|
||||
public int getAdditionalExportedTextures() {
|
||||
return texturesToExport.size();
|
||||
}
|
||||
|
||||
public ProblemReporter.Collector getReporter() {
|
||||
return reporter;
|
||||
}
|
||||
|
||||
@@ -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.geometry().texture().location().getPath());
|
||||
return withTexture(item.textureName(), TEXTURES_FOLDER + item.geometryContext().icon().location().getPath());
|
||||
}
|
||||
|
||||
public Builder withTexture(String name, String texture) {
|
||||
|
||||
@@ -32,6 +32,8 @@ public record BedrockGeometry(BedrockVersion formatVersion, List<GeometryDefinit
|
||||
).apply(instance, BedrockGeometry::new)
|
||||
);
|
||||
|
||||
public static BedrockGeometry EMPTY = new BedrockGeometry(FORMAT_VERSION, List.of());
|
||||
|
||||
public CompletableFuture<?> save(PackSerializer serializer, Path geometryDirectory) {
|
||||
return serializer.saveJson(CODEC, this, geometryDirectory.resolve(definitions.getFirst().info.identifier + ".geo.json"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user