package mods.eln.misc; import cpw.mods.fml.common.FMLLog; import net.minecraft.util.ResourceLocation; import org.jetbrains.annotations.Nullable; import org.lwjgl.opengl.GL11; import java.io.*; import java.util.*; public class Obj3D { private Timer updateTimer = null; private boolean locked = false; List<Vertex> vertex = new ArrayList<Vertex>(); List<Uv> uv = new ArrayList<Uv>(); // Model obj properties read from the txt file Map<String, String> nameToStringHash = new Hashtable<String, String>(); public float xDim, yDim, zDim; public float xMin = 0, yMin = 0, zMin = 0; public float xMax = 0, yMax = 0, zMax = 0; public float dimMax, dimMaxInv; private String dirPath; public void bindTexture(String texFilename) { ResourceLocation textureResource = new ResourceLocation("eln", "model/" + dirPath + "/" + texFilename); UtilsClient.bindTexture(textureResource); } public static class FaceGroup { String mtlName = null; public ResourceLocation textureResource; List<Face> face = new ArrayList<Face>(); boolean listReady = false; int glList; public void bindTexture() { UtilsClient.bindTexture(textureResource); } public BoundingBox boundingBox() { float xMin = 0, xMax = 0, yMin = 0, yMax = 0, zMin = 0, zMax = 0; for (Face f : face) { for (Vertex v : f.vertex) { xMin = xMax = v.x; yMin = yMax = v.y; zMin = zMax = v.z; break; } } for (Face f : face) { for (Vertex v : f.vertex) { xMin = Math.min(xMin, v.x); xMax = Math.max(xMax, v.x); yMin = Math.min(yMin, v.y); yMax = Math.max(yMax, v.y); zMin = Math.min(zMin, v.z); zMax = Math.max(zMax, v.z); } } return new BoundingBox(xMin, xMax, yMin, yMax, zMin, zMax); } public void draw() { if (textureResource != null) { bindTexture(); drawNoBind(); } else { GL11.glDisable(GL11.GL_TEXTURE_2D); drawNoBind(); GL11.glEnable(GL11.GL_TEXTURE_2D); } } private void drawVertex() { drawVertex(0, 0); } private void drawVertex(float offsetX, float offsetY) { int mode = 0; for (Face f : face) { if (f.vertexNbr != mode) { if (mode != 0) GL11.glEnd(); switch (f.vertexNbr) { case 3: GL11.glBegin(GL11.GL_TRIANGLES); break; case 4: GL11.glBegin(GL11.GL_QUADS); break; case 6: // GL11.glBegin(GL11.GL_TRIANGLE_STRIP); break; case 8: // GL11.glBegin(GL11.GL_TRIANGLE_STRIP); break; } mode = f.vertexNbr; } GL11.glNormal3f(f.normal.x, f.normal.y, f.normal.z); for (int idx = 0; idx < mode; idx++) { if (f.uv[idx] != null) GL11.glTexCoord2f(f.uv[idx].u + offsetX, f.uv[idx].v + offsetY); GL11.glVertex3f(f.vertex[idx].x, f.vertex[idx].y, f.vertex[idx].z); } } if (mode != 0) GL11.glEnd(); } public void drawNoBind() { if (!listReady) { listReady = true; glList = GL11.glGenLists(1); GL11.glNewList(glList, GL11.GL_COMPILE); drawVertex(); GL11.glEndList(); } GL11.glCallList(glList); } } public class Obj3DPart { // TODO(Baughn): Profile, see if it makes sense to use vertex arrays. List<Vertex> vertex; List<Uv> uv; List<FaceGroup> faceGroup = new ArrayList<FaceGroup>(); Map<String, Float> nameToFloatHash = new Hashtable<String, Float>(); public float xMin = 0, yMin = 0, zMin = 0; public float xMax = 0, yMax = 0, zMax = 0; private BoundingBox boundingBox = null; float ox, oy, oz; float ox2, oy2, oz2; public Obj3DPart(List<Vertex> vertex, List<Uv> uv) { this.vertex = vertex; this.uv = uv; } void clear() { faceGroup.clear(); boundingBox = null; xMin = 0; yMin = 0; zMin = 0; xMax = 0; yMax = 0; zMax = 0; } void addVertex(Vertex v) { vertex.add(v); xMin = Math.min(xMin, v.x); yMin = Math.min(yMin, v.y); zMin = Math.min(zMin, v.z); xMax = Math.max(xMax, v.x); yMax = Math.max(yMax, v.y); zMax = Math.max(zMax, v.z); boundingBox = null; } public float getFloat(String name) { return nameToFloatHash.getOrDefault(name, 0f); } public void draw(float angle, float x, float y, float z) { if (locked) return; GL11.glPushMatrix(); GL11.glTranslatef(ox, oy, oz); GL11.glRotatef(angle, x, y, z); GL11.glTranslatef(-ox, -oy, -oz); draw(); GL11.glPopMatrix(); } public void draw(float angle, float x, float y, float z, float texOffsetX, float texOffsetY) { if (locked) return; GL11.glPushMatrix(); GL11.glTranslatef(ox, oy, oz); GL11.glRotatef(angle, x, y, z); GL11.glTranslatef(-ox, -oy, -oz); draw(texOffsetX, texOffsetY); GL11.glPopMatrix(); } public void draw(float angle, float x, float y, float z, float angle2, float x2, float y2, float z2) { if (locked) return; GL11.glPushMatrix(); GL11.glTranslatef(ox, oy, oz); GL11.glRotatef(angle, x, y, z); GL11.glTranslatef(ox2, oy2, oz2); GL11.glRotatef(angle2, x2, y2, z2); GL11.glTranslatef(-ox2, -oy2, -oz2); GL11.glTranslatef(-ox, -oy, -oz); draw(); GL11.glPopMatrix(); } public void drawNoBind(float angle, float x, float y, float z) { if (locked) return; GL11.glPushMatrix(); GL11.glTranslatef(ox, oy, oz); GL11.glRotatef(angle, x, y, z); GL11.glTranslatef(-ox, -oy, -oz); drawNoBind(); GL11.glPopMatrix(); } public void drawNoBind() { if (locked) return; for (FaceGroup fg : faceGroup) { fg.drawNoBind(); } } public void draw() { if (locked) return; // Minecraft.getMinecraft().mcProfiler.startSection("OBJ"); for (FaceGroup fg : faceGroup) { fg.draw(); } // Minecraft.getMinecraft().mcProfiler.endSection(); } public void draw(float texOffsetX, float texOffsetY) { if (locked) return; // Minecraft.getMinecraft().mcProfiler.startSection("OBJ"); for (FaceGroup fg : faceGroup) { fg.drawVertex(texOffsetX, texOffsetY); } // Minecraft.getMinecraft().mcProfiler.endSection(); } // Returns the bounding box of the vertices we'd draw. public BoundingBox boundingBox() { if (boundingBox == null) { BoundingBox box = BoundingBox.mergeIdentity(); for (FaceGroup fg : faceGroup) { box = box.merge(fg.boundingBox()); } boundingBox = box; } return boundingBox; } } Hashtable<String, Obj3DPart> nameToPartHash = new Hashtable<String, Obj3DPart>(); public class Vertex { Vertex(float x, float y, float z) { this.x = x; this.y = y; this.z = z; } Vertex(String[] value) { x = Float.parseFloat(value[0]); y = Float.parseFloat(value[1]); z = Float.parseFloat(value[2]); } public float x, y, z; } class Uv { Uv(float u, float v) { this.u = u; this.v = v; } Uv(String[] value) { u = Float.parseFloat(value[0]); v = Float.parseFloat(value[1]); } public float u, v; } class Normal { Normal(float x, float y, float z) { this.x = x; this.y = y; this.z = z; } Normal(String[] value) { x = Float.parseFloat(value[0]); y = Float.parseFloat(value[1]); z = Float.parseFloat(value[2]); } Normal(Vertex o, Vertex a, Vertex b) { float a_x = a.x - o.x; float a_y = a.y - o.y; float a_z = a.z - o.z; float b_x = b.x - o.x; float b_y = b.y - o.y; float b_z = b.z - o.z; x = a_y * b_z - a_z * b_y; y = a_z * b_x - a_x * b_z; z = a_x * b_y - a_y * b_x; float norme = (float) Math.sqrt(x * x + y * y + z * z); x /= norme; y /= norme; z /= norme; } public float x, y, z; } class Face { Face(Vertex[] vertex, Uv[] uv, Normal normal) { this.vertex = vertex; this.uv = uv; this.normal = normal; vertexNbr = vertex.length; } public Vertex[] vertex; public Uv[] uv; Normal normal; public int vertexNbr; } public ResourceLocation getModelResourceLocation(String name) { return new ResourceLocation("eln", "model/" + dirPath + "/" + name); } /** * Load a resource (obj, mtl, txt file) for a model. * * @param filePath the path from the "assets/eln" folder * @return the {@code BufferedReader} or null if the resource does not exist */ @Nullable private BufferedReader getResourceAsStream(String filePath, boolean trySource) { BufferedReader reader = null; if (trySource) { final String path = "../src/main/resources/assets/eln/" + filePath; try { reader = new BufferedReader(new FileReader(path)); } catch (FileNotFoundException e) { System.out.println(e); } } if (reader == null) { final String path = "assets/eln/" + filePath; try { InputStream in = getClass().getClassLoader().getResourceAsStream(path); reader = new BufferedReader(new InputStreamReader(in, "UTF-8")); } catch (Exception e) { System.out.println(e); } } return reader; } public boolean loadFile(final String filePath) { return loadFile(filePath, false); } public boolean loadFile(final String filePath, boolean reload) { Obj3DPart part = null; FaceGroup fg = null; if (reload) { locked = true; vertex.clear(); uv.clear(); xMax = 0; yMin = 0; zMin = 0; xMax = 0; yMax = 0; zMax = 0; } dirPath = filePath.substring(0, filePath.lastIndexOf('/')); String mtlName = null; try { { BufferedReader bufferedReader = getResourceAsStream("model/" + filePath, reload); if (bufferedReader == null) { Utils.println(String.format(" - failed to load obj '%s'", filePath)); return false; } String line; while ((line = bufferedReader.readLine()) != null) { String[] words = line.split(" "); if (words[0].equals("o")) { if (reload) { part = nameToPartHash.get(words[1]); if (part != null) { part.clear(); } else { part = new Obj3DPart(vertex, uv); nameToPartHash.put(words[1], part); } } else { part = new Obj3DPart(vertex, uv); nameToPartHash.put(words[1], part); } } else if (words[0].equals("v")) { Vertex v = new Vertex(Float.parseFloat(words[1]), Float.parseFloat(words[2]), Float.parseFloat(words[3])); part.addVertex(v); xMin = Math.min(xMin, v.x); yMin = Math.min(yMin, v.y); zMin = Math.min(zMin, v.z); xMax = Math.max(xMax, v.x); yMax = Math.max(yMax, v.y); zMax = Math.max(zMax, v.z); } else if (words[0].equals("vt")) { part.uv.add(new Uv(Float.parseFloat(words[1]), 1 - Float.parseFloat(words[2]))); } else if (words[0].equals("f")) { int vertexNbr = words.length - 1; if (vertexNbr == 3) { Vertex[] verticeId = new Vertex[vertexNbr]; Uv[] uvId = new Uv[vertexNbr]; for (int idx = 0; idx < vertexNbr; idx++) { String[] id = words[idx + 1].split("/"); verticeId[idx] = part.vertex.get(Integer.parseInt(id[0]) - 1); if (id.length > 1 && !id[1].equals("")) { uvId[idx] = part.uv.get(Integer.parseInt(id[1]) - 1); } else { uvId[idx] = null; } } fg.face.add(new Face(verticeId, uvId, new Normal(verticeId[0], verticeId[1], verticeId[2]))); } else { Utils.println("obj assert vertexNbr != 3"); } } else if (words[0].equals("mtllib")) { mtlName = words[1]; } else if (words[0].equals("usemtl")) { fg = new FaceGroup(); fg.mtlName = words[1]; part.faceGroup.add(fg); } } } { BufferedReader bufferedReader = getResourceAsStream("model/" + dirPath + "/" + mtlName, reload); if (bufferedReader == null) { Utils.println(String.format(" - failed to load mtl '%s'", mtlName)); return false; } String line; while ((line = bufferedReader.readLine()) != null) { String[] words = line.split(" "); if (words[0].equals("newmtl")) { mtlName = words[1]; } else if (words[0].equals("map_Kd")) { for (Obj3DPart partPtr : nameToPartHash.values()) { for (FaceGroup fgroup : partPtr.faceGroup) { if (fgroup.mtlName != null && fgroup.mtlName.equals(mtlName)) fgroup.textureResource = getModelResourceLocation(words[1]); } } } } } } catch (FileNotFoundException e) { e.printStackTrace(); return false; } catch (IOException e) { e.printStackTrace(); return false; } part = null; try { final String txtPath = filePath.replace(".obj", ".txt").replace(".OBJ", ".txt"); BufferedReader bufferedReader = getResourceAsStream("model/" + txtPath, reload); if (bufferedReader == null) { Utils.println(String.format(" - failed to load txt '%s'", txtPath)); } else { String line; int lineNumber = 0; while ((line = bufferedReader.readLine()) != null) { ++lineNumber; String[] words = line.split(" "); if (words[0].startsWith("#")) { // # is a comment - ignore line. } else if (words[0].equals("o")) { part = nameToPartHash.get(words[1]); } else if (words[0].equals("f")) { if (words[1].equals("originX")) { part.ox = Float.valueOf(words[2]); } else if (words[1].equals("originY")) { part.oy = Float.valueOf(words[2]); } else if (words[1].equals("originZ")) { part.oz = Float.valueOf(words[2]); } else if (words[1].equals("originX2")) { part.ox2 = Float.valueOf(words[2]); } else if (words[1].equals("originY2")) { part.oy2 = Float.valueOf(words[2]); } else if (words[1].equals("originZ2")) { part.oz2 = Float.valueOf(words[2]); } else { part.nameToFloatHash.put(words[1], Float.valueOf(words[2])); } } else if (words[0].equals("s")) { nameToStringHash.put(words[1], words[2]); } else if (!reload && words.length == 2 && words[0].equals("r")) { int refresh = Integer.parseInt(words[1]); if (refresh != 0) { updateTimer = new Timer(); updateTimer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { Utils.println("Reloading model data from " + filePath); loadFile(filePath, true); } }, refresh, refresh); } } else { FMLLog.warning("Invalid syntax in EA model text file %1$s on line %2$d: %3$s", txtPath, lineNumber, line); } } } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } xDim = xMax - xMin; yDim = yMax - yMin; zDim = zMax - zMin; dimMax = Math.max(Math.max(xMax, yMax), zMax); dimMaxInv = 1.0f / dimMax; if (reload) { locked = false; } return true; } public Obj3DPart getPart(String part) { return nameToPartHash.get(part); } public void draw(String part) { Obj3DPart partPtr = getPart(part); if (partPtr != null) partPtr.draw(); } public String getString(String name) { return nameToStringHash.get(name); // Property read from the txt file } }