Skip to content

Instantly share code, notes, and snippets.

@StillManic
Last active August 29, 2015 14:23
Show Gist options
  • Save StillManic/658bb1ce0aec3d973754 to your computer and use it in GitHub Desktop.
Save StillManic/658bb1ce0aec3d973754 to your computer and use it in GitHub Desktop.
public class B3DLoader implements ICustomModelLoader
{
public static final B3DLoader instance = new B3DLoader();
private IResourceManager manager;
private final Set<String> enabledDomains = new HashSet<String>();
private final Map<ResourceLocation, B3DModel> cache = new HashMap<ResourceLocation, B3DModel>();
private Map jsons = new HashMap();
public void addDomain(String domain)
{
enabledDomains.add(domain.toLowerCase());
}
public void onResourceManagerReload(IResourceManager manager)
{
this.manager = manager;
cache.clear();
}
public boolean accepts(ResourceLocation modelLocation)
{
return enabledDomains.contains(modelLocation.getResourceDomain()) && modelLocation.getResourcePath().endsWith(".b3d");
}
public void passJsonToModel(ModelResourceLocation jsonLocation, ResourceLocation model)
{
if (model != null && jsonLocation != null)
{
this.jsons.put(model, jsonLocation);
}
}
@SuppressWarnings("unchecked")
public IModel loadModel(ResourceLocation modelLocation) throws IOException
{
ResourceLocation file = new ResourceLocation(modelLocation.getResourceDomain(), modelLocation.getResourcePath());
if(!cache.containsKey(file))
{
try
{
IResource resource = null;
try
{
resource = manager.getResource(file);
}
catch(FileNotFoundException e)
{
if(modelLocation.getResourcePath().startsWith("models/block/"))
resource = manager.getResource(new ResourceLocation(file.getResourceDomain(), "models/item/" + file.getResourcePath().substring("models/block/".length())));
else if(modelLocation.getResourcePath().startsWith("models/item/"))
resource = manager.getResource(new ResourceLocation(file.getResourceDomain(), "models/block/" + file.getResourcePath().substring("models/item/".length())));
else throw e;
}
B3DModel.Parser parser = new B3DModel.Parser(resource.getInputStream());
B3DModel model = parser.parse();
cache.put(file, model);
}
catch(IOException e)
{
//FMLLog.log(Level.ERROR, e, "Exception loading model %s with B3D loader, skipping", modelLocation);
cache.put(file, null);
throw e;
}
}
B3DModel model = cache.get(file);
if(model == null) return ModelLoaderRegistry.getMissingModel();
if(modelLocation instanceof B3DMeshLocation)
{
String mesh = ((B3DMeshLocation)modelLocation).getMesh();
if(!model.getMeshes().containsKey(mesh))
{
FMLLog.severe("No mesh named %s in model %s, skipping", mesh, modelLocation);
return ModelLoaderRegistry.getMissingModel();
}
Wrapper ret = new Wrapper(modelLocation, model.getTextures(), model.getMeshes().get(mesh));
ModelResourceLocation jsonLocation = (ModelResourceLocation) this.jsons.get(file);
ret.setJsonLocation(jsonLocation);
return ret;
}
if(!(model.getRoot().getKind() instanceof Mesh))
{
FMLLog.severe("No root mesh in model %s and no mesh name in location, skipping", modelLocation);
return ModelLoaderRegistry.getMissingModel();
}
Wrapper ret = new Wrapper(modelLocation, model.getTextures(), (Node<Mesh>)model.getRoot());
ModelResourceLocation jsonLocation = (ModelResourceLocation) this.jsons.get(file);
ret.setJsonLocation(jsonLocation);
return ret;
}
public static class B3DMeshLocation extends ResourceLocation
{
public final String mesh;
public B3DMeshLocation(String domain, String path, String mesh)
{
super(domain, path);
this.mesh = mesh;
}
public String getMesh()
{
return mesh;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((mesh == null) ? 0 : mesh.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj) return true;
if (!super.equals(obj)) return false;
if (getClass() != obj.getClass()) return false;
B3DMeshLocation other = (B3DMeshLocation) obj;
if (mesh == null)
{
if (other.mesh != null) return false;
}
else if (!mesh.equals(other.mesh)) return false;
return true;
}
}
public static class B3DState implements IModelState
{
private final Animation animation;
private final int frame;
public B3DState(Animation animation, int frame)
{
this.animation = animation;
this.frame = frame;
}
public Animation getAnimation()
{
return animation;
}
public int getFrame()
{
return frame;
}
public TRSRTransformation apply(IModelPart part)
{
if(!(part instanceof PartWrapper<?>))
{
throw new IllegalArgumentException("B3DState can only be applied to b3d models");
}
return getNodeMatrix(((PartWrapper<?>)part).getNode());
}
private static LoadingCache<Triple<Animation, Node<?>, Integer>, TRSRTransformation> cache = CacheBuilder.newBuilder()
.maximumSize(16384)
.expireAfterAccess(2, TimeUnit.MINUTES)
.build(new CacheLoader<Triple<Animation, Node<?>, Integer>, TRSRTransformation>()
{
public TRSRTransformation load(Triple<Animation, Node<?>, Integer> key) throws Exception
{
return getNodeMatrix(key.getLeft(), key.getMiddle(), key.getRight());
}
});
public TRSRTransformation getNodeMatrix(Node<?> node)
{
return cache.getUnchecked(Triple.<Animation, Node<?>, Integer>of(animation, node, frame));
}
public static TRSRTransformation getNodeMatrix(Animation animation, Node<?> node, int frame)
{
TRSRTransformation ret = TRSRTransformation.identity();
Key key = null;
if(animation != null) key = animation.getKeys().get(frame, node);
else if(key == null && node.getAnimation() != null && node.getAnimation() != animation) key = node.getAnimation().getKeys().get(frame, node);
if(key != null)
{
Node<?> parent = node.getParent();
if(parent != null)
{
TRSRTransformation pm = cache.getUnchecked(Triple.<Animation, Node<?>, Integer>of(animation, node.getParent(), frame));
ret = ret.compose(pm);
ret = ret.compose(new TRSRTransformation(parent.getPos(), parent.getRot(), parent.getScale(), null));
}
ret = ret.compose(new TRSRTransformation(key.getPos(), key.getRot(), key.getScale(), null));
Matrix4f rm = new TRSRTransformation(node.getPos(), node.getRot(), node.getScale(), null).getMatrix();
rm.invert();
ret = ret.compose(new TRSRTransformation(rm));
if(parent != null)
{
rm = new TRSRTransformation(parent.getPos(), parent.getRot(), parent.getScale(), null).getMatrix();
rm.invert();
ret = ret.compose(new TRSRTransformation(rm));
}
}
return ret;
}
}
public static enum B3DFrameProperty implements IUnlistedProperty<B3DState>
{
instance;
public String getName()
{
return "B3DFrame";
}
public boolean isValid(B3DState value)
{
return value instanceof B3DState;
}
public Class<B3DState> getType()
{
return B3DState.class;
}
public String valueToString(B3DState value)
{
return value.toString();
}
}
public static class PartWrapper<K extends IKind<K>> implements IModelPart
{
private final Node<K> node;
public static <K extends IKind<K>> PartWrapper<K> create(Node<K> node)
{
return new PartWrapper<K>(node);
}
public PartWrapper(Node<K> node)
{
this.node = node;
}
public Node<K> getNode()
{
return node;
}
}
public static class Wrapper extends PartWrapper<Mesh> implements IRetexturableModel, IModelCustomData, ICameraTransformsModel
{
private final ResourceLocation location;
private final ImmutableMap<String, ResourceLocation> textures;
private ModelResourceLocation jsonLocation = new ModelResourceLocation("missingno");
public Wrapper(ResourceLocation location, List<Texture> textures, B3DModel.Node<Mesh> mesh)
{
this(location, buildTextures(textures), mesh);
}
public Wrapper(ResourceLocation location, ImmutableMap<String, ResourceLocation> textures, B3DModel.Node<Mesh> mesh)
{
super(mesh);
this.location = location;
this.textures = textures;
}
private static ImmutableMap<String, ResourceLocation> buildTextures(List<Texture> textures)
{
ImmutableMap.Builder<String, ResourceLocation> builder = ImmutableMap.builder();
for(Texture t : textures)
{
String path = t.getPath();
builder.put(path, new ResourceLocation(getLocation(path)));
}
return builder.build();
}
private static String getLocation(String path)
{
if(path.endsWith(".png")) path = path.substring(0, path.length() - ".png".length());
return path;
}
public Collection<ResourceLocation> getDependencies()
{
// no dependencies for in-file models
// FIXME maybe add child meshes
return Collections.emptyList();
}
public Collection<ResourceLocation> getTextures()
{
return Collections2.filter(textures.values(), new Predicate<ResourceLocation>()
{
public boolean apply(ResourceLocation loc)
{
return !loc.getResourcePath().startsWith("#");
}
});
}
public IFlexibleBakedModel bake(IModelState state, VertexFormat format, Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter)
{
ImmutableMap.Builder<String, TextureAtlasSprite> builder = ImmutableMap.builder();
TextureAtlasSprite missing = bakedTextureGetter.apply(new ResourceLocation("missingno"));
for(Map.Entry<String, ResourceLocation> e : textures.entrySet())
{
if(e.getValue().getResourcePath().startsWith("#"))
{
FMLLog.severe("unresolved texture '%s' for b3d model '%s'", e.getValue().getResourcePath(), location);
builder.put(e.getKey(), missing);
}
else
{
builder.put(e.getKey(), bakedTextureGetter.apply(e.getValue()));
}
}
builder.put("missingno", missing);
return new BakedWrapper(this, state, format, builder.build());
}
public B3DState getDefaultState()
{
return new B3DState(getNode().getAnimation(), 1);
}
public ResourceLocation getLocation()
{
return location;
}
public ImmutableMap<String, ResourceLocation> getTextureMap()
{
return textures;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((location == null) ? 0 : location.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Wrapper other = (Wrapper) obj;
if (location == null)
{
if (other.location != null) return false;
}
else if (!location.equals(other.location)) return false;
return true;
}
@Override
public IModel retexture(ImmutableMap<String, String> textures)
{
ImmutableMap.Builder<String, ResourceLocation> builder = ImmutableMap.builder();
for(Map.Entry<String, ResourceLocation> e : this.textures.entrySet())
{
String path = e.getKey();
String loc = getLocation(path);
if(textures.containsKey(loc))
{
String newLoc = textures.get(loc);
if(newLoc == null) newLoc = getLocation(path);
builder.put(e.getKey(), new ResourceLocation(newLoc));
}
else
{
builder.put(e);
}
}
return new Wrapper(location, builder.build(), getNode());
}
@Override
public IModel process(ImmutableMap<String, String> customData)
{
// TODO keyframe
return null;
}
@Override
public void setJsonLocation(ModelResourceLocation jsonLocation)
{
this.jsonLocation = jsonLocation;
}
}
private static class BakedWrapper implements IFlexibleBakedModel, ISmartBlockModel, ISmartItemModel, IPerspectiveAwareModel
{
private final B3DLoader.Wrapper model;
private final IModelState state;
private final VertexFormat format;
private final ImmutableMap<String, TextureAtlasSprite> textures;
private final ByteBuffer buf;
private ImmutableList<BakedQuad> quads;
private static final int BYTES_IN_INT = Integer.SIZE / Byte.SIZE;
private static final int VERTICES_IN_QUAD = 4;
private ItemCameraTransforms cameraTransforms = ItemCameraTransforms.DEFAULT;
public BakedWrapper(B3DLoader.Wrapper model, IModelState state, VertexFormat format, ImmutableMap<String, TextureAtlasSprite> textures)
{
this.model = model;
this.state = state;
this.format = format;
this.textures = textures;
buf = BufferUtils.createByteBuffer(VERTICES_IN_QUAD * format.getNextOffset());
}
public List<BakedQuad> getFaceQuads(EnumFacing side)
{
return Collections.emptyList();
}
@SuppressWarnings("unchecked")
public List<BakedQuad> getGeneralQuads()
{
if(quads == null)
{
Node<Mesh> mesh = model.getNode();
ImmutableList.Builder<BakedQuad> builder = ImmutableList.builder();
for(Node<?> child : mesh.getNodes().values())
{
if(child.getKind() instanceof Mesh)
{
Node<Mesh> childMesh = (Node<Mesh>)child;
builder.addAll(new BakedWrapper(new B3DLoader.Wrapper(model.getLocation(), model.getTextureMap(), childMesh), state, format, textures).getGeneralQuads());
}
}
Multimap<Vertex, Pair<Float, Node<Bone>>> weightMap = mesh.getKind().getWeightMap();
Collection<Face> faces = mesh.getKind().getFaces();
faces = mesh.getKind().bake(new Function<Node<?>, Matrix4f>()
{
// gets transformation in global space
public Matrix4f apply(Node<?> node)
{
return state.apply(PartWrapper.create(node)).getMatrix();
}
});
for(Face f : faces)
{
buf.clear();
List<Texture> textures = null;
if(f.getBrush() != null) textures = f.getBrush().getTextures();
TextureAtlasSprite sprite;
if(textures == null || textures.isEmpty()) sprite = this.textures.get("missingno");
else if(textures.get(0) == B3DModel.Texture.White) sprite = ModelLoader.White.instance;
else sprite = this.textures.get(textures.get(0).getPath());
putVertexData(f.getV1(), sprite);
putVertexData(f.getV2(), sprite);
putVertexData(f.getV3(), sprite);
putVertexData(f.getV3(), sprite);
buf.flip();
int[] data = new int[VERTICES_IN_QUAD * format.getNextOffset() / BYTES_IN_INT];
buf.asIntBuffer().get(data);
builder.add(new ColoredBakedQuad(data, -1, EnumFacing.getFacingFromVector(f.getNormal().x, f.getNormal().y, f.getNormal().z)));
}
quads = builder.build();
}
return quads;
}
private void put(VertexFormatElement e, Float... fs)
{
Attributes.put(buf, e, true, 0f, fs);
}
@SuppressWarnings("unchecked")
private final void putVertexData(Vertex v, TextureAtlasSprite sprite)
{
// TODO handle everything not handled (texture transformations, bones, transformations, normals, e.t.c)
int oldPos = buf.position();
Number[] ns = new Number[16];
for(int i = 0; i < ns.length; i++) ns[i] = 0f;
for(VertexFormatElement e : (List<VertexFormatElement>)format.getElements())
{
switch(e.getUsage())
{
case POSITION:
put(e, v.getPos().x, v.getPos().y, v.getPos().z, 1f);
break;
case COLOR:
if(v.getColor() != null)
{
put(e, v.getColor().x, v.getColor().y, v.getColor().z, v.getColor().w);
}
else
{
put(e, 1f, 1f, 1f, 1f);
}
break;
case UV:
// TODO handle more brushes
if(e.getIndex() < v.getTexCoords().length)
{
put(e,
sprite.getInterpolatedU(v.getTexCoords()[0].x * 16),
sprite.getInterpolatedV(v.getTexCoords()[0].y * 16),
0f,
1f
);
}
else
{
put(e, 0f, 0f, 0f, 1f);
}
break;
case NORMAL:
// TODO
put(e, 0f, 1f, 0f, 1f);
break;
case GENERIC:
// TODO
put(e, 0f, 0f, 0f, 0f);
break;
default:
break;
}
}
buf.position(oldPos + format.getNextOffset());
}
public boolean isAmbientOcclusion()
{
return true;
}
public boolean isGui3d()
{
return true;
}
public boolean isBuiltInRenderer()
{
return false;
}
public TextureAtlasSprite getTexture()
{
// FIXME somehow specify particle texture in the model
return textures.values().asList().get(0);
}
public ItemCameraTransforms getItemCameraTransforms()
{
if (this.cameraTransforms == null)
{
ModelBlockDefinition definition = ModelLoaderRegistry.getModelLoader().getModelBlockDefinition(this.model.jsonLocation);
Map variantsMap = definition.getAllVariants();
if (variantsMap.containsKey(this.model.jsonLocation.getVariant()))
{
ModelBlockDefinition.Variants variants = (ModelBlockDefinition.Variants) variantsMap.get(this.model.jsonLocation.getVariant());
List<ModelBlockDefinition.Variant> variantList = variants.getVariants();
Iterator varIter = variantList.iterator();
while (varIter.hasNext()) {
ModelBlockDefinition.Variant variant = (ModelBlockDefinition.Variant) varIter.next();
ResourceLocation varLoc = new ResourceLocation(variant.getModelLocation().getResourceDomain(), "models/" + variant.getModelLocation().getResourcePath());
if (varLoc.equals(this.model.location))
{
this.cameraTransforms = variant.getTransforms();
}
}
return this.cameraTransforms;
}
this.cameraTransforms = ItemCameraTransforms.DEFAULT;
}
return this.cameraTransforms;
}
@Override
public BakedWrapper handleBlockState(IBlockState state)
{
if(state instanceof IExtendedBlockState)
{
IExtendedBlockState exState = (IExtendedBlockState)state;
if(exState.getUnlistedNames().contains(B3DFrameProperty.instance))
{
B3DState s = (B3DState)exState.getValue(B3DFrameProperty.instance);
if(s != null)
{
return getCachedModel(s.getFrame());
}
}
}
return this;
}
private final Map<Integer, BakedWrapper> cache = new HashMap<Integer, BakedWrapper>();
public BakedWrapper getCachedModel(int frame)
{
if(!cache.containsKey(frame))
{
cache.put(frame, new BakedWrapper(model, new B3DState(model.getNode().getAnimation(), frame), format, textures));
}
return cache.get(frame);
}
public VertexFormat getFormat()
{
return format;
}
public BakedWrapper handleItemState(ItemStack stack)
{
// TODO specify how to get B3DState from ItemStack
return this;
}
@Override
public Pair<IBakedModel, Matrix4f> handlePerspective(TransformType cameraTransformType)
{
ItemCameraTransforms transforms = this.getItemCameraTransforms();
transforms = transforms == null ? ItemCameraTransforms.DEFAULT : transforms;
TRSRTransformation trsr = TRSRTransformation.identity();
Matrix4f matrix = trsr.getMatrix();
switch(cameraTransformType)
{
case THIRD_PERSON:
trsr = new TRSRTransformation(transforms.thirdPerson != null ? transforms.thirdPerson : ItemTransformVec3f.DEFAULT);
matrix = trsr.getMatrix();
return Pair.of((IBakedModel) this, matrix);
case FIRST_PERSON:
trsr = new TRSRTransformation(transforms.firstPerson != null ? transforms.firstPerson : ItemTransformVec3f.DEFAULT);
matrix = trsr.getMatrix();
return Pair.of((IBakedModel) this, matrix);
case HEAD:
trsr = new TRSRTransformation(transforms.head != null ? transforms.head : ItemTransformVec3f.DEFAULT);
matrix = trsr.getMatrix();
return Pair.of((IBakedModel) this, matrix);
case GUI:
trsr = new TRSRTransformation(transforms.gui != null ? transforms.gui : ItemTransformVec3f.DEFAULT);
matrix = trsr.getMatrix();
return Pair.of((IBakedModel) this, matrix);
case NONE:
default:
return Pair.of((IBakedModel) this, matrix);
}
}
}
}
@SuppressWarnings("deprecation")
public class BlockStateLoader
{
private static final Gson GSON = (new GsonBuilder())
.registerTypeAdapter(ForgeBlockStateV1.class, ForgeBlockStateV1.Deserializer.INSTANCE)
.registerTypeAdapter(ForgeBlockStateV1.Variant.class, ForgeBlockStateV1.Variant.Deserializer.INSTANCE)
.create();
/**
* Loads a BlockStates json file.
* Will attempt to parse it as a Forge Enhanced version if possible.
* Will fall back to standard loading if marker is not present.
*
* Note: This method is NOT thread safe
*
* @param reader json read
* @param vanillaGSON ModelBlockDefinition's GSON reader.
*
* @return Model definition including variants for all known combinations.
*/
@SuppressWarnings("rawtypes")
public static ModelBlockDefinition load(Reader reader, final Gson vanillaGSON)
{
try
{
byte[] data = IOUtils.toByteArray(reader);
reader = new InputStreamReader(new ByteArrayInputStream(data), Charsets.UTF_8);
Marker marker = GSON.fromJson(new String(data), Marker.class); // Read "forge_marker" to determine what to load.
switch (marker.forge_marker)
{
case 1: // Version 1
ForgeBlockStateV1 v1 = GSON.fromJson(reader, ForgeBlockStateV1.class);
List<ModelBlockDefinition.Variants> variants = Lists.newArrayList();
for (Entry<String, Collection<ForgeBlockStateV1.Variant>> entry : v1.variants.asMap().entrySet())
{ // Convert Version1 variants into vanilla variants for the ModelBlockDefinition.
List<ModelBlockDefinition.Variant> mcVars = Lists.newArrayList();
for (ForgeBlockStateV1.Variant var : entry.getValue())
{
ModelRotation rot = var.getRotation().or(ModelRotation.X0_Y0);
boolean uvLock = var.getUvLock().or(false);
int weight = var.getWeight().or(1);
ModelBlockDefinition.Variant mdVar;
if (var.getModel() != null && var.getSubmodels().size() == 0 && var.getTextures().size() == 0 && var.getCustomData().size() == 0)
{
mdVar = new ModelBlockDefinition.Variant(var.getModel(), rot, uvLock, weight);
mdVar.setTransforms(var.getTransforms());
mcVars.add(mdVar);
}
else
{
ForgeVariant forgeVar = new ForgeVariant(var.getModel(), rot, uvLock, weight, var.getTextures(), var.getOnlyPartsVariant(),var.getCustomData());
forgeVar.setTransforms(var.getTransforms());
mcVars.add(forgeVar);
}
}
variants.add(new ModelBlockDefinition.Variants(entry.getKey(), mcVars));
}
return new ModelBlockDefinition((Collection)variants); //Damn lists being collections!
default: //Unknown version.. try loading it as normal.
return vanillaGSON.fromJson(reader, ModelBlockDefinition.class);
}
}
catch (IOException e)
{
Throwables.propagate(e);
}
return null;
}
public static class Marker
{
public int forge_marker = -1;
}
//This is here specifically so that we do not have a hard reference to ForgeBlockStateV1.Variant in ForgeVariant
public static class SubModel
{
private final ModelRotation rotation;
private final boolean uvLock;
private final ImmutableMap<String, String> textures;
private final ResourceLocation model;
private final ImmutableMap<String, String> customData;
public SubModel(ModelRotation rotation, boolean uvLock, ImmutableMap<String, String> textures, ResourceLocation model, ImmutableMap<String, String> customData)
{
this.rotation = rotation;
this.uvLock = uvLock;
this.textures = textures;
this.model = model;
this.customData = customData;
}
public ModelRotation getRotation() { return rotation; }
public boolean isUVLock() { return uvLock; }
public ImmutableMap<String, String> getTextures() { return textures; }
public ResourceLocation getModelLocation() { return model; }
public ImmutableMap<String, String> getCustomData() { return customData; }
}
public static class ForgeVariant extends ModelBlockDefinition.Variant implements ISmartVariant
{
private final ImmutableMap<String, String> textures;
private final ImmutableMap<String, SubModel> parts;
private final ImmutableMap<String, String> customData;
public ForgeVariant(ResourceLocation model, ModelRotation rotation, boolean uvLock, int weight, ImmutableMap<String, String> textures, ImmutableMap<String, SubModel> parts, ImmutableMap<String, String> customData)
{
super(model == null ? new ResourceLocation("builtin/missing") : model, rotation, uvLock, weight);
this.textures = textures;
this.parts = parts;
this.customData = customData;
}
protected IModel runModelHooks(IModel base, ImmutableMap<String, String> textureMap, ImmutableMap<String, String> customData)
{
if (!customData.isEmpty())
{
if (base instanceof IModelCustomData)
base = ((IModelCustomData)base).process(customData);
else
throw new RuntimeException("Attempted to add custom data to a model that doesn't need it: " + base);
}
if (!textureMap.isEmpty())
{
if (base instanceof IRetexturableModel)
base = ((IRetexturableModel)base).retexture(textureMap);
else
throw new RuntimeException("Attempted to retexture a non-retexturable model: " + base);
}
return base;
}
public void setTransforms(ItemCameraTransforms transforms) {
super.setTransforms(transforms);
}
/**
* Used to replace the base model with a retextured model containing submodels.
*/
@Override
public IModel process(IModel base, ModelLoader loader)
{
int size = parts.size();
boolean hasBase = base != loader.getMissingModel();
if (hasBase)
{
base = runModelHooks(base, textures, customData);
if (size <= 0)
return base;
}
// Apply rotation of base model to submodels.
// If baseRot is non-null, then that rotation will be applied instead of the base model's rotation.
// This is used to allow replacing base model with a submodel when there is no base model for a variant.
ModelRotation baseRot = getRotation();
ImmutableMap.Builder<String, Pair<IModel, IModelState>> models = ImmutableMap.builder();
for (Entry<String, SubModel> entry : parts.entrySet())
{
SubModel part = entry.getValue();
Matrix4f matrix = new Matrix4f(baseRot.getMatrix());
matrix.mul(part.getRotation().getMatrix());
IModelState partState = new TRSRTransformation(matrix);
if (part.isUVLock()) partState = new ModelLoader.UVLock(partState);
IModel model = null;
try
{
model = loader.getModel(part.getModelLocation());
}
catch (IOException e)
{
FMLLog.warning("Unable to load block sub-model: \'" + part.getModelLocation() /*+ "\' for variant: \'" + parent*/ + "\': " + e.toString());
model = loader.getMissingModel(); // Will make it look weird, but that is good.
}
models.put(entry.getKey(), Pair.of(runModelHooks(model, part.getTextures(), part.getCustomData()), partState));
}
return new MultiModel(hasBase ? base : null, baseRot, models.build());
}
@Override
public String toString()
{
StringBuilder buf = new StringBuilder();
buf.append("TexturedVariant:");
for (Entry<String, String> e: this.textures.entrySet())
buf.append(" ").append(e.getKey()).append(" = ").append(e.getValue());
return buf.toString();
}
}
}
{
"forge_marker": 1,
"defaults": {
"textures": {
"#texture": "forgedebugmodelloaderregistry:texture",
"#chest": "entity/chest/normal"
},
"model": "forgedebugmodelloaderregistry:chest.b3d"
},
"variants": {
"normal": {
"dummy": ""
},
"inventory": {
"dummy": ""
}
},
"display": {
"thirdperson": {
"rotation": [ 10, -45, 170 ],
"translation": [ 0, 1.5, -2.75 ],
"scale": [ 0.375, 0.375, 0.375 ]
}
}
}
{
"forge_marker": 1,
"defaults": {
"textures": {},
"model": "forgedebugmodelloaderregistry:tesseract.obj"
},
"variants": {
"normal": {
"dummy": ""
},
"inventory": {
"dummy": ""
}
},
"display": {
"thirdperson": {
"rotation": [ 0, 0, 0 ],
"translation": [ 0, 0, 0 ],
"scale": [ 0.375, 0.375, 0.375 ]
}
}
}
{
"forge_marker": 1,
"defaults": {
"textures": {},
"model": "forgedebugmodelloaderregistry:vertex_coloring.obj"
},
"variants": {
"normal": {
"dummy": ""
},
"inventory": {
"dummy": ""
}
},
"display": {
"thirdperson": {
"rotation": [ 0, 0, 0 ],
"translation": [ 0, 0.1, -0.25 ],
"scale": [ 0.375, 0.375, 0.375 ]
}
}
}
public class ForgeBlockStateV1 extends Marker
{
ForgeBlockStateV1.Variant defaults;
Multimap<String, ForgeBlockStateV1.Variant> variants = HashMultimap.create();
public static class Deserializer implements JsonDeserializer<ForgeBlockStateV1>
{
private ItemCameraTransforms transforms = ItemCameraTransforms.DEFAULT;
static ForgeBlockStateV1.Deserializer INSTANCE = new Deserializer();
@Override
public ForgeBlockStateV1 deserialize(JsonElement element, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
JsonObject json = element.getAsJsonObject();
ForgeBlockStateV1 ret = new ForgeBlockStateV1();
ret.forge_marker = JsonUtils.getJsonObjectIntegerFieldValue(json, "forge_marker");
if (json.has("defaults")) // Load defaults Variant.
{
ret.defaults = context.deserialize(json.get("defaults"), ForgeBlockStateV1.Variant.class);
if (ret.defaults.simpleSubmodels.size() > 0)
throw new RuntimeException("\"defaults\" variant cannot contain a simple \"submodel\" definition.");
}
if (json.has("display"))
{
JsonObject displayObj = json.getAsJsonObject("display");
JsonArray jsRot = null;
JsonArray jsTrans = null;
JsonArray jsScale = null;
Vector3f rotation = null;
Vector3f translation = null;
Vector3f scale = null;
ItemTransformVec3f tpVec = ItemTransformVec3f.DEFAULT;
ItemTransformVec3f fpVec = ItemTransformVec3f.DEFAULT;
ItemTransformVec3f hVec = ItemTransformVec3f.DEFAULT;
ItemTransformVec3f gVec = ItemTransformVec3f.DEFAULT;
if (displayObj.has("thirdperson"))
{
JsonObject thirdPerson = displayObj.getAsJsonObject("thirdperson");
jsRot = thirdPerson.getAsJsonArray("rotation");
jsTrans = thirdPerson.getAsJsonArray("translation");
jsScale = thirdPerson.getAsJsonArray("scale");
rotation = new Vector3f(jsRot.get(0).getAsFloat(), jsRot.get(1).getAsFloat(), jsRot.get(2).getAsFloat());
translation = new Vector3f(jsTrans.get(0).getAsFloat(), jsTrans.get(1).getAsFloat(), jsTrans.get(2).getAsFloat());
scale = new Vector3f(jsScale.get(0).getAsFloat(), jsScale.get(1).getAsFloat(), jsScale.get(2).getAsFloat());
tpVec = new ItemTransformVec3f(rotation, translation, scale);
}
if (displayObj.has("firstperson"))
{
JsonObject firstPerson = displayObj.getAsJsonObject("firstperson");
jsRot = firstPerson.getAsJsonArray("rotation");
jsTrans = firstPerson.getAsJsonArray("translation");
jsScale = firstPerson.getAsJsonArray("scale");
rotation = new Vector3f(jsRot.get(0).getAsFloat(), jsRot.get(1).getAsFloat(), jsRot.get(2).getAsFloat());
translation = new Vector3f(jsTrans.get(0).getAsFloat(), jsTrans.get(1).getAsFloat(), jsTrans.get(2).getAsFloat());
scale = new Vector3f(jsScale.get(0).getAsFloat(), jsScale.get(1).getAsFloat(), jsScale.get(2).getAsFloat());
fpVec = new ItemTransformVec3f(rotation, translation, scale);
}
if (displayObj.has("head"))
{
JsonObject head = displayObj.getAsJsonObject("head");
jsRot = head.getAsJsonArray("rotation");
jsTrans = head.getAsJsonArray("translation");
jsScale = head.getAsJsonArray("scale");
rotation = new Vector3f(jsRot.get(0).getAsFloat(), jsRot.get(1).getAsFloat(), jsRot.get(2).getAsFloat());
translation = new Vector3f(jsTrans.get(0).getAsFloat(), jsTrans.get(1).getAsFloat(), jsTrans.get(2).getAsFloat());
scale = new Vector3f(jsScale.get(0).getAsFloat(), jsScale.get(1).getAsFloat(), jsScale.get(2).getAsFloat());
hVec = new ItemTransformVec3f(rotation, translation, scale);
}
if (displayObj.has("gui"))
{
JsonObject gui = displayObj.getAsJsonObject("gui");
jsRot = gui.getAsJsonArray("rotation");
jsTrans = gui.getAsJsonArray("translation");
jsScale = gui.getAsJsonArray("scale");
rotation = new Vector3f(jsRot.get(0).getAsFloat(), jsRot.get(1).getAsFloat(), jsRot.get(2).getAsFloat());
translation = new Vector3f(jsTrans.get(0).getAsFloat(), jsTrans.get(1).getAsFloat(), jsTrans.get(2).getAsFloat());
scale = new Vector3f(jsScale.get(0).getAsFloat(), jsScale.get(1).getAsFloat(), jsScale.get(2).getAsFloat());
gVec = new ItemTransformVec3f(rotation, translation, scale);
}
this.transforms = new ItemCameraTransforms(tpVec, fpVec, hVec, gVec);
// this.transforms = (ItemCameraTransforms)context.deserialize(json.get("display").getAsJsonObject(), ItemCameraTransforms.class);
// ret.transforms = context.deserialize(json.get("display"), ForgeBlockStateV1.Display.class);
}
Map<String, Map<String, ForgeBlockStateV1.Variant>> condensed = Maps.newHashMap(); // map(property name -> map(property value -> variant))
Multimap<String, ForgeBlockStateV1.Variant> specified = HashMultimap.create(); // Multimap containing all the states specified with "property=value".
for (Entry<String, JsonElement> e : JsonUtils.getJsonObject(json, "variants").entrySet())
{
if (e.getValue().isJsonArray())
{
// array of fully-defined variants
for (JsonElement a : e.getValue().getAsJsonArray())
{
Variant.Deserializer.INSTANCE.simpleSubmodelKey = e.getKey();
specified.put(e.getKey(), (ForgeBlockStateV1.Variant)context.deserialize(a, ForgeBlockStateV1.Variant.class));
}
}
else
{
JsonObject obj = e.getValue().getAsJsonObject();
if(obj.entrySet().iterator().next().getValue().isJsonObject())
{
// first value is a json object, so we know it's not a fully-defined variant
Map<String, ForgeBlockStateV1.Variant> subs = Maps.newHashMap();
condensed.put(e.getKey(), subs);
for (Entry<String, JsonElement> se : e.getValue().getAsJsonObject().entrySet())
{
Variant.Deserializer.INSTANCE.simpleSubmodelKey = e.getKey() + "=" + se.getKey();
subs.put(se.getKey(), (ForgeBlockStateV1.Variant)context.deserialize(se.getValue(), ForgeBlockStateV1.Variant.class));
}
}
else
{
// fully-defined variant
Variant.Deserializer.INSTANCE.simpleSubmodelKey = e.getKey();
specified.put(e.getKey(), (ForgeBlockStateV1.Variant)context.deserialize(e.getValue(), ForgeBlockStateV1.Variant.class));
}
}
}
Multimap<String, ForgeBlockStateV1.Variant> permutations = getPermutations(condensed); // Get permutations of Forge-style states.
for (Entry<String, Collection<ForgeBlockStateV1.Variant>> e : specified.asMap().entrySet())
{ // Make fully-specified variants override Forge variant permutations, inheriting the permutations' values.
Collection<ForgeBlockStateV1.Variant> baseVars = permutations.get(e.getKey());
List<ForgeBlockStateV1.Variant> addVars = Lists.newArrayList();
for (ForgeBlockStateV1.Variant specVar : e.getValue())
{
if (!baseVars.isEmpty())
{
for (ForgeBlockStateV1.Variant baseVar : baseVars)
addVars.add(new Variant(specVar).sync(baseVar));
}
else
addVars.add(specVar);
}
baseVars.clear();
baseVars.addAll(addVars);
}
for (Entry<String, ForgeBlockStateV1.Variant> e : permutations.entries()) // Create the output map(state -> Variant).
{
ForgeBlockStateV1.Variant v = e.getValue();
v.setTransforms(this.transforms);
if (ret.defaults != null)
{
v.sync(ret.defaults); // Sync defaults into all permutation variants
for (Entry<String, Object> partKey : v.simpleSubmodels.entrySet())
{ // Sync variant values (including defaults) into simple submodel declarations.
if (partKey.getValue() == null)
continue;
if (!v.submodels.containsKey(partKey.getKey()))
throw new RuntimeException("This should never happen! Simple submodel is not contained in the submodel map!");
List<ForgeBlockStateV1.Variant> partList = v.submodels.get(partKey.getKey());
if (partList.size() > 1)
throw new RuntimeException("This should never happen! Simple submodel has multiple variants!");
ForgeBlockStateV1.Variant part = partList.get(0);
// Must keep old rotation for the part, because the base variant's rotation is applied to the parts already.
Optional<ModelRotation> rotation = part.rotation;
part.sync(v);
part.simpleSubmodels.clear();
part.rotation = rotation;
}
}
if (v.textures != null)
{
for (Entry<String, String> tex : v.textures.entrySet())
{
if (tex.getValue() != null && tex.getValue().charAt(0) == '#')
{
String value = v.textures.get(tex.getValue().substring(1));
if (value == null)
{
FMLLog.severe("Could not resolve texture name \"" + tex.getValue() + "\" for permutation \"" + e.getKey() + "\"");
for (Entry<String, String> t: v.textures.entrySet())
FMLLog.severe(t.getKey() + "=" + t.getValue());
throw new JsonParseException("Could not resolve texture name \"" + tex.getValue() + "\" for permutation \"" + e.getKey() + "\"");
}
v.textures.put(tex.getKey(), value);
}
}
for (List<ForgeBlockStateV1.Variant> part : v.submodels.values()) // Sync base variant's textures (including defaults) into all submodels.
{
for (ForgeBlockStateV1.Variant partVar : part)
{
for (Entry<String, String> texEntry : v.textures.entrySet())
{
if (!partVar.textures.containsKey(texEntry.getKey()))
partVar.textures.put(texEntry.getKey(), texEntry.getValue());
}
}
}
}
if (!v.submodels.isEmpty())
ret.variants.putAll(e.getKey(), getSubmodelPermutations(v, v.submodels)); // Do permutations of submodel variants.
else
ret.variants.put(e.getKey(), v);
}
return ret;
}
private Multimap<String, ForgeBlockStateV1.Variant> getPermutations(List<String> sorted, Map<String, Map<String, ForgeBlockStateV1.Variant>> base, int depth, String prefix, Multimap<String, ForgeBlockStateV1.Variant> ret, ForgeBlockStateV1.Variant parent)
{
if (depth == sorted.size())
{
if(parent != null)
ret.put(prefix, parent);
return ret;
}
String name = sorted.get(depth);
for (Entry<String, ForgeBlockStateV1.Variant> e : base.get(name).entrySet())
{
ForgeBlockStateV1.Variant newHead = parent == null ? new Variant(e.getValue()) : new Variant(parent).sync(e.getValue());
getPermutations(sorted, base, depth + 1, prefix + (depth == 0 ? "" : ",") + name + "=" + e.getKey(), ret, newHead);
}
return ret;
}
private Multimap<String, ForgeBlockStateV1.Variant> getPermutations(Map<String, Map<String, ForgeBlockStateV1.Variant>> base)
{
List<String> sorted = Lists.newArrayList(base.keySet());
Collections.sort(sorted); // Sort to get consistent results.
return getPermutations(sorted, base, 0, "", HashMultimap.<String, ForgeBlockStateV1.Variant>create(), null);
}
private List<ForgeBlockStateV1.Variant> getSubmodelPermutations(ForgeBlockStateV1.Variant baseVar, List<String> sorted, Map<String, List<ForgeBlockStateV1.Variant>> map, int depth, Map<String, ForgeBlockStateV1.Variant> parts, List<ForgeBlockStateV1.Variant> ret)
{
if (depth >= sorted.size())
{
ForgeBlockStateV1.Variant add = new Variant(baseVar); // Create a duplicate variant object so modifying it doesn't modify baseVar.
for (Entry<String, ForgeBlockStateV1.Variant> part : parts.entrySet()) // Put all the parts with single variants for this permutation.
add.submodels.put(part.getKey(), Collections.singletonList(part.getValue()));
ret.add(add);
return ret;
}
String name = sorted.get(depth);
List<ForgeBlockStateV1.Variant> vars = map.get(sorted.get(depth));
if (vars != null)
{
for (ForgeBlockStateV1.Variant v : vars)
{
if (v != null)
{ // We put this part variant in the permutation's map to add further in recursion, and then remove it afterward just in case.
parts.put(name, v);
getSubmodelPermutations(baseVar, sorted, map, depth + 1, parts, ret);
parts.remove(name);
}
}
}
else
{
getSubmodelPermutations(baseVar, sorted, map, depth + 1, parts, ret);
}
return ret;
}
private List<ForgeBlockStateV1.Variant> getSubmodelPermutations(ForgeBlockStateV1.Variant baseVar, Map<String, List<ForgeBlockStateV1.Variant>> variants)
{
List<String> sorted = Lists.newArrayList(variants.keySet());
Collections.sort(sorted); // Sort to get consistent results.
return getSubmodelPermutations(baseVar, sorted, variants, 0, new HashMap<String, ForgeBlockStateV1.Variant>(), new ArrayList<ForgeBlockStateV1.Variant>());
}
}
public static class Variant
{
public static final Object SET_VALUE = new Object();
private ResourceLocation model = null;
private boolean modelSet = false;
private Optional<ModelRotation> rotation = Optional.absent();
private Optional<Boolean> uvLock = Optional.absent();
private Optional<Integer> weight = Optional.absent();
private Map<String, String> textures = Maps.newHashMap();
private Map<String, List<ForgeBlockStateV1.Variant>> submodels = Maps.newHashMap();
private Map<String, Object> simpleSubmodels = Maps.newHashMap(); // Makeshift Set to allow us to "remove" (replace value with null) singleParts when needed.
private Map<String, String> customData = Maps.newHashMap();
private ItemCameraTransforms transforms = ItemCameraTransforms.DEFAULT;
private Variant(){}
/**
* Clone a variant.
* @param other Variant to clone.
*/
private Variant(ForgeBlockStateV1.Variant other)
{
this.model = other.model;
this.modelSet = other.modelSet;
this.rotation = other.rotation;
this.uvLock = other.uvLock;
this.weight = other.weight;
this.textures.putAll(other.textures);
this.submodels.putAll(other.submodels);
this.simpleSubmodels.putAll(other.simpleSubmodels);
this.customData.putAll(other.customData);
}
public void setTransforms(ItemCameraTransforms transforms)
{
this.transforms = transforms;
}
public ItemCameraTransforms getTransforms()
{
return this.transforms;
}
/**
* Sets values in this variant to the input's values only if they haven't been set already. Essentially inherits values from o.
*/
ForgeBlockStateV1.Variant sync(ForgeBlockStateV1.Variant parent)
{
if (!this.modelSet)
{
this.model = parent.model;
this.modelSet = parent.modelSet;
}
if (!this.rotation.isPresent()) this.rotation = parent.rotation;
if (!this.uvLock.isPresent()) this.uvLock = parent.uvLock;
if (!this.weight.isPresent()) this.weight = parent.weight;
for (Entry<String, String> e : parent.textures.entrySet())
{
if (!this.textures.containsKey(e.getKey()))
this.textures.put(e.getKey(), e.getValue());
}
mergeModelPartVariants(this.submodels, parent.submodels);
for (Entry<String, Object> e : parent.simpleSubmodels.entrySet())
{
if (!this.simpleSubmodels.containsKey(e.getKey()))
this.simpleSubmodels.put(e.getKey(), e.getValue());
}
for (Entry<String, String> e : parent.customData.entrySet())
{
if (!this.customData.containsKey(e.getKey()))
this.customData.put(e.getKey(), e.getValue());
}
return this;
}
/**
* Inherits model parts from a parent, creating deep clones of all Variants.
*/
Map<String, List<ForgeBlockStateV1.Variant>> mergeModelPartVariants(Map<String, List<ForgeBlockStateV1.Variant>> output, Map<String, List<ForgeBlockStateV1.Variant>> input)
{
for (Entry<String, List<ForgeBlockStateV1.Variant>> e : input.entrySet())
{
String key = e.getKey();
if (!output.containsKey(key))
{
List<ForgeBlockStateV1.Variant> variants = e.getValue();
if (variants != null)
{
List<ForgeBlockStateV1.Variant> newVariants = Lists.newArrayListWithCapacity(variants.size());
for (ForgeBlockStateV1.Variant v : variants)
newVariants.add(new Variant(v));
output.put(key, newVariants);
}
else
output.put(key, variants);
}
}
return output;
}
protected SubModel asGenericSubModel()
{
return new SubModel(rotation.or(ModelRotation.X0_Y0), uvLock.or(false), getTextures(), model, getCustomData());
}
/**
* Gets a list containing the single variant of each part.
* Will throw an error if this Variant has multiple variants for a submodel.
*/
public ImmutableMap<String, SubModel> getOnlyPartsVariant()
{
if (submodels.size() > 0)
{
ImmutableMap.Builder<String, SubModel> builder = ImmutableMap.builder();
for (Entry<String, List<ForgeBlockStateV1.Variant>> entry : submodels.entrySet())
{
List<ForgeBlockStateV1.Variant> part = entry.getValue();
if (part != null)
{
if (part.size() == 1)
builder.put(entry.getKey(), part.get(0).asGenericSubModel());
else
throw new RuntimeException("Something attempted to get the list of submodels "
+ "for a variant with model \"" + model + "\", when this variant "
+ "contains multiple variants for submodel " + entry.getKey());
}
}
return builder.build();
}
else {
return ImmutableMap.of();
}
}
public static class Deserializer implements JsonDeserializer<ForgeBlockStateV1.Variant>
{
static Variant.Deserializer INSTANCE = new Deserializer();
/** Used <i>once</i> (then set null) for the key to put a simple submodel declaration under in the submodel map. */
public String simpleSubmodelKey = null;
protected ResourceLocation getBlockLocation(String location)
{
ResourceLocation tmp = new ResourceLocation(location);
return new ResourceLocation(tmp.getResourceDomain(), "block/" + tmp.getResourcePath());
}
/** Throws an error if there are submodels in this submodel. */
private void throwIfNestedSubmodels(ForgeBlockStateV1.Variant submodel)
{
if (submodel.submodels.size() > 0)
throw new UnsupportedOperationException("Forge BlockStateLoader V1 does not support nested submodels.");
}
@Override
public ForgeBlockStateV1.Variant deserialize(JsonElement element, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
ForgeBlockStateV1.Variant ret = new Variant();
JsonObject json = element.getAsJsonObject();
if (json.has("model"))
{ // Load base model location.
if (json.get("model").isJsonNull())
ret.model = null; // Allow overriding base model to remove it from a state.
else
ret.model = getBlockLocation(JsonUtils.getJsonObjectStringFieldValue(json, "model"));
ret.modelSet = true;
}
if (json.has("textures"))
{ // Load textures map.
for (Entry<String, JsonElement> e : json.get("textures").getAsJsonObject().entrySet())
{
if (e.getValue().isJsonNull())
ret.textures.put(e.getKey(), ""); // We have to use "" because ImmutibleMaps don't allow nulls -.-
else
ret.textures.put(e.getKey(), e.getValue().getAsString());
}
}
if (json.has("x") || json.has("y"))
{ // Load rotation values.
int x = JsonUtils.getJsonObjectIntegerFieldValueOrDefault(json, "x", 0);
int y = JsonUtils.getJsonObjectIntegerFieldValueOrDefault(json, "y", 0);
ret.rotation = Optional.of(ModelRotation.getModelRotation(x, y));
if (ret.rotation == null)
throw new JsonParseException("Invalid BlockModelRotation x: " + x + " y: " + y);
}
if (json.has("uvlock"))
{ // Load uvlock.
ret.uvLock = Optional.of(JsonUtils.getJsonObjectBooleanFieldValue(json, "uvlock"));
}
if (json.has("weight"))
{ // Load weight.
ret.weight = Optional.of(JsonUtils.getJsonObjectIntegerFieldValue(json, "weight"));
}
if (json.has("submodel"))
{ // Load submodels.
JsonElement submodels = json.get("submodel");
if (submodels.isJsonPrimitive())
{ // Load a simple submodel declaration.
if (simpleSubmodelKey == null)
throw new RuntimeException("Attempted to use a simple submodel declaration outside a valid state variant declaration.");
String key = simpleSubmodelKey;
simpleSubmodelKey = null;
ret.model = getBlockLocation(submodels.getAsString());
ret.modelSet = true;
ForgeBlockStateV1.Variant dummyVar = new Variant(); // Create a dummy Variant to use as the owner of the simple submodel.
dummyVar.submodels.put(key, Collections.singletonList(ret));
dummyVar.simpleSubmodels = Collections.singletonMap(key, Variant.SET_VALUE);
return dummyVar;
}
else
{ // Load full submodel declarations.
// Clear the simple submodel key so that when deserializing submodels, the deserializer doesn't use the key.
simpleSubmodelKey = null;
for (Entry<String, JsonElement> submodel : submodels.getAsJsonObject().entrySet())
{
JsonElement varEl = submodel.getValue();
List<ForgeBlockStateV1.Variant> submodelVariants;
if (varEl.isJsonArray())
{ // Multiple variants of the submodel.
submodelVariants = Lists.newArrayList();
for (JsonElement e : varEl.getAsJsonArray())
submodelVariants.add((ForgeBlockStateV1.Variant)context.deserialize(e, ForgeBlockStateV1.Variant.class));
}
else if (varEl.isJsonNull())
{
submodelVariants = null;
}
else
{
submodelVariants = Collections.singletonList((ForgeBlockStateV1.Variant)context.deserialize(varEl, ForgeBlockStateV1.Variant.class));
}
if (submodelVariants != null) // Throw an error if there are submodels inside a submodel.
for (ForgeBlockStateV1.Variant part : submodelVariants)
throwIfNestedSubmodels(part);
ret.submodels.put(submodel.getKey(), submodelVariants);
// Put null to remove this submodel from the list of simple submodels when something inherits this submodel.
ret.simpleSubmodels.put(submodel.getKey(), null);
}
}
}
if (json.has("custom"))
{
for (Entry<String, JsonElement> e : json.get("custom").getAsJsonObject().entrySet())
{
if (e.getValue().isJsonNull())
ret.customData.put(e.getKey(), null);
else
ret.customData.put(e.getKey(), e.getValue().toString());
}
}
simpleSubmodelKey = null;
return ret;
}
}
public ResourceLocation getModel() { return model; }
public boolean isModelSet() { return modelSet; }
public Optional<ModelRotation> getRotation() { return rotation; }
public Optional<Boolean> getUvLock() { return uvLock; }
public Optional<Integer> getWeight() { return weight; }
public ImmutableMap<String, String> getTextures() { return ImmutableMap.copyOf(textures); }
public ImmutableMap<String, List<ForgeBlockStateV1.Variant>> getSubmodels() { return ImmutableMap.copyOf(submodels); }
public ImmutableMap<String, String> getCustomData() { return ImmutableMap.copyOf(customData); }
}
}
package net.minecraftforge.client.model;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.util.ResourceLocation;
/*
* Model that gets camera transformations from a forge blockstate json file.
* Should be used with an IBakedModel implementing IPerspectiveAwareModel
*/
public interface ICameraTransformsModel extends IModel
{
/*
* Sets the location of the forge blockstate json file.
* Used in handlePerspective to get the ModelBlockDefinition needed to calculate
* the Matrix4f.
*/
void setJsonLocation(ModelResourceLocation jsonLocation);
}
package net.minecraftforge.client.model;
import java.io.IOException;
import net.minecraft.block.Block;
import net.minecraft.client.resources.IResourceManagerReloadListener;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.item.Item;
import net.minecraft.util.ResourceLocation;
public interface ICustomModelLoader extends IResourceManagerReloadListener
{
/*
* Checks if given model should be loaded by this loader.
* Reading file contents is inadvisable, if possible decision should be made based on the location alone.
*/
public boolean accepts(ResourceLocation modelLocation);
/*
* loads (or reloads) specified model
*/
public IModel loadModel(ResourceLocation modelLocation) throws IOException;
/*
* Used to send a block's forge blockstate location to an ICameraTransformsModel during loading
* so that the model can find its ItemCameraTransforms.
* This must be called before the model loader loads the model.
* jsonLocation should be ("<modid>:<block/itemname>.json", "<variant>")
* model should be "<modid>:models/<block/item>/<modelname>.<ext>"
*/
public void passJsonToModel(ModelResourceLocation jsonLocation, ResourceLocation model);
}
public class ModelFluid implements IModelCustomData
{
public static final ModelFluid waterModel = new ModelFluid(FluidRegistry.WATER);
public static final ModelFluid lavaModel = new ModelFluid(FluidRegistry.LAVA);
private final Fluid fluid;
public ModelFluid(Fluid fluid)
{
this.fluid = fluid;
}
public Collection<ResourceLocation> getDependencies()
{
return Collections.emptySet();
}
public Collection<ResourceLocation> getTextures()
{
return ImmutableSet.of(fluid.getStill(), fluid.getFlowing());
}
public IFlexibleBakedModel bake(IModelState state, VertexFormat format, Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter)
{
return new BakedFluid(state.apply(this), format, fluid.getColor(), bakedTextureGetter.apply(fluid.getStill()), bakedTextureGetter.apply(fluid.getFlowing()), fluid.isGaseous());
}
public IModelState getDefaultState()
{
return ModelRotation.X0_Y0;
}
public static enum FluidLoader implements ICustomModelLoader
{
instance;
public void onResourceManagerReload(IResourceManager resourceManager) {}
public boolean accepts(ResourceLocation modelLocation)
{
return modelLocation.getResourceDomain().equals("forge") && (
modelLocation.getResourcePath().equals("fluid") ||
modelLocation.getResourcePath().equals("models/block/fluid") ||
modelLocation.getResourcePath().equals("models/item/fluid"));
}
public IModel loadModel(ResourceLocation modelLocation)
{
return waterModel;
}
@Override
public void passJsonToModel(ModelResourceLocation jsonLocation, ResourceLocation model)
{
}
}
public static class BakedFluid implements IFlexibleBakedModel, ISmartBlockModel
{
private static final int x[] = { 0, 0, 1, 1 };
private static final int z[] = { 0, 1, 1, 0 };
private static final float eps = 1e-3f;
private final TRSRTransformation transformation;
private final VertexFormat format;
private final int color;
private final TextureAtlasSprite still, flowing;
private final boolean gas;
private final Optional<IExtendedBlockState> state;
private final EnumMap<EnumFacing, List<BakedQuad>> faceQuads;
public BakedFluid(TRSRTransformation transformation, VertexFormat format, int color, TextureAtlasSprite still, TextureAtlasSprite flowing, boolean gas)
{
this(transformation, format, color, still, flowing, gas, Optional.<IExtendedBlockState>absent());
}
public BakedFluid(TRSRTransformation transformation, VertexFormat format, int color, TextureAtlasSprite still, TextureAtlasSprite flowing, boolean gas, Optional<IExtendedBlockState> stateOption)
{
this.transformation = transformation;
this.format = format;
this.color = color;
this.still = still;
this.flowing = flowing;
this.gas = gas;
this.state = stateOption;
ByteBuffer buf = BufferUtils.createByteBuffer(4 * format.getNextOffset());
int[] data;
faceQuads = Maps.newEnumMap(EnumFacing.class);
for(EnumFacing side : EnumFacing.values())
{
faceQuads.put(side, ImmutableList.<BakedQuad>of());
}
if(state.isPresent())
{
IExtendedBlockState state = this.state.get();
float[] y = new float[4];
for(int i = 0; i < 4; i++)
{
if(gas)
{
y[i] = 1 - state.getValue(BlockFluidBase.LEVEL_CORNERS[i]);
}
else
{
y[i] = state.getValue(BlockFluidBase.LEVEL_CORNERS[i]);
}
}
float flow = state.getValue(BlockFluidBase.FLOW_DIRECTION);
// top
TextureAtlasSprite topSprite = flowing;
float scale = 4;
if(flow < -999F)
{
flow = 0;
scale = 8;
topSprite = still;
}
float c = MathHelper.cos(flow) * scale;
float s = MathHelper.sin(flow) * scale;
EnumFacing side = gas ? EnumFacing.DOWN : EnumFacing.UP;
buf.clear();
for(int i = gas ? 3 : 0; i != (gas ? -1 : 4); i+= (gas ? -1 : 1))
{
putVertex(
buf, side,
x[i], y[i], z[i],
topSprite.getInterpolatedU(8 + c * (x[i] * 2 - 1) + s * (z[i] * 2 - 1)),
topSprite.getInterpolatedV(8 + c * (x[(i + 1) % 4] * 2 - 1) + s * (z[(i + 1) % 4] * 2 - 1)));
}
buf.flip();
data = new int[4 * format.getNextOffset() / 4];
buf.asIntBuffer().get(data);
faceQuads.put(side, ImmutableList.<BakedQuad>of(new IColoredBakedQuad.ColoredBakedQuad(data, -1, side)));
// bottom
side = side.getOpposite();
buf.clear();
for(int i = gas ? 3 : 0; i != (gas ? -1 : 4); i+= (gas ? -1 : 1))
{
putVertex(
buf, side,
z[i], gas ? 1 : 0, x[i],
still.getInterpolatedU(z[i] * 16),
still.getInterpolatedV(x[i] * 16));
}
buf.flip();
data = new int[4 * format.getNextOffset() / 4];
buf.asIntBuffer().get(data);
faceQuads.put(side, ImmutableList.<BakedQuad>of(new IColoredBakedQuad.ColoredBakedQuad(data, -1, side)));
// sides
for(int i = 0; i < 4; i++)
{
side = EnumFacing.getHorizontal((5 - i) % 4);
BakedQuad q[] = new BakedQuad[2];
for(int k = 0; k < 2; k++)
{
buf.clear();
for(int j = 0; j < 4; j++)
{
int l = (k * 3) + (1 - 2 * k) * j;
float yl = z[l] * y[(i + x[l]) % 4];
if(gas && z[l] == 0) yl = 1;
putVertex(
buf, side,
x[(i + x[l]) % 4], yl, z[(i + x[l]) % 4],
flowing.getInterpolatedU(x[l] * 8),
flowing.getInterpolatedV((gas ? yl : 1 - yl) * 8));
}
buf.flip();
data = new int[4 * format.getNextOffset() / 4];
buf.asIntBuffer().get(data);
q[k] = new IColoredBakedQuad.ColoredBakedQuad(data, -1, side);
}
faceQuads.put(side, ImmutableList.of(q[0], q[1]));
}
}
else
{
// 1 quad for inventory
buf.clear();
for(int i = 0; i < 4; i++)
{
putVertex(
buf, EnumFacing.UP,
z[i], x[i], 0,
still.getInterpolatedU(x[i] * 16),
still.getInterpolatedV(z[i] * 16));
}
buf.flip();
data = new int[4 * format.getNextOffset() / 4];
buf.asIntBuffer().get(data);
faceQuads.put(EnumFacing.SOUTH, ImmutableList.<BakedQuad>of(new IColoredBakedQuad.ColoredBakedQuad(data, -1, EnumFacing.UP)));
}
}
private void put(ByteBuffer buf, VertexFormatElement e, Float... fs)
{
Attributes.put(buf, e, true, 0f, fs);
}
private float diffuse(EnumFacing side)
{
switch(side)
{
case DOWN:
return .5f;
case UP:
return 1f;
case NORTH:
case SOUTH:
return .8f;
default:
return .6f;
}
}
private void putVertex(ByteBuffer buf, EnumFacing side, float x, float y, float z, float u, float v)
{
for(VertexFormatElement e : (List<VertexFormatElement>)format.getElements())
{
// TODO transformation
switch(e.getUsage())
{
case POSITION:
put(buf, e, x - side.getDirectionVec().getX() * eps, y, z - side.getDirectionVec().getZ() * eps, 1f);
break;
case COLOR:
// temporarily add diffuse lighting
float d = diffuse(side);
put(buf, e,
d * ((color >> 16) & 0xFF) / 255f,
d * ((color >> 8) & 0xFF) / 255f,
d * (color & 0xFF) / 255f,
((color >> 24) & 0xFF) / 255f);
break;
case UV:
put(buf, e, u, v, 0f, 1f);
break;
case NORMAL:
put(buf, e, (float)side.getFrontOffsetX(), (float)side.getFrontOffsetX(), (float)side.getFrontOffsetX(), 0f);
break;
default:
put(buf, e);
break;
}
}
}
public boolean isAmbientOcclusion()
{
return false; // FIXME
}
public boolean isGui3d()
{
return false;
}
public boolean isBuiltInRenderer()
{
return false;
}
public TextureAtlasSprite getTexture()
{
return still;
}
public ItemCameraTransforms getItemCameraTransforms()
{
return ItemCameraTransforms.DEFAULT;
}
public List<BakedQuad> getFaceQuads(EnumFacing side)
{
return faceQuads.get(side);
}
public List<BakedQuad> getGeneralQuads()
{
return ImmutableList.of();
}
public VertexFormat getFormat()
{
return format;
}
public IBakedModel handleBlockState(IBlockState state)
{
return new BakedFluid(transformation, format, color, still, flowing, gas, Optional.of((IExtendedBlockState)state));
}
}
@Override
public IModel process(ImmutableMap<String, String> customData)
{
if(!customData.containsKey("fluid")) return this;
String fluidStr = customData.get("fluid");
JsonElement e = new JsonParser().parse(fluidStr);
String fluid = e.getAsString();
if(!FluidRegistry.isFluidRegistered(fluid))
{
FMLLog.severe("fluid '%s' not found", fluid);
return waterModel;
}
return new ModelFluid(FluidRegistry.getFluid(fluid));
}
}
public class ModelLoader extends ModelBakery
{
private final Map<ModelResourceLocation, IModel> stateModels = new HashMap<ModelResourceLocation, IModel>();
private final Set<ResourceLocation> textures = new HashSet<ResourceLocation>();
private final Set<ResourceLocation> loadingModels = new HashSet<ResourceLocation>();
private final Set<ModelResourceLocation> missingVariants = Sets.newHashSet();
private IModel missingModel = null;
private boolean isLoading = false;
public boolean isLoading()
{
return isLoading;
}
public ModelLoader(IResourceManager manager, TextureMap map, BlockModelShapes shapes)
{
super(manager, map, shapes);
VanillaLoader.instance.setLoader(this);
ModelLoaderRegistry.clearModelCache();
}
@Override
public IRegistry setupModelRegistry()
{
isLoading = true;
loadBlocks();
loadItems();
try
{
missingModel = getModel(new ResourceLocation(MODEL_MISSING.getResourceDomain(), MODEL_MISSING.getResourcePath()));
}
catch (IOException e)
{
// If this ever happens things are bad. Should never NOT be able to load the missing model.
Throwables.propagate(e);
}
stateModels.put(MODEL_MISSING, missingModel);
textures.remove(TextureMap.LOCATION_MISSING_TEXTURE);
textures.addAll(LOCATIONS_BUILTIN_TEXTURES);
textureMap.loadSprites(resourceManager, new IIconCreator()
{
public void registerSprites(TextureMap map)
{
for(ResourceLocation t : textures)
{
sprites.put(t, map.registerSprite(t));
}
}
});
sprites.put(new ResourceLocation("missingno"), textureMap.getMissingSprite());
Function<ResourceLocation, TextureAtlasSprite> textureGetter = new Function<ResourceLocation, TextureAtlasSprite>()
{
public TextureAtlasSprite apply(ResourceLocation location)
{
return Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(location.toString());
}
};
IFlexibleBakedModel missingBaked = missingModel.bake(missingModel.getDefaultState(), Attributes.DEFAULT_BAKED_FORMAT, textureGetter);
for (Entry<ModelResourceLocation, IModel> e : stateModels.entrySet())
{
if(e.getValue() == getMissingModel())
{
bakedRegistry.putObject(e.getKey(), missingBaked);
}
else
{
bakedRegistry.putObject(e.getKey(), e.getValue().bake(e.getValue().getDefaultState(), Attributes.DEFAULT_BAKED_FORMAT, textureGetter));
}
}
return bakedRegistry;
}
private void loadBlocks()
{
Map<IBlockState, ModelResourceLocation> stateMap = blockModelShapes.getBlockStateMapper().putAllStateModelLocations();
Collection<ModelResourceLocation> variants = Lists.newArrayList(stateMap.values());
variants.add(new ModelResourceLocation("minecraft:item_frame", "normal")); //Vanilla special cases item_frames so must we
variants.add(new ModelResourceLocation("minecraft:item_frame", "map"));
loadVariants(variants);
}
@Override
protected void registerVariant(ModelBlockDefinition definition, ModelResourceLocation location)
{
Variants variants = null;
try
{
variants = definition.getVariants(location.getVariant());
}
catch(MissingVariantException e)
{
missingVariants.add(location);
}
if (variants != null && !variants.getVariants().isEmpty())
{
try
{
stateModels.put(location, new WeightedRandomModel(location, variants));
}
catch(Throwable e)
{
throw new RuntimeException(e);
}
}
}
private void loadItems()
{
registerVariantNames();
for(Item item : GameData.getItemRegistry().typeSafeIterable())
{
for(String s : (List<String>)getVariantNames(item))
{
ResourceLocation file = getItemLocation(s);
ModelResourceLocation memory = new ModelResourceLocation(s, "inventory");
IModel model = null;
try
{
model = getModel(file);
}
catch (IOException e)
{
// Handled by our finally block.
}
finally
{
if (model == null || model == getMissingModel())
{
FMLLog.fine("Item json isn't found for '" + memory + "', trying to load the variant from the blockstate json");
registerVariant(getModelBlockDefinition(memory), memory);
}
else stateModels.put(memory, model);
}
}
}
}
public IModel getModel(ResourceLocation location) throws IOException
{
if(!ModelLoaderRegistry.loaded(location)) loadAnyModel(location);
return ModelLoaderRegistry.getModel(location);
}
@Override
protected ResourceLocation getModelLocation(ResourceLocation model)
{
return new ResourceLocation(model.getResourceDomain(), model.getResourcePath() + ".json");
}
private void loadAnyModel(ResourceLocation location) throws IOException
{
if(loadingModels.contains(location))
{
throw new IllegalStateException("circular model dependencies involving model " + location);
}
loadingModels.add(location);
IModel model = ModelLoaderRegistry.getModel(location);
for(ResourceLocation dep : model.getDependencies())
{
getModel(dep);
}
textures.addAll(model.getTextures());
loadingModels.remove(location);
}
@Override
public ModelBlockDefinition getModelBlockDefinition(ResourceLocation location)
{
return super.getModelBlockDefinition(location);
}
private class VanillaModelWrapper implements IRetexturableModel
{
private final ResourceLocation location;
private final ModelBlock model;
public VanillaModelWrapper(ResourceLocation location, ModelBlock model)
{
this.location = location;
this.model = model;
}
public Collection<ResourceLocation> getDependencies()
{
if(model.getParentLocation() == null || model.getParentLocation().getResourcePath().startsWith("builtin/")) return Collections.emptyList();
return Collections.singletonList(model.getParentLocation());
}
public Collection<ResourceLocation> getTextures()
{
// setting parent here to make textures resolve properly
if(model.getParentLocation() != null)
{
try
{
IModel parent = getModel(model.getParentLocation());
if(parent instanceof VanillaModelWrapper)
{
model.parent = ((VanillaModelWrapper) parent).model;
}
else
{
throw new IllegalStateException("vanilla model '" + model + "' can't have non-vanilla parent");
}
}
catch (IOException e)
{
FMLLog.warning("Could not load vanilla model parent '" + model.getParentLocation() + "' for '" + model + "': " + e.toString());
IModel missing = ModelLoader.this.getMissingModel();
if (missing instanceof VanillaModelWrapper)
{
model.parent = ((VanillaModelWrapper)missing).model;
}
else
{
throw new IllegalStateException("vanilla model '" + model + "' has missing parent, and missing model is not a vanilla model");
}
}
}
ImmutableSet.Builder<ResourceLocation> builder = ImmutableSet.builder();
if(hasItemModel(model))
{
for(String s : (List<String>)ItemModelGenerator.LAYERS)
{
String r = model.resolveTextureName(s);
ResourceLocation loc = new ResourceLocation(r);
if(!r.equals(s))
{
builder.add(loc);
}
// mojang hardcode
if(model.getRootModel() == MODEL_COMPASS && !loc.equals(TextureMap.LOCATION_MISSING_TEXTURE))
{
TextureAtlasSprite.setLocationNameCompass(loc.toString());
}
else if(model.getRootModel() == MODEL_CLOCK && !loc.equals(TextureMap.LOCATION_MISSING_TEXTURE))
{
TextureAtlasSprite.setLocationNameClock(loc.toString());
}
}
}
for(String s : (Iterable<String>)model.textures.values())
{
if(!s.startsWith("#"))
{
builder.add(new ResourceLocation(s));
}
}
return builder.build();
}
public IFlexibleBakedModel bake(IModelState state, VertexFormat format, Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter)
{
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);
}
ModelBlock model = this.model;
if(hasItemModel(model)) model = makeItemModel(model);
if(model == null) return getMissingModel().bake(state, format, bakedTextureGetter);
if(isCustomRenderer(model)) return new IFlexibleBakedModel.Wrapper(new BuiltInModel(new ItemCameraTransforms(model.getThirdPersonTransform(), model.getFirstPersonTransform(), model.getHeadTransform(), model.getInGuiTransform())), Attributes.DEFAULT_BAKED_FORMAT);
return new IFlexibleBakedModel.Wrapper(bakeModel(model, state.apply(this), state instanceof UVLock), Attributes.DEFAULT_BAKED_FORMAT);
}
public IModelState getDefaultState()
{
return ModelRotation.X0_Y0;
}
@Override
public IModel retexture(ImmutableMap<String, String> textures)
{
if (textures.isEmpty())
return this;
List<BlockPart> elements = Lists.newArrayList(); //We have to duplicate this so we can edit it below.
for (BlockPart part : (List<BlockPart>)this.model.getElements())
{
elements.add(new BlockPart(part.positionFrom, part.positionTo, Maps.newHashMap(part.mapFaces), part.partRotation, part.shade));
}
ModelBlock neweModel = new ModelBlock(this.model.getParentLocation(), elements,
Maps.newHashMap(this.model.textures), this.model.isAmbientOcclusion(), this.model.isGui3d(), //New Textures man VERY IMPORTANT
new ItemCameraTransforms(this.model.getThirdPersonTransform(), this.model.getFirstPersonTransform(), this.model.getHeadTransform(), this.model.getInGuiTransform()));
neweModel.name = this.model.name;
neweModel.parent = this.model.parent;
Set<String> removed = Sets.newHashSet();
for (Entry<String, String> e : textures.entrySet())
{
if ("".equals(e.getValue()))
{
removed.add(e.getKey());
neweModel.textures.remove(e.getKey());
}
else
neweModel.textures.put(e.getKey(), e.getValue());
}
// Map the model's texture references as if it was the parent of a model with the retexture map as its textures.
Map<String, String> remapped = Maps.newHashMap();
for (Entry<String, String> e : (Set<Entry<String, String>>)neweModel.textures.entrySet())
{
if (e.getValue().startsWith("#"))
{
String key = e.getValue().substring(1);
if (neweModel.textures.containsKey(key))
remapped.put(e.getKey(), (String)neweModel.textures.get(key));
}
}
neweModel.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 : (List<BlockPart>)neweModel.getElements())
{
Iterator<Entry<EnumFacing, BlockPartFace>> itr = part.mapFaces.entrySet().iterator();
while (itr.hasNext())
{
Entry<EnumFacing, BlockPartFace> entry = itr.next();
if (removed.contains(entry.getValue().texture))
itr.remove();
}
}
return new VanillaModelWrapper(location, neweModel);
}
}
public static class UVLock implements IModelState
{
private final IModelState state;
public UVLock(IModelState state)
{
this.state = state;
}
public TRSRTransformation apply(IModelPart part)
{
return state.apply(part);
}
}
// Weighted models can contain multiple copies of 1 model with different rotations - this is to make it work with IModelState (different copies will be different objects).
private static class WeightedPartWrapper implements IModel
{
private final IModel model;
public WeightedPartWrapper(IModel model)
{
this.model = model;
}
public Collection<ResourceLocation> getDependencies()
{
return model.getDependencies();
}
public Collection<ResourceLocation> getTextures()
{
return model.getTextures();
}
public IFlexibleBakedModel bake(IModelState state, VertexFormat format, Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter)
{
return model.bake(state, format, bakedTextureGetter);
}
public IModelState getDefaultState()
{
return model.getDefaultState();
}
}
private class WeightedRandomModel implements IModel
{
private final List<Variant> variants;
private final List<ResourceLocation> locations = new ArrayList<ResourceLocation>();
private final List<IModel> models = new ArrayList<IModel>();
private final IModelState defaultState;
@Deprecated public WeightedRandomModel(Variants variants){ this(null, variants); } // Remove 1.9
public WeightedRandomModel(ModelResourceLocation parent, Variants variants)
{
this.variants = variants.getVariants();
ImmutableMap.Builder<IModelPart, TRSRTransformation> builder = ImmutableMap.builder();
for (Variant v : (List<Variant>)variants.getVariants())
{
ResourceLocation loc = v.getModelLocation();
locations.add(loc);
IModel model = null;
try
{
model = getModel(loc);
}
catch (Exception e)
{
/*
* Vanilla eats this, which makes it only show variants that have models.
* But that doesn't help debugging, so we maintain the missing model
* so that resource pack makers have a hint that their states are broken.
*/
FMLLog.warning("Unable to load block model: \'" + loc + "\' for variant: \'" + parent + "\': " + e.toString());
model = getMissingModel();
}
if (v instanceof ISmartVariant)
{
model = ((ISmartVariant)v).process(model, ModelLoader.this);
textures.addAll(model.getTextures()); // Kick this, just in case.
}
model = new WeightedPartWrapper(model);
models.add(model);
builder.put(model, new TRSRTransformation(v.getRotation()));
}
if (models.size() == 0) //If all variants are missing, add one with the missing model and default rotation.
{
IModel missing = getMissingModel();
models.add(missing);
builder.put(missing, new TRSRTransformation(ModelRotation.X0_Y0));
}
defaultState = new MapModelState(builder.build());
}
public Collection<ResourceLocation> getDependencies()
{
return ImmutableList.copyOf(locations);
}
public Collection<ResourceLocation> getTextures()
{
return Collections.emptyList();
}
private IModelState addUV(boolean uv, IModelState state)
{
if(uv) return new UVLock(state);
return state;
}
public IFlexibleBakedModel bake(IModelState state, VertexFormat format, Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter)
{
if(!Attributes.moreSpecific(format, Attributes.DEFAULT_BAKED_FORMAT))
{
throw new IllegalArgumentException("can't bake vanilla weighted models to the format that doesn't fit into the default one: " + format);
}
if(variants.size() == 1)
{
Variant v = variants.get(0);
IModel model = models.get(0);
return model.bake(addUV(v.isUvLocked(), state.apply(model)), format, bakedTextureGetter);
}
WeightedBakedModel.Builder builder = new WeightedBakedModel.Builder();
for(int i = 0; i < variants.size(); i++)
{
IModel model = models.get(i);
Variant v = variants.get(i);
builder.add(model.bake(addUV(v.isUvLocked(), state.apply(model)), format, bakedTextureGetter), variants.get(i).getWeight());
}
return new FlexibleWeightedBakedModel(builder.build(), Attributes.DEFAULT_BAKED_FORMAT);
}
public IModelState getDefaultState()
{
return defaultState;
}
}
private static class FlexibleWeightedBakedModel extends WeightedBakedModel implements IFlexibleBakedModel
{
private final WeightedBakedModel parent;
private final VertexFormat format;
public FlexibleWeightedBakedModel(WeightedBakedModel parent, VertexFormat format)
{
super(parent.models);
this.parent = parent;
this.format = format;
}
public VertexFormat getFormat()
{
return format;
}
}
private boolean isBuiltinModel(ModelBlock model)
{
return model == MODEL_GENERATED || model == MODEL_COMPASS || model == MODEL_CLOCK || model == MODEL_ENTITY;
}
public IModel getMissingModel()
{
if (missingModel == null)
{
try
{
missingModel = getModel(new ResourceLocation(MODEL_MISSING.getResourceDomain(), MODEL_MISSING.getResourcePath()));
}
catch (IOException e)
{
// If this ever happens things are bad. Should never NOT be able to load the missing model.
Throwables.propagate(e);
}
}
return missingModel;
}
static enum VanillaLoader implements ICustomModelLoader
{
instance;
private ModelLoader loader;
void setLoader(ModelLoader loader)
{
this.loader = loader;
}
ModelLoader getLoader()
{
return loader;
}
public void onResourceManagerReload(IResourceManager resourceManager)
{
// do nothing, cause loader will store the reference to the resourceManager
}
public boolean accepts(ResourceLocation modelLocation)
{
return true;
}
public IModel loadModel(ResourceLocation modelLocation) throws IOException
{
return loader.new VanillaModelWrapper(modelLocation, loader.loadModel(modelLocation));
}
@Override
public void passJsonToModel(ModelResourceLocation jsonLocation, ResourceLocation model)
{
// do nothing, vanilla jsons don't need to be passed to their model
}
}
public static class White extends TextureAtlasSprite
{
public static ResourceLocation loc = new ResourceLocation("white");
public static White instance = new White();
protected White()
{
super(loc.toString());
}
@Override
public boolean hasCustomLoader(IResourceManager manager, ResourceLocation location)
{
return true;
}
@Override
public boolean load(IResourceManager manager, ResourceLocation location)
{
BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = image.createGraphics();
graphics.setBackground(Color.WHITE);
graphics.clearRect(0, 0, 16, 16);
BufferedImage[] images = new BufferedImage[Minecraft.getMinecraft().gameSettings.mipmapLevels + 1];
images[0] = image;
loadSprite(images, null);
return false;
}
public void register(TextureMap map)
{
map.setTextureEntry(White.loc.toString(), White.instance);
}
}
public void onPostBakeEvent(IRegistry modelRegistry)
{
Object missingModel = modelRegistry.getObject(MODEL_MISSING);
for(ModelResourceLocation missing : missingVariants)
{
Object model = modelRegistry.getObject(missing);
if(model == null || model == missingModel)
{
FMLLog.severe("Model definition for location %s not found", missing);
}
}
isLoading = false;
}
private static final Map<RegistryDelegate<Block>, IStateMapper> customStateMappers = Maps.newHashMap();
public static void setCustomStateMapper(Block block, IStateMapper mapper)
{
customStateMappers.put(block.delegate, mapper);
}
public static void onRegisterAllBlocks(BlockModelShapes shapes)
{
for (Entry<RegistryDelegate<Block>, IStateMapper> e : customStateMappers.entrySet())
{
shapes.registerBlockWithStateMapper(e.getKey().get(), e.getValue());
}
}
private static final Map<RegistryDelegate<Item>, ItemMeshDefinition> customMeshDefinitions = com.google.common.collect.Maps.newHashMap();
private static final Map<Pair<RegistryDelegate<Item>, Integer>, ModelResourceLocation> customModels = com.google.common.collect.Maps.newHashMap();
public static void setCustomModelResourceLocation(Item item, int metadata, ModelResourceLocation model)
{
customModels.put(Pair.of(item.delegate, metadata), model);
}
public static void setCustomMeshDefinition(Item item, ItemMeshDefinition meshDefinition)
{
customMeshDefinitions.put(item.delegate, meshDefinition);
}
public static boolean hasResourceLocationForItem(Item item, int metadata)
{
return customModels.containsKey(Pair.of(item.delegate, metadata));
}
public static boolean hasMeshDefinitionForItem(Item item)
{
return customMeshDefinitions.containsKey(item.delegate);
}
public static ModelResourceLocation getResourceLocationFromItem(Item item, int metadata)
{
return customModels.get(Pair.of(item.delegate.get(), metadata));
}
public static ItemMeshDefinition getMeshDefinitionFromItem(Item item)
{
return customMeshDefinitions.get(item.delegate.get());
}
public static void onRegisterItems(ItemModelMesher mesher)
{
for (Map.Entry<RegistryDelegate<Item>, ItemMeshDefinition> e : customMeshDefinitions.entrySet())
{
mesher.register(e.getKey().get(), e.getValue());
}
for (Entry<Pair<RegistryDelegate<Item>, Integer>, ModelResourceLocation> e : customModels.entrySet())
{
mesher.register(e.getKey().getLeft().get(), e.getKey().getRight(), e.getValue());
}
}
}
public class ModelLoaderRegistry
{
private static ModelBakery modelBakery;
private static final Set<ICustomModelLoader> loaders = new HashSet<ICustomModelLoader>();
private static final Map<ResourceLocation, IModel> cache = new HashMap<ResourceLocation, IModel>();
// Forge built-in loaders
static
{
registerLoader(B3DLoader.instance);
registerLoader(OBJLoader.instance);
registerLoader(ModelFluid.FluidLoader.instance);
}
public static void setModelBakery(ModelBakery bakery)
{
modelBakery = bakery;
}
/*
* Makes system aware of your loader.
*/
public static void registerLoader(ICustomModelLoader loader)
{
loaders.add(loader);
((IReloadableResourceManager)Minecraft.getMinecraft().getResourceManager()).registerReloadListener(new IResourceManagerReloadListener()
{
public void onResourceManagerReload(IResourceManager manager)
{
for(ICustomModelLoader loader : loaders) loader.onResourceManagerReload(manager);
}
});
}
public static boolean loaded(ResourceLocation location)
{
return cache.containsKey(location);
}
public static ResourceLocation getActualLocation(ResourceLocation location)
{
if(location.getResourcePath().startsWith("builtin/")) return location;
return new ResourceLocation(location.getResourceDomain(), "models/" + location.getResourcePath());
}
public static IModel getModel(ResourceLocation location) throws IOException
{
ResourceLocation actual = getActualLocation(location);
if(cache.containsKey(location)) return cache.get(location);
ICustomModelLoader accepted = null;
for(ICustomModelLoader loader : loaders)
{
try
{
if(loader.accepts(actual))
{
if(accepted != null)
{
FMLLog.severe("2 loaders (%s and %s) want to load the same model %s", accepted, loader, location);
throw new IllegalStateException("2 loaders want to load the same model");
}
accepted = loader;
}
}
catch(Exception e)
{
FMLLog.log(Level.ERROR, e, "Exception checking if model %s can be loaded with loader %s, skipping", location, loader);
}
}
// no custom loaders found, try vanilla one
if(accepted == null)
{
if(VanillaLoader.instance.accepts(actual)) accepted = VanillaLoader.instance;
}
IModel model;
if(accepted == null)
{
FMLLog.severe("no suitable loader found for the model %s, skipping", location);
model = getMissingModel();
}
else
{
try
{
model = accepted.loadModel(actual);
}
catch (IOException e)
{
throw e;
}
catch(Exception e)
{
FMLLog.log(Level.ERROR, e, "Exception loading model %s with loader %s, skipping", location, accepted);
model = getMissingModel();
}
}
cache.put(location, model);
return model;
}
public static IModel getMissingModel()
{
return ModelLoader.VanillaLoader.instance.getLoader().getMissingModel();
}
public static void clearModelCache()
{
cache.clear();
}
public static ModelLoader getModelLoader() {
return ModelLoader.VanillaLoader.instance.getLoader();
}
}
@Mod(modid = ModelLoaderRegistryDebug.MODID, version = ModelLoaderRegistryDebug.VERSION)
public class ModelLoaderRegistryDebug
{
public static final String MODID = "ForgeDebugModelLoaderRegistry";
public static final String VERSION = "1.0";
@EventHandler
public void preInit(FMLPreInitializationEvent event)
{
GameRegistry.registerBlock(CustomModelBlock.instance, CustomModelBlock.name);
GameRegistry.registerBlock(CustomModelBlock2.instance, CustomModelBlock2.name);
GameRegistry.registerBlock(CustomModelBlock3.instance, CustomModelBlock3.name);
if (event.getSide() == Side.CLIENT)
clientPreInit();
}
private void clientPreInit()
{
B3DLoader.instance.addDomain(MODID.toLowerCase());
Item item = Item.getItemFromBlock(CustomModelBlock.instance);
ModelResourceLocation jsonLocation = new ModelResourceLocation(MODID.toLowerCase() + ":" + CustomModelBlock.name, "inventory");
ModelLoader.setCustomModelResourceLocation(item, 0, jsonLocation);
B3DLoader.instance.passJsonToModel(jsonLocation, new ResourceLocation(MODID.toLowerCase() + ":" + "models/block/chest.b3d"));
OBJLoader.instance.addDomain(MODID.toLowerCase());
Item item2 = Item.getItemFromBlock(CustomModelBlock2.instance);
ModelResourceLocation jsonLocation2 = new ModelResourceLocation(MODID.toLowerCase() + ":" + CustomModelBlock2.name, "inventory");
ModelLoader.setCustomModelResourceLocation(item2, 0, jsonLocation2);
OBJLoader.instance.passJsonToModel(jsonLocation2, new ResourceLocation(MODID.toLowerCase() + ":" + "models/block/tesseract.obj"));
OBJLoader.instance.addDomain(MODID.toLowerCase());
Item item3 = Item.getItemFromBlock(CustomModelBlock3.instance);
ModelResourceLocation jsonLocation3 = new ModelResourceLocation(MODID.toLowerCase() + ":" + CustomModelBlock3.name, "inventory");
ModelLoader.setCustomModelResourceLocation(item3, 0, jsonLocation3);
OBJLoader.instance.passJsonToModel(jsonLocation3, new ResourceLocation(MODID.toLowerCase() + ":" + "models/block/vertex_coloring.obj"));
}
public static class CustomModelBlock extends Block
{
public static final CustomModelBlock instance = new CustomModelBlock();
public static final String name = "CustomModelBlock";
private int counter = 1;
private ExtendedBlockState state = new ExtendedBlockState(this, new IProperty[0], new IUnlistedProperty[]{ B3DLoader.B3DFrameProperty.instance });
private CustomModelBlock()
{
super(Material.iron);
setCreativeTab(CreativeTabs.tabBlock);
setUnlocalizedName(MODID + ":" + name);
}
@Override
public boolean isOpaqueCube() { return false; }
@Override
public boolean isFullCube() { return false; }
@Override
public boolean isVisuallyOpaque() { return false; }
@Override
public IBlockState getExtendedState(IBlockState state, IBlockAccess world, BlockPos pos)
{
B3DLoader.B3DState newState = new B3DLoader.B3DState(null, counter);
return ((IExtendedBlockState)this.state.getBaseState()).withProperty(B3DLoader.B3DFrameProperty.instance, newState);
}
@Override
public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumFacing side, float hitX, float hitY, float hitZ)
{
if(world.isRemote)
{
System.out.println("click " + counter);
if(player.isSneaking()) counter--;
else counter++;
//if(counter >= model.getNode().getKeys().size()) counter = 0;
world.markBlockRangeForRenderUpdate(pos, pos);
}
return false;
}
}
public static class CustomModelBlock2 extends Block
{
public static final CustomModelBlock2 instance = new CustomModelBlock2();
public static final String name = "CustomModelBlock2";
private int counter = 0;
private ExtendedBlockState state = new ExtendedBlockState(this, new IProperty[0], new IUnlistedProperty[]{OBJModel.OBJModelProperty.instance});
private CustomModelBlock2()
{
super(Material.iron);
setCreativeTab(CreativeTabs.tabBlock);
setUnlocalizedName(MODID + ":" + name);
}
@Override
public boolean isOpaqueCube() { return false; }
@Override
public boolean isFullCube() { return false; }
@Override
public boolean isVisuallyOpaque() { return false; }
@Override
public IBlockState getExtendedState(IBlockState state, IBlockAccess world, BlockPos pos)
{
String[] all = new String[] {"all"};
String[] none = new String[] {"none"};
String[] all_except = new String[] {"all except", "one", "two", "three", "four"};
String[] first = new String[] {"one", "two", "three", "four", "five", "six", "seven", "eight"};
String[] last = new String[] {"thirtytwo", "thirtyone", "thirty", "twentynine", "twentyeight", "twentyseven", "twentysix", "twentyfive"};
List<String[]> elements = new ArrayList<String[]>();
elements.add(all);
elements.add(all_except);
elements.add(none);
elements.add(first);
elements.add(last);
try
{
IModel model = ModelLoaderRegistry.getModel(new ResourceLocation(MODID.toLowerCase(), "block/tesseract.obj"));
OBJModel.OBJState newState = ((OBJModel) model).new OBJState(Arrays.asList(elements.get(counter)), (OBJModel) model);
return ((IExtendedBlockState) this.state.getBaseState()).withProperty(OBJModel.OBJModelProperty.instance, newState);
}
catch (IOException e)
{
return state;
}
}
@Override
public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumFacing side, float hitX, float hitY, float hitZ)
{
if (counter < 4)
{
counter++;
}
else
{
counter = 0;
}
world.markBlockRangeForRenderUpdate(pos, pos);
return false;
}
}
public static class CustomModelBlock3 extends Block
{
public static final CustomModelBlock3 instance = new CustomModelBlock3();
public static final String name = "CustomModelBlock3";
private CustomModelBlock3()
{
super(Material.iron);
setCreativeTab(CreativeTabs.tabBlock);
setUnlocalizedName(name);
}
@Override
public boolean isOpaqueCube() { return false; }
@Override
public boolean isFullCube() { return false; }
@Override
public boolean isVisuallyOpaque() { return false; }
}
}
public class OBJLoader implements ICustomModelLoader
{
public static final OBJLoader instance = new OBJLoader();
private IResourceManager manager;
private final Set<String> enabledDomains = new HashSet<String>();
private final Map<ResourceLocation, OBJModel> cache = new HashMap<ResourceLocation, OBJModel>();
private Map jsons = new HashMap();
public void addDomain(String domain)
{
enabledDomains.add(domain.toLowerCase());
}
public void onResourceManagerReload(IResourceManager resourceManager)
{
this.manager = resourceManager;
cache.clear();
}
public boolean accepts(ResourceLocation modelLocation)
{
return enabledDomains.contains(modelLocation.getResourceDomain()) && modelLocation.getResourcePath().endsWith(".obj");
}
public void passJsonToModel(ModelResourceLocation jsonLocation, ResourceLocation model)
{
if (model != null && jsonLocation != null)
{
this.jsons.put(model, jsonLocation);
}
}
@SuppressWarnings("unchecked")
public IModel loadModel(ResourceLocation modelLocation)
{
ResourceLocation file = new ResourceLocation(modelLocation.getResourceDomain(), modelLocation.getResourcePath());
if (!cache.containsKey(file))
{
try
{
IResource resource = null;
try
{
resource = manager.getResource(file);
}
catch (FileNotFoundException e)
{
if (modelLocation.getResourcePath().startsWith("models/block/"))
resource = manager.getResource(new ResourceLocation(file.getResourceDomain(), "models/item/" + file.getResourcePath().substring("models/block/".length())));
else if (modelLocation.getResourcePath().startsWith("models/item/"))
resource = manager.getResource(new ResourceLocation(file.getResourceDomain(), "models/block/" + file.getResourcePath().substring("models/item/".length())));
else
throw e;
}
OBJModel.Parser parser = new OBJModel.Parser(resource, manager);
OBJModel model = parser.parse();
ModelResourceLocation jsonLocation = (ModelResourceLocation) this.jsons.get(file);
model.setJsonLocation(jsonLocation);
cache.put(modelLocation, model);
}
catch (IOException e)
{
FMLLog.log(Level.ERROR, e, "Exception loading model %s with OBJ loader, skipping", modelLocation);
cache.put(modelLocation, null);
}
}
OBJModel model = cache.get(file);
if (model == null) return ModelLoaderRegistry.getMissingModel();
return model;
}
}
public class OBJModel implements IRetexturableModel, IModelCustomData, ICameraTransformsModel
{
private MaterialLibrary matLib;
private IModelState state;
private final ResourceLocation location;
private ModelResourceLocation jsonLocation = new ModelResourceLocation("missingno");
public OBJModel(MaterialLibrary matLib, ResourceLocation location)
{
this.matLib = matLib;
this.location = location;
}
private void setModelState(IModelState state)
{
this.state = state;
}
@Override
public void setJsonLocation(ModelResourceLocation jsonLocation)
{
this.jsonLocation = jsonLocation;
}
@Override
public Collection<ResourceLocation> getDependencies()
{
return Collections.emptyList();
}
@Override
public Collection<ResourceLocation> getTextures()
{
Iterator<Material> materialIterator = this.matLib.materials.values().iterator();
List<ResourceLocation> textures = new ArrayList<ResourceLocation>();
while (materialIterator.hasNext())
{
Material mat = materialIterator.next();
ResourceLocation textureLoc = new ResourceLocation(mat.getTexture().getPath());
if (!textures.contains(textureLoc))
textures.add(textureLoc);
}
return textures;
}
@Override
public IFlexibleBakedModel bake(IModelState state, VertexFormat format, Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter)
{
ImmutableMap.Builder<String, TextureAtlasSprite> builder = ImmutableMap.builder();
TextureAtlasSprite missing = bakedTextureGetter.apply(new ResourceLocation("missingno"));
for (Map.Entry<String, Material> e : matLib.materials.entrySet())
{
if (e.getValue().getTexture().getTextureLocation().getResourcePath().startsWith("#"))
{
FMLLog.severe("unresolved texture '%s' for obj model '%s'", e.getValue().getTexture().getTextureLocation().getResourcePath(), location);
builder.put(e.getKey(), missing);
}
else
{
builder.put(e.getKey(), bakedTextureGetter.apply(e.getValue().getTexture().getTextureLocation()));
}
}
builder.put("missingno", missing);
return new OBJBakedModel(this, state, format, builder.build());
}
public OBJState getDefaultState()
{
return new OBJState(null, this);
}
public MaterialLibrary getMatLib()
{
return this.matLib;
}
@Override
public IModel process(ImmutableMap<String, String> customData)
{
// TODO: use for defining different visibility configurations?
return null;
}
private static String getLocation(String path)
{
if (path.endsWith(".png"))
path = path.substring(0, path.length() - ".png".length());
return path;
}
@Override
public IModel retexture(ImmutableMap<String, String> textures)
{
for (Map.Entry<String, Material> e : matLib.materials.entrySet())
{
Material material = e.getValue();
Texture texture = material.getTexture();
String path = texture.getPath();
String loc = getLocation(path);
if (textures.containsKey(loc))
{
String newLoc = textures.get(loc);
if (newLoc == null) newLoc = getLocation(path);
texture = new Texture(newLoc);
}
}
return new OBJModel(matLib, location);
}
public static class Parser
{
private static Set<String> unknownObjectCommands = new HashSet<String>();
public MaterialLibrary materialLibrary = new MaterialLibrary();
private IResourceManager manager;
private InputStreamReader objStream;
private BufferedReader objReader;
private ResourceLocation objFrom;
private List<String> elementList = new ArrayList<String>();
private List<Vertex> vertices = new ArrayList<Vertex>();
private List<Normal> normals = new ArrayList<Normal>();
private List<TextureCoordinate> texCoords = new ArrayList<TextureCoordinate>();
public Parser(IResource from, IResourceManager manager) throws IOException
{
this.manager = manager;
this.objFrom = from.getResourceLocation();
this.objStream = new InputStreamReader(from.getInputStream(), Charsets.UTF_8);
this.objReader = new BufferedReader(objStream);
}
public List<String> getElements()
{
return this.elementList;
}
public OBJModel parse() throws IOException
{
String currentLine = "";
Material material = new Material();
for (;;)
{
currentLine = objReader.readLine();
if (currentLine == null) break;
if (currentLine.isEmpty() || currentLine.startsWith("#")) continue;
String[] fields = currentLine.split(" ", 2);
String key = fields[0];
String data = fields[1];
if (key.equalsIgnoreCase("mtllib"))
materialLibrary.parseMaterials(manager, data, objFrom);
else if (key.equalsIgnoreCase("usemtl"))
material = materialLibrary.materials.get(data);
else if (key.equalsIgnoreCase("v"))
{
String[] splitData = data.split(" ");
float[] floatSplitData = new float[splitData.length];
for (int i = 0; i < splitData.length; i++)
floatSplitData[i] = Float.parseFloat(splitData[i]);
Vector4f pos = new Vector4f(floatSplitData[0], floatSplitData[1], floatSplitData[2], floatSplitData.length == 4 ? floatSplitData[3] : 1);
Vector4f color = new Vector4f(1f, 1f, 1f, 1f);
if (material.isWhite()) color = material.getColor();
Vertex vertex = new Vertex(pos, color);
this.vertices.add(vertex);
}
else if (key.equalsIgnoreCase("vn"))
{
String[] splitData = data.split(" ");
float[] floatSplitData = new float[splitData.length];
for (int i = 0; i < splitData.length; i++)
floatSplitData[i] = Float.parseFloat(splitData[i]);
Normal normal = new Normal(new Vector3f(floatSplitData[0], floatSplitData[1], floatSplitData[2]));
this.normals.add(normal);
}
else if (key.equalsIgnoreCase("vt"))
{
String[] splitData = data.split(" ");
float[] floatSplitData = new float[splitData.length];
for (int i = 0; i < splitData.length; i++)
floatSplitData[i] = Float.parseFloat(splitData[i]);
TextureCoordinate texCoord = new TextureCoordinate(new Vector3f(floatSplitData[0], floatSplitData[1],
floatSplitData.length == 3 ? floatSplitData[2] : 1));
this.texCoords.add(texCoord);
}
else if (key.equalsIgnoreCase("f"))
{
String[] splitSpace = data.split(" ");
String[][] splitSlash = new String[splitSpace.length][];
int vert = 0;
int texCoord = 0;
int norm = 0;
List<Vertex> v = new ArrayList<Vertex>(splitSpace.length);
List<TextureCoordinate> t = new ArrayList<TextureCoordinate>(splitSpace.length);
List<Normal> n = new ArrayList<Normal>(splitSpace.length);
for (int i = 0; i < splitSpace.length; i++)
{
if (splitSpace[i].contains("//"))
{
splitSlash[i] = splitSpace[i].split("//");
vert = Integer.parseInt(splitSlash[i][0]);
vert = vert < 0 ? this.vertices.size() - 1 : vert - 1;
norm = Integer.parseInt(splitSlash[i][1]);
norm = norm < 0 ? this.normals.size() - 1 : norm - 1;
v.add(this.vertices.get(vert));
n.add(this.normals.get(norm));
}
else if (splitSpace[i].contains("/"))
{
splitSlash[i] = splitSpace[i].split("/");
vert = Integer.parseInt(splitSlash[i][0]);
vert = vert < 0 ? this.vertices.size() - 1 : vert - 1;
texCoord = Integer.parseInt(splitSlash[i][1]);
texCoord = texCoord < 0 ? this.texCoords.size() - 1 : texCoord - 1;
if (splitSlash[i].length > 2)
{
norm = Integer.parseInt(splitSlash[i][2]);
norm = norm < 0 ? this.normals.size() - 1 : norm - 1;
}
v.add(this.vertices.get(vert));
t.add(this.texCoords.get(texCoord));
if (splitSlash[i].length > 2) n.add(this.normals.get(norm));
}
else
{
splitSlash[i] = splitSpace[i].split("");
vert = Integer.parseInt(splitSlash[i][0]);
vert = vert < 0 ? this.vertices.size() - 1 : vert - 1;
v.add(this.vertices.get(vert));
}
}
Vertex[] va = new Vertex[v.size()];
v.toArray(va);
TextureCoordinate[] ta = new TextureCoordinate[t.size()];
t.toArray(ta);
Normal[] na = new Normal[n.size()];
n.toArray(na);
Face face = new Face(va, ta, na);
this.materialLibrary.library.put(face, material);
if (elementList.isEmpty())
{
if (this.materialLibrary.getElements().containsKey("default"))
{
this.materialLibrary.getElements().get("default").addFace(face);
}
else
{
Element def = new OBJModel(null, null).new Element("default", null);
def.addFace(face);
this.materialLibrary.getElements().put("default", def);
}
}
else
{
for (String s : elementList)
{
if (this.materialLibrary.getElements().containsKey(s))
{
this.materialLibrary.getElements().get(s).addFace(face);
}
else
{
Element e = new OBJModel(null, null).new Element(s, null);
e.addFace(face);
this.materialLibrary.getElements().put(s, e);
}
}
}
}
else if (key.equalsIgnoreCase("g") || key.equalsIgnoreCase("o"))
{
elementList.clear();
if (key.equalsIgnoreCase("g"))
{
String[] splitSpace = data.split(" ");
for (String s : splitSpace)
elementList.add(s);
}
else
{
elementList.add(data);
}
}
else
{
if (!unknownObjectCommands.contains(key))
{
unknownObjectCommands.add(key);
FMLLog.info("OBJLoader.Parser: command '%s' (model: '%s') is not currently supported, skipping", key, objFrom);
}
}
}
return new OBJModel(this.materialLibrary, this.objFrom);
}
}
public static class MaterialLibrary
{
private static Set<String> unknownMaterialCommands = new HashSet<String>();
private Map<String, Material> materials = new HashMap<String, Material>();
private Map<Face, Material> library = new HashMap<Face, Material>();
private Map<String, Element> elements = new HashMap<String, Element>();
private InputStreamReader mtlStream;
private BufferedReader mtlReader;
public MaterialLibrary()
{
this.elements.put("default", new OBJModel(null, null).new Element("default", null));
}
public void parseMaterials(IResourceManager manager, String path, ResourceLocation from) throws IOException
{
String domain = from.getResourceDomain();
mtlStream = new InputStreamReader(manager.getResource(new ResourceLocation(domain, path)).getInputStream(), Charsets.UTF_8);
mtlReader = new BufferedReader(mtlStream);
String currentLine = "";
Material material = new Material();
material.setName("white");
material.setTexture(Texture.White);
this.materials.put("white", material);
for (;;)
{
currentLine = mtlReader.readLine();
if (currentLine == null) break;
if (currentLine.isEmpty() || currentLine.startsWith("#")) continue;
String[] fields = currentLine.split(" ", 2);
String key = fields[0];
String data = fields[1];
if (key.equalsIgnoreCase("newmtl"))
{
material = new Material();
material.setName(data);
this.materials.put(data, material);
}
else if (key.equalsIgnoreCase("Ka") || key.equalsIgnoreCase("Kd") || key.equalsIgnoreCase("Ks"))
{
String[] rgbStrings = data.split(" ", 3);
Vector4f color = new Vector4f(Float.parseFloat(rgbStrings[0]), Float.parseFloat(rgbStrings[1]), Float.parseFloat(rgbStrings[2]), 1.0f);
material.setColor(color);
}
else if (key.equalsIgnoreCase("map_Ka") || key.equalsIgnoreCase("map_Kd") || key.equalsIgnoreCase("map_Ks"))
{
if (data.contains(" "))
{
String[] mapStrings = data.split(" ");
String texturePath = mapStrings[mapStrings.length - 1];
Texture texture = new Texture(texturePath);
material.setTexture(texture);
}
else
{
Texture texture = new Texture(data);
material.setTexture(texture);
}
}
else
{
if (!unknownMaterialCommands.contains(key))
{
unknownMaterialCommands.add(key);
FMLLog.info("OBJLoader.MaterialLibrary: command '%s' (model: '%s') is not currently supported, skipping", key, new ResourceLocation(domain, path));
}
}
}
}
public Map<String, Element> getElements()
{
return this.elements;
}
}
public static class Material
{
private Vector4f color;
private Texture texture = Texture.White;
private String name;
public Material()
{
this(new Vector4f(1f, 1f, 1f, 1f));
}
public Material(Vector4f color)
{
this(color, Texture.White, "white");
}
public Material(Texture texture)
{
this(new Vector4f(1, 1, 1, 1), texture, "default");
}
public Material(Vector4f color, Texture texture, String name)
{
this.color = color;
this.texture = texture;
this.name = name;
}
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return this.name;
}
public void setColor(Vector4f color)
{
this.color = color;
}
public Vector4f getColor()
{
return this.color;
}
public void setTexture(Texture texture)
{
this.texture = texture;
}
public Texture getTexture()
{
return this.texture;
}
public boolean isWhite()
{
if (this.texture.equals(Texture.White))
return true;
else
return false;
}
}
public static class Texture
{
public static Texture White = new Texture("builtin/white", new Vector2f(0, 0), new Vector2f(1, 1), 0);
private String path;
private Vector2f position;
private Vector2f scale;
private float rotation;
public Texture(String path)
{
this(path, new Vector2f(0, 0), new Vector2f(1, 1), 0);
}
public Texture(String path, Vector2f position, Vector2f scale, float rotation)
{
this.path = path;
this.position = position;
this.scale = scale;
this.rotation = rotation;
}
public ResourceLocation getTextureLocation()
{
ResourceLocation loc = new ResourceLocation(this.path);
return loc;
}
public void setPath(String path)
{
this.path = path;
}
public String getPath()
{
return this.path;
}
public void setPosition(Vector2f position)
{
this.position = position;
}
public Vector2f getPosition()
{
return this.position;
}
public void setScale(Vector2f scale)
{
this.scale = scale;
}
public Vector2f getScale()
{
return this.scale;
}
public void setRotation(float rotation)
{
this.rotation = rotation;
}
public float getRotation()
{
return this.rotation;
}
}
public static class Face
{
private Vertex[] verts = new Vertex[4];
private Normal[] norms = new Normal[4];
private TextureCoordinate[] texCoords = new TextureCoordinate[4];
public Face(Vertex[] verts)
{
this(verts, null, null);
}
public Face(Vertex[] verts, Normal[] norms)
{
this(verts, null, norms);
}
public Face(Vertex[] verts, TextureCoordinate[] texCoords)
{
this(verts, texCoords, null);
}
public Face(Vertex[] verts, TextureCoordinate[] texCoords, Normal[] norms)
{
this.verts = verts;
this.verts = this.verts.length > 0 ? this.verts : null;
this.norms = norms;
this.norms = this.norms.length > 0 ? this.norms : null;
this.texCoords = texCoords;
this.texCoords = this.texCoords.length > 0 ? this.texCoords : null;
}
public void setVertices(Vertex[] verts)
{
this.verts = verts;
}
public Vertex[] getVertices()
{
return this.verts;
}
public void setNormals(Normal[] norms)
{
this.norms = norms;
}
public Normal[] getNormals()
{
return this.norms;
}
public void setTextureCoordinates(TextureCoordinate[] texCoords)
{
this.texCoords = texCoords;
}
public TextureCoordinate[] getTextureCoordinates()
{
return this.texCoords;
}
public Normal getNormal()
{
if (norms == null)
{ // use vertices to calculate normal
Vector3f v1 = new Vector3f(this.verts[0].getPosition().x, this.verts[0].getPosition().y, this.verts[0].getPosition().z);
Vector3f v2 = new Vector3f(this.verts[1].getPosition().x, this.verts[1].getPosition().y, this.verts[1].getPosition().z);
Vector3f v3 = new Vector3f(this.verts[2].getPosition().x, this.verts[2].getPosition().y, this.verts[2].getPosition().z);
Vector3f v4 = this.verts.length > 3 ? new Vector3f(this.verts[3].getPosition().x, this.verts[3].getPosition().y, this.verts[3].getPosition().z)
: null;
if (v4 == null)
{
Vector3f v2c = new Vector3f(v2.x, v2.y, v2.z);
Vector3f v1c = new Vector3f(v1.x, v1.y, v1.z);
v1c.sub(v2c);
Vector3f v3c = new Vector3f(v3.x, v3.y, v3.z);
v3c.sub(v2c);
Vector3f c = new Vector3f();
c.cross(v1c, v3c);
c.normalize();
Normal normal = new Normal(c);
return normal;
}
else
{
Vector3f v2c = new Vector3f(v2.x, v2.y, v2.z);
Vector3f v1c = new Vector3f(v1.x, v1.y, v1.z);
v1c.sub(v2c);
Vector3f v3c = new Vector3f(v3.x, v3.y, v3.z);
v3c.sub(v2c);
Vector3f c = new Vector3f();
c.cross(v1c, v3c);
c.normalize();
v1c = new Vector3f(v1.x, v1.y, v1.z);
v3c = new Vector3f(v3.x, v3.y, v3.z);
Vector3f v4c = new Vector3f(v4.x, v4.y, v4.z);
v1c.sub(v4c);
v3c.sub(v4c);
Vector3f d = new Vector3f();
d.cross(v1c, v3c);
d.normalize();
Vector3f avg = new Vector3f();
avg.x = (c.x + d.x) * 0.5f;
avg.y = (c.y + d.y) * 0.5f;
avg.z = (c.z + d.z) * 0.5f;
avg.normalize();
Normal normal = new Normal(avg);
return normal;
}
}
else
{ // use normals to calculate normal
Vector3f n1 = this.norms[0].getNormal();
Vector3f n2 = this.norms[1].getNormal();
Vector3f n3 = this.norms[2].getNormal();
Vector3f n4 = this.norms.length > 3 ? this.norms[3].getNormal() : null;
if (n4 == null)
{
Vector3f n2c = new Vector3f(n2.x, n2.y, n2.z);
Vector3f n1c = new Vector3f(n1.x, n1.y, n1.z);
n1c.sub(n2c);
Vector3f n3c = new Vector3f(n3.x, n3.y, n3.z);
n3c.sub(n2c);
Vector3f c = new Vector3f();
c.cross(n1c, n3c);
c.normalize();
Normal normal = new Normal(c);
return normal;
}
else
{
Vector3f n2c = new Vector3f(n2.x, n2.y, n2.z);
Vector3f n1c = new Vector3f(n1.x, n1.y, n1.z);
n1c.sub(n2c);
Vector3f n3c = new Vector3f(n3.x, n3.y, n3.z);
n3c.sub(n2c);
Vector3f c = new Vector3f();
c.cross(n1c, n3c);
c.normalize();
n1c = new Vector3f(n1.x, n1.y, n1.z);
n3c = new Vector3f(n3.x, n3.y, n3.z);
Vector3f n4c = new Vector3f(n4.x, n4.y, n4.z);
n1c.sub(n4c);
n3c.sub(n4c);
Vector3f d = new Vector3f();
d.cross(n1c, n3c);
d.normalize();
Vector3f avg = new Vector3f();
avg.x = (c.x + d.x) * 0.5f;
avg.y = (c.y + d.y) * 0.5f;
avg.z = (c.z + d.z) * 0.5f;
avg.normalize();
Normal normal = new Normal(avg);
return normal;
}
}
}
}
public static class Vertex
{
private Vector4f position;
private Vector4f color;
public Vertex(Vector4f position, Vector4f color)
{
this.position = position;
this.color = color;
}
public void setPos(Vector4f position)
{
this.position = position;
}
public Vector4f getPosition()
{
return this.position;
}
public void setColor(Vector4f color)
{
this.color = color;
}
public Vector4f getColor()
{
return this.color;
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append(String.format("v:%n"));
builder.append(String.format(" position: %s %s %s%n", position.x, position.y, position.z));
builder.append(String.format(" color: %s %s %s %s%n", color.x, color.y, color.z, color.w));
return builder.toString();
}
}
public static class Normal
{
private Vector3f normal;
public Normal(Vector3f normal)
{
this.normal = normal;
}
public void setNormal(Vector3f normal)
{
this.normal = normal;
}
public Vector3f getNormal()
{
return this.normal;
}
}
public static class TextureCoordinate
{
private Vector3f position;
public TextureCoordinate(Vector3f position)
{
this.position = position;
}
public void setPosition(Vector3f position)
{
this.position = position;
}
public Vector3f getPosition()
{
return this.position;
}
}
public class OBJState implements IModelState
{
private List<String> visibleElements = new ArrayList<String>();
private OBJModel model;
public OBJState(List<String> visibleElements, OBJModel model)
{
if (visibleElements == null || visibleElements.isEmpty())
this.visibleElements.add("all");
else
this.visibleElements = visibleElements;
this.model = model;
setElementVisibilities();
}
private void setElementVisibilities()
{
String s = visibleElements.get(0);
if (s.equalsIgnoreCase("all"))
{
showAllElements();
}
else if (s.equalsIgnoreCase("none"))
{
hideAllElements();
}
else if (s.equalsIgnoreCase("all except"))
{
showAllElements();
for (int i = 1; i < visibleElements.size(); i++)
{
if (this.model.getMatLib().getElements().containsKey(visibleElements.get(i)))
{
this.model.getMatLib().getElements().get(visibleElements.get(i)).setVisible(false);
}
else
{
FMLLog.severe("OBJState: could not find element of name '%s' whilst setting element visibility, skipping", visibleElements.get(i));
}
}
}
else
{
hideAllElements();
for (String v : visibleElements)
{
if (!this.model.getMatLib().getElements().containsKey(v))
{
FMLLog.severe("OBJState: could not find element of name '%s' whilst setting element visibility, skipping", v);
}
else
{
this.model.getMatLib().getElements().get(v).setVisible(true);
}
}
}
}
private void showAllElements()
{
for (Element e : this.model.getMatLib().getElements().values())
{
e.setVisible(true);
}
}
private void hideAllElements()
{
for (Element e : this.model.getMatLib().getElements().values())
{
e.setVisible(false);
}
}
public void setVisibleElements(List<String> visibleElements)
{
this.visibleElements = visibleElements;
}
public List<String> getVisibleElements()
{
return this.visibleElements;
}
@Override
public TRSRTransformation apply(IModelPart part)
{
TRSRTransformation ret = TRSRTransformation.identity();
if (part instanceof Element)
{
Element element = (Element) part;
ret = ret.compose(new TRSRTransformation(element.getPos(), element.getRot(), element.getScale(), null));
Matrix4f matrix = ret.getMatrix();
matrix.invert();
ret = ret.compose(new TRSRTransformation(matrix));
}
return ret;
}
}
public static enum OBJModelProperty implements IUnlistedProperty<OBJState>
{
instance;
public String getName()
{
return "OBJModel";
}
@Override
public boolean isValid(OBJState value)
{
return value instanceof OBJState;
}
@Override
public Class<OBJState> getType()
{
return OBJState.class;
}
@Override
public String valueToString(OBJState value)
{
return value.toString();
}
}
public class Element implements IModelPart
{
private String name;
private Vector3f pos;
private Vector3f scale;
private Quat4f rot;
private LinkedHashSet<Face> faces = new LinkedHashSet<Face>();
private boolean visible = true;
public Element(String name, LinkedHashSet<Face> faces)
{
this(name, new Vector3f(0.5f, 0.5f, 0.5f), new Vector3f(1.0f, 1.0f, 1.0f), new Quat4f(0f, 0f, 0f, 1f), faces);
}
public Element(String name, Vector3f pos, Vector3f scale, Quat4f rot, LinkedHashSet<Face> faces)
{
this.name = name;
this.pos = pos;
this.scale = scale;
this.rot = rot;
if (faces == null)
this.faces = new LinkedHashSet<Face>();
else
this.faces = faces;
}
public ImmutableList<Face> bake()
{
ImmutableList.Builder<Face> builder = ImmutableList.builder();
for (Face f : faces)
{
builder.add(f);
}
return builder.build();
}
public String getName()
{
return this.name;
}
public void setName(String name)
{
this.name = name;
}
public Vector3f getPos()
{
return this.pos;
}
public void setPos(Vector3f pos)
{
this.pos = pos;
}
public Vector3f getScale()
{
return this.scale;
}
public void setScale(Vector3f scale)
{
this.scale = scale;
}
public Quat4f getRot()
{
return this.rot;
}
public void setRot(Quat4f rot)
{
this.rot = rot;
}
public LinkedHashSet<Face> getFaces()
{
return this.faces;
}
public void setFaces(LinkedHashSet<Face> faces)
{
this.faces = faces;
}
public void addFace(Face face)
{
this.faces.add(face);
}
public void addFaces(List<Face> faces)
{
this.faces.addAll(faces);
}
public boolean isVisible()
{
return this.visible;
}
public void setVisible(boolean visible)
{
this.visible = visible;
}
}
private class OBJBakedModel implements IFlexibleBakedModel, ISmartBlockModel, ISmartItemModel, IPerspectiveAwareModel
{
private final OBJModel model;
private final IModelState state;
private final VertexFormat format;
private final ByteBuffer buffer;
private LinkedHashSet<BakedQuad> quads;
private static final int BYTES_IN_INT = Integer.SIZE / Byte.SIZE;
private static final int VERTICES_IN_QUAD = 4;
private Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter;
private ImmutableMap<String, TextureAtlasSprite> textures;
private ItemCameraTransforms cameraTransforms = null;
public OBJBakedModel(OBJModel model, IModelState state, VertexFormat format, ImmutableMap<String, TextureAtlasSprite> textures)
{
this.model = model;
this.state = state;
this.format = format;
this.textures = textures;
buffer = BufferUtils.createByteBuffer(VERTICES_IN_QUAD * format.getNextOffset());
model.setModelState(state);
}
@Override
public List<BakedQuad> getFaceQuads(EnumFacing side)
{
return Collections.emptyList();
}
@Override
public List<BakedQuad> getGeneralQuads()
{
if (quads == null)
{
quads = new LinkedHashSet<BakedQuad>();
LinkedHashSet<Face> faces = new LinkedHashSet<Face>();
Iterator<Element> elementIterator = this.model.getMatLib().getElements().values().iterator();
while (elementIterator.hasNext())
{
Element e = elementIterator.next();
if (e.isVisible()) faces.addAll(e.getFaces());
}
for (Face f : faces)
{
buffer.clear();
String texture = this.model.getMatLib().library.get(f).getName();
TextureAtlasSprite sprite = this.textures.get("missingno");
if (this.model.getMatLib().materials.get(texture).isWhite())
sprite = ModelLoader.White.instance;
else
sprite = this.textures.get(texture);
putVertexData(f.verts[0], f.texCoords != null ? f.texCoords[0] : null, f.norms != null ? f.norms[0] : f.getNormal(), sprite);
putVertexData(f.verts[1], f.texCoords != null ? f.texCoords[1] : null, f.norms != null ? f.norms[1] : f.getNormal(), sprite);
putVertexData(f.verts[2], f.texCoords != null ? f.texCoords[2] : null, f.norms != null ? f.norms[2] : f.getNormal(), sprite);
putVertexData(f.verts[3], f.texCoords != null ? f.texCoords[3] : null, f.norms != null ? f.norms[3] : f.getNormal(), sprite);
buffer.flip();
int[] data = new int[VERTICES_IN_QUAD * format.getNextOffset() / BYTES_IN_INT];
buffer.asIntBuffer().get(data);
quads.add(new ColoredBakedQuad(data, -1, EnumFacing.getFacingFromVector(f.getNormal().normal.x, f.getNormal().normal.y, f.getNormal().normal.z)));
}
}
List<BakedQuad> quadList = new ArrayList<BakedQuad>();
quadList.addAll(quads);
return quadList;
}
private void put(VertexFormatElement e, Float... fs)
{
Attributes.put(buffer, e, true, 0f, fs);
}
@SuppressWarnings("unchecked")
private final void putVertexData(Vertex v, TextureCoordinate t, Normal n, TextureAtlasSprite sprite)
{
int oldPos = buffer.position();
Number[] ns = new Number[16];
for (int i = 0; i < ns.length; i++)
ns[i] = 0f;
for (VertexFormatElement e : (List<VertexFormatElement>) format.getElements())
{
switch (e.getUsage())
{
case POSITION:
put(e, v.position.x, v.position.y, v.position.z, 1f);
break;
case COLOR:
if (v.color != null)
put(e, v.color.x, v.color.y, v.color.z, v.color.w);
else
put(e, 1f, 1f, 1f, 1f);
break;
case UV:
if (t != null)
{
put(e, sprite.getInterpolatedU(t.getPosition().x * 16), sprite.getInterpolatedV(t.getPosition().y * 16), 0f, 1f);
}
else
put(e, 0f, 0f, 0f, 1f);
break;
case NORMAL:
put(e, n.normal.x, n.normal.y, n.normal.z, 1f);
break;
case GENERIC:
put(e, 0f, 0f, 0f, 0f);
break;
default:
break;
}
}
buffer.position(oldPos + format.getNextOffset());
}
@Override
public boolean isAmbientOcclusion()
{
return true;
}
@Override
public boolean isGui3d()
{
return true;
}
@Override
public boolean isBuiltInRenderer()
{
return false;
}
@Override
public TextureAtlasSprite getTexture()
{
return this.textures.get(0);
}
@Override
public ItemCameraTransforms getItemCameraTransforms()
{
if (this.cameraTransforms == null)
{
ModelBlockDefinition definition = ModelLoaderRegistry.getModelLoader().getModelBlockDefinition(this.model.jsonLocation);
Map variantsMap = definition.getAllVariants();
if (variantsMap.containsKey(this.model.jsonLocation.getVariant()))
{
ModelBlockDefinition.Variants variants = (ModelBlockDefinition.Variants) variantsMap.get(this.model.jsonLocation.getVariant());
List<ModelBlockDefinition.Variant> variantList = variants.getVariants();
Iterator varIter = variantList.iterator();
while (varIter.hasNext()) {
ModelBlockDefinition.Variant variant = (ModelBlockDefinition.Variant) varIter.next();
ResourceLocation varLoc = new ResourceLocation(variant.getModelLocation().getResourceDomain(), "models/" + variant.getModelLocation().getResourcePath());
if (varLoc.equals(this.model.location))
{
this.cameraTransforms = variant.getTransforms();
}
}
return this.cameraTransforms;
}
this.cameraTransforms = ItemCameraTransforms.DEFAULT;
}
return this.cameraTransforms;
}
@Override
public VertexFormat getFormat()
{
return format;
}
@Override
public IBakedModel handleItemState(ItemStack stack)
{
return this;
}
@Override
public OBJBakedModel handleBlockState(IBlockState state)
{
if (state instanceof IExtendedBlockState)
{
IExtendedBlockState exState = (IExtendedBlockState) state;
if (exState.getUnlistedNames().contains(OBJModelProperty.instance))
{
OBJState s = (OBJState) exState.getValue(OBJModelProperty.instance);
if (s != null) return getModel(s.getVisibleElements());
}
}
return this;
}
public OBJBakedModel getModel(List<String> visibleElements)
{
return new OBJBakedModel(model, new OBJState(visibleElements, model), format, this.textures);
}
@Override
public Pair<IBakedModel, Matrix4f> handlePerspective(TransformType cameraTransformType)
{
ItemCameraTransforms transforms = this.getItemCameraTransforms();
transforms = transforms == null ? ItemCameraTransforms.DEFAULT : transforms;
TRSRTransformation trsr = TRSRTransformation.identity();
Matrix4f matrix = trsr.getMatrix();
switch(cameraTransformType)
{
case THIRD_PERSON:
trsr = new TRSRTransformation(transforms.thirdPerson != null ? transforms.thirdPerson : ItemTransformVec3f.DEFAULT);
matrix = trsr.getMatrix();
return Pair.of((IBakedModel) this, matrix);
case FIRST_PERSON:
trsr = new TRSRTransformation(transforms.firstPerson != null ? transforms.firstPerson : ItemTransformVec3f.DEFAULT);
matrix = trsr.getMatrix();
return Pair.of((IBakedModel) this, matrix);
case HEAD:
trsr = new TRSRTransformation(transforms.head != null ? transforms.head : ItemTransformVec3f.DEFAULT);
matrix = trsr.getMatrix();
return Pair.of((IBakedModel) this, matrix);
case GUI:
trsr = new TRSRTransformation(transforms.gui != null ? transforms.gui : ItemTransformVec3f.DEFAULT);
matrix = trsr.getMatrix();
return Pair.of((IBakedModel) this, matrix);
case NONE:
default:
return Pair.of((IBakedModel) this, matrix);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment