356 lines
16 KiB
Java
356 lines
16 KiB
Java
/*
|
|
* 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<ResourceLocation, IModelLoader<?>> 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[\\\\/](?<namespace>[a-z_-]+)[\\\\/]textures[\\\\/])?(?<path>[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<IModelTransform> 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<ItemCameraTransforms.TransformType, TransformationMatrix> 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<ItemCameraTransforms.TransformType, TransformationMatrix> 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<Material, TextureAtlasSprite> 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<VanillaProxy>
|
|
{
|
|
private final List<BlockPart> elements;
|
|
|
|
public VanillaProxy(List<BlockPart> list)
|
|
{
|
|
this.elements = list;
|
|
}
|
|
|
|
@Override
|
|
public void addQuads(IModelConfiguration owner, IModelBuilder<?> modelBuilder, ModelBakery bakery, Function<Material, TextureAtlasSprite> 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<Material> getTextures(IModelConfiguration owner, Function<ResourceLocation, IUnbakedModel> modelGetter, Set<Pair<String, String>> missingTextureErrors)
|
|
{
|
|
Set<Material> 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<VanillaProxy>
|
|
{
|
|
public static final Loader INSTANCE = new Loader();
|
|
|
|
private Loader()
|
|
{
|
|
}
|
|
|
|
@Override
|
|
public void onResourceManagerReload(IResourceManager resourceManager)
|
|
{
|
|
|
|
}
|
|
|
|
@Override
|
|
public VanillaProxy read(JsonDeserializationContext deserializationContext, JsonObject modelContents)
|
|
{
|
|
List<BlockPart> list = this.getModelElements(deserializationContext, modelContents);
|
|
return new VanillaProxy(list);
|
|
}
|
|
|
|
private List<BlockPart> getModelElements(JsonDeserializationContext deserializationContext, JsonObject object) {
|
|
List<BlockPart> 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<BlockPart> 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<String, JsonElement> part : visibility.entrySet())
|
|
{
|
|
model.customData.visibilityData.setVisibilityState(part.getKey(), part.getValue().getAsBoolean());
|
|
}
|
|
}
|
|
|
|
return model;
|
|
}
|
|
}
|
|
}
|