/* * Minecraft Forge * Copyright (c) 2016-2019. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation version 2.1 * of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package net.minecraftforge.client.model; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.gson.*; import com.mojang.datafixers.util.Pair; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.TransformationMatrix; import net.minecraft.client.renderer.model.*; import net.minecraft.client.renderer.texture.AtlasTexture; import net.minecraft.client.renderer.texture.MissingTextureSprite; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.resources.IReloadableResourceManager; import net.minecraft.resources.IResourceManager; import net.minecraft.util.Direction; import net.minecraft.util.JSONUtils; import net.minecraft.util.ResourceLocation; import net.minecraftforge.client.model.geometry.IModelGeometry; import net.minecraftforge.client.model.geometry.ISimpleModelGeometry; import net.minecraftforge.client.model.obj.OBJLoader; import net.minecraftforge.common.model.TransformationHelper; import javax.annotation.Nullable; import java.lang.reflect.Type; import java.util.*; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Central hub for custom model loaders. */ public class ModelLoaderRegistry { private static final Map> loaders = Maps.newHashMap(); // Forge built-in loaders public static void init() { registerLoader(new ResourceLocation("forge:obj"), OBJLoader.INSTANCE); registerLoader(new ResourceLocation("forge:bucket"), DynamicBucketModel.Loader.INSTANCE); registerLoader(new ResourceLocation("forge:composite"), CompositeModel.Loader.INSTANCE); registerLoader(new ResourceLocation("minecraft:elements"), VanillaProxy.Loader.INSTANCE); registerLoader(new ResourceLocation("forge:multi-layer"), MultiLayerModel.Loader.INSTANCE); // TODO: Implement as new model loaders //registerLoader(new ResourceLocation("forge:b3d"), new ModelLoaderAdapter(B3DLoader.INSTANCE)); //registerLoader(new ResourceLocation("forge:fluid"), new ModelLoaderAdapter(ModelFluid.FluidLoader.INSTANCE)); } /** * Makes system aware of your loader. */ public static void registerLoader(ResourceLocation id, IModelLoader loader) { loaders.put(id, loader); ((IReloadableResourceManager) Minecraft.getInstance().getResourceManager()).addReloadListener(loader); } public static IModelGeometry getModel(ResourceLocation loaderId, JsonDeserializationContext deserializationContext, JsonObject data) { try { if (!loaders.containsKey(loaderId)) throw new IllegalStateException(String.format("Model loader '%s' not found.", loaderId)); IModelLoader loader = loaders.get(loaderId); return loader.read(deserializationContext, data); } catch(Exception e) { e.printStackTrace(); throw e; } } @Nullable public static IModelGeometry deserializeGeometry(JsonDeserializationContext deserializationContext, JsonObject object) { if (!object.has("loader")) { return null; } ResourceLocation loader = new ResourceLocation(JSONUtils.getString(object,"loader")); return getModel(loader, deserializationContext, object); } private static final Pattern FILESYSTEM_PATH_TO_RESLOC = Pattern.compile("(?:.*[\\\\/]assets[\\\\/](?[a-z_-]+)[\\\\/]textures[\\\\/])?(?[a-z_\\\\/-]+)\\.png"); public static Material resolveTexture(@Nullable String tex, IModelConfiguration owner) { if (tex == null) return blockMaterial("forge:white"); if (tex.startsWith("#")) return owner.resolveTexture(tex); // Attempt to convert a common (windows/linux/mac) filesystem path to a ResourceLocation. // This makes no promises, if it doesn't work, too bad, fix your mtl file. Matcher match = FILESYSTEM_PATH_TO_RESLOC.matcher(tex); if (match.matches()) { String namespace = match.group("namespace"); String path = match.group("path").replace("\\", "/"); if (namespace != null) return blockMaterial(new ResourceLocation(namespace, path)); return blockMaterial(path); } return blockMaterial(tex); } public static Material blockMaterial(String location) { return new Material(AtlasTexture.LOCATION_BLOCKS_TEXTURE, new ResourceLocation(location)); } public static Material blockMaterial(ResourceLocation location) { return new Material(AtlasTexture.LOCATION_BLOCKS_TEXTURE, location); } @Nullable public static IModelTransform deserializeModelTransforms(JsonDeserializationContext deserializationContext, JsonObject modelData) { if (!modelData.has("transform")) return null; return deserializeTransform(deserializationContext, modelData.get("transform")).orElse(null); } public static Optional deserializeTransform(JsonDeserializationContext context, JsonElement transformData) { if (!transformData.isJsonObject()) { try { TransformationMatrix base = context.deserialize(transformData, TransformationMatrix.class); return Optional.of(new SimpleModelTransform(ImmutableMap.of(), base.blockCenterToCorner())); } catch (JsonParseException e) { throw new JsonParseException("transform: expected a string, object or valid base transformation, got: " + transformData); } } else { JsonObject transform = transformData.getAsJsonObject(); EnumMap transforms = Maps.newEnumMap(ItemCameraTransforms.TransformType.class); deserializeTRSR(context, transforms, transform, "thirdperson", ItemCameraTransforms.TransformType.THIRD_PERSON_RIGHT_HAND); deserializeTRSR(context, transforms, transform, "thirdperson_righthand", ItemCameraTransforms.TransformType.THIRD_PERSON_RIGHT_HAND); deserializeTRSR(context, transforms, transform, "thirdperson_lefthand", ItemCameraTransforms.TransformType.THIRD_PERSON_LEFT_HAND); deserializeTRSR(context, transforms, transform, "firstperson", ItemCameraTransforms.TransformType.FIRST_PERSON_RIGHT_HAND); deserializeTRSR(context, transforms, transform, "firstperson_righthand", ItemCameraTransforms.TransformType.FIRST_PERSON_RIGHT_HAND); deserializeTRSR(context, transforms, transform, "firstperson_lefthand", ItemCameraTransforms.TransformType.FIRST_PERSON_LEFT_HAND); deserializeTRSR(context, transforms, transform, "head", ItemCameraTransforms.TransformType.HEAD); deserializeTRSR(context, transforms, transform, "gui", ItemCameraTransforms.TransformType.GUI); deserializeTRSR(context, transforms, transform, "ground", ItemCameraTransforms.TransformType.GROUND); deserializeTRSR(context, transforms, transform, "fixed", ItemCameraTransforms.TransformType.FIXED); int k = transform.entrySet().size(); if(transform.has("matrix")) k--; if(transform.has("translation")) k--; if(transform.has("rotation")) k--; if(transform.has("scale")) k--; if(transform.has("post-rotation")) k--; if(k > 0) { throw new JsonParseException("transform: allowed keys: 'thirdperson', 'firstperson', 'gui', 'head', 'matrix', 'translation', 'rotation', 'scale', 'post-rotation'"); } TransformationMatrix base = TransformationMatrix.func_227983_a_(); if(!transform.entrySet().isEmpty()) { base = context.deserialize(transform, TransformationMatrix.class); base = base.blockCenterToCorner(); } IModelTransform state = new SimpleModelTransform(Maps.immutableEnumMap(transforms), base); return Optional.of(state); } } private static void deserializeTRSR(JsonDeserializationContext context, EnumMap transforms, JsonObject transform, String name, ItemCameraTransforms.TransformType itemCameraTransform) { if(transform.has(name)) { TransformationMatrix t = context.deserialize(transform.remove(name), TransformationMatrix.class); transforms.put(itemCameraTransform, t.blockCenterToCorner()); } } public static IBakedModel bakeHelper(BlockModel blockModel, ModelBakery modelBakery, BlockModel otherModel, Function spriteGetter, IModelTransform modelTransform, ResourceLocation modelLocation, boolean guiLight3d) { IBakedModel model; IModelGeometry customModel = blockModel.customData.getCustomGeometry(); IModelTransform customModelState = blockModel.customData.getCustomModelState(); if (customModelState != null) modelTransform = new ModelTransformComposition(customModelState, modelTransform, modelTransform.isUvLock()); if (customModel != null) model = customModel.bake(blockModel.customData, modelBakery, spriteGetter, modelTransform, blockModel.getOverrides(modelBakery, otherModel, spriteGetter), modelLocation); else model = blockModel.bakeVanilla(modelBakery, otherModel, spriteGetter, modelTransform, modelLocation, guiLight3d); if (customModelState != null && !model.doesHandlePerspectives()) model = new PerspectiveMapWrapper(model, customModelState); return model; } public static class VanillaProxy implements ISimpleModelGeometry { private final List elements; public VanillaProxy(List list) { this.elements = list; } @Override public void addQuads(IModelConfiguration owner, IModelBuilder modelBuilder, ModelBakery bakery, Function spriteGetter, IModelTransform modelTransform, ResourceLocation modelLocation) { for(BlockPart blockpart : elements) { for(Direction direction : blockpart.mapFaces.keySet()) { BlockPartFace blockpartface = blockpart.mapFaces.get(direction); TextureAtlasSprite textureatlassprite1 = spriteGetter.apply(owner.resolveTexture(blockpartface.texture)); if (blockpartface.cullFace == null) { modelBuilder.addGeneralQuad(BlockModel.makeBakedQuad(blockpart, blockpartface, textureatlassprite1, direction, modelTransform, modelLocation)); } else { modelBuilder.addFaceQuad( modelTransform.func_225615_b_().rotateTransform(blockpartface.cullFace), BlockModel.makeBakedQuad(blockpart, blockpartface, textureatlassprite1, direction, modelTransform, modelLocation)); } } } } @Override public Collection getTextures(IModelConfiguration owner, Function modelGetter, Set> missingTextureErrors) { Set textures = Sets.newHashSet(); for(BlockPart part : elements) { for(BlockPartFace face : part.mapFaces.values()) { Material texture = owner.resolveTexture(face.texture); if (Objects.equals(texture, MissingTextureSprite.getLocation().toString())) { missingTextureErrors.add(Pair.of(face.texture, owner.getModelName())); } textures.add(texture); } } return textures; } public static class Loader implements IModelLoader { public static final Loader INSTANCE = new Loader(); private Loader() { } @Override public void onResourceManagerReload(IResourceManager resourceManager) { } @Override public VanillaProxy read(JsonDeserializationContext deserializationContext, JsonObject modelContents) { List list = this.getModelElements(deserializationContext, modelContents); return new VanillaProxy(list); } private List getModelElements(JsonDeserializationContext deserializationContext, JsonObject object) { List list = Lists.newArrayList(); if (object.has("elements")) { for(JsonElement jsonelement : JSONUtils.getJsonArray(object, "elements")) { list.add(deserializationContext.deserialize(jsonelement, BlockPart.class)); } } return list; } } } public static class ExpandedBlockModelDeserializer extends BlockModel.Deserializer { public static final Gson INSTANCE = (new GsonBuilder()) .registerTypeAdapter(BlockModel.class, new ExpandedBlockModelDeserializer()) .registerTypeAdapter(BlockPart.class, new BlockPart.Deserializer()) .registerTypeAdapter(BlockPartFace.class, new BlockPartFace.Deserializer()) .registerTypeAdapter(BlockFaceUV.class, new BlockFaceUV.Deserializer()) .registerTypeAdapter(ItemTransformVec3f.class, new ItemTransformVec3f.Deserializer()) .registerTypeAdapter(ItemCameraTransforms.class, new ItemCameraTransforms.Deserializer()) .registerTypeAdapter(ItemOverride.class, new ItemOverride.Deserializer()) .registerTypeAdapter(TransformationMatrix.class, new TransformationHelper.Deserializer()) .create(); public BlockModel deserialize(JsonElement element, Type targetType, JsonDeserializationContext deserializationContext) throws JsonParseException { BlockModel model = super.deserialize(element, targetType, deserializationContext); JsonObject jsonobject = element.getAsJsonObject(); IModelGeometry geometry = deserializeGeometry(deserializationContext, jsonobject); List elements = model.getElements(); if (geometry != null) { elements.clear(); model.customData.setCustomGeometry(geometry); } IModelTransform modelState = deserializeModelTransforms(deserializationContext, jsonobject); if (modelState != null) { model.customData.setCustomModelState(modelState); } if (jsonobject.has("visibility")) { JsonObject visibility = JSONUtils.getJsonObject(jsonobject, "visibility"); for(Map.Entry part : visibility.entrySet()) { model.customData.visibilityData.setVisibilityState(part.getKey(), part.getValue().getAsBoolean()); } } return model; } } }