349 lines
13 KiB
Java
349 lines
13 KiB
Java
package net.minecraft.client.renderer.block.model;
|
|
|
|
import com.google.common.annotations.VisibleForTesting;
|
|
import com.google.common.base.Joiner;
|
|
import com.google.common.collect.Lists;
|
|
import com.google.common.collect.Maps;
|
|
import com.google.gson.Gson;
|
|
import com.google.gson.GsonBuilder;
|
|
import com.google.gson.JsonDeserializationContext;
|
|
import com.google.gson.JsonDeserializer;
|
|
import com.google.gson.JsonElement;
|
|
import com.google.gson.JsonObject;
|
|
import com.google.gson.JsonParseException;
|
|
import com.mojang.datafixers.util.Either;
|
|
import com.mojang.logging.LogUtils;
|
|
import java.io.Reader;
|
|
import java.lang.reflect.Type;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import java.util.Map.Entry;
|
|
import java.util.function.Function;
|
|
import net.fabricmc.api.EnvType;
|
|
import net.fabricmc.api.Environment;
|
|
import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite;
|
|
import net.minecraft.client.renderer.texture.TextureAtlas;
|
|
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
|
import net.minecraft.client.resources.model.BakedModel;
|
|
import net.minecraft.client.resources.model.BuiltInModel;
|
|
import net.minecraft.client.resources.model.Material;
|
|
import net.minecraft.client.resources.model.ModelBaker;
|
|
import net.minecraft.client.resources.model.ModelState;
|
|
import net.minecraft.client.resources.model.SimpleBakedModel;
|
|
import net.minecraft.client.resources.model.SpecialModels;
|
|
import net.minecraft.client.resources.model.UnbakedModel;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
import net.minecraft.util.GsonHelper;
|
|
import net.minecraft.world.item.ItemDisplayContext;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public class BlockModel implements UnbakedModel {
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private static final FaceBakery FACE_BAKERY = new FaceBakery();
|
|
@VisibleForTesting
|
|
static final Gson GSON = new GsonBuilder()
|
|
.registerTypeAdapter(BlockModel.class, new BlockModel.Deserializer())
|
|
.registerTypeAdapter(BlockElement.class, new BlockElement.Deserializer())
|
|
.registerTypeAdapter(BlockElementFace.class, new BlockElementFace.Deserializer())
|
|
.registerTypeAdapter(BlockFaceUV.class, new BlockFaceUV.Deserializer())
|
|
.registerTypeAdapter(ItemTransform.class, new ItemTransform.Deserializer())
|
|
.registerTypeAdapter(ItemTransforms.class, new ItemTransforms.Deserializer())
|
|
.registerTypeAdapter(ItemOverride.class, new ItemOverride.Deserializer())
|
|
.create();
|
|
private static final char REFERENCE_CHAR = '#';
|
|
public static final String PARTICLE_TEXTURE_REFERENCE = "particle";
|
|
private static final boolean DEFAULT_AMBIENT_OCCLUSION = true;
|
|
public static final Material MISSING_MATERIAL = new Material(TextureAtlas.LOCATION_BLOCKS, MissingTextureAtlasSprite.getLocation());
|
|
private final List<BlockElement> elements;
|
|
@Nullable
|
|
private final BlockModel.GuiLight guiLight;
|
|
@Nullable
|
|
private final Boolean hasAmbientOcclusion;
|
|
private final ItemTransforms transforms;
|
|
private final List<ItemOverride> overrides;
|
|
public String name = "";
|
|
@VisibleForTesting
|
|
protected final Map<String, Either<Material, String>> textureMap;
|
|
@Nullable
|
|
protected BlockModel parent;
|
|
@Nullable
|
|
protected ResourceLocation parentLocation;
|
|
|
|
public static BlockModel fromStream(Reader reader) {
|
|
return GsonHelper.fromJson(GSON, reader, BlockModel.class);
|
|
}
|
|
|
|
public BlockModel(
|
|
@Nullable ResourceLocation parentLocation,
|
|
List<BlockElement> elements,
|
|
Map<String, Either<Material, String>> textureMap,
|
|
@Nullable Boolean hasAmbientOcclusion,
|
|
@Nullable BlockModel.GuiLight guiLight,
|
|
ItemTransforms transforms,
|
|
List<ItemOverride> overrides
|
|
) {
|
|
this.elements = elements;
|
|
this.hasAmbientOcclusion = hasAmbientOcclusion;
|
|
this.guiLight = guiLight;
|
|
this.textureMap = textureMap;
|
|
this.parentLocation = parentLocation;
|
|
this.transforms = transforms;
|
|
this.overrides = overrides;
|
|
}
|
|
|
|
public List<BlockElement> getElements() {
|
|
return this.elements.isEmpty() && this.parent != null ? this.parent.getElements() : this.elements;
|
|
}
|
|
|
|
public boolean hasAmbientOcclusion() {
|
|
if (this.hasAmbientOcclusion != null) {
|
|
return this.hasAmbientOcclusion;
|
|
} else {
|
|
return this.parent != null ? this.parent.hasAmbientOcclusion() : true;
|
|
}
|
|
}
|
|
|
|
public BlockModel.GuiLight getGuiLight() {
|
|
if (this.guiLight != null) {
|
|
return this.guiLight;
|
|
} else {
|
|
return this.parent != null ? this.parent.getGuiLight() : BlockModel.GuiLight.SIDE;
|
|
}
|
|
}
|
|
|
|
public boolean isResolved() {
|
|
return this.parentLocation == null || this.parent != null && this.parent.isResolved();
|
|
}
|
|
|
|
public List<ItemOverride> getOverrides() {
|
|
return this.overrides;
|
|
}
|
|
|
|
@Override
|
|
public void resolveDependencies(UnbakedModel.Resolver resolver) {
|
|
if (this.parentLocation != null) {
|
|
if (!(resolver.resolve(this.parentLocation) instanceof BlockModel blockModel)) {
|
|
throw new IllegalStateException("BlockModel parent has to be a block model.");
|
|
}
|
|
|
|
this.parent = blockModel;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public BakedModel bake(ModelBaker baker, Function<Material, TextureAtlasSprite> spriteGetter, ModelState state) {
|
|
return this.bake(spriteGetter, state, true);
|
|
}
|
|
|
|
public BakedModel bake(Function<Material, TextureAtlasSprite> function, ModelState modelState, boolean bl) {
|
|
TextureAtlasSprite textureAtlasSprite = (TextureAtlasSprite)function.apply(this.getMaterial("particle"));
|
|
if (this.getRootModel() == SpecialModels.BLOCK_ENTITY_MARKER) {
|
|
return new BuiltInModel(this.getTransforms(), textureAtlasSprite, this.getGuiLight().lightLikeBlock());
|
|
} else {
|
|
SimpleBakedModel.Builder builder = new SimpleBakedModel.Builder(this, bl).particle(textureAtlasSprite);
|
|
|
|
for (BlockElement blockElement : this.getElements()) {
|
|
for (Direction direction : blockElement.faces.keySet()) {
|
|
BlockElementFace blockElementFace = (BlockElementFace)blockElement.faces.get(direction);
|
|
TextureAtlasSprite textureAtlasSprite2 = (TextureAtlasSprite)function.apply(this.getMaterial(blockElementFace.texture()));
|
|
if (blockElementFace.cullForDirection() == null) {
|
|
builder.addUnculledFace(bakeFace(blockElement, blockElementFace, textureAtlasSprite2, direction, modelState));
|
|
} else {
|
|
builder.addCulledFace(
|
|
Direction.rotate(modelState.getRotation().getMatrix(), blockElementFace.cullForDirection()),
|
|
bakeFace(blockElement, blockElementFace, textureAtlasSprite2, direction, modelState)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return builder.build();
|
|
}
|
|
}
|
|
|
|
private static BakedQuad bakeFace(BlockElement element, BlockElementFace face, TextureAtlasSprite sprite, Direction facing, ModelState state) {
|
|
return FACE_BAKERY.bakeQuad(element.from, element.to, face, sprite, facing, state, element.rotation, element.shade, element.lightEmission);
|
|
}
|
|
|
|
public boolean hasTexture(String textureName) {
|
|
return !MissingTextureAtlasSprite.getLocation().equals(this.getMaterial(textureName).texture());
|
|
}
|
|
|
|
public Material getMaterial(String name) {
|
|
if (isTextureReference(name)) {
|
|
name = name.substring(1);
|
|
}
|
|
|
|
List<String> list = Lists.<String>newArrayList();
|
|
|
|
while (true) {
|
|
Either<Material, String> either = this.findTextureEntry(name);
|
|
Optional<Material> optional = either.left();
|
|
if (optional.isPresent()) {
|
|
return (Material)optional.get();
|
|
}
|
|
|
|
name = (String)either.right().get();
|
|
if (list.contains(name)) {
|
|
LOGGER.warn("Unable to resolve texture due to reference chain {}->{} in {}", Joiner.on("->").join(list), name, this.name);
|
|
return MISSING_MATERIAL;
|
|
}
|
|
|
|
list.add(name);
|
|
}
|
|
}
|
|
|
|
private Either<Material, String> findTextureEntry(String name) {
|
|
for (BlockModel blockModel = this; blockModel != null; blockModel = blockModel.parent) {
|
|
Either<Material, String> either = (Either<Material, String>)blockModel.textureMap.get(name);
|
|
if (either != null) {
|
|
return either;
|
|
}
|
|
}
|
|
|
|
return Either.left(MISSING_MATERIAL);
|
|
}
|
|
|
|
static boolean isTextureReference(String str) {
|
|
return str.charAt(0) == '#';
|
|
}
|
|
|
|
public BlockModel getRootModel() {
|
|
return this.parent == null ? this : this.parent.getRootModel();
|
|
}
|
|
|
|
public ItemTransforms getTransforms() {
|
|
ItemTransform itemTransform = this.getTransform(ItemDisplayContext.THIRD_PERSON_LEFT_HAND);
|
|
ItemTransform itemTransform2 = this.getTransform(ItemDisplayContext.THIRD_PERSON_RIGHT_HAND);
|
|
ItemTransform itemTransform3 = this.getTransform(ItemDisplayContext.FIRST_PERSON_LEFT_HAND);
|
|
ItemTransform itemTransform4 = this.getTransform(ItemDisplayContext.FIRST_PERSON_RIGHT_HAND);
|
|
ItemTransform itemTransform5 = this.getTransform(ItemDisplayContext.HEAD);
|
|
ItemTransform itemTransform6 = this.getTransform(ItemDisplayContext.GUI);
|
|
ItemTransform itemTransform7 = this.getTransform(ItemDisplayContext.GROUND);
|
|
ItemTransform itemTransform8 = this.getTransform(ItemDisplayContext.FIXED);
|
|
return new ItemTransforms(itemTransform, itemTransform2, itemTransform3, itemTransform4, itemTransform5, itemTransform6, itemTransform7, itemTransform8);
|
|
}
|
|
|
|
private ItemTransform getTransform(ItemDisplayContext displayContext) {
|
|
return this.parent != null && !this.transforms.hasTransform(displayContext)
|
|
? this.parent.getTransform(displayContext)
|
|
: this.transforms.getTransform(displayContext);
|
|
}
|
|
|
|
public String toString() {
|
|
return this.name;
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public static class Deserializer implements JsonDeserializer<BlockModel> {
|
|
public BlockModel deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
|
|
JsonObject jsonObject = json.getAsJsonObject();
|
|
List<BlockElement> list = this.getElements(context, jsonObject);
|
|
String string = this.getParentName(jsonObject);
|
|
Map<String, Either<Material, String>> map = this.getTextureMap(jsonObject);
|
|
Boolean boolean_ = this.getAmbientOcclusion(jsonObject);
|
|
ItemTransforms itemTransforms = ItemTransforms.NO_TRANSFORMS;
|
|
if (jsonObject.has("display")) {
|
|
JsonObject jsonObject2 = GsonHelper.getAsJsonObject(jsonObject, "display");
|
|
itemTransforms = context.deserialize(jsonObject2, ItemTransforms.class);
|
|
}
|
|
|
|
List<ItemOverride> list2 = this.getOverrides(context, jsonObject);
|
|
BlockModel.GuiLight guiLight = null;
|
|
if (jsonObject.has("gui_light")) {
|
|
guiLight = BlockModel.GuiLight.getByName(GsonHelper.getAsString(jsonObject, "gui_light"));
|
|
}
|
|
|
|
ResourceLocation resourceLocation = string.isEmpty() ? null : ResourceLocation.parse(string);
|
|
return new BlockModel(resourceLocation, list, map, boolean_, guiLight, itemTransforms, list2);
|
|
}
|
|
|
|
protected List<ItemOverride> getOverrides(JsonDeserializationContext context, JsonObject json) {
|
|
List<ItemOverride> list = Lists.<ItemOverride>newArrayList();
|
|
if (json.has("overrides")) {
|
|
for (JsonElement jsonElement : GsonHelper.getAsJsonArray(json, "overrides")) {
|
|
list.add((ItemOverride)context.deserialize(jsonElement, ItemOverride.class));
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
private Map<String, Either<Material, String>> getTextureMap(JsonObject json) {
|
|
ResourceLocation resourceLocation = TextureAtlas.LOCATION_BLOCKS;
|
|
Map<String, Either<Material, String>> map = Maps.<String, Either<Material, String>>newHashMap();
|
|
if (json.has("textures")) {
|
|
JsonObject jsonObject = GsonHelper.getAsJsonObject(json, "textures");
|
|
|
|
for (Entry<String, JsonElement> entry : jsonObject.entrySet()) {
|
|
map.put((String)entry.getKey(), parseTextureLocationOrReference(resourceLocation, ((JsonElement)entry.getValue()).getAsString()));
|
|
}
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
private static Either<Material, String> parseTextureLocationOrReference(ResourceLocation location, String name) {
|
|
if (BlockModel.isTextureReference(name)) {
|
|
return Either.right(name.substring(1));
|
|
} else {
|
|
ResourceLocation resourceLocation = ResourceLocation.tryParse(name);
|
|
if (resourceLocation == null) {
|
|
throw new JsonParseException(name + " is not valid resource location");
|
|
} else {
|
|
return Either.left(new Material(location, resourceLocation));
|
|
}
|
|
}
|
|
}
|
|
|
|
private String getParentName(JsonObject json) {
|
|
return GsonHelper.getAsString(json, "parent", "");
|
|
}
|
|
|
|
@Nullable
|
|
protected Boolean getAmbientOcclusion(JsonObject json) {
|
|
return json.has("ambientocclusion") ? GsonHelper.getAsBoolean(json, "ambientocclusion") : null;
|
|
}
|
|
|
|
protected List<BlockElement> getElements(JsonDeserializationContext context, JsonObject json) {
|
|
List<BlockElement> list = Lists.<BlockElement>newArrayList();
|
|
if (json.has("elements")) {
|
|
for (JsonElement jsonElement : GsonHelper.getAsJsonArray(json, "elements")) {
|
|
list.add((BlockElement)context.deserialize(jsonElement, BlockElement.class));
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public static enum GuiLight {
|
|
FRONT("front"),
|
|
SIDE("side");
|
|
|
|
private final String name;
|
|
|
|
private GuiLight(final String name) {
|
|
this.name = name;
|
|
}
|
|
|
|
public static BlockModel.GuiLight getByName(String name) {
|
|
for (BlockModel.GuiLight guiLight : values()) {
|
|
if (guiLight.name.equals(name)) {
|
|
return guiLight;
|
|
}
|
|
}
|
|
|
|
throw new IllegalArgumentException("Invalid gui light: " + name);
|
|
}
|
|
|
|
public boolean lightLikeBlock() {
|
|
return this == SIDE;
|
|
}
|
|
}
|
|
}
|