forked from KiltMC/NeoForge
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDynamicFluidContainerModel.java
More file actions
245 lines (210 loc) · 11.8 KB
/
DynamicFluidContainerModel.java
File metadata and controls
245 lines (210 loc) · 11.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.model;
import com.google.common.collect.Maps;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import com.mojang.math.Transformation;
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.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 net.minecraftforge.client.ForgeHooksClient;
import net.minecraftforge.client.ForgeRenderTypes;
import net.minecraftforge.client.RenderTypeGroup;
import net.minecraftforge.client.extensions.common.IClientFluidTypeExtensions;
import net.minecraftforge.client.model.geometry.IGeometryBakingContext;
import net.minecraftforge.client.model.geometry.IGeometryLoader;
import net.minecraftforge.client.model.geometry.IUnbakedGeometry;
import net.minecraftforge.client.model.geometry.StandaloneGeometryBakingContext;
import net.minecraftforge.client.model.geometry.UnbakedGeometryHelper;
import net.minecraftforge.fluids.FluidUtil;
import net.minecraftforge.registries.ForgeRegistries;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.Map;
import java.util.function.Function;
/**
* A dynamic fluid container model, capable of re-texturing itself at runtime to match the contained fluid.
* <p>
* 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.
* <p>
* Fluid tinting requires registering a separate {@link ItemColor}. An implementation is provided in {@link Colors}.
*
* @see Colors
*/
public class DynamicFluidContainerModel implements IUnbakedGeometry<DynamicFluidContainerModel>
{
// 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 ? ForgeRenderTypes.ITEM_UNSORTED_UNLIT_TRANSLUCENT.get() : ForgeRenderTypes.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<Material, TextureAtlasSprite> spriteGetter, ModelState modelState, ItemOverrides overrides, ResourceLocation modelLocation)
{
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(ForgeHooksClient.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.forge$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(modelLocation);
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.contents());
var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> baseSprite, modelState, modelLocation);
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.contents()); // Use template as mask
var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> fluidSprite, transformedState, modelLocation); // Bake with fluid texture
var emissive = applyFluidLuminosity && fluid.forge$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.contents()); // Use cover as mask
var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> sprite, transformedState, modelLocation); // Bake with selected texture
modelBuilder.addQuads(normalRenderTypes, quads);
}
}
modelBuilder.setParticle(particleSprite);
return modelBuilder.build();
}
public static final class Loader implements IGeometryLoader<DynamicFluidContainerModel>
{
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 = new ResourceLocation(jsonObject.get("fluid").getAsString());
Fluid fluid = ForgeRegistries.FLUIDS.getValue(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<String, BakedModel> 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 = ForgeRegistries.FLUIDS.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, new ResourceLocation("forge:bucket_override"));
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(@NotNull ItemStack stack, int tintIndex)
{
if (tintIndex != 1) return 0xFFFFFFFF;
return FluidUtil.getFluidContained(stack)
.map(fluidStack -> IClientFluidTypeExtensions.of(fluidStack.getFluid()).getTintColor(fluidStack))
.orElse(0xFFFFFFFF);
}
}
}