From d7138994eb39f1d438edf23c3dcf038e2aa6a2b7 Mon Sep 17 00:00:00 2001 From: Salandora Date: Wed, 16 Oct 2024 21:16:00 +0200 Subject: [PATCH 1/4] fix: IModelBuilder.Simple needs to call emit or else the data will be lost on the next getEmitter call --- .../porting_lib/models/IModelBuilder.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/model_loader/src/main/java/io/github/fabricators_of_create/porting_lib/models/IModelBuilder.java b/modules/model_loader/src/main/java/io/github/fabricators_of_create/porting_lib/models/IModelBuilder.java index 88e818a5..ed83efc3 100644 --- a/modules/model_loader/src/main/java/io/github/fabricators_of_create/porting_lib/models/IModelBuilder.java +++ b/modules/model_loader/src/main/java/io/github/fabricators_of_create/porting_lib/models/IModelBuilder.java @@ -73,19 +73,19 @@ private Simple(boolean hasAmbientOcclusion, boolean usesBlockLight, boolean isGu @Override public Simple addCulledFace(Direction facing, BakedQuad quad) { - builder.getEmitter().fromVanilla(quad, material, facing); + builder.getEmitter().fromVanilla(quad, material, facing).emit(); return this; } @Override public Simple addUnculledFace(BakedQuad quad) { - builder.getEmitter().fromVanilla(quad, material, null); + builder.getEmitter().fromVanilla(quad, material, null).emit(); return this; } @Override public Simple addFace(QuadView quad) { - builder.getEmitter().copyFrom(quad); + builder.getEmitter().copyFrom(quad).emit(); return this; } From b976143e6e51e66e2d24297cafb956d9c57ac2fe Mon Sep 17 00:00:00 2001 From: Salandora Date: Sat, 19 Oct 2024 14:10:20 +0200 Subject: [PATCH 2/4] feat(porting): added getFluidContained function --- .../porting_lib/util/FluidUtil.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/modules/transfer/src/main/java/io/github/fabricators_of_create/porting_lib/util/FluidUtil.java b/modules/transfer/src/main/java/io/github/fabricators_of_create/porting_lib/util/FluidUtil.java index b25e479e..b810a72f 100644 --- a/modules/transfer/src/main/java/io/github/fabricators_of_create/porting_lib/util/FluidUtil.java +++ b/modules/transfer/src/main/java/io/github/fabricators_of_create/porting_lib/util/FluidUtil.java @@ -1,14 +1,21 @@ package io.github.fabricators_of_create.porting_lib.util; +import io.github.fabricators_of_create.porting_lib.fluids.FluidStack; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.transfer.v1.context.ContainerItemContext; +import net.fabricmc.fabric.api.transfer.v1.fluid.FluidStorage; +import net.fabricmc.fabric.api.transfer.v1.storage.StorageUtil; import net.minecraft.Util; import net.minecraft.client.resources.language.I18n; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.Fluids; +import java.util.Optional; + public class FluidUtil { @Environment(EnvType.CLIENT) public static String getTranslationKey(Fluid fluid) { @@ -29,4 +36,21 @@ public static String getTranslationKey(Fluid fluid) { return translationKey; } + + /** + * Helper method to get the fluid contained in an itemStack + */ + public static Optional getFluidContained(ItemStack container) { + if (!container.isEmpty()) { + container = container.copyWithCount(1); + Optional fluidContained = Optional.ofNullable(ContainerItemContext.withConstant(container).find(FluidStorage.ITEM)) + .map(handler -> StorageUtil.findExtractableContent(handler, null)) + .map(FluidStack::new); + + if (fluidContained.isPresent() && !fluidContained.get().isEmpty()) { + return fluidContained; + } + } + return Optional.empty(); + } } From 7aa1f4d841e04f03af703d875c7961d9c6cffacd Mon Sep 17 00:00:00 2001 From: Salandora Date: Sat, 19 Oct 2024 14:17:29 +0200 Subject: [PATCH 3/4] feat(porting): added emitBlockQuads and emitItemQuads to CompositeModel in preparation for DynamicFluidContainerModel porting --- .../porting_lib/models/CompositeModel.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/modules/models/src/main/java/io/github/fabricators_of_create/porting_lib/models/CompositeModel.java b/modules/models/src/main/java/io/github/fabricators_of_create/porting_lib/models/CompositeModel.java index 84144b9a..5a4e0d36 100644 --- a/modules/models/src/main/java/io/github/fabricators_of_create/porting_lib/models/CompositeModel.java +++ b/modules/models/src/main/java/io/github/fabricators_of_create/porting_lib/models/CompositeModel.java @@ -9,17 +9,17 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.Function; +import java.util.function.Supplier; import io.github.fabricators_of_create.porting_lib.models.geometry.IGeometryBakingContext; import io.github.fabricators_of_create.porting_lib.models.geometry.IGeometryLoader; import io.github.fabricators_of_create.porting_lib.models.geometry.IUnbakedGeometry; -import net.minecraft.client.renderer.RenderType; +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.block.model.BlockModel; import net.minecraft.client.renderer.block.model.ItemOverrides; @@ -115,6 +115,20 @@ public Baked(boolean isGui3d, boolean isSideLit, boolean isAmbientOcclusion, Tex this.itemPasses = itemPasses; } + @Override + public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { + for (Map.Entry entry : children.entrySet()) { + entry.getValue().emitBlockQuads(blockView, state, pos, randomSupplier, context); + } + } + + @Override + public void emitItemQuads(ItemStack stack, Supplier randomSupplier, RenderContext context) { + for (Map.Entry entry : children.entrySet()) { + entry.getValue().emitItemQuads(stack, randomSupplier, context); + } + } + @Override public List getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand/*, ModelData data, @Nullable RenderType renderType*/) { List> quadLists = new ArrayList<>(); From ff7374f81a781cc8958f10b4713bdcd5c0d58fcb Mon Sep 17 00:00:00 2001 From: Salandora Date: Sat, 19 Oct 2024 14:17:45 +0200 Subject: [PATCH 4/4] feat(porting): port DynamicFluidContainerModel --- modules/models/build.gradle | 2 +- .../models/DynamicFluidContainerModel.java | 546 +++++++++++------- .../porting_lib/models/PortingLibModels.java | 3 +- .../assets/neoforge/models/item/bucket.json | 7 + .../neoforge/models/item/bucket_drip.json | 7 + .../assets/neoforge/models/item/default.json | 29 + .../textures/item/mask/bucket_fluid.png | Bin 0 -> 158 bytes .../textures/item/mask/bucket_fluid_cover.png | Bin 0 -> 168 bytes .../item/mask/bucket_fluid_cover_drip.png | Bin 0 -> 181 bytes .../textures/item/mask/bucket_fluid_drip.png | Bin 0 -> 172 bytes .../testmod/PortingLibModelsTestmod.java | 15 +- .../client/PortingLibModelsTestmodClient.java | 6 + .../assets/porting_lib/lang/en_us.json | 4 +- .../porting_lib/models/item/lava_bucket.json | 5 + .../porting_lib/models/item/water_bucket.json | 5 + 15 files changed, 411 insertions(+), 218 deletions(-) create mode 100644 modules/models/src/main/resources/assets/neoforge/models/item/bucket.json create mode 100644 modules/models/src/main/resources/assets/neoforge/models/item/bucket_drip.json create mode 100644 modules/models/src/main/resources/assets/neoforge/models/item/default.json create mode 100644 modules/models/src/main/resources/assets/neoforge/textures/item/mask/bucket_fluid.png create mode 100644 modules/models/src/main/resources/assets/neoforge/textures/item/mask/bucket_fluid_cover.png create mode 100644 modules/models/src/main/resources/assets/neoforge/textures/item/mask/bucket_fluid_cover_drip.png create mode 100644 modules/models/src/main/resources/assets/neoforge/textures/item/mask/bucket_fluid_drip.png create mode 100644 modules/models/src/testmod/resources/assets/porting_lib/models/item/lava_bucket.json create mode 100644 modules/models/src/testmod/resources/assets/porting_lib/models/item/water_bucket.json diff --git a/modules/models/build.gradle b/modules/models/build.gradle index 654322e4..ae754529 100644 --- a/modules/models/build.gradle +++ b/modules/models/build.gradle @@ -1,5 +1,5 @@ portingLib { - addModuleDependencies(["model_loader", "transfer", "data"]) + addModuleDependencies(["model_loader", "transfer", "data", "fluids"]) enableTestMod() } diff --git a/modules/models/src/main/java/io/github/fabricators_of_create/porting_lib/models/DynamicFluidContainerModel.java b/modules/models/src/main/java/io/github/fabricators_of_create/porting_lib/models/DynamicFluidContainerModel.java index b668b6b4..4f250089 100644 --- a/modules/models/src/main/java/io/github/fabricators_of_create/porting_lib/models/DynamicFluidContainerModel.java +++ b/modules/models/src/main/java/io/github/fabricators_of_create/porting_lib/models/DynamicFluidContainerModel.java @@ -1,213 +1,333 @@ -//package io.github.fabricators_of_create.porting_lib.models; -// -//import com.google.common.collect.Maps; -//import com.google.gson.JsonDeserializationContext; -//import com.google.gson.JsonObject; -//import com.mojang.math.Transformation; -//import java.util.Map; -//import java.util.function.Function; -// -//import io.github.fabricators_of_create.porting_lib.models.geometry.IGeometryBakingContext; -//import io.github.fabricators_of_create.porting_lib.models.geometry.IGeometryLoader; -//import io.github.fabricators_of_create.porting_lib.models.geometry.IUnbakedGeometry; -//import io.github.fabricators_of_create.porting_lib.models.geometry.SimpleModelState; -//import io.github.fabricators_of_create.porting_lib.models.geometry.StandaloneGeometryBakingContext; -//import net.minecraft.client.color.item.ItemColor; -//import net.minecraft.client.multiplayer.ClientLevel; -//import net.minecraft.client.renderer.RenderType; -//import net.minecraft.client.renderer.block.model.ItemOverrides; -//import net.minecraft.client.renderer.texture.TextureAtlasSprite; -//import net.minecraft.client.resources.model.BakedModel; -//import net.minecraft.client.resources.model.BlockModelRotation; -//import net.minecraft.client.resources.model.Material; -//import net.minecraft.client.resources.model.ModelBaker; -//import net.minecraft.client.resources.model.ModelState; -//import net.minecraft.core.registries.BuiltInRegistries; -//import net.minecraft.resources.ResourceLocation; -//import net.minecraft.util.GsonHelper; -//import net.minecraft.world.entity.LivingEntity; -//import net.minecraft.world.item.ItemStack; -//import net.minecraft.world.level.material.Fluid; -//import net.minecraft.world.level.material.Fluids; -//import org.jetbrains.annotations.Nullable; -//import org.joml.Quaternionf; -//import org.joml.Vector3f; -// -///** -// * A dynamic fluid container model, capable of re-texturing itself at runtime to match the contained fluid. -// *

-// * Composed of a base layer, a fluid layer (applied with a mask) and a cover layer (optionally applied with a mask). -// * The entire model may optionally be flipped if the fluid is gaseous, and the fluid layer may glow if light-emitting. -// *

-// * Fluid tinting requires registering a separate {@link ItemColor}. An implementation is provided in {@link Colors}. -// * -// * @see Colors -// */ -//public class DynamicFluidContainerModel implements IUnbakedGeometry { -// // Depth offsets to prevent Z-fighting -// private static final Transformation FLUID_TRANSFORM = new Transformation(new Vector3f(), new Quaternionf(), new Vector3f(1, 1, 1.002f), new Quaternionf()); -// private static final Transformation COVER_TRANSFORM = new Transformation(new Vector3f(), new Quaternionf(), new Vector3f(1, 1, 1.004f), new Quaternionf()); -// -// private final Fluid fluid; -// private final boolean flipGas; -// private final boolean coverIsMask; -// private final boolean applyFluidLuminosity; -// -// private DynamicFluidContainerModel(Fluid fluid, boolean flipGas, boolean coverIsMask, boolean applyFluidLuminosity) { -// this.fluid = fluid; -// this.flipGas = flipGas; -// this.coverIsMask = coverIsMask; -// this.applyFluidLuminosity = applyFluidLuminosity; -// } -// -// public static RenderTypeGroup getLayerRenderTypes(boolean unlit) { -// return new RenderTypeGroup(RenderType.translucent(), unlit ? NeoForgeRenderTypes.ITEM_UNSORTED_UNLIT_TRANSLUCENT.get() : NeoForgeRenderTypes.ITEM_UNSORTED_TRANSLUCENT.get()); -// } -// -// /** -// * Returns a new ModelDynBucket representing the given fluid, but with the same -// * other properties (flipGas, tint, coverIsMask). -// */ -// public DynamicFluidContainerModel withFluid(Fluid newFluid) { -// return new DynamicFluidContainerModel(newFluid, flipGas, coverIsMask, applyFluidLuminosity); -// } -// -// @Override -// public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, ItemOverrides overrides) { -// Material particleLocation = context.hasMaterial("particle") ? context.getMaterial("particle") : null; -// Material baseLocation = context.hasMaterial("base") ? context.getMaterial("base") : null; -// Material fluidMaskLocation = context.hasMaterial("fluid") ? context.getMaterial("fluid") : null; -// Material coverLocation = context.hasMaterial("cover") ? context.getMaterial("cover") : null; -// -// TextureAtlasSprite baseSprite = baseLocation != null ? spriteGetter.apply(baseLocation) : null; -// TextureAtlasSprite fluidSprite = fluid != Fluids.EMPTY ? spriteGetter.apply(ClientHooks.getBlockMaterial(IClientFluidTypeExtensions.of(fluid).getStillTexture())) : null; -// TextureAtlasSprite coverSprite = (coverLocation != null && (!coverIsMask || baseLocation != null)) ? spriteGetter.apply(coverLocation) : null; -// -// TextureAtlasSprite particleSprite = particleLocation != null ? spriteGetter.apply(particleLocation) : null; -// -// if (particleSprite == null) particleSprite = fluidSprite; -// if (particleSprite == null) particleSprite = baseSprite; -// if (particleSprite == null && !coverIsMask) particleSprite = coverSprite; -// -// // If the fluid is lighter than air, rotate 180deg to turn it upside down -// if (flipGas && fluid != Fluids.EMPTY && fluid.getFluidType().isLighterThanAir()) { -// modelState = new SimpleModelState( -// modelState.getRotation().compose( -// new Transformation(null, new Quaternionf(0, 0, 1, 0), null, null))); -// } -// -// // We need to disable GUI 3D and block lighting for this to render properly -// var itemContext = StandaloneGeometryBakingContext.builder(context).withGui3d(false).withUseBlockLight(false).build(ResourceLocation.fromNamespaceAndPath("neoforge", "dynamic_fluid_container")); -// var modelBuilder = CompositeModel.Baked.builder(itemContext, particleSprite, new ContainedFluidOverrideHandler(overrides, baker, itemContext, this), context.getTransforms()); -// -// var normalRenderTypes = getLayerRenderTypes(false); -// -// if (baseLocation != null && baseSprite != null) { -// // Base texture -// var unbaked = UnbakedGeometryHelper.createUnbakedItemElements(0, baseSprite); -// var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> baseSprite, modelState); -// modelBuilder.addQuads(normalRenderTypes, quads); -// } -// -// if (fluidMaskLocation != null && fluidSprite != null) { -// TextureAtlasSprite templateSprite = spriteGetter.apply(fluidMaskLocation); -// if (templateSprite != null) { -// // Fluid layer -// var transformedState = new SimpleModelState(modelState.getRotation().compose(FLUID_TRANSFORM), modelState.isUvLocked()); -// var unbaked = UnbakedGeometryHelper.createUnbakedItemMaskElements(1, templateSprite); // Use template as mask -// var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> fluidSprite, transformedState); // Bake with fluid texture -// -// var emissive = applyFluidLuminosity && fluid.getFluidType().getLightLevel() > 0; -// var renderTypes = getLayerRenderTypes(emissive); -// if (emissive) QuadTransformers.settingMaxEmissivity().processInPlace(quads); -// -// modelBuilder.addQuads(renderTypes, quads); -// } -// } -// -// if (coverSprite != null) { -// var sprite = coverIsMask ? baseSprite : coverSprite; -// if (sprite != null) { -// // Cover/overlay -// var transformedState = new SimpleModelState(modelState.getRotation().compose(COVER_TRANSFORM), modelState.isUvLocked()); -// var unbaked = UnbakedGeometryHelper.createUnbakedItemMaskElements(2, coverSprite); // Use cover as mask -// var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> sprite, transformedState); // Bake with selected texture -// modelBuilder.addQuads(normalRenderTypes, quads); -// } -// } -// -// modelBuilder.setParticle(particleSprite); -// -// return modelBuilder.build(); -// } -// -// public static final class Loader implements IGeometryLoader { -// public static final Loader INSTANCE = new Loader(); -// -// private Loader() {} -// -// @Override -// public DynamicFluidContainerModel read(JsonObject jsonObject, JsonDeserializationContext deserializationContext) { -// if (!jsonObject.has("fluid")) -// throw new RuntimeException("Bucket model requires 'fluid' value."); -// -// ResourceLocation fluidName = ResourceLocation.parse(jsonObject.get("fluid").getAsString()); -// -// Fluid fluid = BuiltInRegistries.FLUID.get(fluidName); -// -// boolean flip = GsonHelper.getAsBoolean(jsonObject, "flip_gas", false); -// boolean coverIsMask = GsonHelper.getAsBoolean(jsonObject, "cover_is_mask", true); -// boolean applyFluidLuminosity = GsonHelper.getAsBoolean(jsonObject, "apply_fluid_luminosity", true); -// -// // create new model with correct liquid -// return new DynamicFluidContainerModel(fluid, flip, coverIsMask, applyFluidLuminosity); -// } -// } -// -// private static final class ContainedFluidOverrideHandler extends ItemOverrides { -// private final Map cache = Maps.newHashMap(); // contains all the baked models since they'll never change -// private final ItemOverrides nested; -// private final ModelBaker baker; -// private final IGeometryBakingContext owner; -// private final DynamicFluidContainerModel parent; -// -// private ContainedFluidOverrideHandler(ItemOverrides nested, ModelBaker baker, IGeometryBakingContext owner, DynamicFluidContainerModel parent) { -// this.nested = nested; -// this.baker = baker; -// this.owner = owner; -// this.parent = parent; -// } -// -// @Override -// public BakedModel resolve(BakedModel originalModel, ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity entity, int seed) { -// BakedModel overridden = nested.resolve(originalModel, stack, level, entity, seed); -// if (overridden != originalModel) return overridden; -// return FluidUtil.getFluidContained(stack) -// .map(fluidStack -> { -// Fluid fluid = fluidStack.getFluid(); -// String name = BuiltInRegistries.FLUID.getKey(fluid).toString(); -// -// if (!cache.containsKey(name)) { -// DynamicFluidContainerModel unbaked = this.parent.withFluid(fluid); -// BakedModel bakedModel = unbaked.bake(owner, baker, Material::sprite, BlockModelRotation.X0_Y0, this); -// cache.put(name, bakedModel); -// return bakedModel; -// } -// -// return cache.get(name); -// }) -// // not a fluid item apparently -// .orElse(originalModel); // empty bucket -// } -// } -// -// public static class Colors implements ItemColor { -// @Override -// public int getColor(ItemStack stack, int tintIndex) { -// if (tintIndex != 1) return 0xFFFFFFFF; -// return FluidUtil.getFluidContained(stack) -// .map(fluidStack -> IClientFluidTypeExtensions.of(fluidStack.getFluid()).getTintColor(fluidStack)) -// .orElse(0xFFFFFFFF); -// } -// } -//} +package io.github.fabricators_of_create.porting_lib.models; + +import com.google.common.collect.Maps; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonObject; +import com.mojang.math.Transformation; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Supplier; + +import io.github.fabricators_of_create.porting_lib.core.PortingLib; +import io.github.fabricators_of_create.porting_lib.models.geometry.IGeometryBakingContext; +import io.github.fabricators_of_create.porting_lib.models.geometry.IGeometryLoader; +import io.github.fabricators_of_create.porting_lib.models.geometry.IUnbakedGeometry; +import io.github.fabricators_of_create.porting_lib.models.geometry.SimpleModelState; +import io.github.fabricators_of_create.porting_lib.models.geometry.StandaloneGeometryBakingContext; +import io.github.fabricators_of_create.porting_lib.util.FluidUtil; +import net.fabricmc.fabric.api.renderer.v1.RendererAccess; +import net.fabricmc.fabric.api.renderer.v1.material.BlendMode; +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; +import net.fabricmc.fabric.api.transfer.v1.client.fluid.FluidVariantRendering; +import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant; +import net.minecraft.client.color.item.ItemColor; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.ItemOverrides; +import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.BlockModelRotation; +import net.minecraft.client.resources.model.Material; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.ModelState; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.GsonHelper; +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.BlockAndTintGetter; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.Fluids; +import org.jetbrains.annotations.Nullable; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +/** + * A dynamic fluid container model, capable of re-texturing itself at runtime to match the contained fluid. + *

+ * Composed of a base layer, a fluid layer (applied with a mask) and a cover layer (optionally applied with a mask). + * The entire model may optionally be flipped if the fluid is gaseous, and the fluid layer may glow if light-emitting. + *

+ * Fluid tinting requires registering a separate {@link ItemColor}. An implementation is provided in {@link Colors}. + * + * @see Colors + */ +public class DynamicFluidContainerModel implements IUnbakedGeometry { + // Depth offsets to prevent Z-fighting + private static final Transformation FLUID_TRANSFORM = new Transformation(new Vector3f(), new Quaternionf(), new Vector3f(1, 1, 1.002f), new Quaternionf()); + private static final Transformation COVER_TRANSFORM = new Transformation(new Vector3f(), new Quaternionf(), new Vector3f(1, 1, 1.004f), new Quaternionf()); + + private final Fluid fluid; + private final boolean flipGas; + private final boolean coverIsMask; + private final boolean applyFluidLuminosity; + + private DynamicFluidContainerModel(Fluid fluid, boolean flipGas, boolean coverIsMask, boolean applyFluidLuminosity) { + this.fluid = fluid; + this.flipGas = flipGas; + this.coverIsMask = coverIsMask; + this.applyFluidLuminosity = applyFluidLuminosity; + } + + public static RenderTypeGroup getLayerRenderTypes(boolean unlit) { + // Must be solid or else water texture will break the rendering + return new RenderTypeGroup(RenderType.translucent(), RenderType.solid()); // unlit ? NeoForgeRenderTypes.ITEM_UNSORTED_UNLIT_TRANSLUCENT.get() : NeoForgeRenderTypes.ITEM_UNSORTED_TRANSLUCENT.get()); + } + + /** + * Returns a new ModelDynBucket representing the given fluid, but with the same + * other properties (flipGas, tint, coverIsMask). + */ + public DynamicFluidContainerModel withFluid(Fluid newFluid) { + return new DynamicFluidContainerModel(newFluid, flipGas, coverIsMask, applyFluidLuminosity); + } + + @Nullable + @Override + public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, ItemOverrides overrides) { + Material particleLocation = context.hasMaterial("particle") ? context.getMaterial("particle") : null; + Material baseLocation = context.hasMaterial("base") ? context.getMaterial("base") : null; + Material fluidMaskLocation = context.hasMaterial("fluid") ? context.getMaterial("fluid") : null; + Material coverLocation = context.hasMaterial("cover") ? context.getMaterial("cover") : null; + + TextureAtlasSprite baseSprite = baseLocation != null ? spriteGetter.apply(baseLocation) : null; + TextureAtlasSprite templateSprite = fluidMaskLocation != null ? spriteGetter.apply(fluidMaskLocation) : null; + TextureAtlasSprite coverSprite = (coverLocation != null && (!coverIsMask || baseLocation != null)) ? spriteGetter.apply(coverLocation) : null; + + TextureAtlasSprite particleSprite = particleLocation != null ? spriteGetter.apply(particleLocation) : null; + + // We need to disable GUI 3D and block lighting for this to render properly + var itemContext = StandaloneGeometryBakingContext.builder(context).withGui3d(false).withUseBlockLight(false).build(PortingLib.id("dynamic_fluid_container")); + var overrideHandler = new ContainedFluidOverrideHandler(overrides, baker, itemContext, this); + + // It is necessary to use a LazyBakedModel here because fluid textures are not loaded yet on game start + // and would lead to fluid containers without fluids. + return new LazyBakedModel(itemContext, baseSprite, templateSprite, coverSprite, particleSprite, modelState, overrideHandler); + } + + public final class LazyBakedModel implements BakedModel { + private final IGeometryBakingContext itemContext; + private final TextureAtlasSprite baseSprite; + private final TextureAtlasSprite templateSprite; + private final TextureAtlasSprite coverSprite; + private final TextureAtlasSprite particleSprite; + private final ModelState modelState; + private final ItemOverrides overrides; + + private BakedModel compositeModel; + + private LazyBakedModel(IGeometryBakingContext itemContext, TextureAtlasSprite baseSprite, TextureAtlasSprite templateSprite, TextureAtlasSprite coverSprite, TextureAtlasSprite particleSprite, ModelState modelState, ItemOverrides overrides) { + this.itemContext = itemContext; + this.baseSprite = baseSprite; + this.templateSprite = templateSprite; + this.coverSprite = coverSprite; + this.particleSprite = particleSprite; + this.modelState = modelState; + this.overrides = overrides; + } + + private BakedModel wrapped() { + if (compositeModel == null) { + compositeModel = initializeWrappedModel(); + } + + return compositeModel; + } + + private BakedModel initializeWrappedModel() { + ModelState modelState = this.modelState; + // If the fluid is lighter than air, rotate 180deg to turn it upside down + if (flipGas && fluid != Fluids.EMPTY && fluid.getFluidType().isLighterThanAir()) { + modelState = new SimpleModelState( + this.modelState.getRotation().compose( + new Transformation(null, new Quaternionf(0, 0, 1, 0), null, null))); + } + + // Initializer must be in the if statement to make it usable in lambdas + TextureAtlasSprite fluidSprite; + if (fluid != Fluids.EMPTY) { + fluidSprite = FluidVariantRendering.getSprite(FluidVariant.of(fluid)); + } else { + fluidSprite = null; + } + + var modelBuilder = CompositeModel.Baked.builder(itemContext, particleSprite, overrides, itemContext.getTransforms()); + + TextureAtlasSprite particleSprite = this.particleSprite; + if (particleSprite == null) particleSprite = fluidSprite; + if (particleSprite == null) particleSprite = baseSprite; + if (particleSprite == null && !coverIsMask) particleSprite = coverSprite; + + var normalRenderTypes = getLayerRenderTypes(false); + if (baseSprite != null) { + // Base texture + var unbaked = UnbakedGeometryHelper.createUnbakedItemElements(0, baseSprite); + var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> baseSprite, modelState); + modelBuilder.addQuads(normalRenderTypes, quads); + } + + if (templateSprite != null && fluidSprite != null) { + // Fluid layer + var transformedState = new SimpleModelState(modelState.getRotation().compose(FLUID_TRANSFORM), modelState.isUvLocked()); + var unbaked = UnbakedGeometryHelper.createUnbakedItemMaskElements(1, templateSprite); // Use template as mask + var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> fluidSprite, transformedState); // Bake with fluid texture + + var emissive = applyFluidLuminosity && fluid.getFluidType().getLightLevel() > 0; + var renderTypes = getLayerRenderTypes(emissive); + + var material = RendererAccess.INSTANCE.getRenderer().materialFinder().blendMode(BlendMode.fromRenderLayer(renderTypes.entity())).emissive(emissive).find(); + var builder = RendererAccess.INSTANCE.getRenderer().meshBuilder(); + quads.forEach(quad -> builder.getEmitter().fromVanilla(quad, material, null).emit()); + modelBuilder.addLayer(new MeshBakedModel(builder.build(), itemContext.useAmbientOcclusion(), itemContext.useBlockLight(), itemContext.isGui3d(), particleSprite, itemContext.getTransforms(), overrides)); + } + + if (coverSprite != null) { + var sprite = coverIsMask ? baseSprite : coverSprite; + if (sprite != null) { + // Cover/overlay + var transformedState = new SimpleModelState(modelState.getRotation().compose(COVER_TRANSFORM), modelState.isUvLocked()); + var unbaked = UnbakedGeometryHelper.createUnbakedItemMaskElements(2, coverSprite); // Use cover as mask + var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> sprite, transformedState); // Bake with selected texture + modelBuilder.addQuads(normalRenderTypes, quads); + } + } + + modelBuilder.setParticle(particleSprite); + + return modelBuilder.build(); + } + + @Override + public boolean isVanillaAdapter() { + // Need to use fabrics rendering api because with the getQuads function water textures still break the rendering + return false; + } + + @Override + public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { + wrapped().emitBlockQuads(blockView, state, pos, randomSupplier, context); + } + + @Override + public void emitItemQuads(ItemStack stack, Supplier randomSupplier, RenderContext context) { + wrapped().emitItemQuads(stack, randomSupplier, context); + } + + @Override + public List getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource random) { + return wrapped().getQuads(state, side, random); + } + + @Override + public boolean useAmbientOcclusion() { + return wrapped().useAmbientOcclusion(); + } + + @Override + public boolean isGui3d() { + return wrapped().isGui3d(); + } + + @Override + public boolean isCustomRenderer() { + return false; + } + + @Override + public TextureAtlasSprite getParticleIcon() { + return wrapped().getParticleIcon(); + } + + @Override + public boolean usesBlockLight() { + return wrapped().usesBlockLight(); + } + + @Override + public ItemTransforms getTransforms() { + return wrapped().getTransforms(); + } + + @Override + public ItemOverrides getOverrides() { + return wrapped().getOverrides(); + } + } + + public static final class Loader implements IGeometryLoader { + public static final Loader INSTANCE = new Loader(); + + private Loader() {} + + @Override + public DynamicFluidContainerModel read(JsonObject jsonObject, JsonDeserializationContext deserializationContext) { + if (!jsonObject.has("fluid")) + throw new RuntimeException("Bucket model requires 'fluid' value."); + + ResourceLocation fluidName = ResourceLocation.parse(jsonObject.get("fluid").getAsString()); + + Fluid fluid = BuiltInRegistries.FLUID.get(fluidName); + + boolean flip = GsonHelper.getAsBoolean(jsonObject, "flip_gas", false); + boolean coverIsMask = GsonHelper.getAsBoolean(jsonObject, "cover_is_mask", true); + boolean applyFluidLuminosity = GsonHelper.getAsBoolean(jsonObject, "apply_fluid_luminosity", true); + + // create new model with correct liquid + return new DynamicFluidContainerModel(fluid, flip, coverIsMask, applyFluidLuminosity); + } + } + + private static final class ContainedFluidOverrideHandler extends ItemOverrides { + private final Map cache = Maps.newHashMap(); // contains all the baked models since they'll never change + private final ItemOverrides nested; + private final ModelBaker baker; + private final IGeometryBakingContext owner; + private final DynamicFluidContainerModel parent; + + private ContainedFluidOverrideHandler(ItemOverrides nested, ModelBaker baker, IGeometryBakingContext owner, DynamicFluidContainerModel parent) { + this.nested = nested; + this.baker = baker; + this.owner = owner; + this.parent = parent; + } + + @Override + public BakedModel resolve(BakedModel originalModel, ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity entity, int seed) { + BakedModel overridden = nested.resolve(originalModel, stack, level, entity, seed); + if (overridden != originalModel) return overridden; + return FluidUtil.getFluidContained(stack) + .map(fluidStack -> { + Fluid fluid = fluidStack.getFluid(); + String name = BuiltInRegistries.FLUID.getKey(fluid).toString(); + + if (!cache.containsKey(name)) { + DynamicFluidContainerModel unbaked = this.parent.withFluid(fluid); + BakedModel bakedModel = unbaked.bake(owner, baker, Material::sprite, BlockModelRotation.X0_Y0, this); + cache.put(name, bakedModel); + return bakedModel; + } + + return cache.get(name); + }) + // not a fluid item apparently + .orElse(originalModel); // empty bucket + } + } + + public static class Colors implements ItemColor { + @Override + public int getColor(ItemStack stack, int tintIndex) { + if (tintIndex != 1) return 0xFFFFFFFF; + return FluidUtil.getFluidContained(stack) + .map(fluidStack -> FluidVariantRendering.getColor(fluidStack.getVariant())) + .orElse(0xFFFFFFFF); + } + } +} diff --git a/modules/models/src/main/java/io/github/fabricators_of_create/porting_lib/models/PortingLibModels.java b/modules/models/src/main/java/io/github/fabricators_of_create/porting_lib/models/PortingLibModels.java index ba535e77..bad357ae 100644 --- a/modules/models/src/main/java/io/github/fabricators_of_create/porting_lib/models/PortingLibModels.java +++ b/modules/models/src/main/java/io/github/fabricators_of_create/porting_lib/models/PortingLibModels.java @@ -8,7 +8,6 @@ import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.resources.ResourceLocation; public class PortingLibModels implements ClientModInitializer { @Override @@ -21,7 +20,7 @@ public void onInitializeClient() { loaders.put(PortingLib.id("composite"), CompositeModel.Loader.INSTANCE); loaders.put(PortingLib.id("item_layers"), ItemLayerModel.Loader.INSTANCE); -// loaders.put(PortingLib.id("fluid_container"), DynamicFluidContainerModel.Loader.INSTANCE); TODO: PORT + loaders.put(PortingLib.id("fluid_container"), DynamicFluidContainerModel.Loader.INSTANCE); }); BlockModel.GSON = BlockModel.GSON.newBuilder() diff --git a/modules/models/src/main/resources/assets/neoforge/models/item/bucket.json b/modules/models/src/main/resources/assets/neoforge/models/item/bucket.json new file mode 100644 index 00000000..3ef34031 --- /dev/null +++ b/modules/models/src/main/resources/assets/neoforge/models/item/bucket.json @@ -0,0 +1,7 @@ +{ + "parent": "neoforge:item/default", + "textures": { + "base": "item/bucket", + "fluid": "neoforge:item/mask/bucket_fluid" + } +} diff --git a/modules/models/src/main/resources/assets/neoforge/models/item/bucket_drip.json b/modules/models/src/main/resources/assets/neoforge/models/item/bucket_drip.json new file mode 100644 index 00000000..366b9379 --- /dev/null +++ b/modules/models/src/main/resources/assets/neoforge/models/item/bucket_drip.json @@ -0,0 +1,7 @@ +{ + "parent": "neoforge:item/default", + "textures": { + "base": "item/bucket", + "fluid": "neoforge:item/mask/bucket_fluid_drip" + } +} diff --git a/modules/models/src/main/resources/assets/neoforge/models/item/default.json b/modules/models/src/main/resources/assets/neoforge/models/item/default.json new file mode 100644 index 00000000..2b194b21 --- /dev/null +++ b/modules/models/src/main/resources/assets/neoforge/models/item/default.json @@ -0,0 +1,29 @@ +{ + "gui_light": "front", + "display": { + "ground": { + "rotation": [ 0, 0, 0 ], + "translation": [ 0, 2, 0], + "scale":[ 0.5, 0.5, 0.5 ] + }, + "head": { + "rotation": [ 0, 180, 0 ], + "translation": [ 0, 13, 7], + "scale":[ 1, 1, 1] + }, + "thirdperson_righthand": { + "rotation": [ 0, 0, 0 ], + "translation": [ 0, 3, 1 ], + "scale": [ 0.55, 0.55, 0.55 ] + }, + "firstperson_righthand": { + "rotation": [ 0, -90, 25 ], + "translation": [ 1.13, 3.2, 1.13], + "scale": [ 0.68, 0.68, 0.68 ] + }, + "fixed": { + "rotation": [ 0, 180, 0 ], + "scale": [ 1, 1, 1 ] + } + } +} diff --git a/modules/models/src/main/resources/assets/neoforge/textures/item/mask/bucket_fluid.png b/modules/models/src/main/resources/assets/neoforge/textures/item/mask/bucket_fluid.png new file mode 100644 index 0000000000000000000000000000000000000000..58f08bc71a08dc26af84e6ab771a4b1775740909 GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucL1RxB#}EtuWQku5f?kIY)+;m^ZDeu-k_Q=D xojr_s6r`ITG6@_=71;H5ae@es*ntBK4EqoAuxv|Ai3VzC@O1TaS?83{1OT9HD literal 0 HcmV?d00001 diff --git a/modules/models/src/main/resources/assets/neoforge/textures/item/mask/bucket_fluid_cover.png b/modules/models/src/main/resources/assets/neoforge/textures/item/mask/bucket_fluid_cover.png new file mode 100644 index 0000000000000000000000000000000000000000..e97bcb54928106d5cff1ce0b9e3a5a519b384d39 GIT binary patch literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucL0eB3#}EtumdK II;Vst0MZ650ssI2 literal 0 HcmV?d00001 diff --git a/modules/models/src/main/resources/assets/neoforge/textures/item/mask/bucket_fluid_cover_drip.png b/modules/models/src/main/resources/assets/neoforge/textures/item/mask/bucket_fluid_cover_drip.png new file mode 100644 index 0000000000000000000000000000000000000000..153f731df1a18d2df5688e45d20846277ccf1ab2 GIT binary patch literal 181 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucK`&1i#}Etujfu>>6ebNd_PEBv^MaFf%huKQ8@cT9rmN&@cv1 LS3j3^P6 EquipmentSlot.HEAD)); public static final Item STONE_2 = new Item(new Item.Properties()); public static final Block NOT_GLASS = new Block(BlockBehaviour. Properties. ofFullCopy(Blocks.GLASS)); + public static final Item WATER_BUCKET = new Item(new Item.Properties()); + public static final Item LAVA_BUCKET = new Item(new Item.Properties()); @Override public void onInitialize() { Registry.register(BuiltInRegistries.ITEM, PortingLib.id("derp_helmet"), DERPY_HELMET); Registry.register(BuiltInRegistries.ITEM, PortingLib.id("stone_2"), STONE_2); Registry.register(BuiltInRegistries.BLOCK, PortingLib.id("not_glass"), NOT_GLASS); + Registry.register(BuiltInRegistries.ITEM, PortingLib.id("water_bucket"), WATER_BUCKET); + Registry.register(BuiltInRegistries.ITEM, PortingLib.id("lava_bucket"), LAVA_BUCKET); + + FluidStorage.ITEM.registerForItems((stack, context) -> new FullItemFluidStorage(context, Items.BUCKET, FluidVariant.of(Fluids.WATER), FluidConstants.BUCKET), WATER_BUCKET); + FluidStorage.ITEM.registerForItems((stack, context) -> new FullItemFluidStorage(context, Items.BUCKET, FluidVariant.of(Fluids.LAVA), FluidConstants.BUCKET), LAVA_BUCKET); } } diff --git a/modules/models/src/testmod/java/io/github/fabricators_of_create/porting_lib/models/testmod/client/PortingLibModelsTestmodClient.java b/modules/models/src/testmod/java/io/github/fabricators_of_create/porting_lib/models/testmod/client/PortingLibModelsTestmodClient.java index 64cd26f1..ef550343 100644 --- a/modules/models/src/testmod/java/io/github/fabricators_of_create/porting_lib/models/testmod/client/PortingLibModelsTestmodClient.java +++ b/modules/models/src/testmod/java/io/github/fabricators_of_create/porting_lib/models/testmod/client/PortingLibModelsTestmodClient.java @@ -1,11 +1,15 @@ package io.github.fabricators_of_create.porting_lib.models.testmod.client; +import io.github.fabricators_of_create.porting_lib.models.DynamicFluidContainerModel; import io.github.fabricators_of_create.porting_lib.models.testmod.PortingLibModelsTestmod; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; +import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry; import net.minecraft.client.resources.model.ModelResourceLocation; import net.minecraft.core.registries.BuiltInRegistries; +import static io.github.fabricators_of_create.porting_lib.models.testmod.PortingLibModelsTestmod.WATER_BUCKET; + public class PortingLibModelsTestmodClient implements ClientModInitializer { @Override public void onInitializeClient() { @@ -13,5 +17,7 @@ public void onInitializeClient() { ModelLoadingPlugin.register(pluginCtx -> pluginCtx.modifyModelAfterBake().register( (model, ctx) -> ctx.topLevelId().equals(location) ? new DerpyItemModel(model) : model )); + + ColorProviderRegistry.ITEM.register(new DynamicFluidContainerModel.Colors(), WATER_BUCKET); } } diff --git a/modules/models/src/testmod/resources/assets/porting_lib/lang/en_us.json b/modules/models/src/testmod/resources/assets/porting_lib/lang/en_us.json index a4985ae8..a8afe131 100644 --- a/modules/models/src/testmod/resources/assets/porting_lib/lang/en_us.json +++ b/modules/models/src/testmod/resources/assets/porting_lib/lang/en_us.json @@ -1,5 +1,7 @@ { "item.porting_lib.derp_helmet": "Derpy Helmet", "item.porting_lib.stone_2": "Stone 2", - "block.porting_lib.not_glass": "Not Glass" + "block.porting_lib.not_glass": "Not Glass", + "item.porting_lib.water_bucket": "Water Bucket", + "item.porting_lib.lava_bucket": "Lava Bucket" } diff --git a/modules/models/src/testmod/resources/assets/porting_lib/models/item/lava_bucket.json b/modules/models/src/testmod/resources/assets/porting_lib/models/item/lava_bucket.json new file mode 100644 index 00000000..6637c053 --- /dev/null +++ b/modules/models/src/testmod/resources/assets/porting_lib/models/item/lava_bucket.json @@ -0,0 +1,5 @@ +{ + "parent": "neoforge:item/bucket", + "loader": "porting_lib:fluid_container", + "fluid": "lava" +} \ No newline at end of file diff --git a/modules/models/src/testmod/resources/assets/porting_lib/models/item/water_bucket.json b/modules/models/src/testmod/resources/assets/porting_lib/models/item/water_bucket.json new file mode 100644 index 00000000..02f6aef4 --- /dev/null +++ b/modules/models/src/testmod/resources/assets/porting_lib/models/item/water_bucket.json @@ -0,0 +1,5 @@ +{ + "parent": "neoforge:item/bucket", + "loader": "porting_lib:fluid_container", + "fluid": "water" +} \ No newline at end of file