Skip to content

Instantly share code, notes, and snippets.

@StillManic
Created June 30, 2015 21:11
Show Gist options
  • Save StillManic/08d0e8fe200e69dd7cf3 to your computer and use it in GitHub Desktop.
Save StillManic/08d0e8fe200e69dd7cf3 to your computer and use it in GitHub Desktop.
package net.minecraftforge.client.model.obj;
import java.io.BufferedReader;
public class OBJModel implements IRetexturableModel, IModelCustomData
{
private MaterialLibrary matLib;
private IModelState state;
private final ResourceLocation location;
private float minU = 0f;
private float maxU = 1f;
private float minV = 0f;
private float maxV = 1f;
public OBJModel(MaterialLibrary matLib, ResourceLocation location)
{
this.matLib = matLib;
this.location = location;
}
public IModelState getModelState()
{
return this.state;
}
private void setModelState(IModelState state)
{
this.state = state;
}
public float getMinU()
{
return this.minU;
}
public float getMaxU()
{
return this.maxU;
}
public float getMinV()
{
return this.minV;
}
public float getMaxV()
{
return this.maxV;
}
public void setUVBounds(float minU, float maxU, float minV, float maxV)
{
this.minU = minU;
this.maxU = maxU;
this.minV = minV;
this.maxV = maxV;
}
@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)
{
MaterialLibrary lib = this.matLib;
for (Map.Entry<String, String> e : textures.entrySet())
{
FMLLog.info("OBJModel: Retexturing: %s with %s", e.getKey(), e.getValue());
String name = e.getKey();
String loc = e.getValue();
if (name.equalsIgnoreCase("all"))
{
for (Map.Entry<String, Material> m : lib.materials.entrySet())
{
if (!m.getKey().equals(Material.WHITE_NAME))
{
m.getValue().getTexture().setPath(loc);
}
}
}
else if (lib.materials.containsKey(name))
{
lib.materials.get(name).getTexture().setPath(loc);
}
}
return new OBJModel(lib, location);
}
public static class Parser
{
private static Set<String> unknownObjectCommands = new HashSet<String>();
public MaterialLibrary materialLibrary = new OBJModel(null, null).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>();
private float minU = 0f;
private float maxU = 1f;
private float minV = 0f;
private float maxV = 1f;
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));
if (floatSplitData[0] < this.minU)
{
this.minU = floatSplitData[0];
}
else if (floatSplitData[0] > this.maxU)
{
this.maxU = floatSplitData[0];
}
if (floatSplitData[1] < this.minV)
{
this.minV = floatSplitData[1];
}
else if (floatSplitData[1] > this.maxV)
{
this.maxV = floatSplitData[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);
if (material.getTexture() == null || material.getTexture().equals(Texture.White))
{
this.materialLibrary.library.put(face, this.materialLibrary.materials.get(Material.WHITE_NAME));
}
else
{
this.materialLibrary.library.put(face, material);
}
if (elementList.isEmpty())
{
if (this.materialLibrary.getElements().containsKey(Element.DEFAULT_NAME))
{
this.materialLibrary.getElements().get(Element.DEFAULT_NAME).addFace(face);
}
else
{
Element def = new Element(Element.DEFAULT_NAME, null);
def.addFace(face);
this.materialLibrary.getElements().put(Element.DEFAULT_NAME, def);
}
}
else
{
for (String s : elementList)
{
if (this.materialLibrary.getElements().containsKey(s))
{
this.materialLibrary.getElements().get(s).addFace(face);
}
else
{
Element e = 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);
}
}
}
OBJModel model = new OBJModel(this.materialLibrary, this.objFrom);
model.setUVBounds(minU, maxU, minV, maxV);
return model;
}
}
public class MaterialLibrary
{
private 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(Element.DEFAULT_NAME, new Element(Element.DEFAULT_NAME, null));
}
public void parseMaterials(IResourceManager manager, String path, ResourceLocation from) throws IOException
{
boolean hasSetTexture = false;
boolean hasSetColor = false;
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(Material.WHITE_NAME);
material.setTexture(Texture.White);
this.materials.put(Material.WHITE_NAME, 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"))
{
hasSetColor = false;
hasSetTexture = false;
material = new Material();
material.setName(data);
this.materials.put(data, material);
}
else if (key.equalsIgnoreCase("Ka") || key.equalsIgnoreCase("Kd") || key.equalsIgnoreCase("Ks"))
{
if (!hasSetColor)
{
String[] rgbStrings = data.split(" ", 3);
Vector4f color = new Vector4f(Float.parseFloat(rgbStrings[0]), Float.parseFloat(rgbStrings[1]), Float.parseFloat(rgbStrings[2]), 1.0f);
hasSetColor = true;
material.setColor(color);
}
else
{
FMLLog.info("OBJModel: A color has already been defined for material '%s' in '%s'. The color defined by key '%s' will not be applied!", material.getName(), new ResourceLocation(domain, path).toString(), key);
}
}
else if (key.equalsIgnoreCase("map_Ka") || key.equalsIgnoreCase("map_Kd") || key.equalsIgnoreCase("map_Ks"))
{
if (!hasSetTexture)
{
if (data.contains(" "))
{
String[] mapStrings = data.split(" ");
String texturePath = mapStrings[mapStrings.length - 1];
Texture texture = new Texture(texturePath);
hasSetTexture = true;
material.setTexture(texture);
}
else
{
Texture texture = new Texture(data);
hasSetTexture = true;
material.setTexture(texture);
}
}
else
{
FMLLog.info("OBJModel: A texture has already been defined for material '%s' in '%s'. The texture defined by key '%s' will not be applied!", material.getName(), new ResourceLocation(domain, path).toString(), key);
}
}
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 Map<String, Element> getVisibleElements()
{
Map<String, Element> ret = new HashMap<String, Element>();
for (Map.Entry<String, Element> e : this.elements.entrySet())
{
if (e.getValue().isVisible())
ret.put(e.getKey(), e.getValue());
}
return ret;
}
}
public static class Material
{
public static final String WHITE_NAME = "OBJModel.White.Texture.Name";
public static final String DEFAULT_NAME = "OBJModel.Default.Texture.Name";
private Vector4f color;
private Texture texture = Texture.White;
private String name = DEFAULT_NAME;
public Material()
{
this(new Vector4f(1f, 1f, 1f, 1f));
}
public Material(Vector4f color)
{
this(color, Texture.White, WHITE_NAME);
}
public Material(Texture texture)
{
this(new Vector4f(1, 1, 1, 1), texture, DEFAULT_NAME);
}
public Material(Vector4f color, Texture texture, String name)
{
this.color = color;
this.texture = texture;
this.name = name != null ? name : DEFAULT_NAME;
}
public void setName(String name)
{
this.name = name != null ? name : DEFAULT_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()
{
return this.texture.equals(Texture.White);
}
}
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;
ensureQuads();
}
private void ensureQuads()
{
if (this.verts != null && this.verts.length == 3)
{
this.verts = new Vertex[]{this.verts[0], this.verts[1], this.verts[2], this.verts[2]};
}
if (this.norms != null && this.norms.length == 3)
{
this.norms = new Normal[]{this.norms[0], this.norms[1], this.norms[2], this.norms[2]};
}
if (this.texCoords != null && this.texCoords.length == 3)
{
this.texCoords = new TextureCoordinate[]{this.texCoords[0], this.texCoords[1], this.texCoords[2], this.texCoords[2]};
}
}
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 boolean areUVsNormalized()
{
for (TextureCoordinate t : this.texCoords)
{
if (!(t.getPosition().getX() > 0.0f && t.getPosition().getX() < 1.0f && t.getPosition().getY() > 0.0f && t.getPosition().getY() < 1.0f))
{
return false;
}
}
return true;
}
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 static class OBJState implements IModelState
{
private OBJModel model;
private List<String> visibleElements = new ArrayList<String>();
public IModelState parent;
public OBJState(List<String> visibleElements, OBJModel model)
{
this(visibleElements, model, null);
}
public OBJState(List<String> visibleElements, OBJModel model, IModelState parent)
{
if (visibleElements == null || visibleElements.isEmpty())
this.visibleElements.add("all");
else
this.visibleElements = visibleElements;
this.model = model;
this.parent = getParent(parent);
setVisibleElements(this.visibleElements);
}
private IModelState getParent(IModelState parent)
{
if (parent == null) return null;
else if (parent instanceof OBJState) return ((OBJState) parent).parent;
return parent;
}
public void setVisibleElements(List<String> visibleElements)
{
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 List<String> getVisibleElements()
{
List<String> visible = new ArrayList<String>();
for (Map.Entry<String, Element> e : this.model.getMatLib().getElements().entrySet())
{
if (e.getValue().isVisible())
{
visible.add(e.getKey());
}
}
return visible;
}
public TRSRTransformation apply(IModelPart part)
{
TRSRTransformation ret = TRSRTransformation.identity();
if (parent != null)
{
ret = parent.apply(part).compose(composePart(part));
}
return ret;
}
private TRSRTransformation composePart(IModelPart part)
{
TRSRTransformation ret = TRSRTransformation.identity();
if (part instanceof Element)
{
Element element = (Element) part;
ret = ret.compose(element.getTransform());
Matrix4f matrix = ret.getMatrix();
matrix.invert();
ret = ret.compose(new TRSRTransformation(matrix));
}
return ret;
}
}
public enum OBJProperty implements IUnlistedProperty<OBJState>
{
instance;
public String getName()
{
return "OBJPropery";
}
@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 static class Element implements IModelPart
{
public static final String DEFAULT_NAME = "OBJModel.Default.Element.Name";
private String name = DEFAULT_NAME;
private TRSRTransformation transform = TRSRTransformation.identity();
private LinkedHashSet<Face> faces = new LinkedHashSet<Face>();
private boolean visible = true;
public Element(String name, LinkedHashSet<Face> faces)
{
this(name, TRSRTransformation.identity(), faces);
}
public Element(String name, TRSRTransformation transform, LinkedHashSet<Face> faces)
{
this.name = name != null ? name : DEFAULT_NAME;
this.transform = transform;
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 setTransform(TRSRTransformation transform)
{
if (transform == null)
this.transform = TRSRTransformation.identity();
else
this.transform = transform;
}
public TRSRTransformation getTransform()
{
return this.transform;
}
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 EnumFacing facing = EnumFacing.NORTH;
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);
if (f.texCoords != null && !f.areUVsNormalized())
{
float minU = 0.0f;
float maxU = 1.0f;
float minV = 0.0f;
float maxV = 1.0f;
for (TextureCoordinate t : f.texCoords)
{
minU = t.getPosition().getX() < minU ? t.getPosition().getX() : minU;
maxU = t.getPosition().getX() > maxU ? t.getPosition().getX() : maxU;
minV = t.getPosition().getY() < minV ? t.getPosition().getY() : minV;
maxV = t.getPosition().getY() > maxV ? t.getPosition().getY() : maxV;
}
for (int i = 0; i < f.texCoords.length; i++)
{
TextureCoordinate t = f.texCoords[i];
float U = (t.getPosition().getX() - minU) / (maxU - minU);
float V = (t.getPosition().getY() - minV) / (maxV - minV);
Vector3f normPos = new Vector3f(U, V, t.getPosition().getZ());
f.texCoords[i] = new TextureCoordinate(normPos);
}
}
putVertexData(f.verts[0], f.texCoords != null ? f.texCoords[0] : null, f.norms != null ? f.norms[0] : f.getNormal(), sprite, false);
putVertexData(f.verts[1], f.texCoords != null ? f.texCoords[1] : null, f.norms != null ? f.norms[1] : f.getNormal(), sprite, false);
putVertexData(f.verts[2], f.texCoords != null ? f.texCoords[2] : null, f.norms != null ? f.norms[2] : f.getNormal(), sprite, false);
putVertexData(f.verts[3], f.texCoords != null ? f.texCoords[3] : null, f.norms != null ? f.norms[3] : f.getNormal(), sprite, false);
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(ByteBuffer buffer, 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, boolean isTriangle)
{
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(buffer, e, v.position.x, v.position.y, v.position.z, 1f);
break;
case COLOR:
if (v.color != null)
put(buffer, e, v.color.x, v.color.y, v.color.z, v.color.w);
else
put(buffer, e, 1f, 1f, 1f, 1f);
break;
case UV:
if (t != null)
{
put(buffer, e, sprite.getInterpolatedU(t.getPosition().x * 16), sprite.getInterpolatedV(t.getPosition().y * 16), 0f, 1f);
}
else
put(buffer, e, 0f, 0f, 0f, 1f);
break;
case NORMAL:
put(buffer, e, n.normal.x, n.normal.y, n.normal.z, 1f);
break;
case GENERIC:
put(buffer, 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()
{
return ItemCameraTransforms.DEFAULT;
}
@Override
public VertexFormat getFormat()
{
return format;
}
@Override
public IBakedModel handleItemState(ItemStack stack)
{
return this;
}
@Override
public OBJBakedModel handleBlockState(IBlockState state)
{
FMLLog.info("handleBlockState for %s", state.getBlock().getUnlocalizedName());
if (state instanceof IExtendedBlockState)
{
IExtendedBlockState exState = (IExtendedBlockState) state;
if (exState.getUnlistedNames().contains(OBJProperty.instance))
{
OBJState s = (OBJState) exState.getValue(OBJProperty.instance);
if (s != null)
{
return getCachedModel(s);
}
}
}
return this;
}
private final Map<IModelState, OBJBakedModel> cache = new HashMap<IModelState, OBJBakedModel>();
public OBJBakedModel getCachedModel(IModelState state)
{
if (!cache.containsKey(state))
{
cache.put(state, new OBJBakedModel(this.model, new OBJState(((OBJState) state).getVisibleElements(), this.model, this.state), this.format, this.textures));
// cache.put(state, new OBJBakedModel(this.model, new OBJState(((OBJState) this.model.getModelState()).getVisibleElements(), this.model, this.state), this.format, this.textures));
// cache.put(state, new OBJBakedModel(this.model, new OBJState(((OBJState) this.state).getVisibleElements(), this.model, state), this.format, this.textures));
}
return cache.get(state);
}
public OBJModel getModel()
{
return this.model;
}
public IModelState getState()
{
return this.state;
}
public OBJBakedModel getBakedModel(List<String> visibleElements)
{
return new OBJBakedModel(model, new OBJState(visibleElements, this.model, this.state), format, this.textures);
}
@Override
public Pair<IBakedModel, Matrix4f> handlePerspective(TransformType cameraTransformType)
{
if (state instanceof IPerspectiveState)
{
return Pair.of((IBakedModel) this, TRSRTransformation.blockCornerToCenter(((IPerspectiveState) state).forPerspective(cameraTransformType).apply(model)).getMatrix());
}
return Pair.of((IBakedModel) this, null);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment