Last active
August 29, 2015 14:23
-
-
Save StillManic/658bb1ce0aec3d973754 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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(); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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 ] | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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 ] | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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 ] | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); } | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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; } | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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