Backport implementation of the new model system from 1.15: (#6180)

- Extended block model format to support a "loader" value, as an alternative to "elements.
- Extended block model format to support "gui_3d":false as a way to mark a model as being intended to be flat and lit from the front.
    (No this is not equivalent to the  "gui_light" flag in 1.15.2. It changes more than just the light direction)
- Added a new OBJ loader designed from the ground up to work with the new model system.
- Added stock models to serve as parent models when using custom loaders (forge:item/default, forge:item/default-tool, and forge:block/default).
- Added stock white texture (forge:white) for use in loaders that want to draw untextured geometry.
- Deprecated the old loader components, since they are gone in 1.15 already.
This commit is contained in:
David Quintana authored and GitHub committed 2020-02-28 18:26:59 +01:00
1 parent 4e45da3cc3
commit dc145a3dfc
82 files changed
+5576 -305

No files matched your search

@@ -181,7 +181,6 @@ public class ForgeHooksClient
{
ModLoader.get().postEvent(new TextureStitchEvent.Pre(map, resourceLocations));
// ModelLoader.White.INSTANCE.register(map); // TODO Custom TAS
ModelDynBucket.LoaderDynBucket.INSTANCE.register(map);
}
public static void onTextureStitchedPost(AtlasTexture map)
@@ -24,6 +24,7 @@ import java.util.Random;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.vecmath.Matrix4f;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.model.BakedQuad;
@@ -34,6 +35,7 @@ import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IEnviromentBlockReader;
import net.minecraftforge.client.model.data.IModelData;
import org.apache.commons.lang3.tuple.Pair;
public interface IForgeBakedModel
{
@@ -50,11 +52,16 @@ public interface IForgeBakedModel
default boolean isAmbientOcclusion(BlockState state) { return getBakedModel().isAmbientOcclusion(); }
/**
* Override to tell the new model loader that it shouldn't wrap this model
*/
default boolean doesHandlePerspectives() { return false; }
/*
* Returns the pair of the model for the given perspective, and the matrix that
* should be applied to the GL state before rendering it (matrix may be null).
*/
default org.apache.commons.lang3.tuple.Pair<? extends IBakedModel, javax.vecmath.Matrix4f> handlePerspective(ItemCameraTransforms.TransformType cameraTransformType)
default Pair<? extends IBakedModel, Matrix4f> handlePerspective(ItemCameraTransforms.TransformType cameraTransformType)
{
return net.minecraftforge.client.ForgeHooksClient.handlePerspective(getBakedModel(), cameraTransformType);
}
@@ -121,6 +121,12 @@ public class BakedItemModel implements IBakedModel
return ImmutableList.of();
}
@Override
public boolean doesHandlePerspectives()
{
return true;
}
@Override
public Pair<? extends IBakedModel, Matrix4f> handlePerspective(TransformType type)
{
@@ -34,7 +34,6 @@ import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IEnviromentBlockReader;
import net.minecraft.world.IWorldReader;
import net.minecraftforge.client.model.data.IModelData;
import org.apache.commons.lang3.tuple.Pair;
@@ -95,6 +94,12 @@ public abstract class BakedModelWrapper<T extends IBakedModel> implements IBaked
return originalModel.getOverrides();
}
@Override
public boolean doesHandlePerspectives()
{
return originalModel.doesHandlePerspectives();
}
@Override
public Pair<? extends IBakedModel, Matrix4f> handlePerspective(ItemCameraTransforms.TransformType cameraTransformType)
{
@@ -0,0 +1,183 @@
/*
* 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 net.minecraft.client.renderer.model.*;
import net.minecraft.client.renderer.texture.ISprite;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.model.geometry.IModelGeometry;
import net.minecraftforge.client.model.geometry.IModelGeometryPart;
import net.minecraftforge.common.model.IModelState;
import javax.annotation.Nullable;
import java.util.*;
import java.util.function.Function;
public class BlockModelConfiguration implements IModelConfiguration
{
public final BlockModel owner;
public final VisibilityData visibilityData = new VisibilityData();
@Nullable
private IModelGeometry<?> customGeometry;
@Nullable
private IModelState customModelState;
public BlockModelConfiguration(BlockModel owner)
{
this.owner = owner;
}
@Nullable
@Override
public IUnbakedModel getOwnerModel()
{
return owner;
}
@Override
public String getModelName()
{
return owner.name;
}
public boolean hasCustomGeometry()
{
return getCustomGeometry() != null;
}
@Nullable
public IModelGeometry<?> getCustomGeometry()
{
return owner.parent != null && customGeometry == null ? owner.parent.customData.getCustomGeometry() : customGeometry;
}
public void setCustomGeometry(IModelGeometry<?> geometry)
{
this.customGeometry = geometry;
}
@Nullable
public IModelState getCustomModelState()
{
return owner.parent != null && customModelState == null ? owner.parent.customData.getCustomModelState() : customModelState;
}
public void setCustomModelState(IModelState modelState)
{
this.customModelState = modelState;
}
@Override
public boolean getPartVisibility(IModelGeometryPart part, boolean fallback)
{
return owner.parent != null && !visibilityData.hasCustomVisibility(part) ?
owner.parent.customData.getPartVisibility(part, fallback):
visibilityData.isVisible(part, fallback);
}
@Override
public boolean isTexturePresent(String name)
{
return owner.isTexturePresent(name);
}
@Override
public String resolveTexture(String name)
{
return owner.resolveTextureName(name);
}
@Override
public boolean isShadedInGui() {
return owner.isGui3d();
}
@Override
public boolean useSmoothLighting()
{
return owner.isAmbientOcclusion();
}
@Override
public ItemCameraTransforms getCameraTransforms()
{
return owner.getAllTransforms();
}
@Override
public IModelState getCombinedState()
{
IModelState state = getCustomModelState();
return state != null
? new SimpleModelState(PerspectiveMapWrapper.getTransformsWithFallback(state, getCameraTransforms()), state.apply(Optional.empty()))
: new SimpleModelState(PerspectiveMapWrapper.getTransforms(getCameraTransforms()));
}
public void copyFrom(BlockModelConfiguration other)
{
this.customGeometry = other.customGeometry;
this.customModelState = other.customModelState;
this.visibilityData.copyFrom(other.visibilityData);
}
public Collection<ResourceLocation> getTextureDependencies(Function<ResourceLocation, IUnbakedModel> modelGetter, Set<String> missingTextureErrors)
{
IModelGeometry<?> geometry = getCustomGeometry();
return geometry == null ? Collections.emptySet() :
geometry.getTextureDependencies(this, modelGetter, missingTextureErrors);
}
public IBakedModel bake(ModelBakery bakery, Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter, ISprite sprite, VertexFormat format, ItemOverrideList overrides)
{
IModelGeometry<?> geometry = getCustomGeometry();
if (geometry == null)
throw new IllegalStateException("Can not use custom baking without custom geometry");
return geometry.bake(this, bakery, bakedTextureGetter, sprite, format, overrides);
}
public static class VisibilityData
{
private final Map<String, Boolean> data = new HashMap<>();
public boolean hasCustomVisibility(IModelGeometryPart part)
{
return data.containsKey(part.name());
}
public boolean isVisible(IModelGeometryPart part, boolean fallback)
{
return data.getOrDefault(part.name(), fallback);
}
public void setVisibilityState(String partName, boolean type)
{
data.put(partName, type);
}
public void copyFrom(VisibilityData visibilityData)
{
data.clear();
data.putAll(visibilityData.data);
}
}
}
@@ -46,6 +46,7 @@ import com.google.gson.GsonBuilder;
import javax.annotation.Nullable;
@Deprecated // Use the new model loading system and data generators instead.
public class BlockStateLoader
{
private static final Gson GSON = (new GsonBuilder())
@@ -103,7 +104,7 @@ public class BlockStateLoader
entry.getKey().replace("=","_").replace(",","_"))
);
IUnbakedModel model = ForgeVariantHelper.prepareInjectedModel((ModelLoader)bakery, modelLocation, var.getModel(), var.getSmooth(), var.getGui3d(), var.getTextures(), var.getOnlyPartsVariant(), var.getCustomData());
IUnbakedModel model = ForgeVariantHelper.prepareInjectedModel(modelLocation, var.getModel(), var.getSmooth(), var.getGui3d(), var.getTextures(), var.getOnlyPartsVariant(), var.getCustomData());
modelConsumer.accept(modelLocation, model);
}
@@ -185,7 +186,7 @@ public class BlockStateLoader
private static class ForgeVariantHelper
{
public static IUnbakedModel prepareInjectedModel(ModelLoader bakery, ResourceLocation blockstateLocation, @Nullable ResourceLocation modelLocation, Optional<Boolean> smooth, Optional<Boolean> gui3d, ImmutableMap<String, String> textures, ImmutableMap<String, SubModel> parts, ImmutableMap<String, String> customData)
public static IUnbakedModel prepareInjectedModel(ResourceLocation blockstateLocation, @Nullable ResourceLocation modelLocation, Optional<Boolean> smooth, Optional<Boolean> gui3d, ImmutableMap<String, String> textures, ImmutableMap<String, SubModel> parts, ImmutableMap<String, String> customData)
{
int size = parts.size();
@@ -0,0 +1,340 @@
/*
* 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.gson.JsonDeserializationContext;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.model.*;
import net.minecraft.client.renderer.texture.ISprite;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.resources.IResourceManager;
import net.minecraft.util.Direction;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IEnviromentBlockReader;
import net.minecraftforge.client.model.data.*;
import net.minecraftforge.client.model.geometry.IModelGeometryPart;
import net.minecraftforge.client.model.geometry.IMultipartModelGeometry;
import net.minecraftforge.common.model.IModelState;
import org.apache.commons.lang3.tuple.Pair;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.vecmath.Matrix4f;
import java.util.*;
import java.util.function.Function;
public class CompositeModel implements IDynamicBakedModel
{
private final ImmutableMap<String, IBakedModel> bakedParts;
private final boolean isAmbientOcclusion;
private final boolean isGui3d;
private final TextureAtlasSprite particle;
private final ItemOverrideList overrides;
private final IModelState transforms;
public CompositeModel(boolean isGui3d, boolean isAmbientOcclusion, TextureAtlasSprite particle, ImmutableMap<String, IBakedModel> bakedParts, IModelState combinedState, ItemOverrideList overrides)
{
this.bakedParts = bakedParts;
this.isAmbientOcclusion = isAmbientOcclusion;
this.isGui3d = isGui3d;
this.particle = particle;
this.overrides = overrides;
this.transforms = combinedState;
}
@Nonnull
@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, @Nonnull Random rand, @Nonnull IModelData extraData)
{
List<BakedQuad> quads = new ArrayList<>();
for(Map.Entry<String, IBakedModel> entry : bakedParts.entrySet())
{
quads.addAll(entry.getValue().getQuads(state, side, rand, CompositeModelData.get(extraData, entry.getKey())));
}
return quads;
}
@Nonnull
@Override
public IModelData getModelData(@Nonnull IEnviromentBlockReader world, @Nonnull BlockPos pos, @Nonnull BlockState state, @Nonnull IModelData tileData)
{
CompositeModelData composite = new CompositeModelData();
for(Map.Entry<String, IBakedModel> entry : bakedParts.entrySet())
{
composite.putSubmodelData(entry.getKey(), entry.getValue().getModelData(world, pos, state, new ModelDataWrapper(tileData)));
}
return composite;
}
@Override
public boolean isAmbientOcclusion()
{
return isAmbientOcclusion;
}
@Override
public boolean isGui3d()
{
return isGui3d;
}
@Override
public boolean isBuiltInRenderer()
{
return false;
}
@Override
public TextureAtlasSprite getParticleTexture()
{
return particle;
}
@Override
public ItemOverrideList getOverrides()
{
return overrides;
}
@Override
public boolean doesHandlePerspectives()
{
return true;
}
@Override
public Pair<? extends IBakedModel, Matrix4f> handlePerspective(ItemCameraTransforms.TransformType cameraTransformType)
{
return PerspectiveMapWrapper.handlePerspective(this, transforms, cameraTransformType);
}
@Nullable
public IBakedModel getPart(String name)
{
return bakedParts.get(name);
}
private static class Submodel implements IModelGeometryPart
{
private final String name;
private final BlockModel model;
private final ISprite sprite;
private Submodel(String name, BlockModel model, ISprite sprite)
{
this.name = name;
this.model = model;
this.sprite = sprite;
}
@Override
public String name()
{
return name;
}
@Override
public void addQuads(IModelConfiguration owner, IModelBuilder<?> modelBuilder, ModelBakery bakery, Function<ResourceLocation, TextureAtlasSprite> spriteGetter, ISprite sprite, VertexFormat format)
{
throw new UnsupportedOperationException("Attempted to call adQuads on a Submodel instance. Please don't.");
}
public IBakedModel bake(ModelBakery bakery, Function<ResourceLocation, TextureAtlasSprite> spriteGetter, ISprite sprite, VertexFormat format)
{
return model.bake(bakery, spriteGetter, new ModelStateComposition(this.sprite.getState(), sprite.getState(),
this.sprite.isUvLock() || sprite.isUvLock()), format);
}
@Override
public Collection<ResourceLocation> getTextureDependencies(IModelConfiguration owner, Function<ResourceLocation, IUnbakedModel> modelGetter, Set<String> missingTextureErrors)
{
return model.getTextures(modelGetter, missingTextureErrors);
}
}
public static class Geometry implements IMultipartModelGeometry<Geometry>
{
private final ImmutableMap<String, Submodel> parts;
Geometry(ImmutableMap<String, Submodel> parts)
{
this.parts = parts;
}
@Override
public Collection<? extends IModelGeometryPart> getParts()
{
return parts.values();
}
@Override
public Optional<? extends IModelGeometryPart> getPart(String name)
{
return Optional.ofNullable(parts.get(name));
}
@Override
public IBakedModel bake(IModelConfiguration owner, ModelBakery bakery, Function<ResourceLocation, TextureAtlasSprite> spriteGetter, ISprite sprite, VertexFormat format, ItemOverrideList overrides)
{
String particleLocation = owner.resolveTexture("particle");
TextureAtlasSprite particle = spriteGetter.apply(new ResourceLocation(particleLocation));
ImmutableMap.Builder<String, IBakedModel> bakedParts = ImmutableMap.builder();
for(Map.Entry<String, Submodel> part : parts.entrySet())
{
Submodel submodel = part.getValue();
if (!owner.getPartVisibility(submodel))
continue;
bakedParts.put(part.getKey(), submodel.bake(bakery, spriteGetter, sprite, format));
}
return new CompositeModel(owner.isShadedInGui(), owner.useSmoothLighting(), particle, bakedParts.build(), owner.getCombinedState(), overrides);
}
@Override
public Collection<ResourceLocation> getTextureDependencies(IModelConfiguration owner, Function<ResourceLocation, IUnbakedModel> modelGetter, Set<String> missingTextureErrors)
{
Set<ResourceLocation> textures = new HashSet<>();
for(Submodel part : parts.values())
{
textures.addAll(part.getTextureDependencies(owner, modelGetter, missingTextureErrors));
}
return textures;
}
}
public static class Loader implements IModelLoader<Geometry>
{
public static final Loader INSTANCE = new Loader();
private Loader() {}
@Override
public void onResourceManagerReload(IResourceManager resourceManager)
{
}
@Override
public Geometry read(JsonDeserializationContext deserializationContext, JsonObject modelContents)
{
if (!modelContents.has("parts"))
throw new RuntimeException("Composite model requires a \"parts\" element.");
ImmutableMap.Builder<String, Submodel> parts = ImmutableMap.builder();
for(Map.Entry<String, JsonElement> part : modelContents.get("parts").getAsJsonObject().entrySet())
{
ISprite sprite = ModelRotation.X0_Y0;
parts.put(part.getKey(), new Submodel(
part.getKey(),
deserializationContext.deserialize(part.getValue(), BlockModel.class),
sprite));
}
return new Geometry(parts.build());
}
}
private static class CompositeModelData implements IModelData
{
public static final ModelProperty<CompositeModelData> SUBMODEL_DATA = new ModelProperty<>();
public static Optional<CompositeModelData> get(IModelData modelData)
{
return Optional.ofNullable(modelData.getData(SUBMODEL_DATA));
}
public static IModelData get(IModelData modelData, String name)
{
return get(modelData).map(data -> data.getSubmodelData(name))
.orElse(EmptyModelData.INSTANCE);
}
private final Map<String, IModelData> parts = new HashMap<>();
public IModelData getSubmodelData(String name)
{
if (parts.containsKey(name))
return parts.get(name);
return EmptyModelData.INSTANCE;
}
public void putSubmodelData(String name, IModelData data)
{
parts.put(name, data);
}
@Override
public boolean hasProperty(ModelProperty<?> prop)
{
return prop == SUBMODEL_DATA;
}
@SuppressWarnings("unchecked")
@Nullable
@Override
public <T> T getData(ModelProperty<T> prop)
{
if (prop == SUBMODEL_DATA)
return (T)this;
return null;
}
@Nullable
@Override
public <T> T setData(ModelProperty<T> prop, T data)
{
return null;
}
}
private static class ModelDataWrapper extends ModelDataMap
{
private final IModelData parent;
public ModelDataWrapper(IModelData parent)
{
this.parent = parent;
}
@Override
public boolean hasProperty(ModelProperty<?> prop)
{
return super.hasProperty(prop) || parent.hasProperty(prop);
}
@Nullable
@Override
public <T> T getData(ModelProperty<T> prop)
{
return super.hasProperty(prop) ? super.getData(prop) : parent.getData(prop);
}
@Nullable
@Override
public <T> T setData(ModelProperty<T> prop, T data)
{
// We do not want to delegate setting to the parent
return super.setData(prop, data);
}
}
}
@@ -187,6 +187,12 @@ final class FancyMissingModel implements IUnbakedModel
@Override
public ItemOverrideList getOverrides() { return ItemOverrideList.EMPTY; }
@Override
public boolean doesHandlePerspectives()
{
return true;
}
@Override
public Pair<? extends IBakedModel, Matrix4f> handlePerspective(ItemCameraTransforms.TransformType cameraTransformType)
{
@@ -35,6 +35,7 @@ import javax.vecmath.Matrix4f;
import javax.vecmath.Quat4f;
import javax.vecmath.Vector3f;
import net.minecraft.client.renderer.model.ItemCameraTransforms;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -62,6 +63,7 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
@Deprecated // Use the new model loading system and data generators instead.
public class ForgeBlockStateV1 extends Marker
{
private static final Logger LOGGER = LogManager.getLogger();
@@ -390,7 +392,11 @@ public class ForgeBlockStateV1 extends Marker
boolean isVanillaCompatible()
{
return model != null && submodels.isEmpty() && textures.isEmpty() && customData.isEmpty() && !smooth.isPresent() && !gui3d.isPresent() && state.orElse(ModelRotation.X0_Y0) instanceof ModelRotation;
return model != null && !ModelLoaderRegistry.isCustomModel(model)
&& submodels.isEmpty()
&& textures.isEmpty() && customData.isEmpty()
&& !smooth.isPresent() && !gui3d.isPresent()
&& state.orElse(ModelRotation.X0_Y0) instanceof ModelRotation;
}
protected SubModel asGenericSubModel()
@@ -495,118 +501,8 @@ public class ForgeBlockStateV1 extends Marker
if (json.has("transform"))
{
if (json.get("transform").isJsonPrimitive() && json.get("transform").getAsJsonPrimitive().isString())
{
String transform = json.get("transform").getAsString();
ret.state = Transforms.get(transform);
if (!ret.state.isPresent())
{
throw new JsonParseException("transform: unknown default string: " + transform);
}
}
else if (!json.get("transform").isJsonObject())
{
try
{
TRSRTransformation base = context.deserialize(json.get("transform"), TRSRTransformation.class);
ret.state = Optional.of(TRSRTransformation.blockCenterToCorner(base));
}
catch (JsonParseException e)
{
throw new JsonParseException("transform: expected a string, object or valid base transformation, got: " + json.get("transform"));
}
}
else
{
JsonObject transform = json.get("transform").getAsJsonObject();
EnumMap<TransformType, TRSRTransformation> transforms = Maps.newEnumMap(TransformType.class);
if(transform.has("thirdperson"))
{
TRSRTransformation t = context.deserialize(transform.get("thirdperson"), TRSRTransformation.class);
transform.remove("thirdperson");
transforms.put(TransformType.THIRD_PERSON_RIGHT_HAND, TRSRTransformation.blockCenterToCorner(t));
}
if(transform.has("thirdperson_righthand"))
{
TRSRTransformation t = context.deserialize(transform.get("thirdperson_righthand"), TRSRTransformation.class);
transform.remove("thirdperson_righthand");
transforms.put(TransformType.THIRD_PERSON_RIGHT_HAND, TRSRTransformation.blockCenterToCorner(t));
}
if(transform.has("thirdperson_lefthand"))
{
TRSRTransformation t = context.deserialize(transform.get("thirdperson_lefthand"), TRSRTransformation.class);
transform.remove("thirdperson_lefthand");
transforms.put(TransformType.THIRD_PERSON_LEFT_HAND, TRSRTransformation.blockCenterToCorner(t));
}
if(transform.has("firstperson"))
{
TRSRTransformation t = context.deserialize(transform.get("firstperson"), TRSRTransformation.class);
transform.remove("firstperson");
transforms.put(TransformType.FIRST_PERSON_RIGHT_HAND, TRSRTransformation.blockCenterToCorner(t));
}
if(transform.has("firstperson_righthand"))
{
TRSRTransformation t = context.deserialize(transform.get("firstperson_righthand"), TRSRTransformation.class);
transform.remove("firstperson_righthand");
transforms.put(TransformType.FIRST_PERSON_RIGHT_HAND, TRSRTransformation.blockCenterToCorner(t));
}
if(transform.has("firstperson_lefthand"))
{
TRSRTransformation t = context.deserialize(transform.get("firstperson_lefthand"), TRSRTransformation.class);
transform.remove("firstperson_lefthand");
transforms.put(TransformType.FIRST_PERSON_LEFT_HAND, TRSRTransformation.blockCenterToCorner(t));
}
if(transform.has("head"))
{
TRSRTransformation t = context.deserialize(transform.get("head"), TRSRTransformation.class);
transform.remove("head");
transforms.put(TransformType.HEAD, TRSRTransformation.blockCenterToCorner(t));
}
if(transform.has("gui"))
{
TRSRTransformation t = context.deserialize(transform.get("gui"), TRSRTransformation.class);
transform.remove("gui");
transforms.put(TransformType.GUI, TRSRTransformation.blockCenterToCorner(t));
}
if(transform.has("ground"))
{
TRSRTransformation t = context.deserialize(transform.get("ground"), TRSRTransformation.class);
transform.remove("ground");
transforms.put(TransformType.GROUND, TRSRTransformation.blockCenterToCorner(t));
}
if(transform.has("fixed"))
{
TRSRTransformation t = context.deserialize(transform.get("fixed"), TRSRTransformation.class);
transform.remove("fixed");
transforms.put(TransformType.FIXED, TRSRTransformation.blockCenterToCorner(t));
}
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'");
}
TRSRTransformation base = TRSRTransformation.identity();
if(!transform.entrySet().isEmpty())
{
base = context.deserialize(transform, TRSRTransformation.class);
base = TRSRTransformation.blockCenterToCorner(base);
}
IModelState state;
if(transforms.isEmpty())
{
state = base;
}
else
{
state = new SimpleModelState(Maps.immutableEnumMap(transforms), Optional.of(base));
}
ret.state = Optional.of(state);
}
JsonElement transformData = json.get("transform");
ret.state = ModelLoaderRegistry2.deserializeTransform(context, transformData);
}
if (json.has("uvlock"))
@@ -923,6 +819,7 @@ public class ForgeBlockStateV1 extends Marker
return TRSRTransformation.blockCenterToCorner(flipX.compose(TRSRTransformation.blockCornerToCenter(transform)).compose(flipX));
}
@Deprecated
public static Optional<IModelState> get(String name)
{
return Optional.ofNullable(transforms.get(name));
@@ -28,6 +28,7 @@ import net.minecraftforge.resource.IResourceType;
import net.minecraftforge.resource.ISelectiveResourceReloadListener;
import net.minecraftforge.resource.VanillaResourceType;
@Deprecated // Use the new model loading system and data generators instead.
public interface ICustomModelLoader extends ISelectiveResourceReloadListener
{
@Override
@@ -0,0 +1,52 @@
/*
* 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 net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.model.ModelBakery;
import net.minecraft.client.renderer.texture.ISprite;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.model.IModelState;
import net.minecraftforge.common.model.animation.IClip;
import javax.annotation.Nullable;
import java.util.Optional;
import java.util.function.Function;
public interface IForgeUnbakedModel
{
/**
* @param spriteGetter Where textures will be looked up when baking
* @param sprite Transforms to apply while baking. Usually will be an instance of {@link IModelState}.
*/
@Nullable
IBakedModel bake(ModelBakery bakery, Function<ResourceLocation, TextureAtlasSprite> spriteGetter, ISprite sprite, VertexFormat format);
/**
* Retrieves information about an animation clip in the model.
* @param name The clip name
* @return
*/
default Optional<? extends IClip> getClip(String name) {
return Optional.empty();
}
}
@@ -41,15 +41,9 @@ import net.minecraftforge.common.model.animation.IClip;
* Models can be baked to different vertex formats and with different state.
*/
@SuppressWarnings("unchecked")
public interface IModel<T extends IModel<T>>
@Deprecated // Use the new model loading system and data generators instead.
public interface IModel<T extends IModel<T>> extends IForgeUnbakedModel
{
/**
* @param spriteGetter Where textures will be looked up when baking
* @param sprite Transforms to apply while baking. Usually will be an instance of {@link IModelState}.
*/
@Nullable
IBakedModel bake(ModelBakery bakery, Function<ResourceLocation, TextureAtlasSprite> spriteGetter, ISprite sprite, VertexFormat format);
/**
* Default state this model will be baked with.
* @see IModelState
@@ -58,10 +52,6 @@ public interface IModel<T extends IModel<T>>
return TRSRTransformation.identity();
}
default Optional<? extends IClip> getClip(String name) {
return Optional.empty();
}
/**
* Allows the model to process custom data from the variant definition.
* If unknown data is encountered it should be skipped.
@@ -0,0 +1,70 @@
/*
* 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 net.minecraft.client.renderer.model.BakedQuad;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.model.ItemOverrideList;
import net.minecraft.client.renderer.model.SimpleBakedModel;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.util.Direction;
public interface IModelBuilder<T extends IModelBuilder<T>>
{
static IModelBuilder<?> of(IModelConfiguration owner, ItemOverrideList overrides, TextureAtlasSprite particle)
{
return new Simple(new SimpleBakedModel.Builder(owner, overrides).setTexture(particle));
}
T addFaceQuad(Direction facing, BakedQuad quad);
T addGeneralQuad(BakedQuad quad);
IBakedModel build();
class Simple implements IModelBuilder<Simple> {
final SimpleBakedModel.Builder builder;
Simple(SimpleBakedModel.Builder builder)
{
this.builder = builder;
}
@Override
public Simple addFaceQuad(Direction facing, BakedQuad quad)
{
builder.addFaceQuad(facing, quad);
return this;
}
@Override
public Simple addGeneralQuad(BakedQuad quad)
{
builder.addGeneralQuad(quad);
return this;
}
@Override
public IBakedModel build()
{
return builder.build();
}
}
}
@@ -0,0 +1,99 @@
/*
* 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 net.minecraft.client.renderer.model.IUnbakedModel;
import net.minecraft.client.renderer.model.ItemCameraTransforms;
import net.minecraftforge.client.model.geometry.IModelGeometryPart;
import net.minecraftforge.common.model.IModelState;
import javax.annotation.Nullable;
/*
* Interface that provides generic access to the data within BlockModel,
* while allowing non-blockmodel usage of models
*/
public interface IModelConfiguration {
/**
* If available, gets the owning model (usually BlockModel) of this configuration
*/
@Nullable
IUnbakedModel getOwnerModel();
/**
* @return The name of the model being baked, for logging and cache purposes.
*/
String getModelName();
/**
* Checks if a texture is present in the model.
* @param name The name of a texture channel.
*/
boolean isTexturePresent(String name);
/**
* Resolves the final texture name, taking into account texture aliases and replacements.
* @param name The name of a texture channel.
* @return The location of the texture, or the missing texture if not found.
*/
String resolveTexture(String name);
/**
* @return True if the item uses 3D lighting.
*/
boolean isShadedInGui();
/**
* @return True if the item requires per-vertex lighting.
*/
boolean useSmoothLighting();
/**
* Gets the vanilla camera transforms data.
* Do not use for non-vanilla code. For general usage, prefer getCombinedState.
*/
@Deprecated
ItemCameraTransforms getCameraTransforms();
/**
* @return The combined transformation state including vanilla and forge transforms data.
*/
IModelState getCombinedState();
/**
* Queries the visibility information for the model parts.
* @param part A part for which to query visibility.
* @param fallback A boolean specifying the default visibility if an override isn't found in the model data.
* @return The final computed visibility.
*/
default boolean getPartVisibility(IModelGeometryPart part, boolean fallback) {
return fallback;
}
/**
* Queries the visibility information for the model parts. Same as above, but defaulting to visible.
* @param part A part for which to query visibility.
* @return The final computed visibility.
*/
default boolean getPartVisibility(IModelGeometryPart part) {
return getPartVisibility(part, true);
}
}
@@ -0,0 +1,53 @@
/*
* 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.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import net.minecraft.resources.IResourceManager;
import net.minecraftforge.client.model.geometry.IModelGeometry;
import net.minecraftforge.resource.IResourceType;
import net.minecraftforge.resource.ISelectiveResourceReloadListener;
import net.minecraftforge.resource.VanillaResourceType;
import java.util.function.Predicate;
public interface IModelLoader<T extends IModelGeometry<T>> extends ISelectiveResourceReloadListener
{
@Override
default IResourceType getResourceType()
{
return VanillaResourceType.MODELS;
}
@Override
void onResourceManagerReload(IResourceManager resourceManager);
@Override
default void onResourceManagerReload(IResourceManager resourceManager, Predicate<IResourceType> resourcePredicate)
{
if (resourcePredicate.test(getResourceType()))
{
onResourceManagerReload(resourceManager);
}
}
T read(JsonDeserializationContext deserializationContext, JsonObject modelContents);
}
@@ -20,49 +20,46 @@
package net.minecraftforge.client.model;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.vecmath.Quat4f;
import com.google.common.collect.*;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.model.*;
import net.minecraft.client.renderer.model.ItemCameraTransforms.TransformType;
import net.minecraft.client.renderer.texture.ISprite;
import net.minecraft.client.renderer.texture.PngSizeInfo;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.texture.AtlasTexture;
import net.minecraft.client.renderer.texture.*;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.client.resources.data.AnimationMetadataSection;
import net.minecraft.entity.LivingEntity;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.ItemStack;
import net.minecraft.resources.IResource;
import net.minecraft.resources.IResourceManager;
import net.minecraft.util.Direction;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.client.model.geometry.IModelGeometry;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.resource.IResourceType;
import net.minecraftforge.resource.VanillaResourceType;
import net.minecraftforge.versions.forge.ForgeVersion;
import net.minecraftforge.common.model.IModelState;
import net.minecraftforge.common.model.TRSRTransformation;
import net.minecraftforge.fluids.FluidUtil;
import java.util.function.Function;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.function.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public final class ModelDynBucket implements IUnbakedModel
public final class ModelDynBucket implements IUnbakedModel, IModelGeometry<ModelDynBucket>
{
private static final Logger LOGGER = LogManager.getLogger();
public static final ModelResourceLocation LOCATION = new ModelResourceLocation(new ResourceLocation(ForgeVersion.MOD_ID, "dynbucket"), "inventory");
@@ -73,111 +70,88 @@ public final class ModelDynBucket implements IUnbakedModel
private static final float NORTH_Z_FLUID = 7.498f / 16f;
private static final float SOUTH_Z_FLUID = 8.502f / 16f;
public static final IUnbakedModel MODEL = new ModelDynBucket();
@Deprecated
public static final ModelDynBucket MODEL = new ModelDynBucket();
@Deprecated
@Nullable
private final ResourceLocation baseLocation;
private ResourceLocation particleLocation;
@Deprecated
@Nullable
private final ResourceLocation liquidLocation;
private ResourceLocation baseLocation;
@Deprecated
@Nullable
private final ResourceLocation coverLocation;
private ResourceLocation fluidMaskLocation;
@Deprecated
@Nullable
private ResourceLocation coverLocation;
@Nonnull
private final Fluid fluid;
private final boolean flipGas;
private final boolean tint;
private final boolean coverIsMask;
@Deprecated
public ModelDynBucket()
{
this(null, null, null, null, false, true);
this(Fluids.EMPTY, false, true, false);
}
public ModelDynBucket(@Nullable ResourceLocation baseLocation, @Nullable ResourceLocation liquidLocation, @Nullable ResourceLocation coverLocation, @Nullable Fluid fluid, boolean flipGas, boolean tint)
@Deprecated
public ModelDynBucket(@Nullable ResourceLocation baseLocation, @Nullable ResourceLocation fluidMaskLocation, @Nullable ResourceLocation coverLocation, @Nullable Fluid fluid, boolean flipGas, boolean tint)
{
this(fluid != null ? fluid : Fluids.EMPTY, flipGas, tint, false);
this.baseLocation = baseLocation;
this.liquidLocation = liquidLocation;
this.fluidMaskLocation = fluidMaskLocation;
this.coverLocation = coverLocation;
}
public ModelDynBucket(Fluid fluid, boolean flipGas, boolean tint, boolean coverIsMask)
{
this.fluid = fluid;
this.flipGas = flipGas;
this.tint = tint;
this.coverIsMask = coverIsMask;
}
@Deprecated
@Override
public Collection<ResourceLocation> getTextures(Function<ResourceLocation, IUnbakedModel> modelGetter, Set<String> missingTextureErrors)
{
ImmutableSet.Builder<ResourceLocation> builder = ImmutableSet.builder();
if (particleLocation != null)
builder.add(particleLocation);
if (baseLocation != null)
builder.add(baseLocation);
if (liquidLocation != null)
builder.add(liquidLocation);
if (fluidMaskLocation != null)
builder.add(fluidMaskLocation);
if (coverLocation != null)
builder.add(coverLocation);
if (fluid != null)
if (fluid != Fluids.EMPTY)
builder.add(fluid.getAttributes().getStillTexture());
return builder.build();
}
@Deprecated
@Override
public Collection<ResourceLocation> getDependencies()
{
return Collections.emptyList();
}
@Deprecated
@Nullable
@Override
public IBakedModel bake(ModelBakery bakery, Function<ResourceLocation, TextureAtlasSprite> spriteGetter, ISprite sprite, VertexFormat format)
{
IModelState state = sprite.getState();
ImmutableMap<TransformType, TRSRTransformation> transformMap = PerspectiveMapWrapper.getTransforms(state);
// if the fluid is lighter than air, will manipulate the initial state to be rotated 180° to turn it upside down
if (flipGas && fluid != null && fluid.getAttributes().isLighterThanAir())
{
sprite = new ModelStateComposition(state, TRSRTransformation.blockCenterToCorner(new TRSRTransformation(null, new Quat4f(0, 0, 1, 0), null, null)));
state = sprite.getState();
}
TRSRTransformation transform = state.apply(Optional.empty()).orElse(TRSRTransformation.identity());
TextureAtlasSprite fluidSprite = null;
TextureAtlasSprite particleSprite = null;
ImmutableList.Builder<BakedQuad> builder = ImmutableList.builder();
if(fluid != null) {
fluidSprite = spriteGetter.apply(fluid.getAttributes().getStillTexture());
}
Random random = new Random();
if (baseLocation != null)
{
// build base (insidest)
IBakedModel model = (new ItemLayerModel(ImmutableList.of(baseLocation))).bake(bakery, spriteGetter, sprite, format);
random.setSeed(42);
builder.addAll(model.getQuads(null, null, random));
particleSprite = model.getParticleTexture();
}
if (liquidLocation != null && fluidSprite != null)
{
TextureAtlasSprite liquid = spriteGetter.apply(liquidLocation);
// build liquid layer (inside)
builder.addAll(ItemTextureQuadConverter.convertTexture(format, transform, liquid, fluidSprite, NORTH_Z_FLUID, Direction.NORTH, tint ? fluid.getAttributes().getColor() : 0xFFFFFFFF, 1));
builder.addAll(ItemTextureQuadConverter.convertTexture(format, transform, liquid, fluidSprite, SOUTH_Z_FLUID, Direction.SOUTH, tint ? fluid.getAttributes().getColor() : 0xFFFFFFFF, 1));
particleSprite = fluidSprite;
}
if (coverLocation != null)
{
// cover (the actual item around the other two)
TextureAtlasSprite cover = spriteGetter.apply(coverLocation);
builder.add(ItemTextureQuadConverter.genQuad(format, transform, 0, 0, 16, 16, NORTH_Z_COVER, cover, Direction.NORTH, 0xFFFFFFFF, 2));
builder.add(ItemTextureQuadConverter.genQuad(format, transform, 0, 0, 16, 16, SOUTH_Z_COVER, cover, Direction.SOUTH, 0xFFFFFFFF, 2));
if (particleSprite == null)
{
particleSprite = cover;
}
}
return new BakedDynBucket(bakery, this, builder.build(), particleSprite, format, Maps.immutableEnumMap(transformMap), Maps.newHashMap(), transform.isIdentity());
return bakeInternal(bakery, spriteGetter, sprite, format, null, particleLocation, baseLocation, fluidMaskLocation, coverLocation);
}
/**
@@ -188,6 +162,7 @@ public final class ModelDynBucket implements IUnbakedModel
* <p/>
* If the fluid can't be found, water is used.
*/
@Deprecated
@Override
public ModelDynBucket process(ImmutableMap<String, String> customData)
{
@@ -219,7 +194,7 @@ public final class ModelDynBucket implements IUnbakedModel
}
// create new model with correct liquid
return new ModelDynBucket(baseLocation, liquidLocation, coverLocation, fluid, flip, tint);
return new ModelDynBucket(baseLocation, fluidMaskLocation, coverLocation, fluid, flip, tint);
}
/**
@@ -231,12 +206,12 @@ public final class ModelDynBucket implements IUnbakedModel
* <p/>
* If no liquid is given a hardcoded variant for the bucket is used.
*/
@Deprecated
@Override
public ModelDynBucket retexture(ImmutableMap<String, String> textures)
{
ResourceLocation base = baseLocation;
ResourceLocation liquid = liquidLocation;
ResourceLocation liquid = fluidMaskLocation;
ResourceLocation cover = coverLocation;
if (textures.containsKey("base"))
@@ -248,7 +223,8 @@ public final class ModelDynBucket implements IUnbakedModel
return new ModelDynBucket(base, liquid, cover, fluid, flipGas, tint);
}
@Deprecated
@Nullable
protected static IResource getResource(ResourceLocation resourceLocation)
{
@@ -261,7 +237,8 @@ public final class ModelDynBucket implements IUnbakedModel
return null;
}
}
@Deprecated
@Nullable
protected static PngSizeInfo getSizeInfo(IResource resource)
{
@@ -274,7 +251,128 @@ public final class ModelDynBucket implements IUnbakedModel
throw new RuntimeException(e);
}
}
@Override
public IBakedModel bake(IModelConfiguration owner, ModelBakery bakery, Function<ResourceLocation, TextureAtlasSprite> spriteGetter, ISprite sprite, VertexFormat format, ItemOverrideList overrides)
{
String particle = owner.resolveTexture("particle");
if (!MissingTextureSprite.getLocation().toString().equals(particle))
{
particleLocation = new ResourceLocation(particle);
}
String base = owner.resolveTexture("base");
if (!MissingTextureSprite.getLocation().toString().equals(base))
{
baseLocation = new ResourceLocation(base);
}
String template = owner.resolveTexture("fluid");
if (!MissingTextureSprite.getLocation().toString().equals(template))
{
fluidMaskLocation = new ResourceLocation(template);
}
String cover = owner.resolveTexture("cover");
if (!MissingTextureSprite.getLocation().toString().equals(cover))
{
// cover (the actual item around the other two)
coverLocation = new ResourceLocation(cover);
}
return bakeInternal(bakery, spriteGetter, sprite, format, owner.getCombinedState(), particleLocation, baseLocation, fluidMaskLocation, coverLocation);
}
private IBakedModel bakeInternal(
ModelBakery bakery, Function<ResourceLocation, TextureAtlasSprite> spriteGetter, ISprite sprite, VertexFormat format,
@Nullable IModelState transformsFromModel,
@Nullable ResourceLocation particleLocation, @Nullable ResourceLocation baseLocation,
@Nullable ResourceLocation fluidMaskLocation, @Nullable ResourceLocation coverLocation)
{
IModelState state = sprite.getState();
ImmutableMap<TransformType, TRSRTransformation> transformMap = transformsFromModel != null ?
PerspectiveMapWrapper.getTransforms(new ModelStateComposition(transformsFromModel, state)) :
PerspectiveMapWrapper.getTransforms(state);
TextureAtlasSprite particle = particleLocation != null ? spriteGetter.apply(particleLocation) : null;
// if the fluid is lighter than air, will manipulate the initial state to be rotated 180° to turn it upside down
if (flipGas && fluid != Fluids.EMPTY && fluid.getAttributes().isLighterThanAir())
{
sprite = new ModelStateComposition(state, TRSRTransformation.blockCenterToCorner(new TRSRTransformation(null, new Quat4f(0, 0, 1, 0), null, null)));
state = sprite.getState();
}
TRSRTransformation transform = state.apply(Optional.empty()).orElse(TRSRTransformation.identity());
ResourceLocation fluidLocation = fluid != Fluids.EMPTY ? fluid.getAttributes().getStillTexture() : null;
TextureAtlasSprite fluidSprite = fluidLocation != null ? spriteGetter.apply(fluidLocation) : null;
if (particle == null) particle = fluidSprite;
Random random = new Random();
ImmutableList.Builder<BakedQuad> builder = ImmutableList.builder();
if (baseLocation != null)
{
// build base (insidest)
IBakedModel model = new ItemLayerModel(ImmutableList.of(baseLocation)).bake(bakery, spriteGetter, sprite, format);
random.setSeed(42);
builder.addAll(model.getQuads(null, null, random));
}
if (fluidMaskLocation != null && fluidSprite != null)
{
TextureAtlasSprite templateSprite = spriteGetter.apply(fluidMaskLocation);
if (templateSprite != null)
{
// build liquid layer (inside)
builder.addAll(ItemTextureQuadConverter.convertTexture(format, transform, templateSprite, fluidSprite, NORTH_Z_FLUID, Direction.NORTH, tint ? fluid.getAttributes().getColor() : 0xFFFFFFFF, 1));
builder.addAll(ItemTextureQuadConverter.convertTexture(format, transform, templateSprite, fluidSprite, SOUTH_Z_FLUID, Direction.SOUTH, tint ? fluid.getAttributes().getColor() : 0xFFFFFFFF, 1));
}
}
if (coverLocation != null && (!coverIsMask || baseLocation != null))
{
// cover (the actual item around the other two)
TextureAtlasSprite coverSprite = spriteGetter.apply(coverLocation);
if (coverSprite != null)
{
if (coverIsMask)
{
TextureAtlasSprite baseSprite = spriteGetter.apply(baseLocation);
builder.addAll(ItemTextureQuadConverter.convertTexture(format, transform, coverSprite, baseSprite, NORTH_Z_COVER, Direction.NORTH, 0xFFFFFFFF, 1));
builder.addAll(ItemTextureQuadConverter.convertTexture(format, transform, coverSprite, baseSprite, SOUTH_Z_COVER, Direction.SOUTH, 0xFFFFFFFF, 1));
}
else
{
builder.add(ItemTextureQuadConverter.genQuad(format, transform, 0, 0, 16, 16, NORTH_Z_COVER, coverSprite, Direction.NORTH, 0xFFFFFFFF, 2));
builder.add(ItemTextureQuadConverter.genQuad(format, transform, 0, 0, 16, 16, SOUTH_Z_COVER, coverSprite, Direction.SOUTH, 0xFFFFFFFF, 2));
if (particle == null)
{
particle = coverSprite;
}
}
}
}
return new BakedDynBucket(bakery, this, builder.build(), particle, format, Maps.immutableEnumMap(transformMap), Maps.newHashMap(), transform.isIdentity());
}
@Override
public Collection<ResourceLocation> getTextureDependencies(IModelConfiguration owner, Function<ResourceLocation, IUnbakedModel> modelGetter, Set<String> missingTextureErrors)
{
Set<ResourceLocation> texs = Sets.newHashSet();
texs.add(new ResourceLocation(owner.resolveTexture("particle")));
texs.add(new ResourceLocation(owner.resolveTexture("base")));
texs.add(new ResourceLocation(owner.resolveTexture("fluid")));
texs.add(new ResourceLocation(owner.resolveTexture("cover")));
return texs;
}
@Deprecated
public enum LoaderDynBucket implements ICustomModelLoader
{
INSTANCE;
@@ -291,12 +389,25 @@ public final class ModelDynBucket implements IUnbakedModel
return MODEL;
}
@Override
public IResourceType getResourceType()
{
return VanillaResourceType.MODELS;
}
@Override
public void onResourceManagerReload(IResourceManager resourceManager)
{
// no need to clear cache since we create a new model instance
}
@Override
public void onResourceManagerReload(IResourceManager resourceManager, Predicate<IResourceType> resourcePredicate)
{
// no need to clear cache since we create a new model instance
}
@Deprecated
public void register(AtlasTexture map)
{
// only create these textures if they are not added by a resource pack
@@ -318,6 +429,62 @@ public final class ModelDynBucket implements IUnbakedModel
}
}
public enum Loader implements IModelLoader<ModelDynBucket>
{
INSTANCE;
@Override
public IResourceType getResourceType()
{
return VanillaResourceType.MODELS;
}
@Override
public void onResourceManagerReload(IResourceManager resourceManager)
{
// no need to clear cache since we create a new model instance
}
@Override
public void onResourceManagerReload(IResourceManager resourceManager, Predicate<IResourceType> resourcePredicate)
{
// no need to clear cache since we create a new model instance
}
@Override
public ModelDynBucket read(JsonDeserializationContext deserializationContext, JsonObject modelContents)
{
if (!modelContents.has("fluid"))
throw new RuntimeException("Bucket model requires 'fluid' value.");
ResourceLocation fluidName = new ResourceLocation(modelContents.get("fluid").getAsString());
Fluid fluid = ForgeRegistries.FLUIDS.getValue(fluidName);
boolean flip = false;
if (modelContents.has("flipGas"))
{
flip = modelContents.get("flipGas").getAsBoolean();
}
boolean tint = true;
if (modelContents.has("applyTint"))
{
tint = modelContents.get("applyTint").getAsBoolean();
}
boolean coverIsMask = true;
if (modelContents.has("coverIsMask"))
{
coverIsMask = modelContents.get("coverIsMask").getAsBoolean();
}
// create new model with correct liquid
return new ModelDynBucket(fluid, flip, tint, coverIsMask);
}
}
@Deprecated
private static final class BucketBaseSprite extends TextureAtlasSprite
{
private final ResourceLocation bucket = new ResourceLocation("item/bucket");
@@ -359,6 +526,7 @@ public final class ModelDynBucket implements IUnbakedModel
/**
* Creates a bucket cover sprite from the vanilla resource.
*/
@Deprecated
private static final class BucketCoverSprite extends TextureAtlasSprite
{
private final ResourceLocation bucket = new ResourceLocation("item/bucket");
@@ -499,6 +499,12 @@ public final class ModelFluid implements IUnbakedModel
return ItemOverrideList.EMPTY;
}
@Override
public boolean doesHandlePerspectives()
{
return true;
}
@Override
public Pair<? extends IBakedModel, Matrix4f> handlePerspective(TransformType type)
{
@@ -19,25 +19,17 @@
package net.minecraftforge.client.model;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BlockModelShapes;
import net.minecraft.client.renderer.ItemModelMesher;
import net.minecraft.client.renderer.Vector3f;
import net.minecraft.client.renderer.color.BlockColors;
import net.minecraft.client.renderer.model.BakedQuad;
@@ -45,76 +37,61 @@ import net.minecraft.client.renderer.model.BlockPart;
import net.minecraft.client.renderer.model.BlockPartFace;
import net.minecraft.client.renderer.model.BlockPartRotation;
import net.minecraft.client.renderer.model.BuiltInModel;
import net.minecraft.client.renderer.model.FaceBakery;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.model.IUnbakedModel;
import net.minecraft.client.renderer.model.ItemCameraTransforms;
import net.minecraft.client.renderer.model.ItemCameraTransforms.TransformType;
import net.minecraft.client.renderer.model.ItemModelGenerator;
import net.minecraft.client.renderer.model.ItemOverrideList;
import net.minecraft.client.renderer.model.ModelBakery;
import net.minecraft.client.renderer.model.BlockModel;
import net.minecraft.client.renderer.model.BlockModelDefinition;
import net.minecraft.client.renderer.model.ModelResourceLocation;
import net.minecraft.client.renderer.model.ModelRotation;
import net.minecraft.client.renderer.model.MultipartBakedModel;
import net.minecraft.client.renderer.model.SimpleBakedModel;
import net.minecraft.client.renderer.model.Variant;
import net.minecraft.client.renderer.model.VariantList;
import net.minecraft.client.renderer.model.WeightedBakedModel;
import net.minecraft.client.renderer.model.multipart.Multipart;
import net.minecraft.client.renderer.model.multipart.Selector;
import net.minecraft.client.renderer.texture.ISprite;
import net.minecraft.client.renderer.texture.MissingTextureSprite;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.texture.AtlasTexture;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.item.Item;
import net.minecraft.profiler.IProfiler;
import net.minecraft.resources.IResourceManager;
import net.minecraft.state.StateContainer;
import net.minecraft.util.Direction;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.model.animation.AnimationItemOverrideList;
import net.minecraftforge.client.model.animation.ModelBlockAnimation;
import net.minecraftforge.client.model.data.IModelData;
import net.minecraftforge.common.ForgeMod;
import net.minecraftforge.client.model.geometry.IModelGeometry;
import net.minecraftforge.client.model.geometry.IModelGeometryPart;
import net.minecraftforge.common.model.IModelState;
import net.minecraftforge.common.model.Models;
import net.minecraftforge.common.model.TRSRTransformation;
import net.minecraftforge.common.model.animation.IClip;
import net.minecraftforge.common.property.Properties;
import net.minecraftforge.fml.client.ClientModLoader;
import net.minecraftforge.logging.ModelLoaderErrorMessage;
import net.minecraftforge.registries.IRegistryDelegate;
import org.apache.commons.lang3.tuple.Pair;
import java.util.function.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import java.util.Optional;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static net.minecraftforge.fml.Logging.MODELLOADING;
@@ -274,25 +251,35 @@ public final class ModelLoader extends ModelBakery
public IBakedModel bakeImpl(ModelBakery bakery, Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter, IModelState state, boolean uvlock, VertexFormat format)
{
if(!Attributes.moreSpecific(format, Attributes.DEFAULT_BAKED_FORMAT))
{
throw new IllegalArgumentException("can't bake vanilla models to the format that doesn't fit into the default one: " + format);
}
BlockModel model = this.model;
if(model == null) return getMissingModel().bake(bakery, bakedTextureGetter, new BasicState(getMissingModel().getDefaultState(), uvlock), format);
List<TRSRTransformation> newTransforms = Lists.newArrayList();
for(int i = 0; i < model.getElements().size(); i++)
IModelGeometry<?> customGeometry = model.customData.getCustomGeometry();
if (customGeometry != null)
{
BlockPart part = model.getElements().get(i);
newTransforms.add(animation.getPartTransform(state, part, i));
Collection<? extends IModelGeometryPart> parts = customGeometry.getParts();
for (int i = 0; i < parts.size(); i++)
{
newTransforms.add(animation.getPartTransform(state, i));
}
}
else
{
List<BlockPart> elements = model.getElements();
for (int i = 0; i < elements.size(); i++)
{
BlockPart part = elements.get(i);
newTransforms.add(animation.getPartTransform(state, part, i));
}
}
ItemCameraTransforms transforms = model.getAllTransforms();
Map<TransformType, TRSRTransformation> tMap = Maps.newEnumMap(TransformType.class);
tMap.putAll(PerspectiveMapWrapper.getTransforms(transforms));
tMap.putAll(PerspectiveMapWrapper.getTransforms(state));
IModelState perState = new SimpleModelState(ImmutableMap.copyOf(tMap), state.apply(Optional.empty()));
IModelState perState = model.customData.getCustomModelState();
if (perState == null)
{
perState = new SimpleModelState(PerspectiveMapWrapper.getTransformsWithFallback(state, transforms), state.apply(Optional.empty()));
}
if(model == ModelBakery.MODEL_GENERATED)
{
@@ -300,7 +287,39 @@ public final class ModelLoader extends ModelBakery
}
TextureAtlasSprite textureatlassprite = bakedTextureGetter.apply(new ResourceLocation(model.resolveTextureName("particle")));
if(model == ModelBakery.MODEL_ENTITY) return new BuiltInModel(transforms, model.getOverrides(bakery, model, bakedTextureGetter, format), textureatlassprite);
return bakeNormal(bakery, model, perState, state, newTransforms, format, bakedTextureGetter, uvlock);
if(model.customData.hasCustomGeometry())
return bakeNew(bakery, model, perState, state, newTransforms, format, bakedTextureGetter, uvlock);
else
return bakeNormal(bakery, model, perState, state, newTransforms, format, bakedTextureGetter, uvlock);
}
private IBakedModel bakeNew(ModelBakery bakery, BlockModel model, IModelState perState, final IModelState modelState, List<TRSRTransformation> newTransforms, final VertexFormat format, final Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter, boolean uvLocked)
{
final TRSRTransformation baseState = modelState.apply(Optional.empty()).orElse(TRSRTransformation.identity());
IBakedModel baked = model.customData.bake(bakery, bakedTextureGetter, new BasicState(baseState, uvLocked), format, model.getOverrides(bakery, model, bakedTextureGetter, format));
return new PerspectiveMapWrapper(baked, perState)
{
private final ItemOverrideList overrides = new AnimationItemOverrideList(bakery, VanillaModelWrapper.this, modelState, format, bakedTextureGetter, super.getOverrides());
@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, Random rand, IModelData modelData)
{
IModelState newState = modelData.getData(Properties.AnimationProperty);
if(newState != null)
{
return VanillaModelWrapper.this.bake(bakery, bakedTextureGetter, new ModelStateComposition(modelState, newState, uvlock), format).getQuads(state, side, rand, modelData);
}
return super.getQuads(state, side, rand, modelData);
}
@Override
public ItemOverrideList getOverrides()
{
return overrides;
}
};
}
private IBakedModel bakeNormal(ModelBakery bakery, BlockModel model, IModelState perState, final IModelState modelState, List<TRSRTransformation> newTransforms, final VertexFormat format, final Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter, boolean uvLocked)
@@ -308,13 +327,14 @@ public final class ModelLoader extends ModelBakery
final TRSRTransformation baseState = modelState.apply(Optional.empty()).orElse(TRSRTransformation.identity());
TextureAtlasSprite particle = bakedTextureGetter.apply(new ResourceLocation(model.resolveTextureName("particle")));
SimpleBakedModel.Builder builder = (new SimpleBakedModel.Builder(model, model.getOverrides(bakery, model, bakedTextureGetter, format))).setTexture(particle);
for(int i = 0; i < model.getElements().size(); i++)
List<BlockPart> elements = model.getElements();
for(int i = 0; i < elements.size(); i++)
{
if(modelState.apply(Optional.of(Models.getHiddenModelPart(ImmutableList.of(Integer.toString(i))))).isPresent())
{
continue;
}
BlockPart part = model.getElements().get(i);
BlockPart part = elements.get(i);
TRSRTransformation transformation = baseState;
if(newTransforms.get(i) != null)
{
@@ -350,7 +370,7 @@ public final class ModelLoader extends ModelBakery
{
return VanillaModelWrapper.this.bake(bakery, bakedTextureGetter, new ModelStateComposition(modelState, newState, uvlock), format).getQuads(state, side, rand, modelData);
}
return super.getQuads(state, side, rand);
return super.getQuads(state, side, rand, modelData);
}
@Override
@@ -368,16 +388,19 @@ public final class ModelLoader extends ModelBakery
return this;
List<BlockPart> elements = Lists.newArrayList(); //We have to duplicate this so we can edit it below.
for (BlockPart part : this.model.getElements())
if (!model.customData.hasCustomGeometry())
{
elements.add(new BlockPart(part.positionFrom, part.positionTo, Maps.newHashMap(part.mapFaces), part.partRotation, part.shade));
for (BlockPart part : this.model.getElements())
{
elements.add(new BlockPart(part.positionFrom, part.positionTo, Maps.newHashMap(part.mapFaces), part.partRotation, part.shade));
}
}
BlockModel newModel = new BlockModel(this.model.getParentLocation(), elements,
Maps.newHashMap(this.model.textures), this.model.isAmbientOcclusion(), this.model.isGui3d(), //New Textures man VERY IMPORTANT
model.getAllTransforms(), Lists.newArrayList(model.getOverrides()));
newModel.name = this.model.name;
newModel.parent = this.model.parent;
newModel.customData.copyFrom(this.model.customData);
Set<String> removed = Sets.newHashSet();
@@ -408,9 +431,12 @@ public final class ModelLoader extends ModelBakery
newModel.textures.putAll(remapped);
//Remove any faces that use a null texture, this is for performance reasons, also allows some cool layering stuff.
for (BlockPart part : newModel.getElements())
if (!newModel.customData.hasCustomGeometry())
{
part.mapFaces.entrySet().removeIf(entry -> removed.contains(entry.getValue().texture));
for (BlockPart part : newModel.getElements())
{
part.mapFaces.entrySet().removeIf(entry -> removed.contains(entry.getValue().texture));
}
}
return new VanillaModelWrapper(location, newModel, uvlock, animation);
@@ -436,6 +462,7 @@ public final class ModelLoader extends ModelBakery
BlockModel newModel = new BlockModel(model.getParentLocation(), model.getElements(), model.textures, value, model.isGui3d(), model.getAllTransforms(), Lists.newArrayList(model.getOverrides()));
newModel.parent = model.parent;
newModel.name = model.name;
newModel.customData.copyFrom(model.customData);
return new VanillaModelWrapper(location, newModel, uvlock, animation);
}
@@ -449,6 +476,7 @@ public final class ModelLoader extends ModelBakery
BlockModel newModel = new BlockModel(model.getParentLocation(), model.getElements(), model.textures, model.ambientOcclusion, value, model.getAllTransforms(), Lists.newArrayList(model.getOverrides()));
newModel.parent = model.parent;
newModel.name = model.name;
newModel.customData.copyFrom(model.customData);
return new VanillaModelWrapper(location, newModel, uvlock, animation);
}
}
@@ -50,6 +50,7 @@ import net.minecraftforge.common.model.animation.IAnimationStateMachine;
/**
* Central hub for custom model loaders.
*/
@Deprecated // Use the new model loading system and data generators instead.
public class ModelLoaderRegistry
{
private static final Logger LOGGER = LogManager.getLogger();
@@ -193,6 +194,27 @@ public class ModelLoaderRegistry
return model;
}
public static boolean isCustomModel(ResourceLocation location)
{
ResourceLocation actual = getActualLocation(location);
for(ICustomModelLoader loader : loaders)
{
try
{
if(loader.accepts(actual))
{
return true;
}
}
catch(Exception e)
{
LOGGER.warn("Exception checking if model {} can be loaded with loader {}", location, loader, e);
}
}
return false;
}
/**
* Use this if you don't care about the exception and want some model anyway.
*/
@@ -0,0 +1,559 @@
/*
* 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 net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.model.*;
import net.minecraft.client.renderer.texture.ISprite;
import net.minecraft.client.renderer.texture.MissingTextureSprite;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.VertexFormat;
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.b3d.B3DLoader;
import net.minecraftforge.client.model.geometry.IModelGeometry;
import net.minecraftforge.client.model.geometry.ISimpleModelGeometry;
import net.minecraftforge.client.model.obj.OBJLoader2;
import net.minecraftforge.common.model.IModelState;
import net.minecraftforge.common.model.TRSRTransformation;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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;
import java.util.stream.Collectors;
/**
* Central hub for custom model loaders.
*/
public class ModelLoaderRegistry2
{
private static final Logger LOGGER = LogManager.getLogger();
public static final String WHITE_TEXTURE = "forge:white";
private static final Map<ResourceLocation, IModelLoader<?>> loaders = Maps.newHashMap();
private static volatile boolean registryFrozen = false;
// Forge built-in loaders
public static void init()
{
registerLoader(new ResourceLocation("minecraft","elements"), VanillaProxy.Loader.INSTANCE);
registerLoader(new ResourceLocation("forge","obj"), OBJLoader2.INSTANCE);
registerLoader(new ResourceLocation("forge","bucket"), ModelDynBucket.Loader.INSTANCE);
registerLoader(new ResourceLocation("forge","composite"), CompositeModel.Loader.INSTANCE);
// Model adapters from the old loading system
registerLoader(new ResourceLocation("forge","b3d"), new ModelLoaderAdapter(B3DLoader.INSTANCE));
registerLoader(new ResourceLocation("forge","fluid"), new ModelLoaderAdapter(ModelFluid.FluidLoader.INSTANCE));
registerLoader(new ResourceLocation("forge","multi-layer"), new ModelLoaderAdapter(MultiLayerModel.Loader.INSTANCE));
}
public static void initComplete()
{
registryFrozen = true;
}
/**
* Makes system aware of your loader.
*/
public static void registerLoader(ResourceLocation id, IModelLoader<?> loader)
{
if (registryFrozen)
throw new IllegalStateException("Can not register model loaders after models have started loading. Please use FMLClientSetupEvent or ModelRegistryEvent to register your loaders.");
synchronized(loaders)
{
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. Registered loaders: %s", loaderId,
loaders.keySet().stream().map(ResourceLocation::toString).collect(Collectors.joining(", "))));
}
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);
}
/* Explanation:
* This takes anything that looks like a valid resourcepack texture location, and tries to extract a resourcelocation out of it.
* 1. it will ignore anything up to and including an /assets/ folder,
* 2. it will take the next path component as a namespace,
* 3. it will match but skip the /textures/ part of the path,
* 4. it will take the rest of the path up to but excluding the .png extension as the resource path
* It's a best-effort situation, to allow model files exported by modelling software to be used without post-processing.
* Example:
* C:\Something\Or Other\src\main\resources\assets\mymodid\textures\item\my_thing.png
* ........................................--------_______----------_____________----
* <namespace> <path>
* Result after replacing '\' to '/': mymodid:item/my_thing
*/
private static final Pattern FILESYSTEM_PATH_TO_RESLOC =
Pattern.compile("(?:.*[\\\\/]assets[\\\\/](?<namespace>[a-z_-]+)[\\\\/]textures[\\\\/])?(?<path>[a-z_\\\\/-]+)\\.png");
public static ResourceLocation resolveTexture(@Nullable String tex, IModelConfiguration owner)
{
if (tex == null)
return new ResourceLocation(WHITE_TEXTURE);
if (tex.startsWith("#"))
return new ResourceLocation(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 new ResourceLocation(namespace, path);
return new ResourceLocation(path);
}
return new ResourceLocation(tex);
}
@Nullable
public static IModelState deserializeModelTransforms(JsonDeserializationContext deserializationContext, JsonObject modelData)
{
if (!modelData.has("transform"))
return null;
return deserializeTransform(deserializationContext, modelData.get("transform")).orElse(null);
}
public static Optional<IModelState> deserializeTransform(JsonDeserializationContext context, JsonElement transformData)
{
// TODO: remove the default string option, in favor of the base models
// "forge:default-item" --> "parent": "forge:item/default"
// "forge:default-tool" --> "parent": "forge:item/default-tool"
// "forge:default-block" --> "parent": "minecraft:block/block"
if (transformData.isJsonPrimitive() && transformData.getAsJsonPrimitive().isString())
{
String transform = transformData.getAsString();
Optional<IModelState> state = ForgeBlockStateV1.Transforms.get(transform);
if (!state.isPresent())
{
throw new JsonParseException("transform: unknown default string: " + transform);
}
return state;
}
else if (!transformData.isJsonObject())
{
try
{
TRSRTransformation base = context.deserialize(transformData, TRSRTransformation.class);
return Optional.of(TRSRTransformation.blockCenterToCorner(base));
}
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, TRSRTransformation> 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'");
}
TRSRTransformation base = TRSRTransformation.identity();
if(!transform.entrySet().isEmpty())
{
base = context.deserialize(transform, TRSRTransformation.class);
base = TRSRTransformation.blockCenterToCorner(base);
}
IModelState state;
if(transforms.isEmpty())
{
state = base;
}
else
{
state = new SimpleModelState(Maps.immutableEnumMap(transforms), Optional.of(base));
}
return Optional.of(state);
}
}
private static void deserializeTRSR(JsonDeserializationContext context, EnumMap<ItemCameraTransforms.TransformType, TRSRTransformation> transforms, JsonObject transform, String name, ItemCameraTransforms.TransformType itemCameraTransform)
{
if(transform.has(name))
{
TRSRTransformation t = context.deserialize(transform.remove(name), TRSRTransformation.class);
transforms.put(itemCameraTransform, TRSRTransformation.blockCenterToCorner(t));
}
}
public static IBakedModel bakeHelper(BlockModel blockModel, ModelBakery modelBakery, BlockModel otherModel, Function<ResourceLocation, TextureAtlasSprite> spriteGetter, ISprite sprite, VertexFormat format)
{
try
{
IBakedModel model;
IModelGeometry<?> customModel = blockModel.customData.getCustomGeometry();
IModelState customModelState = blockModel.customData.getCustomModelState();
if (customModelState != null)
{
sprite = new ModelStateComposition(customModelState.apply(Optional.empty()).orElse(TRSRTransformation.identity()), sprite.getState(), sprite.isUvLock());
}
if (customModel != null)
{
model = customModel.bake(blockModel.customData, modelBakery, spriteGetter, sprite, format, blockModel.getOverrides(modelBakery, otherModel, spriteGetter, format));
}
else
{
if (!Attributes.moreSpecific(format, Attributes.DEFAULT_BAKED_FORMAT))
{
// TODO: Provide custom vanilla baking handler for special formats.
throw new IllegalArgumentException("can't bake vanilla models to the format that doesn't fit into the default one: " + format);
}
model = blockModel.bakeVanilla(modelBakery, otherModel, spriteGetter, sprite, format);
}
if (customModelState != null && !model.doesHandlePerspectives())
{
model = new PerspectiveMapWrapper(model, customModelState);
}
return model;
}
catch(Exception e)
{
// Minecraft eats the stack trace, so print it first.
LOGGER.error("Error baking model", e);
throw e;
}
}
public static class ModelLoaderAdapter implements IModelLoader<ModelAdapter>
{
private final ICustomModelLoader parent;
public ModelLoaderAdapter(ICustomModelLoader parent)
{
this.parent = parent;
}
@Override
public void onResourceManagerReload(IResourceManager resourceManager)
{
}
@Override
public ModelAdapter read(JsonDeserializationContext deserializationContext, JsonObject modelContents)
{
if (!modelContents.has("model"))
throw new RuntimeException("ModelLoaderAdapter requires a 'model' key that points to a model file.");
String modelLocation = modelContents.get("model").getAsString();
boolean forceLoad = JSONUtils.getBoolean(modelContents,"force", false);
try
{
ResourceLocation loc = new ResourceLocation(modelLocation);
if (!forceLoad && !parent.accepts(loc))
throw new IllegalStateException("The loader does not accept the requested model.");
IUnbakedModel model = parent.loadModel(loc);
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
if (modelContents.has("custom"))
{
for (Map.Entry<String, JsonElement> e : modelContents.get("custom").getAsJsonObject().entrySet())
{
if (e.getValue().isJsonNull())
builder.put(e.getKey(), null);
else
builder.put(e.getKey(), e.getValue().toString());
}
}
return new ModelAdapter(model, builder.build());
}
catch (Exception e)
{
throw new RuntimeException("Error loading model from legacy model loader", e);
}
}
}
public static class ModelAdapter implements IModelGeometry<ModelAdapter>
{
private final ImmutableMap<String, String> customData;
private IUnbakedModel wrappedModel;
private boolean processed;
public ModelAdapter(IUnbakedModel wrappedModel, ImmutableMap<String,String> customData)
{
this.wrappedModel = wrappedModel;
this.customData = customData;
}
private void process(IModelConfiguration owner, @Nullable TextureAtlasSprite particle)
{
if (!processed)
{
processed = true;
wrappedModel = wrappedModel.process(customData);
if (owner instanceof BlockModel)
{
Map<String, String> replacements = Maps.newHashMap();
BlockModel src = (BlockModel)owner;
while (src != null)
{
for (Map.Entry<String, String> entry : src.textures.entrySet())
{
replacements.putIfAbsent(entry.getKey(), entry.getValue());
}
src = src.parent;
}
for (String key : replacements.keySet())
{
String value = replacements.get(key);
boolean replaced = value.startsWith("#");
while (value.startsWith("#"))
{
String newkey = value.substring(1);
value = replacements.get(newkey);
}
if (replaced)
{
replacements.put(key, value);
}
}
wrappedModel = wrappedModel.retexture(ImmutableMap.copyOf(replacements));
}
if (owner.useSmoothLighting()) wrappedModel = wrappedModel.smoothLighting(true);
if (owner.isShadedInGui()) wrappedModel = wrappedModel.gui3d(true);
}
}
@Override
public Collection<ResourceLocation> getTextureDependencies(IModelConfiguration owner, Function<ResourceLocation, IUnbakedModel> modelGetter, Set<String> missingTextureErrors)
{
process(owner, null);
return wrappedModel.getTextures(modelGetter, missingTextureErrors);
}
@Override
public IBakedModel bake(IModelConfiguration owner, ModelBakery bakery, Function<ResourceLocation, TextureAtlasSprite> spriteGetter, ISprite sprite, VertexFormat format, ItemOverrideList overrides)
{
TextureAtlasSprite particle = spriteGetter.apply(new ResourceLocation(owner.resolveTexture("particle")));
process(owner, particle);
sprite = new ModelStateComposition(owner.getCombinedState(), sprite.getState(), sprite.isUvLock());
return wrappedModel.bake(bakery, spriteGetter, sprite, format);
}
}
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<ResourceLocation, TextureAtlasSprite> spriteGetter, ISprite sprite, VertexFormat format)
{
if(!Attributes.moreSpecific(format, Attributes.DEFAULT_BAKED_FORMAT))
{
// TODO: Provide custom handler for special formats.
throw new IllegalArgumentException("can't bake vanilla models to the format that doesn't fit into the default one: " + format);
}
for(BlockPart blockpart : elements) {
for(Direction direction : blockpart.mapFaces.keySet()) {
BlockPartFace blockpartface = blockpart.mapFaces.get(direction);
TextureAtlasSprite textureatlassprite1 = spriteGetter.apply(new ResourceLocation(owner.resolveTexture(blockpartface.texture)));
if (blockpartface.cullFace == null) {
modelBuilder.addGeneralQuad(BlockModel.makeBakedQuad(blockpart, blockpartface, textureatlassprite1, direction, sprite));
} else {
modelBuilder.addFaceQuad(
sprite.getState().apply(Optional.empty())
.map(trsr -> trsr.rotateTransform(blockpartface.cullFace)).orElse(blockpartface.cullFace),
BlockModel.makeBakedQuad(blockpart, blockpartface, textureatlassprite1, direction, sprite));
}
}
}
}
@Override
public Collection<ResourceLocation> getTextureDependencies(IModelConfiguration owner, Function<ResourceLocation, IUnbakedModel> modelGetter, Set<String> missingTextureErrors)
{
Set<ResourceLocation> textures = Sets.newHashSet();
for(BlockPart part : elements) {
for(BlockPartFace face : part.mapFaces.values()) {
String texture = owner.resolveTexture(face.texture);
if (Objects.equals(texture, MissingTextureSprite.getLocation().toString())) {
missingTextureErrors.add(String.format("%s in %s", face.texture, owner.getModelName()));
}
textures.add(new ResourceLocation(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(TRSRTransformation.class, ForgeBlockStateV1.TRSRDeserializer.INSTANCE)
.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);
}
IModelState 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;
}
}
}
@@ -63,9 +63,9 @@ public final class MultiLayerModel implements IUnbakedModel
private static final Logger LOGGER = LogManager.getLogger();
public static final MultiLayerModel INSTANCE = new MultiLayerModel(ImmutableMap.of());
private final ImmutableMap<Optional<BlockRenderLayer>, ModelResourceLocation> models;
private final ImmutableMap<Optional<BlockRenderLayer>, ? extends ResourceLocation> models;
public MultiLayerModel(ImmutableMap<Optional<BlockRenderLayer>, ModelResourceLocation> models)
public MultiLayerModel(ImmutableMap<Optional<BlockRenderLayer>, ? extends ResourceLocation> models)
{
this.models = models;
}
@@ -82,7 +82,7 @@ public final class MultiLayerModel implements IUnbakedModel
return Collections.emptyList();
}
private static ImmutableMap<Optional<BlockRenderLayer>, IBakedModel> buildModels(ImmutableMap<Optional<BlockRenderLayer>, ModelResourceLocation> models, ISprite sprite, VertexFormat format, ModelBakery bakery, Function<ResourceLocation, TextureAtlasSprite> spriteGetter)
private static ImmutableMap<Optional<BlockRenderLayer>, IBakedModel> buildModels(ImmutableMap<Optional<BlockRenderLayer>, ? extends ResourceLocation> models, ISprite sprite, VertexFormat format, ModelBakery bakery, Function<ResourceLocation, TextureAtlasSprite> spriteGetter)
{
ImmutableMap.Builder<Optional<BlockRenderLayer>, IBakedModel> builder = ImmutableMap.builder();
for(Optional<BlockRenderLayer> key : models.keySet())
@@ -108,7 +108,7 @@ public final class MultiLayerModel implements IUnbakedModel
@Override
public MultiLayerModel process(ImmutableMap<String, String> customData)
{
ImmutableMap.Builder<Optional<BlockRenderLayer>, ModelResourceLocation> builder = ImmutableMap.builder();
ImmutableMap.Builder<Optional<BlockRenderLayer>, ResourceLocation> builder = ImmutableMap.builder();
for(String key : customData.keySet())
{
if("base".equals(key))
@@ -123,12 +123,12 @@ public final class MultiLayerModel implements IUnbakedModel
}
}
}
ImmutableMap<Optional<BlockRenderLayer>, ModelResourceLocation> models = builder.build();
ImmutableMap<Optional<BlockRenderLayer>, ResourceLocation> models = builder.build();
if(models.isEmpty()) return INSTANCE;
return new MultiLayerModel(models);
}
private ModelResourceLocation getLocation(String json)
private ResourceLocation getLocation(String json)
{
JsonElement e = new JsonParser().parse(json);
if(e.isJsonPrimitive() && e.getAsJsonPrimitive().isString())
@@ -201,6 +201,12 @@ public final class MultiLayerModel implements IUnbakedModel
return base.getParticleTexture();
}
@Override
public boolean doesHandlePerspectives()
{
return true;
}
@Override
public Pair<? extends IBakedModel, Matrix4f> handlePerspective(TransformType cameraTransformType)
{
@@ -199,6 +199,12 @@ public final class MultiModel implements IUnbakedModel
return quads.build();
}
@Override
public boolean doesHandlePerspectives()
{
return true;
}
@Override
public Pair<? extends IBakedModel, Matrix4f> handlePerspective(TransformType cameraTransformType)
{
@@ -23,26 +23,31 @@ import java.util.EnumMap;
import java.util.Optional;
import java.util.Random;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.model.BakedQuad;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.model.ItemCameraTransforms;
import net.minecraft.client.renderer.model.ItemOverrideList;
import net.minecraft.client.renderer.model.*;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Direction;
import net.minecraft.world.World;
import net.minecraftforge.client.model.data.IDynamicBakedModel;
import net.minecraftforge.client.model.data.IModelData;
import net.minecraftforge.common.model.IModelState;
import net.minecraftforge.common.model.TRSRTransformation;
import org.apache.commons.lang3.tuple.Pair;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.vecmath.Matrix4f;
import java.util.List;
public class PerspectiveMapWrapper implements IBakedModel
public class PerspectiveMapWrapper implements IDynamicBakedModel
{
private final IBakedModel parent;
private final ImmutableMap<ItemCameraTransforms.TransformType, TRSRTransformation> transforms;
private final OverrideListWrapper overrides = new OverrideListWrapper();
public PerspectiveMapWrapper(IBakedModel parent, ImmutableMap<ItemCameraTransforms.TransformType, TRSRTransformation> transforms)
{
@@ -83,6 +88,25 @@ public class PerspectiveMapWrapper implements IBakedModel
return ImmutableMap.copyOf(map);
}
@SuppressWarnings("deprecation")
public static ImmutableMap<ItemCameraTransforms.TransformType, TRSRTransformation> getTransformsWithFallback(IModelState state, ItemCameraTransforms transforms)
{
EnumMap<ItemCameraTransforms.TransformType, TRSRTransformation> map = new EnumMap<>(ItemCameraTransforms.TransformType.class);
for(ItemCameraTransforms.TransformType type : ItemCameraTransforms.TransformType.values())
{
Optional<TRSRTransformation> tr = state.apply(Optional.of(type));
if(tr.isPresent())
{
map.put(type, tr.get());
}
else if (transforms.hasCustomTransform(type))
{
map.put(type, TRSRTransformation.blockCenterToCorner(TRSRTransformation.from(transforms.getTransform(type))));
}
}
return ImmutableMap.copyOf(map);
}
public static Pair<? extends IBakedModel, Matrix4f> handlePerspective(IBakedModel model, ImmutableMap<ItemCameraTransforms.TransformType, TRSRTransformation> transforms, ItemCameraTransforms.TransformType cameraTransformType)
{
TRSRTransformation tr = transforms.getOrDefault(cameraTransformType, TRSRTransformation.identity());
@@ -110,12 +134,48 @@ public class PerspectiveMapWrapper implements IBakedModel
@Override public TextureAtlasSprite getParticleTexture() { return parent.getParticleTexture(); }
@SuppressWarnings("deprecation")
@Override public ItemCameraTransforms getItemCameraTransforms() { return parent.getItemCameraTransforms(); }
@Override public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, Random rand) { return parent.getQuads(state, side, rand); }
@Override public ItemOverrideList getOverrides() { return parent.getOverrides(); }
@Override public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, @Nonnull Random rand, @Nonnull IModelData extraData)
{
return parent.getQuads(state, side, rand, extraData);
}
@Override
public ItemOverrideList getOverrides()
{
return overrides;
}
@Override
public boolean doesHandlePerspectives()
{
return true;
}
@Override
public Pair<? extends IBakedModel, Matrix4f> handlePerspective(ItemCameraTransforms.TransformType cameraTransformType)
{
return handlePerspective(this, transforms, cameraTransformType);
}
private class OverrideListWrapper extends ItemOverrideList
{
public OverrideListWrapper()
{
super();
}
@Nullable
@Override
public IBakedModel getModelWithOverrides(IBakedModel model, ItemStack stack, @Nullable World worldIn, @Nullable LivingEntity entityIn)
{
model = parent.getOverrides().getModelWithOverrides(parent, stack, worldIn, entityIn);
return new PerspectiveMapWrapper(model, transforms);
}
@Override
public ImmutableList<ItemOverride> getOverrides()
{
return parent.getOverrides().getOverrides();
}
}
}
@@ -0,0 +1,234 @@
/*
* 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.Lists;
import net.minecraft.client.renderer.model.BakedQuad;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.client.renderer.vertex.VertexFormatElement;
import net.minecraftforge.common.model.TRSRTransformation;
import javax.vecmath.Matrix4f;
import javax.vecmath.Vector3f;
import javax.vecmath.Vector4f;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class QuadTransformer
{
private final VertexFormat format;
private final int positionOffset;
private final int normalOffset;
private final TRSRTransformation transform;
public QuadTransformer(VertexFormat format, TRSRTransformation transform)
{
this.format = format;
this.positionOffset = findPositionOffset(format);
this.normalOffset = findNormalOffset(format);
this.transform = transform;
}
private void processVertices(int[] inData, int[] outData)
{
int stride = format.getSize();
int count = (inData.length * 4) / stride;
for (int i=0;i<count;i++)
{
int offset = positionOffset + i * stride;
float x = Float.intBitsToFloat(getAtByteOffset(inData, offset ));
float y = Float.intBitsToFloat(getAtByteOffset(inData, offset + 4));
float z = Float.intBitsToFloat(getAtByteOffset(inData, offset + 8));
Vector4f pos = new Vector4f(x, y, z, 1);
transform.transformPosition(pos);
if (pos.w != 1.0f)
pos.scale(1 / pos.w);
putAtByteOffset(outData, offset, Float.floatToRawIntBits(pos.getX()));
putAtByteOffset(outData,offset + 4, Float.floatToRawIntBits(pos.getY()));
putAtByteOffset(outData,offset + 8, Float.floatToRawIntBits(pos.getZ()));
}
if (normalOffset >= 0)
{
for (int i=0;i<count;i++)
{
int offset = normalOffset + i * stride;
int normalIn = getAtByteOffset(inData,offset);
if (normalIn != 0)
{
float x = (((normalIn) >> 24) & 0xFF) / 255.0f;
float y = (((normalIn << 8) >> 24) & 0xFF) / 255.0f;
float z = (((normalIn << 16) >> 24) & 0xFF) / 255.0f;
Vector3f pos = new Vector3f(x, y, z);
transform.transformNormal(pos);
pos.normalize();
int normalOut = (((int) (x / 255.0) & 0xFF) << 24) |
(((int) (y / 255.0) & 0xFF) << 16) |
(((int) (z / 255.0) & 0xFF) << 8) |
(normalIn & 0xFF);
putAtByteOffset(outData, offset, normalOut);
}
}
}
}
private static int getAtByteOffset(int[] inData, int offset)
{
int index = offset / 4;
int lsb = inData[index];
int shift = (offset % 4) * 8;
if (shift == 0)
return inData[index];
int msb = inData[index+1];
return (lsb >>> shift) | (msb << (32-shift));
}
private static void putAtByteOffset(int[] outData, int offset, int value)
{
int index = offset / 4;
int shift = (offset % 4) * 8;
if (shift == 0)
outData[index] = value;
int lsbMask = 0xFFFFFFFF >>> (32-shift);
int msbMask = 0xFFFFFFFF << shift;
outData[index] = (outData[index] & lsbMask) | (value << shift);
outData[index+1] = (outData[index+1] & msbMask) | (value >>> (32-shift));
}
private static int findPositionOffset(VertexFormat fmt)
{
int index;
VertexFormatElement element = null;
for (index = 0; index < fmt.getElementCount(); index++)
{
VertexFormatElement el = fmt.getElement(index);
if (el.getUsage() == VertexFormatElement.Usage.POSITION)
{
element = el;
break;
}
}
if (index == fmt.getElementCount() || element == null)
throw new RuntimeException("Expected vertex format to have a POSITION attribute");
if (element.getType() != VertexFormatElement.Type.FLOAT)
throw new RuntimeException("Expected POSITION attribute to have data type FLOAT");
if (element.getSize() < 3)
throw new RuntimeException("Expected POSITION attribute to have at least 3 dimensions");
return fmt.getOffset(index);
}
private static int findNormalOffset(VertexFormat fmt)
{
int index;
VertexFormatElement element = null;
for (index = 0; index < fmt.getElementCount(); index++)
{
VertexFormatElement el = fmt.getElement(index);
if (el.getUsage() == VertexFormatElement.Usage.NORMAL)
{
element = el;
break;
}
}
if (index == fmt.getElementCount() || element == null)
return -1;
if (element.getType() != VertexFormatElement.Type.BYTE)
throw new RuntimeException("Expected NORMAL attribute to have data type BYTE");
if (element.getSize() < 3)
throw new RuntimeException("Expected NORMAL attribute to have at least 3 dimensions");
return fmt.getOffset(index);
}
/**
* Processes a single quad, producing a new quad.
* @param input A single quad to transform.
* @return A new BakedQuad object with the new position.
*/
public BakedQuad processOne(BakedQuad input)
{
int[] inData = input.getVertexData();
int[] outData = Arrays.copyOf(inData, inData.length);
processVertices(inData, outData);
return new BakedQuad(outData, input.getTintIndex(), input.getFace(), input.getSprite(), input.shouldApplyDiffuseLighting(), format);
}
/**
* Processes a single quad, modifying the input quad.
* @param input A single quad to transform.
* @return The input BakedQuad object with the new position applied.
*/
public BakedQuad processOneInPlace(BakedQuad input)
{
int[] data = input.getVertexData();
processVertices(data, data);
return input;
}
/**
* Processes multiple quads, producing a new array of new quads.
* @param inputs The list of quads to transform
* @return A new array of new BakedQuad objects.
*/
public List<BakedQuad> processMany(List<BakedQuad> inputs)
{
if(inputs.size() == 0)
return Collections.emptyList();
List<BakedQuad> outputs = Lists.newArrayList();
for(BakedQuad input : inputs)
{
int[] inData = input.getVertexData();
int[] outData = Arrays.copyOf(inData, inData.length);
processVertices(inData, outData);
outputs.add(new BakedQuad(outData, input.getTintIndex(), input.getFace(), input.getSprite(), input.shouldApplyDiffuseLighting(), format));
}
return outputs;
}
/**
* Processes multiple quads in place, modifying the input quads.
* @param inputs The list of quads to transform
*/
public void processManyInPlace(List<BakedQuad> inputs)
{
if(inputs.size() == 0)
return;
for(BakedQuad input : inputs)
{
int[] data = input.getVertexData();
processVertices(data, data);
}
}
}
@@ -519,6 +519,12 @@ public class ModelBlockAnimation
@Nullable
public TRSRTransformation getPartTransform(IModelState state, BlockPart part, int i)
{
return getPartTransform(state, i);
}
@Nullable
public TRSRTransformation getPartTransform(IModelState state, int i)
{
ImmutableCollection<MBJointWeight> infos = getJoint(i);
if(!infos.isEmpty())
@@ -32,6 +32,7 @@ import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.vecmath.Matrix4f;
@@ -410,7 +411,7 @@ public enum B3DLoader implements ICustomModelLoader
private final ResourceLocation modelLocation;
private final B3DModel model;
private final ImmutableSet<String> meshes;
private final ImmutableMap<String, ResourceLocation> textures;
private final ImmutableMap<String, String> textures;
private final boolean smooth;
private final boolean gui3d;
private final int defaultKey;
@@ -420,7 +421,7 @@ public enum B3DLoader implements ICustomModelLoader
this(modelLocation, model, meshes, smooth, gui3d, defaultKey, buildTextures(model.getTextures()));
}
public ModelWrapper(ResourceLocation modelLocation, B3DModel model, ImmutableSet<String> meshes, boolean smooth, boolean gui3d, int defaultKey, ImmutableMap<String, ResourceLocation> textures)
public ModelWrapper(ResourceLocation modelLocation, B3DModel model, ImmutableSet<String> meshes, boolean smooth, boolean gui3d, int defaultKey, ImmutableMap<String, String> textures)
{
this.modelLocation = modelLocation;
this.model = model;
@@ -431,16 +432,16 @@ public enum B3DLoader implements ICustomModelLoader
this.defaultKey = defaultKey;
}
private static ImmutableMap<String, ResourceLocation> buildTextures(List<Texture> textures)
private static ImmutableMap<String, String> buildTextures(List<Texture> textures)
{
ImmutableMap.Builder<String, ResourceLocation> builder = ImmutableMap.builder();
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
for(Texture t : textures)
{
String path = t.getPath();
String location = getLocation(path);
if(!location.startsWith("#")) location = "#" + location;
builder.put(path, new ResourceLocation(location));
builder.put(path, location);
}
return builder.build();
}
@@ -454,7 +455,7 @@ public enum B3DLoader implements ICustomModelLoader
@Override
public Collection<ResourceLocation> getTextures(Function<ResourceLocation, IUnbakedModel> modelGetter, Set<String> missingTextureErrors)
{
return Collections2.filter(textures.values(), loc -> !loc.getPath().startsWith("#"));
return textures.values().stream().filter(loc -> !loc.startsWith("#")).map(ResourceLocation::new).collect(Collectors.toList());
}
@Override
@@ -469,16 +470,16 @@ public enum B3DLoader implements ICustomModelLoader
{
ImmutableMap.Builder<String, TextureAtlasSprite> builder = ImmutableMap.builder();
TextureAtlasSprite missing = spriteGetter.apply(new ResourceLocation("missingno"));
for(Map.Entry<String, ResourceLocation> e : textures.entrySet())
for(Map.Entry<String, String> e : textures.entrySet())
{
if(e.getValue().getPath().startsWith("#"))
if(e.getValue().startsWith("#"))
{
LOGGER.fatal("unresolved texture '{}' for b3d model '{}'", e.getValue().getPath(), modelLocation);
LOGGER.fatal("unresolved texture '{}' for b3d model '{}'", e.getValue(), modelLocation);
builder.put(e.getKey(), missing);
}
else
{
builder.put(e.getKey(), spriteGetter.apply(e.getValue()));
builder.put(e.getKey(), spriteGetter.apply(new ResourceLocation(e.getValue())));
}
}
builder.put("missingno", missing);
@@ -488,16 +489,19 @@ public enum B3DLoader implements ICustomModelLoader
@Override
public ModelWrapper retexture(ImmutableMap<String, String> textures)
{
ImmutableMap.Builder<String, ResourceLocation> builder = ImmutableMap.builder();
for(Map.Entry<String, ResourceLocation> e : this.textures.entrySet())
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
for(Map.Entry<String, String> e : this.textures.entrySet())
{
String path = e.getKey();
String loc = getLocation(path);
if(textures.containsKey(loc))
// FIXME: Backward compatibilty: support finding textures that start with #, even though this is not how vanilla works
if(loc.startsWith("#") && (textures.containsKey(loc) || textures.containsKey(loc.substring(1))))
{
String alt = loc.substring(1);
String newLoc = textures.get(loc);
if(newLoc == null) newLoc = getLocation(path);
builder.put(e.getKey(), new ResourceLocation(newLoc));
if(newLoc == null) newLoc = textures.get(alt);
if(newLoc == null) newLoc = path.substring(1);
builder.put(e.getKey(), newLoc);
}
else
{
@@ -816,6 +820,12 @@ public enum B3DLoader implements ICustomModelLoader
return textures.values().asList().get(0);
}
@Override
public boolean doesHandlePerspectives()
{
return true;
}
@Override
public Pair<? extends IBakedModel, Matrix4f> handlePerspective(TransformType cameraTransformType)
{
@@ -32,7 +32,12 @@ public class ModelDataMap implements IModelData
{
this.backingMap = new IdentityHashMap<>(map);
}
protected ModelDataMap()
{
this.backingMap = new IdentityHashMap<>();
}
@Override
public boolean hasProperty(ModelProperty<?> prop)
{
@@ -1,3 +1,22 @@
/*
* 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.generators;
import net.minecraft.data.DataGenerator;
@@ -1,3 +1,22 @@
/*
* 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.generators;
import java.util.Arrays;
@@ -1,3 +1,22 @@
/*
* 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.generators;
import java.io.File;
@@ -1,3 +1,22 @@
/*
* 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.generators;
import com.google.common.annotations.VisibleForTesting;
@@ -1,3 +1,22 @@
/*
* 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.generators;
import net.minecraft.data.DataGenerator;
@@ -1,3 +1,22 @@
/*
* 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.generators;
import java.util.ArrayList;
@@ -0,0 +1,52 @@
/*
* 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.geometry;
import net.minecraft.client.renderer.model.*;
import net.minecraft.client.renderer.texture.ISprite;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.model.IModelConfiguration;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
/**
* General interface for any model that can be baked, superset of vanilla {@link net.minecraft.client.renderer.model.IUnbakedModel}.
* Models can be baked to different vertex formats and with different state.
*/
public interface IModelGeometry<T extends IModelGeometry<T>>
{
default Collection<? extends IModelGeometryPart> getParts() {
return Collections.emptyList();
}
default Optional<? extends IModelGeometryPart> getPart(String name) {
return Optional.empty();
}
IBakedModel bake(IModelConfiguration owner, ModelBakery bakery, Function<ResourceLocation, TextureAtlasSprite> spriteGetter, ISprite sprite, VertexFormat format, ItemOverrideList overrides);
Collection<ResourceLocation> getTextureDependencies(IModelConfiguration owner, Function<ResourceLocation, IUnbakedModel> modelGetter, Set<String> missingTextureErrors);
}
@@ -0,0 +1,46 @@
/*
* 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.geometry;
import net.minecraft.client.renderer.model.IUnbakedModel;
import net.minecraft.client.renderer.model.ModelBakery;
import net.minecraft.client.renderer.texture.ISprite;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.model.IModelBuilder;
import net.minecraftforge.client.model.IModelConfiguration;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.function.Function;
public interface IModelGeometryPart
{
String name();
void addQuads(IModelConfiguration owner, IModelBuilder<?> modelBuilder, ModelBakery bakery, Function<ResourceLocation, TextureAtlasSprite> spriteGetter, ISprite sprite, VertexFormat format);
default Collection<ResourceLocation> getTextureDependencies(IModelConfiguration owner, Function<ResourceLocation, IUnbakedModel> modelGetter, Set<String> missingTextureErrors) {
// No texture dependencies
return Collections.emptyList();
}
}
@@ -0,0 +1,59 @@
/*
* 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.geometry;
import com.google.common.collect.Sets;
import net.minecraft.client.renderer.model.IUnbakedModel;
import net.minecraft.client.renderer.model.ModelBakery;
import net.minecraft.client.renderer.texture.ISprite;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.model.IModelBuilder;
import net.minecraftforge.client.model.IModelConfiguration;
import java.util.Collection;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
public interface IMultipartModelGeometry<T extends IMultipartModelGeometry<T>> extends ISimpleModelGeometry<T>
{
@Override
Collection<? extends IModelGeometryPart> getParts();
Optional<? extends IModelGeometryPart> getPart(String name);
@Override
default void addQuads(IModelConfiguration owner, IModelBuilder<?> modelBuilder, ModelBakery bakery, Function<ResourceLocation, TextureAtlasSprite> spriteGetter, ISprite sprite, VertexFormat format)
{
getParts().stream().filter(part -> owner.getPartVisibility(part))
.forEach(part -> part.addQuads(owner, modelBuilder, bakery, spriteGetter, sprite, format));
}
@Override
default Collection<ResourceLocation> getTextureDependencies(IModelConfiguration owner, Function<ResourceLocation, IUnbakedModel> modelGetter, Set<String> missingTextureErrors)
{
Set<ResourceLocation> combined = Sets.newHashSet();
for (IModelGeometryPart part : getParts())
combined.addAll(part.getTextureDependencies(owner, modelGetter, missingTextureErrors));
return combined;
}
}
@@ -0,0 +1,54 @@
/*
* 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.geometry;
import net.minecraft.client.renderer.model.*;
import net.minecraft.client.renderer.texture.ISprite;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.model.IModelBuilder;
import net.minecraftforge.client.model.IModelConfiguration;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
public interface ISimpleModelGeometry<T extends ISimpleModelGeometry<T>> extends IModelGeometry<T>
{
@Override
default IBakedModel bake(IModelConfiguration owner, ModelBakery bakery, Function<ResourceLocation, TextureAtlasSprite> spriteGetter, ISprite sprite, VertexFormat format, ItemOverrideList overrides)
{
TextureAtlasSprite particle = spriteGetter.apply(new ResourceLocation(owner.resolveTexture("particle")));
IModelBuilder<?> builder = IModelBuilder.of(owner, overrides, particle);
addQuads(owner, builder, bakery, spriteGetter, sprite, format);
return builder.build();
}
void addQuads(IModelConfiguration owner, IModelBuilder<?> modelBuilder, ModelBakery bakery, Function<ResourceLocation, TextureAtlasSprite> spriteGetter, ISprite sprite, VertexFormat format);
@Override
Collection<ResourceLocation> getTextureDependencies(IModelConfiguration owner, Function<ResourceLocation, IUnbakedModel> modelGetter, Set<String> missingTextureErrors);
}
@@ -0,0 +1,97 @@
/*
* 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.obj;
import com.google.common.base.Charsets;
import joptsimple.internal.Strings;
import net.minecraft.resources.IResource;
import javax.annotation.Nullable;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class LineReader implements AutoCloseable
{
InputStreamReader lineStream;
BufferedReader lineReader;
public LineReader(IResource resource)
{
this.lineStream = new InputStreamReader(resource.getInputStream(), Charsets.UTF_8);
this.lineReader = new BufferedReader(lineStream);
}
@Nullable
public String[] readAndSplitLine(boolean ignoreEmptyLines) throws IOException
{
//noinspection LoopConditionNotUpdatedInsideLoop
do
{
String currentLine = lineReader.readLine();
if (currentLine == null)
return null;
List<String> lineParts = new ArrayList<>();
if (currentLine.startsWith("#"))
currentLine = "";
if (currentLine.length() > 0)
{
boolean hasContinuation;
do
{
hasContinuation = currentLine.endsWith("\\");
String tmp = hasContinuation ? currentLine.substring(0, currentLine.length() - 1) : currentLine;
Arrays.stream(tmp.split("[\t ]+")).filter(s -> !Strings.isNullOrEmpty(s)).forEach(lineParts::add);
if (hasContinuation)
{
currentLine = lineReader.readLine();
if (currentLine == null)
break;
if (currentLine.length() == 0 || currentLine.startsWith("#"))
break;
}
} while (hasContinuation);
}
if (lineParts.size() > 0)
return lineParts.toArray(new String[0]);
}
while (ignoreEmptyLines);
return new String[0];
}
@Override
public void close() throws Exception
{
lineReader.close();
lineStream.close();
}
}
@@ -0,0 +1,133 @@
/*
* 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.obj;
import com.google.common.collect.Maps;
import joptsimple.internal.Strings;
import javax.vecmath.Vector4f;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.NoSuchElementException;
public class MaterialLibrary2
{
public static final MaterialLibrary2 EMPTY = new MaterialLibrary2();
final Map<String, Material> materials = Maps.newHashMap();
private MaterialLibrary2()
{
}
public MaterialLibrary2(LineReader reader) throws IOException
{
Material currentMaterial = null;
String[] line;
while((line = reader.readAndSplitLine(true)) != null)
{
switch(line[0])
{
case "newmtl":
{
String name = Strings.join(Arrays.copyOfRange(line, 1, line.length), " ");
currentMaterial = new Material(name);
materials.put(name, currentMaterial);
break;
}
case "Ka":
currentMaterial.ambientColor = OBJModel2.parseVector4(line);
break;
case "map_Ka":
// Ignores all options params
currentMaterial.ambientColorMap = line[line.length-1];
break;
case "Kd":
currentMaterial.diffuseColor = OBJModel2.parseVector4(line);
break;
case "forge_TintIndex":
currentMaterial.diffuseTintIndex = Integer.parseInt(line[1]);
break;
case "map_Kd":
// Ignores all options params
currentMaterial.diffuseColorMap = line[line.length-1];
break;
case "Ks":
currentMaterial.specularColor = OBJModel2.parseVector4(line);
break;
case "Ns":
currentMaterial.specularHighlight = Float.parseFloat(line[1]);
break;
case "map_Ks":
// Ignores all options params
currentMaterial.specularColorMap = line[line.length-1];
break;
case "d":
// Ignores all options params
currentMaterial.dissolve = Float.parseFloat(line[1]);
break;
case "Tr":
// Ignores all options params
currentMaterial.transparency = Float.parseFloat(line[1]);
break;
}
}
}
public Material getMaterial(String mat)
{
if (!materials.containsKey(mat))
throw new NoSuchElementException("The material was not found in the library: " + mat);
return materials.get(mat);
}
public class Material
{
public final String name;
public Vector4f ambientColor = new Vector4f(0,0,0,1);
public String ambientColorMap;
public Vector4f diffuseColor = new Vector4f(1,1,1,1);
public String diffuseColorMap;
public Vector4f specularColor = new Vector4f(0,0,0,1);
public float specularHighlight = 0;
public String specularColorMap;
public float dissolve = 1.0f;
public float transparency = 0.0f;
// non-standard
public int diffuseTintIndex = 0;
public Material(String name)
{
this.name = name;
}
}
}
@@ -42,6 +42,7 @@ import org.apache.logging.log4j.Logger;
* To enable your mod call instance.addDomain(modid).
* If you need more control over accepted resources - extend the class, and register a new instance with ModelLoaderRegistry.
*/
@Deprecated // Use the new model loading system and data generators instead.
public enum OBJLoader implements ICustomModelLoader {
INSTANCE;
@@ -0,0 +1,118 @@
/*
* 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.obj;
import com.google.common.collect.Maps;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.IResource;
import net.minecraft.resources.IResourceManager;
import net.minecraft.util.JSONUtils;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.model.IModelLoader;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.*;
public class OBJLoader2 implements IModelLoader<OBJModel2>
{
public static OBJLoader2 INSTANCE = new OBJLoader2();
private final Map<OBJModel2.ModelSettings, OBJModel2> modelCache = Maps.newHashMap();
private final Map<ResourceLocation, MaterialLibrary2> materialCache = Maps.newHashMap();
private IResourceManager manager = Minecraft.getInstance().getResourceManager();
@Override
public void onResourceManagerReload(IResourceManager resourceManager)
{
modelCache.clear();
materialCache.clear();
manager = resourceManager;
}
@Override
public OBJModel2 read(JsonDeserializationContext deserializationContext, JsonObject modelContents)
{
if (!modelContents.has("model"))
throw new RuntimeException("OBJ Loader requires a 'model' key that points to a valid .OBJ model.");
String modelLocation = modelContents.get("model").getAsString();
boolean detectCullableFaces = JSONUtils.getBoolean(modelContents, "detectCullableFaces", true);
boolean diffuseLighting = JSONUtils.getBoolean(modelContents, "diffuseLighting", false);
boolean flipV = JSONUtils.getBoolean(modelContents, "flip-v", false);
boolean ambientToFullbright = JSONUtils.getBoolean(modelContents, "ambientToFullbright", true);
@Nullable
String materialLibraryOverrideLocation = modelContents.has("materialLibraryOverride") ? JSONUtils.getString(modelContents, "materialLibraryOverride") : null;
return loadModel(new OBJModel2.ModelSettings(new ResourceLocation(modelLocation), detectCullableFaces, diffuseLighting, flipV, ambientToFullbright, materialLibraryOverrideLocation));
}
public OBJModel2 loadModel(OBJModel2.ModelSettings settings)
{
return modelCache.computeIfAbsent(settings, (data) -> {
IResource resource;
try
{
resource = manager.getResource(settings.modelLocation);
}
catch (IOException e)
{
throw new RuntimeException("Could not find OBJ model", e);
}
try(LineReader rdr = new LineReader(resource))
{
return new OBJModel2(rdr, settings);
}
catch (Exception e)
{
throw new RuntimeException("Could not read OBJ model", e);
}
});
}
public MaterialLibrary2 loadMaterialLibrary(ResourceLocation materialLocation)
{
return materialCache.computeIfAbsent(materialLocation, (location) -> {
IResource resource;
try
{
resource = manager.getResource(location);
}
catch (IOException e)
{
throw new RuntimeException("Could not find OBJ material library", e);
}
try(LineReader rdr = new LineReader(resource))
{
return new MaterialLibrary2(rdr);
}
catch (Exception e)
{
throw new RuntimeException("Could not read OBJ material library", e);
}
});
}
}
@@ -79,6 +79,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@Deprecated // Use the new model loading system and data generators instead.
public class OBJModel implements IUnbakedModel
{
private static final Logger LOGGER = LogManager.getLogger();
@@ -1535,6 +1536,12 @@ public class OBJModel implements IUnbakedModel
return new OBJBakedModel(this.model, this.state, this.format, this.textures);
}
@Override
public boolean doesHandlePerspectives()
{
return true;
}
@Override
public Pair<? extends IBakedModel, Matrix4f> handlePerspective(TransformType cameraTransformType)
{
@@ -0,0 +1,671 @@
/*
* 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.obj;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import joptsimple.internal.Strings;
import net.minecraft.client.renderer.model.*;
import net.minecraft.client.renderer.texture.ISprite;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.client.renderer.vertex.VertexFormatElement;
import net.minecraft.util.Direction;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import net.minecraftforge.client.model.*;
import net.minecraftforge.client.model.geometry.IModelGeometryPart;
import net.minecraftforge.client.model.geometry.IMultipartModelGeometry;
import net.minecraftforge.client.model.pipeline.IVertexConsumer;
import net.minecraftforge.client.model.pipeline.UnpackedBakedQuad;
import net.minecraftforge.common.model.TRSRTransformation;
import org.apache.commons.lang3.tuple.Pair;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.vecmath.Vector2f;
import javax.vecmath.Vector3f;
import javax.vecmath.Vector4f;
import java.io.IOException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
public class OBJModel2 implements IMultipartModelGeometry<OBJModel2>
{
private static Vector2f[] DEFAULT_COORDS = {
new Vector2f(0, 0),
new Vector2f(0, 1),
new Vector2f(1, 1),
new Vector2f(1, 0),
};
private final Map<String, ModelGroup> parts = Maps.newHashMap();
private final List<Vector3f> positions = Lists.newArrayList();
private final List<Vector2f> texCoords = Lists.newArrayList();
private final List<Vector3f> normals = Lists.newArrayList();
private final List<Vector4f> colors = Lists.newArrayList();
public final boolean detectCullableFaces;
public final boolean diffuseLighting;
public final boolean flipV;
public final boolean ambientToFullbright;
public final ResourceLocation modelLocation;
@Nullable
public final String materialLibraryOverrideLocation;
OBJModel2(LineReader reader, ModelSettings settings) throws IOException
{
this.modelLocation = settings.modelLocation;
this.detectCullableFaces = settings.detectCullableFaces;
this.diffuseLighting = settings.diffuseLighting;
this.flipV = settings.flipV;
this.ambientToFullbright = settings.ambientToFullbright;
this.materialLibraryOverrideLocation = settings.materialLibraryOverrideLocation;
// for relative references to material libraries
String modelDomain = modelLocation.getNamespace();
String modelPath = modelLocation.getPath();
int lastSlash = modelPath.lastIndexOf('/');
if (lastSlash >= 0)
modelPath = modelPath.substring(0,lastSlash+1); // include the '/'
else
modelPath = "";
MaterialLibrary2 mtllib = MaterialLibrary2.EMPTY;
MaterialLibrary2.Material currentMat = null;
String currentSmoothingGroup = null;
ModelGroup currentGroup = null;
ModelObject currentObject = null;
ModelMesh currentMesh = null;
boolean objAboveGroup = false;
if (materialLibraryOverrideLocation != null)
{
String lib = materialLibraryOverrideLocation;
if (lib.contains(":"))
mtllib = OBJLoader2.INSTANCE.loadMaterialLibrary(new ResourceLocation(lib));
else
mtllib = OBJLoader2.INSTANCE.loadMaterialLibrary(new ResourceLocation(modelDomain, modelPath + lib));
}
String[] line;
while((line = reader.readAndSplitLine(true)) != null)
{
switch(line[0])
{
case "mtllib": // Loads material library
{
if (materialLibraryOverrideLocation != null)
break;
String lib = line[1];
if (lib.contains(":"))
mtllib = OBJLoader2.INSTANCE.loadMaterialLibrary(new ResourceLocation(lib));
else
mtllib = OBJLoader2.INSTANCE.loadMaterialLibrary(new ResourceLocation(modelDomain, modelPath + lib));
break;
}
case "usemtl": // Sets the current material (starts new mesh)
{
String mat = Strings.join(Arrays.copyOfRange(line, 1, line.length), " ");
MaterialLibrary2.Material newMat = mtllib.getMaterial(mat);
if (!Objects.equals(newMat, currentMat))
{
currentMat = newMat;
if (currentMesh != null && currentMesh.mat == null && currentMesh.faces.size() == 0)
{
currentMesh.mat = currentMat;
}
else
{
// Start new mesh
currentMesh = null;
}
}
break;
}
case "v": // Vertex
positions.add(parseVector4To3(line));
break;
case "vt": // Vertex texcoord
texCoords.add(parseVector2(line));
break;
case "vn": // Vertex normal
normals.add(parseVector3(line));
break;
case "vc": // Vertex color (non-standard)
colors.add(parseVector4(line));
break;
case "f": // Face
{
if (currentMesh == null)
{
currentMesh = new ModelMesh(currentMat, currentSmoothingGroup);
if (currentObject != null)
{
currentObject.meshes.add(currentMesh);
}
else
{
if (currentGroup == null)
{
currentGroup = new ModelGroup("");
parts.put("", currentGroup);
}
currentGroup.meshes.add(currentMesh);
}
}
int[][] vertices = new int[line.length-1][];
for(int i=0;i<vertices.length;i++)
{
String vertexData = line[i+1];
String[] vertexParts = vertexData.split("/");
int[] vertex = Arrays.stream(vertexParts).mapToInt(num -> Strings.isNullOrEmpty(num) ? 0 : Integer.parseInt(num)).toArray();
if (vertex[0] < 0) vertex[0] = positions.size() + vertex[0];
else vertex[0]--;
if (vertex.length > 1)
{
if (vertex[1] < 0) vertex[1] = texCoords.size() + vertex[1];
else vertex[1]--;
if (vertex.length > 2)
{
if (vertex[2] < 0) vertex[2] = normals.size() + vertex[2];
else vertex[2]--;
if (vertex.length > 3)
{
if (vertex[3] < 0) vertex[3] = colors.size() + vertex[3];
else vertex[3]--;
}
}
}
vertices[i] = vertex;
}
currentMesh.faces.add(vertices);
break;
}
case "s": // Smoothing group (starts new mesh)
{
String smoothingGroup = "off".equals(line[1]) ? null : line[1];
if (!Objects.equals(currentSmoothingGroup, smoothingGroup))
{
currentSmoothingGroup = smoothingGroup;
if (currentMesh != null && currentMesh.smoothingGroup == null && currentMesh.faces.size() == 0)
{
currentMesh.smoothingGroup = currentSmoothingGroup;
}
else
{
// Start new mesh
currentMesh = null;
}
}
break;
}
case "g":
{
String name = line[1];
if (objAboveGroup)
{
currentObject = new ModelObject(currentGroup.name() + "/" + name);
currentGroup.parts.put(name, currentObject);
}
else
{
currentGroup = new ModelGroup(name);
parts.put(name, currentGroup);
currentObject = null;
}
// Start new mesh
currentMesh = null;
break;
}
case "o":
{
String name = line[1];
if (objAboveGroup || currentGroup == null)
{
objAboveGroup = true;
currentGroup = new ModelGroup(name);
parts.put(name, currentGroup);
currentObject = null;
}
else
{
currentObject = new ModelObject(currentGroup.name() + "/" + name);
currentGroup.parts.put(name, currentObject);
}
// Start new mesh
currentMesh = null;
break;
}
}
}
}
public static Vector3f parseVector4To3(String[] line)
{
switch (line.length) {
case 1: return new Vector3f(0,0,0);
case 2: return new Vector3f(Float.parseFloat(line[1]), 0, 0);
case 3: return new Vector3f(Float.parseFloat(line[1]), Float.parseFloat(line[2]), 0);
case 4: return new Vector3f(Float.parseFloat(line[1]), Float.parseFloat(line[2]), Float.parseFloat(line[3]));
default:
{
Vector4f vec4 = parseVector4(line);
return new Vector3f(
vec4.getX() / vec4.getW(),
vec4.getY() / vec4.getW(),
vec4.getZ() / vec4.getW()
);
}
}
}
public static Vector2f parseVector2(String[] line)
{
switch (line.length) {
case 1: return new Vector2f(0,0);
case 2: return new Vector2f(Float.parseFloat(line[1]), 0);
default: return new Vector2f(Float.parseFloat(line[1]), Float.parseFloat(line[2]));
}
}
public static Vector3f parseVector3(String[] line)
{
switch (line.length) {
case 1: return new Vector3f(0,0,0);
case 2: return new Vector3f(Float.parseFloat(line[1]), 0, 0);
case 3: return new Vector3f(Float.parseFloat(line[1]), Float.parseFloat(line[2]), 0);
default: return new Vector3f(Float.parseFloat(line[1]), Float.parseFloat(line[2]), Float.parseFloat(line[3]));
}
}
public static Vector4f parseVector4(String[] line)
{
switch (line.length) {
case 1: return new Vector4f(0,0,0,1);
case 2: return new Vector4f(Float.parseFloat(line[1]), 0, 0,1);
case 3: return new Vector4f(Float.parseFloat(line[1]), Float.parseFloat(line[2]), 0,1);
case 4: return new Vector4f(Float.parseFloat(line[1]), Float.parseFloat(line[2]), Float.parseFloat(line[3]),1);
default: return new Vector4f(Float.parseFloat(line[1]), Float.parseFloat(line[2]), Float.parseFloat(line[3]), Float.parseFloat(line[4]));
}
}
@Override
public Collection<? extends IModelGeometryPart> getParts()
{
return parts.values();
}
@Override
public Optional<? extends IModelGeometryPart> getPart(String name)
{
return Optional.ofNullable(parts.get(name));
}
private Pair<BakedQuad,Direction> makeQuad(int[][] indices, int tintIndex, Vector4f colorTint, Vector4f ambientColor, boolean isFullbright, TextureAtlasSprite texture, VertexFormat format, Optional<TRSRTransformation> transform)
{
boolean needsNormalRecalculation = false;
for (int[] ints : indices)
{
needsNormalRecalculation |= ints.length < 3;
}
Vector3f faceNormal = new Vector3f(0,0,0);
if (needsNormalRecalculation) {
Vector3f a = positions.get(indices[0][0]);
Vector3f ab = positions.get(indices[1][0]);
Vector3f ac = positions.get(indices[2][0]);
Vector3f abs = new Vector3f(ab);
abs.sub(a);
Vector3f acs = new Vector3f(ac);
acs.sub(a);
abs.cross(abs,acs);
abs.normalize();
faceNormal = abs;
}
Vector4f[] pos = new Vector4f[4];
Vector3f[] norm = new Vector3f[4];
UnpackedBakedQuad.Builder builder = new UnpackedBakedQuad.Builder(format);
builder.setQuadTint(tintIndex);
builder.setTexture(texture);
builder.setApplyDiffuseLighting(!isFullbright);
Vector2f uv2 = new Vector2f(0, 0);
if (ambientToFullbright)
{
int fakeLight = (int) ((ambientColor.getX() + ambientColor.getY() + ambientColor.getZ()) * 15 / 3.0f);
uv2 = new Vector2f((fakeLight << 4) / 32767.0f, (fakeLight << 4) / 32767.0f);
builder.setApplyDiffuseLighting(fakeLight == 0);
}
TRSRTransformation transformation = transform.orElse(null);
boolean hasTransform = transform.isPresent() && !transform.get().isIdentity();
for(int i=0;i<4;i++)
{
int[] index = indices[Math.min(i,indices.length-1)];
Vector3f pos0 = positions.get(index[0]);
Vector4f position = new Vector4f(pos0.x, pos0.y, pos0.z, 1);
Vector2f texCoord = index.length >= 2 && texCoords.size() > 0 ? texCoords.get(index[1]) : DEFAULT_COORDS[i];
Vector3f norm0 = !needsNormalRecalculation && index.length >= 3 && normals.size() > 0 ? normals.get(index[2]) : faceNormal;
Vector3f normal = norm0;
Vector4f color = index.length >= 4 && colors.size() > 0 ? colors.get(index[3]) : new Vector4f(1, 1, 1, 1);
if (hasTransform)
{
normal = new Vector3f(norm0);
transformation.transformPosition(position);
transformation.transformNormal(normal);
};
Vector4f tintedColor = new Vector4f(
color.getX() * colorTint.getX(),
color.getY() * colorTint.getY(),
color.getZ() * colorTint.getZ(),
color.getW() * colorTint.getW());
putVertexData(builder, format, position, texCoord, normal, tintedColor, uv2, texture);
pos[i] = position;
norm[i] = normal;
}
builder.setQuadOrientation(Direction.getFacingFromVector(norm[0].x, norm[0].y,norm[0].z));
Direction cull = null;
if (detectCullableFaces)
{
if (MathHelper.epsilonEquals(pos[0].getX(), 0) && // vertex.position.x
MathHelper.epsilonEquals(pos[1].getX(), 0) &&
MathHelper.epsilonEquals(pos[2].getX(), 0) &&
MathHelper.epsilonEquals(pos[3].getX(), 0) &&
norm[0].getX() < 0) // vertex.normal.x
{
cull = Direction.WEST;
}
else if (MathHelper.epsilonEquals(pos[0].getX(), 1) && // vertex.position.x
MathHelper.epsilonEquals(pos[1].getX(), 1) &&
MathHelper.epsilonEquals(pos[2].getX(), 1) &&
MathHelper.epsilonEquals(pos[3].getX(), 1) &&
norm[0].getX() > 0) // vertex.normal.x
{
cull = Direction.EAST;
}
else if (MathHelper.epsilonEquals(pos[0].getZ(), 0) && // vertex.position.z
MathHelper.epsilonEquals(pos[1].getZ(), 0) &&
MathHelper.epsilonEquals(pos[2].getZ(), 0) &&
MathHelper.epsilonEquals(pos[3].getZ(), 0) &&
norm[0].getZ() < 0) // vertex.normal.z
{
cull = Direction.NORTH; // can never remember
}
else if (MathHelper.epsilonEquals(pos[0].getZ(), 1) && // vertex.position.z
MathHelper.epsilonEquals(pos[1].getZ(), 1) &&
MathHelper.epsilonEquals(pos[2].getZ(), 1) &&
MathHelper.epsilonEquals(pos[3].getZ(), 1) &&
norm[0].getZ() > 0) // vertex.normal.z
{
cull = Direction.SOUTH;
}
else if (MathHelper.epsilonEquals(pos[0].getY(), 0) && // vertex.position.y
MathHelper.epsilonEquals(pos[1].getY(), 0) &&
MathHelper.epsilonEquals(pos[2].getY(), 0) &&
MathHelper.epsilonEquals(pos[3].getY(), 0) &&
norm[0].getY() < 0) // vertex.normal.z
{
cull = Direction.DOWN; // can never remember
}
else if (MathHelper.epsilonEquals(pos[0].getY(), 1) && // vertex.position.y
MathHelper.epsilonEquals(pos[1].getY(), 1) &&
MathHelper.epsilonEquals(pos[2].getY(), 1) &&
MathHelper.epsilonEquals(pos[3].getY(), 1) &&
norm[0].getY() > 0) // vertex.normal.y
{
cull = Direction.UP;
}
}
return Pair.of(builder.build(), cull);
}
private void putVertexData(IVertexConsumer consumer, VertexFormat format, Vector4f position0, Vector2f texCoord0, Vector3f normal0, Vector4f color0, Vector2f uv2, TextureAtlasSprite texture)
{
int elementCount = format.getElementCount();
for(int j=0;j<elementCount;j++)
{
VertexFormatElement e = format.getElement(j);
switch(e.getUsage())
{
case POSITION:
consumer.put(j, position0.getX(), position0.getY(), position0.getZ(), position0.getW());
break;
case COLOR:
consumer.put(j, color0.getX(), color0.getY(), color0.getZ(), color0.getW());
break;
case UV:
switch (e.getIndex())
{
case 0:
consumer.put(j,
texture.getInterpolatedU(texCoord0.getX() * 16),
texture.getInterpolatedV((flipV ? (1 - texCoord0.getY()) : texCoord0.getY()) * 16)
);
break;
case 1:
consumer.put(j, uv2.x, uv2.y);
break;
default:
consumer.put(j);
break;
}
break;
case NORMAL:
consumer.put(j, normal0.getX(), normal0.getY(), normal0.getZ());
break;
default:
consumer.put(j);
break;
}
}
}
public static class ModelSettings
{
@Nonnull
public final ResourceLocation modelLocation;
public final boolean detectCullableFaces;
public final boolean diffuseLighting;
public final boolean flipV;
public final boolean ambientToFullbright;
@Nullable
public final String materialLibraryOverrideLocation;
public ModelSettings(@Nonnull ResourceLocation modelLocation, boolean detectCullableFaces, boolean diffuseLighting, boolean flipV, boolean ambientToFullbright,
@Nullable String materialLibraryOverrideLocation)
{
this.modelLocation = modelLocation;
this.detectCullableFaces = detectCullableFaces;
this.diffuseLighting = diffuseLighting;
this.flipV = flipV;
this.ambientToFullbright = ambientToFullbright;
this.materialLibraryOverrideLocation = materialLibraryOverrideLocation;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ModelSettings that = (ModelSettings) o;
return equals(that);
}
public boolean equals(@Nonnull ModelSettings that)
{
return detectCullableFaces == that.detectCullableFaces &&
diffuseLighting == that.diffuseLighting &&
flipV == that.flipV &&
ambientToFullbright == that.ambientToFullbright &&
modelLocation.equals(that.modelLocation) &&
Objects.equals(materialLibraryOverrideLocation, that.materialLibraryOverrideLocation);
}
@Override
public int hashCode()
{
return Objects.hash(modelLocation, detectCullableFaces, diffuseLighting, flipV, ambientToFullbright, materialLibraryOverrideLocation);
}
}
public class ModelObject implements IModelGeometryPart
{
public final String name;
List<ModelMesh> meshes = Lists.newArrayList();
ModelObject(String name)
{
this.name = name;
}
@Override
public String name()
{
return name;
}
@Override
public void addQuads(IModelConfiguration owner, IModelBuilder<?> modelBuilder, ModelBakery bakery, Function<ResourceLocation, TextureAtlasSprite> spriteGetter, ISprite sprite, VertexFormat format)
{
for(ModelMesh mesh : meshes)
{
MaterialLibrary2.Material mat = mesh.mat;
if (mat == null)
continue;
TextureAtlasSprite texture = spriteGetter.apply(ModelLoaderRegistry2.resolveTexture(mat.diffuseColorMap, owner));
int tintIndex = mat.diffuseTintIndex;
Vector4f colorTint = mat.diffuseColor;
boolean isFullbright = ambientToFullbright && mesh.isFullbright();
if (format.equals(DefaultVertexFormats.ITEM) && isFullbright)
{
format = DefaultVertexFormats.BLOCK;
}
for (int[][] face : mesh.faces)
{
Pair<BakedQuad, Direction> quad = makeQuad(face, tintIndex, colorTint, mat.ambientColor, isFullbright, texture, format, sprite.getState().apply(Optional.empty()));
if (quad.getRight() == null)
modelBuilder.addGeneralQuad(quad.getLeft());
else
modelBuilder.addFaceQuad(quad.getRight(), quad.getLeft());
}
}
}
@Override
public Collection<ResourceLocation> getTextureDependencies(IModelConfiguration owner, Function<ResourceLocation, IUnbakedModel> modelGetter, Set<String> missingTextureErrors)
{
return meshes.stream().map(mesh -> ModelLoaderRegistry2.resolveTexture(mesh.mat.diffuseColorMap, owner)).collect(Collectors.toSet());
}
public boolean hasAnyFullBright()
{
return meshes.stream().anyMatch(ModelMesh::isFullbright);
}
}
public class ModelGroup extends ModelObject
{
final Map<String, ModelObject> parts = Maps.newHashMap();
ModelGroup(String name)
{
super(name);
}
public Collection<? extends IModelGeometryPart> getParts()
{
return parts.values();
}
@Override
public void addQuads(IModelConfiguration owner, IModelBuilder<?> modelBuilder, ModelBakery bakery, Function<ResourceLocation, TextureAtlasSprite> spriteGetter, ISprite sprite, VertexFormat format)
{
super.addQuads(owner, modelBuilder, bakery, spriteGetter, sprite, format);
getParts().stream().filter(part -> owner.getPartVisibility(part))
.forEach(part -> part.addQuads(owner, modelBuilder, bakery, spriteGetter, sprite, format));
}
@Override
public Collection<ResourceLocation> getTextureDependencies(IModelConfiguration owner, Function<ResourceLocation, IUnbakedModel> modelGetter, Set<String> missingTextureErrors)
{
Set<ResourceLocation> combined = Sets.newHashSet();
combined.addAll(super.getTextureDependencies(owner, modelGetter, missingTextureErrors));
for (IModelGeometryPart part : getParts())
combined.addAll(part.getTextureDependencies(owner, modelGetter, missingTextureErrors));
return combined;
}
@Override
public boolean hasAnyFullBright()
{
return super.hasAnyFullBright() || parts.values().stream().anyMatch(ModelObject::hasAnyFullBright);
}
}
private class ModelMesh
{
@Nullable
public MaterialLibrary2.Material mat;
@Nullable
public String smoothingGroup;
public final List<int[][]> faces = Lists.newArrayList();
public ModelMesh(@Nullable MaterialLibrary2.Material currentMat, @Nullable String currentSmoothingGroup)
{
this.mat = currentMat;
this.smoothingGroup = currentSmoothingGroup;
}
public boolean isFullbright()
{
return mat != null && mat.ambientColor.epsilonEquals(new Vector4f(1,1,1,1), 1/256f);
}
}
}
@@ -16,6 +16,7 @@
* 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.common.data;
import java.io.BufferedWriter;
@@ -0,0 +1,108 @@
/*
* 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.common.util;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.function.Supplier;
/**
* Proxy object for a value that is calculated on first access
* @param <T> The type of the value
*/
public interface Lazy<T> extends Supplier<T>
{
/**
* Constructs a lazy-initialized object
* @param supplier The supplier for the value, to be called the first time the value is needed.
*/
static <T> Lazy<T> of(@Nonnull Supplier<T> supplier)
{
return new Lazy.Fast<>(supplier);
}
/**
* Constructs a thread-safe lazy-initialized object
* @param supplier The supplier for the value, to be called the first time the value is needed.
*/
static <T> Lazy<T> concurrentOf(@Nonnull Supplier<T> supplier)
{
return new Lazy.Concurrent<>(supplier);
}
/**
* Non-thread-safe implementation.
*/
final class Fast<T> implements Lazy<T>
{
private Supplier<T> supplier;
private T instance;
private Fast(Supplier<T> supplier)
{
this.supplier = supplier;
}
@Nullable
@Override
public final T get()
{
if (supplier != null)
{
instance = supplier.get();
supplier = null;
}
return instance;
}
}
/**
* Thread-safe implementation.
*/
final class Concurrent<T> implements Lazy<T>
{
private static final Object lock = new Object();
private volatile Supplier<T> supplier;
private volatile T instance;
private Concurrent(Supplier<T> supplier)
{
this.supplier = supplier;
}
@Nullable
@Override
public final T get()
{
if (supplier != null)
{
synchronized (lock)
{
if (supplier != null)
{
instance = supplier.get();
supplier = null;
}
}
}
return instance;
}
}
}
@@ -0,0 +1,109 @@
/*
* 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.common.util;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.function.Supplier;
/**
* Proxy object for a value that is calculated on first access.
* Same as {@link Lazy}, but with a nonnull contract.
* @param <T> The type of the value
*/
public interface NonNullLazy<T> extends NonNullSupplier<T>
{
/**
* Constructs a lazy-initialized object
* @param supplier The supplier for the value, to be called the first time the value is needed.
*/
static <T> NonNullLazy<T> of(@Nonnull NonNullSupplier<T> supplier)
{
return new NonNullLazy.Fast<>(supplier);
}
/**
* Constructs a thread-safe lazy-initialized object
* @param supplier The supplier for the value, to be called the first time the value is needed.
*/
static <T> NonNullLazy<T> concurrentOf(@Nonnull NonNullSupplier<T> supplier)
{
return new NonNullLazy.Concurrent<>(supplier);
}
/**
* Non-thread-safe implementation.
*/
final class Fast<T> implements NonNullLazy<T>
{
private NonNullSupplier<T> supplier;
private T instance;
private Fast(NonNullSupplier<T> supplier)
{
this.supplier = supplier;
}
@Nonnull
@Override
public final T get()
{
if (supplier != null)
{
instance = supplier.get();
supplier = null;
}
return instance;
}
}
/**
* Thread-safe implementation.
*/
final class Concurrent<T> implements NonNullLazy<T>
{
private static final Object lock = new Object();
private volatile NonNullSupplier<T> supplier;
private volatile T instance;
private Concurrent(NonNullSupplier<T> supplier)
{
this.supplier = supplier;
}
@Nonnull
@Override
public final T get()
{
if (supplier != null)
{
synchronized (lock)
{
if (supplier != null)
{
instance = supplier.get();
supplier = null;
}
}
}
return instance;
}
}
}
@@ -54,6 +54,7 @@ import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.model.ModelLoaderRegistry;
import net.minecraftforge.client.model.ModelLoaderRegistry2;
import net.minecraftforge.common.ForgeConfig;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Event;
@@ -99,6 +100,7 @@ public class ClientModLoader
mcResourceManager.addReloadListener(ClientModLoader::onreload);
mcResourceManager.addReloadListener(BrandingControl.resourceManagerReloadListener());
ModelLoaderRegistry.init();
ModelLoaderRegistry2.init();
}
private static CompletableFuture<Void> onreload(final IFutureReloadListener.IStage stage, final IResourceManager resourceManager, final IProfiler prepareProfiler, final IProfiler executeProfiler, final Executor asyncExecutor, final Executor syncExecutor) {
@@ -125,6 +127,7 @@ public class ClientModLoader
private static void postSidedRunnable(Consumer<Supplier<Event>> perModContainerEventProcessor) {
RenderingRegistry.loadEntityRenderers(mc.getRenderManager());
ModelLoaderRegistry2.initComplete();
}
private static void preSidedRunnable(Consumer<Supplier<Event>> perModContainerEventProcessor) {
@@ -1,3 +1,22 @@
/*
* 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.fml.packs;
import java.io.File;
@@ -1,3 +1,22 @@
/*
* 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.fml.packs;
import java.io.File;