From 6712e8c48830582b005cfded5193f478ecf187e9 Mon Sep 17 00:00:00 2001 From: CrushedPixel Date: Thu, 27 Aug 2015 10:57:08 +0200 Subject: [PATCH] Locally added removed Forge Methods from 1.7 which allow us to load .obj and Techne Models --- .../client/model/AdvancedModelLoader.java | 78 +++ .../client/model/IModelCustom.java | 21 + .../client/model/IModelCustomLoader.java | 32 + .../client/model/ModelFormatException.java | 36 + .../minecraftforge/client/model/obj/Face.java | 89 +++ .../client/model/obj/GroupObject.java | 60 ++ .../client/model/obj/ObjModelLoader.java | 33 + .../client/model/obj/TextureCoordinate.java | 22 + .../client/model/obj/Vertex.java | 22 + .../client/model/obj/WavefrontObject.java | 662 ++++++++++++++++++ .../client/model/techne/TechneModel.java | 363 ++++++++++ .../model/techne/TechneModelLoader.java | 33 + 12 files changed, 1451 insertions(+) create mode 100644 src/main/java/net/minecraftforge/client/model/AdvancedModelLoader.java create mode 100644 src/main/java/net/minecraftforge/client/model/IModelCustom.java create mode 100644 src/main/java/net/minecraftforge/client/model/IModelCustomLoader.java create mode 100644 src/main/java/net/minecraftforge/client/model/ModelFormatException.java create mode 100644 src/main/java/net/minecraftforge/client/model/obj/Face.java create mode 100644 src/main/java/net/minecraftforge/client/model/obj/GroupObject.java create mode 100644 src/main/java/net/minecraftforge/client/model/obj/ObjModelLoader.java create mode 100644 src/main/java/net/minecraftforge/client/model/obj/TextureCoordinate.java create mode 100644 src/main/java/net/minecraftforge/client/model/obj/Vertex.java create mode 100644 src/main/java/net/minecraftforge/client/model/obj/WavefrontObject.java create mode 100644 src/main/java/net/minecraftforge/client/model/techne/TechneModel.java create mode 100644 src/main/java/net/minecraftforge/client/model/techne/TechneModelLoader.java diff --git a/src/main/java/net/minecraftforge/client/model/AdvancedModelLoader.java b/src/main/java/net/minecraftforge/client/model/AdvancedModelLoader.java new file mode 100644 index 00000000..89eaa96f --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/AdvancedModelLoader.java @@ -0,0 +1,78 @@ +package net.minecraftforge.client.model; + +import com.google.common.collect.Maps; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.model.obj.ObjModelLoader; +import net.minecraftforge.client.model.techne.TechneModelLoader; +import net.minecraftforge.fml.common.FMLLog; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import java.util.Collection; +import java.util.Map; + +/** + * DISCLAIMER: This class has been copied from Minecraftforge for 1.7.10 and will be removed + * once Forge for 1.8 adds support for .obj model files. + * + * Common interface for advanced model loading from files, based on file suffix + * Model support can be queried through the {@link #getSupportedSuffixes()} method. + * Instances can be created by calling {loadModel(String)} with a class-loadable-path + * + * @author cpw + */ +@SideOnly(Side.CLIENT) +public class AdvancedModelLoader { + private static Map instances = Maps.newHashMap(); + + /** + * Register a new model handler + * @param modelHandler The model handler to register + */ + public static void registerModelHandler(IModelCustomLoader modelHandler) + { + for (String suffix : modelHandler.getSuffixes()) + { + instances.put(suffix, modelHandler); + } + } + + /** + * Load the model from the supplied classpath resolvable resource name + * @param resource The resource name + * @return A model + * @throws IllegalArgumentException if the resource name cannot be understood + * @throws ModelFormatException if the underlying model handler cannot parse the model format + */ + public static IModelCustom loadModel(ResourceLocation resource) throws IllegalArgumentException, ModelFormatException + { + String name = resource.getResourcePath(); + int i = name.lastIndexOf('.'); + if (i == -1) + { + FMLLog.severe("The resource name %s is not valid", resource); + throw new IllegalArgumentException("The resource name is not valid"); + } + String suffix = name.substring(i+1); + IModelCustomLoader loader = instances.get(suffix); + if (loader == null) + { + FMLLog.severe("The resource name %s is not supported", resource); + throw new IllegalArgumentException("The resource name is not supported"); + } + + return loader.loadInstance(resource); + } + + public static Collection getSupportedSuffixes() + { + return instances.keySet(); + } + + + static + { + registerModelHandler(new ObjModelLoader()); + registerModelHandler(new TechneModelLoader()); + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/client/model/IModelCustom.java b/src/main/java/net/minecraftforge/client/model/IModelCustom.java new file mode 100644 index 00000000..dbe32346 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/IModelCustom.java @@ -0,0 +1,21 @@ +package net.minecraftforge.client.model; + +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +/** + * DISCLAIMER: This class has been copied from Minecraftforge for 1.7.10 and will be removed + * once Forge for 1.8 adds support for .obj model files. + */ +public interface IModelCustom +{ + String getType(); + @SideOnly(Side.CLIENT) + void renderAll(); + @SideOnly(Side.CLIENT) + void renderOnly(String... groupNames); + @SideOnly(Side.CLIENT) + void renderPart(String partName); + @SideOnly(Side.CLIENT) + void renderAllExcept(String... excludedGroupNames); +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/client/model/IModelCustomLoader.java b/src/main/java/net/minecraftforge/client/model/IModelCustomLoader.java new file mode 100644 index 00000000..9bff9ad7 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/IModelCustomLoader.java @@ -0,0 +1,32 @@ +package net.minecraftforge.client.model; + +import net.minecraft.util.ResourceLocation; + +/** + * DISCLAIMER: This class has been copied from Minecraftforge for 1.7.10 and will be removed + * once Forge for 1.8 adds support for .obj model files. + * + * Instances of this class act as factories for their model type + * + * @author cpw + * + */ +public interface IModelCustomLoader { + /** + * Get the main type name for this loader + * @return the type name + */ + String getType(); + /** + * Get resource suffixes this model loader recognizes + * @return a list of suffixes + */ + String[] getSuffixes(); + /** + * Load a model instance from the supplied path + * @param resource The ResourceLocation of the model + * @return A model instance + * @throws ModelFormatException if the model format is not correct + */ + IModelCustom loadInstance(ResourceLocation resource) throws ModelFormatException; +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/client/model/ModelFormatException.java b/src/main/java/net/minecraftforge/client/model/ModelFormatException.java new file mode 100644 index 00000000..d47a0ec1 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/ModelFormatException.java @@ -0,0 +1,36 @@ +package net.minecraftforge.client.model; + +/** + * DISCLAIMER: This class has been copied from Minecraftforge for 1.7.10 and will be removed + * once Forge for 1.8 adds support for .obj model files. + * + * Thrown if there is a problem parsing the model + * + * @author cpw + * + */ +public class ModelFormatException extends RuntimeException { + + private static final long serialVersionUID = 2023547503969671835L; + + public ModelFormatException() + { + super(); + } + + public ModelFormatException(String message, Throwable cause) + { + super(message, cause); + } + + public ModelFormatException(String message) + { + super(message); + } + + public ModelFormatException(Throwable cause) + { + super(cause); + } + +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/client/model/obj/Face.java b/src/main/java/net/minecraftforge/client/model/obj/Face.java new file mode 100644 index 00000000..bd28042f --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/obj/Face.java @@ -0,0 +1,89 @@ +package net.minecraftforge.client.model.obj; + +import net.minecraft.client.renderer.WorldRenderer; +import net.minecraft.util.Vec3; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +/** + * DISCLAIMER: This class has been copied from Minecraftforge for 1.7.10 and will be removed + * once Forge for 1.8 adds support for .obj model files. + */ +public class Face +{ + public Vertex[] vertices; + public Vertex[] vertexNormals; + public Vertex faceNormal; + public TextureCoordinate[] textureCoordinates; + + @SideOnly(Side.CLIENT) + public void addFaceForRender(WorldRenderer worldRenderer) + { + addFaceForRender(worldRenderer, 0.0005F); + } + + @SideOnly(Side.CLIENT) + public void addFaceForRender(WorldRenderer worldRenderer, float textureOffset) + { + if (faceNormal == null) + { + faceNormal = this.calculateFaceNormal(); + } + + worldRenderer.setNormal(faceNormal.x, faceNormal.y, faceNormal.z); + + float averageU = 0F; + float averageV = 0F; + + if ((textureCoordinates != null) && (textureCoordinates.length > 0)) + { + for (int i = 0; i < textureCoordinates.length; ++i) + { + averageU += textureCoordinates[i].u; + averageV += textureCoordinates[i].v; + } + + averageU = averageU / textureCoordinates.length; + averageV = averageV / textureCoordinates.length; + } + + float offsetU, offsetV; + + for (int i = 0; i < vertices.length; ++i) + { + + if ((textureCoordinates != null) && (textureCoordinates.length > 0)) + { + offsetU = textureOffset; + offsetV = textureOffset; + + if (textureCoordinates[i].u > averageU) + { + offsetU = -offsetU; + } + if (textureCoordinates[i].v > averageV) + { + offsetV = -offsetV; + } + + worldRenderer.addVertexWithUV(vertices[i].x, vertices[i].y, vertices[i].z, textureCoordinates[i].u + offsetU, + textureCoordinates[i].v + offsetV); + } + else + { + worldRenderer.addVertex(vertices[i].x, vertices[i].y, vertices[i].z); + } + } + } + + public Vertex calculateFaceNormal() + { + Vec3 v1 = new Vec3(vertices[1].x - vertices[0].x, vertices[1].y - vertices[0].y, vertices[1].z - vertices[0].z); + Vec3 v2 = new Vec3(vertices[2].x - vertices[0].x, vertices[2].y - vertices[0].y, vertices[2].z - vertices[0].z); + Vec3 normalVector = null; + + normalVector = v1.crossProduct(v2).normalize(); + + return new Vertex((float) normalVector.xCoord, (float) normalVector.yCoord, (float) normalVector.zCoord); + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/client/model/obj/GroupObject.java b/src/main/java/net/minecraftforge/client/model/obj/GroupObject.java new file mode 100644 index 00000000..159c6e58 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/obj/GroupObject.java @@ -0,0 +1,60 @@ +package net.minecraftforge.client.model.obj; + +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.WorldRenderer; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import java.util.ArrayList; + +/** + * DISCLAIMER: This class has been copied from Minecraftforge for 1.7.10 and will be removed + * once Forge for 1.8 adds support for .obj model files. + */ +public class GroupObject +{ + public String name; + public ArrayList faces = new ArrayList(); + public int glDrawingMode; + + public GroupObject() + { + this(""); + } + + public GroupObject(String name) + { + this(name, -1); + } + + public GroupObject(String name, int glDrawingMode) + { + this.name = name; + this.glDrawingMode = glDrawingMode; + } + + @SideOnly(Side.CLIENT) + public void render() + { + if (faces.size() > 0) + { + Tessellator tessellator = Tessellator.getInstance(); + WorldRenderer worldRenderer = tessellator.getWorldRenderer(); + worldRenderer.startDrawing(glDrawingMode); + render(worldRenderer); + tessellator.draw(); + } + } + + @SideOnly(Side.CLIENT) + public void render(WorldRenderer worldRenderer) + { + if (faces.size() > 0) + { + for (Face face : faces) + { + face.addFaceForRender(worldRenderer); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/client/model/obj/ObjModelLoader.java b/src/main/java/net/minecraftforge/client/model/obj/ObjModelLoader.java new file mode 100644 index 00000000..dd5a079b --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/obj/ObjModelLoader.java @@ -0,0 +1,33 @@ +package net.minecraftforge.client.model.obj; + +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.model.IModelCustom; +import net.minecraftforge.client.model.IModelCustomLoader; +import net.minecraftforge.client.model.ModelFormatException; + +/** + * DISCLAIMER: This class has been copied from Minecraftforge for 1.7.10 and will be removed + * once Forge for 1.8 adds support for .obj model files. + */ +public class ObjModelLoader implements IModelCustomLoader +{ + + @Override + public String getType() + { + return "OBJ model"; + } + + private static final String[] types = { "obj" }; + @Override + public String[] getSuffixes() + { + return types; + } + + @Override + public IModelCustom loadInstance(ResourceLocation resource) throws ModelFormatException + { + return new WavefrontObject(resource); + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/client/model/obj/TextureCoordinate.java b/src/main/java/net/minecraftforge/client/model/obj/TextureCoordinate.java new file mode 100644 index 00000000..6018c545 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/obj/TextureCoordinate.java @@ -0,0 +1,22 @@ +package net.minecraftforge.client.model.obj; + +/** + * DISCLAIMER: This class has been copied from Minecraftforge for 1.7.10 and will be removed + * once Forge for 1.8 adds support for .obj model files. + */ +public class TextureCoordinate +{ + public float u, v, w; + + public TextureCoordinate(float u, float v) + { + this(u, v, 0F); + } + + public TextureCoordinate(float u, float v, float w) + { + this.u = u; + this.v = v; + this.w = w; + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/client/model/obj/Vertex.java b/src/main/java/net/minecraftforge/client/model/obj/Vertex.java new file mode 100644 index 00000000..7fb0c630 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/obj/Vertex.java @@ -0,0 +1,22 @@ +package net.minecraftforge.client.model.obj; + +/** + * DISCLAIMER: This class has been copied from Minecraftforge for 1.7.10 and will be removed + * once Forge for 1.8 adds support for .obj model files. + */ +public class Vertex +{ + public float x, y, z; + + public Vertex(float x, float y) + { + this(x, y, 0F); + } + + public Vertex(float x, float y, float z) + { + this.x = x; + this.y = y; + this.z = z; + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/client/model/obj/WavefrontObject.java b/src/main/java/net/minecraftforge/client/model/obj/WavefrontObject.java new file mode 100644 index 00000000..c85864f3 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/obj/WavefrontObject.java @@ -0,0 +1,662 @@ +package net.minecraftforge.client.model.obj; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.WorldRenderer; +import net.minecraft.client.resources.IResource; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.model.IModelCustom; +import net.minecraftforge.client.model.ModelFormatException; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import org.lwjgl.opengl.GL11; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * DISCLAIMER: This class has been copied from Minecraftforge for 1.7.10 and will be removed + * once Forge for 1.8 adds support for .obj model files. + * + * Wavefront Object importer + * Based heavily off of the specifications found at http://en.wikipedia.org/wiki/Wavefront_.obj_file + */ +public class WavefrontObject implements IModelCustom +{ + private static Pattern vertexPattern = Pattern.compile("(v( (\\-){0,1}\\d+\\.\\d+){3,4} *\\n)|(v( (\\-){0,1}\\d+\\.\\d+){3,4} *$)"); + private static Pattern vertexNormalPattern = Pattern.compile("(vn( (\\-){0,1}\\d+\\.\\d+){3,4} *\\n)|(vn( (\\-){0,1}\\d+\\.\\d+){3,4} *$)"); + private static Pattern textureCoordinatePattern = Pattern.compile("(vt( (\\-){0,1}\\d+\\.\\d+){2,3} *\\n)|(vt( (\\-){0,1}\\d+\\.\\d+){2,3} *$)"); + private static Pattern face_V_VT_VN_Pattern = Pattern.compile("(f( \\d+/\\d+/\\d+){3,4} *\\n)|(f( \\d+/\\d+/\\d+){3,4} *$)"); + private static Pattern face_V_VT_Pattern = Pattern.compile("(f( \\d+/\\d+){3,4} *\\n)|(f( \\d+/\\d+){3,4} *$)"); + private static Pattern face_V_VN_Pattern = Pattern.compile("(f( \\d+//\\d+){3,4} *\\n)|(f( \\d+//\\d+){3,4} *$)"); + private static Pattern face_V_Pattern = Pattern.compile("(f( \\d+){3,4} *\\n)|(f( \\d+){3,4} *$)"); + private static Pattern groupObjectPattern = Pattern.compile("([go]( [\\w\\d]+) *\\n)|([go]( [\\w\\d]+) *$)"); + + private static Matcher vertexMatcher, vertexNormalMatcher, textureCoordinateMatcher; + private static Matcher face_V_VT_VN_Matcher, face_V_VT_Matcher, face_V_VN_Matcher, face_V_Matcher; + private static Matcher groupObjectMatcher; + + public ArrayList vertices = new ArrayList(); + public ArrayList vertexNormals = new ArrayList(); + public ArrayList textureCoordinates = new ArrayList(); + public ArrayList groupObjects = new ArrayList(); + private GroupObject currentGroupObject; + private String fileName; + + public WavefrontObject(ResourceLocation resource) throws ModelFormatException + { + this.fileName = resource.toString(); + + try + { + IResource res = Minecraft.getMinecraft().getResourceManager().getResource(resource); + loadObjModel(res.getInputStream()); + } + catch (IOException e) + { + throw new ModelFormatException("IO Exception reading model format", e); + } + } + + public WavefrontObject(String filename, InputStream inputStream) throws ModelFormatException + { + this.fileName = filename; + loadObjModel(inputStream); + } + + private void loadObjModel(InputStream inputStream) throws ModelFormatException + { + BufferedReader reader = null; + + String currentLine = null; + int lineCount = 0; + + try + { + reader = new BufferedReader(new InputStreamReader(inputStream)); + + while ((currentLine = reader.readLine()) != null) + { + lineCount++; + currentLine = currentLine.replaceAll("\\s+", " ").trim(); + + if (currentLine.startsWith("#") || currentLine.length() == 0) + { + continue; + } + else if (currentLine.startsWith("v ")) + { + Vertex vertex = parseVertex(currentLine, lineCount); + if (vertex != null) + { + vertices.add(vertex); + } + } + else if (currentLine.startsWith("vn ")) + { + Vertex vertex = parseVertexNormal(currentLine, lineCount); + if (vertex != null) + { + vertexNormals.add(vertex); + } + } + else if (currentLine.startsWith("vt ")) + { + TextureCoordinate textureCoordinate = parseTextureCoordinate(currentLine, lineCount); + if (textureCoordinate != null) + { + textureCoordinates.add(textureCoordinate); + } + } + else if (currentLine.startsWith("f ")) + { + + if (currentGroupObject == null) + { + currentGroupObject = new GroupObject("Default"); + } + + Face face = parseFace(currentLine, lineCount); + + if (face != null) + { + currentGroupObject.faces.add(face); + } + } + else if (currentLine.startsWith("g ") | currentLine.startsWith("o ")) + { + GroupObject group = parseGroupObject(currentLine, lineCount); + + if (group != null) + { + if (currentGroupObject != null) + { + groupObjects.add(currentGroupObject); + } + } + + currentGroupObject = group; + } + } + + groupObjects.add(currentGroupObject); + } + catch (IOException e) + { + throw new ModelFormatException("IO Exception reading model format", e); + } + finally + { + try + { + reader.close(); + } + catch (IOException e) + { + // hush + } + + try + { + inputStream.close(); + } + catch (IOException e) + { + // hush + } + } + } + + @Override + @SideOnly(Side.CLIENT) + public void renderAll() + { + Tessellator tessellator = Tessellator.getInstance(); + WorldRenderer worldRenderer = tessellator.getWorldRenderer(); + if (currentGroupObject != null) + { + worldRenderer.startDrawing(currentGroupObject.glDrawingMode); + } + else + { + worldRenderer.startDrawing(GL11.GL_TRIANGLES); + } + tessellateAll(worldRenderer); + + tessellator.draw(); + } + + @SideOnly(Side.CLIENT) + public void tessellateAll(WorldRenderer worldRenderer) + { + for (GroupObject groupObject : groupObjects) + { + groupObject.render(worldRenderer); + } + } + + @Override + @SideOnly(Side.CLIENT) + public void renderOnly(String... groupNames) + { + for (GroupObject groupObject : groupObjects) + { + for (String groupName : groupNames) + { + if (groupName.equalsIgnoreCase(groupObject.name)) + { + groupObject.render(); + } + } + } + } + + @SideOnly(Side.CLIENT) + public void tessellateOnly(WorldRenderer worldRenderer, String... groupNames) { + for (GroupObject groupObject : groupObjects) + { + for (String groupName : groupNames) + { + if (groupName.equalsIgnoreCase(groupObject.name)) + { + groupObject.render(worldRenderer); + } + } + } + } + + @Override + @SideOnly(Side.CLIENT) + public void renderPart(String partName) + { + for (GroupObject groupObject : groupObjects) + { + if (partName.equalsIgnoreCase(groupObject.name)) + { + groupObject.render(); + } + } + } + + @SideOnly(Side.CLIENT) + public void tessellatePart(WorldRenderer worldRenderer, String partName) { + for (GroupObject groupObject : groupObjects) + { + if (partName.equalsIgnoreCase(groupObject.name)) + { + groupObject.render(worldRenderer); + } + } + } + + @Override + @SideOnly(Side.CLIENT) + public void renderAllExcept(String... excludedGroupNames) + { + for (GroupObject groupObject : groupObjects) + { + boolean skipPart=false; + for (String excludedGroupName : excludedGroupNames) + { + if (excludedGroupName.equalsIgnoreCase(groupObject.name)) + { + skipPart=true; + } + } + if(!skipPart) + { + groupObject.render(); + } + } + } + + @SideOnly(Side.CLIENT) + public void tessellateAllExcept(WorldRenderer worldRenderer, String... excludedGroupNames) + { + boolean exclude; + for (GroupObject groupObject : groupObjects) + { + exclude=false; + for (String excludedGroupName : excludedGroupNames) + { + if (excludedGroupName.equalsIgnoreCase(groupObject.name)) + { + exclude=true; + } + } + if(!exclude) + { + groupObject.render(worldRenderer); + } + } + } + + private Vertex parseVertex(String line, int lineCount) throws ModelFormatException + { + Vertex vertex = null; + + if (isValidVertexLine(line)) + { + line = line.substring(line.indexOf(" ") + 1); + String[] tokens = line.split(" "); + + try + { + if (tokens.length == 2) + { + return new Vertex(Float.parseFloat(tokens[0]), Float.parseFloat(tokens[1])); + } + else if (tokens.length == 3) + { + return new Vertex(Float.parseFloat(tokens[0]), Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2])); + } + } + catch (NumberFormatException e) + { + throw new ModelFormatException(String.format("Number formatting error at line %d",lineCount), e); + } + } + else + { + throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Incorrect format"); + } + + return vertex; + } + + private Vertex parseVertexNormal(String line, int lineCount) throws ModelFormatException + { + Vertex vertexNormal = null; + + if (isValidVertexNormalLine(line)) + { + line = line.substring(line.indexOf(" ") + 1); + String[] tokens = line.split(" "); + + try + { + if (tokens.length == 3) + return new Vertex(Float.parseFloat(tokens[0]), Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2])); + } + catch (NumberFormatException e) + { + throw new ModelFormatException(String.format("Number formatting error at line %d",lineCount), e); + } + } + else + { + throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Incorrect format"); + } + + return vertexNormal; + } + + private TextureCoordinate parseTextureCoordinate(String line, int lineCount) throws ModelFormatException + { + TextureCoordinate textureCoordinate = null; + + if (isValidTextureCoordinateLine(line)) + { + line = line.substring(line.indexOf(" ") + 1); + String[] tokens = line.split(" "); + + try + { + if (tokens.length == 2) + return new TextureCoordinate(Float.parseFloat(tokens[0]), 1 - Float.parseFloat(tokens[1])); + else if (tokens.length == 3) + return new TextureCoordinate(Float.parseFloat(tokens[0]), 1 - Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2])); + } + catch (NumberFormatException e) + { + throw new ModelFormatException(String.format("Number formatting error at line %d",lineCount), e); + } + } + else + { + throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Incorrect format"); + } + + return textureCoordinate; + } + + private Face parseFace(String line, int lineCount) throws ModelFormatException + { + Face face = null; + + if (isValidFaceLine(line)) + { + face = new Face(); + + String trimmedLine = line.substring(line.indexOf(" ") + 1); + String[] tokens = trimmedLine.split(" "); + String[] subTokens = null; + + if (tokens.length == 3) + { + if (currentGroupObject.glDrawingMode == -1) + { + currentGroupObject.glDrawingMode = GL11.GL_TRIANGLES; + } + else if (currentGroupObject.glDrawingMode != GL11.GL_TRIANGLES) + { + throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Invalid number of points for face (expected 4, found " + tokens.length + ")"); + } + } + else if (tokens.length == 4) + { + if (currentGroupObject.glDrawingMode == -1) + { + currentGroupObject.glDrawingMode = GL11.GL_QUADS; + } + else if (currentGroupObject.glDrawingMode != GL11.GL_QUADS) + { + throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Invalid number of points for face (expected 3, found " + tokens.length + ")"); + } + } + + // f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 ... + if (isValidFace_V_VT_VN_Line(line)) + { + face.vertices = new Vertex[tokens.length]; + face.textureCoordinates = new TextureCoordinate[tokens.length]; + face.vertexNormals = new Vertex[tokens.length]; + + for (int i = 0; i < tokens.length; ++i) + { + subTokens = tokens[i].split("/"); + + face.vertices[i] = vertices.get(Integer.parseInt(subTokens[0]) - 1); + face.textureCoordinates[i] = textureCoordinates.get(Integer.parseInt(subTokens[1]) - 1); + face.vertexNormals[i] = vertexNormals.get(Integer.parseInt(subTokens[2]) - 1); + } + + face.faceNormal = face.calculateFaceNormal(); + } + // f v1/vt1 v2/vt2 v3/vt3 ... + else if (isValidFace_V_VT_Line(line)) + { + face.vertices = new Vertex[tokens.length]; + face.textureCoordinates = new TextureCoordinate[tokens.length]; + + for (int i = 0; i < tokens.length; ++i) + { + subTokens = tokens[i].split("/"); + + face.vertices[i] = vertices.get(Integer.parseInt(subTokens[0]) - 1); + face.textureCoordinates[i] = textureCoordinates.get(Integer.parseInt(subTokens[1]) - 1); + } + + face.faceNormal = face.calculateFaceNormal(); + } + // f v1//vn1 v2//vn2 v3//vn3 ... + else if (isValidFace_V_VN_Line(line)) + { + face.vertices = new Vertex[tokens.length]; + face.vertexNormals = new Vertex[tokens.length]; + + for (int i = 0; i < tokens.length; ++i) + { + subTokens = tokens[i].split("//"); + + face.vertices[i] = vertices.get(Integer.parseInt(subTokens[0]) - 1); + face.vertexNormals[i] = vertexNormals.get(Integer.parseInt(subTokens[1]) - 1); + } + + face.faceNormal = face.calculateFaceNormal(); + } + // f v1 v2 v3 ... + else if (isValidFace_V_Line(line)) + { + face.vertices = new Vertex[tokens.length]; + + for (int i = 0; i < tokens.length; ++i) + { + face.vertices[i] = vertices.get(Integer.parseInt(tokens[i]) - 1); + } + + face.faceNormal = face.calculateFaceNormal(); + } + else + { + throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Incorrect format"); + } + } + else + { + throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Incorrect format"); + } + + return face; + } + + private GroupObject parseGroupObject(String line, int lineCount) throws ModelFormatException + { + GroupObject group = null; + + if (isValidGroupObjectLine(line)) + { + String trimmedLine = line.substring(line.indexOf(" ") + 1); + + if (trimmedLine.length() > 0) + { + group = new GroupObject(trimmedLine); + } + } + else + { + throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Incorrect format"); + } + + return group; + } + + /*** + * Verifies that the given line from the model file is a valid vertex + * @param line the line being validated + * @return true if the line is a valid vertex, false otherwise + */ + private static boolean isValidVertexLine(String line) + { + if (vertexMatcher != null) + { + vertexMatcher.reset(); + } + + vertexMatcher = vertexPattern.matcher(line); + return vertexMatcher.matches(); + } + + /*** + * Verifies that the given line from the model file is a valid vertex normal + * @param line the line being validated + * @return true if the line is a valid vertex normal, false otherwise + */ + private static boolean isValidVertexNormalLine(String line) + { + if (vertexNormalMatcher != null) + { + vertexNormalMatcher.reset(); + } + + vertexNormalMatcher = vertexNormalPattern.matcher(line); + return vertexNormalMatcher.matches(); + } + + /*** + * Verifies that the given line from the model file is a valid texture coordinate + * @param line the line being validated + * @return true if the line is a valid texture coordinate, false otherwise + */ + private static boolean isValidTextureCoordinateLine(String line) + { + if (textureCoordinateMatcher != null) + { + textureCoordinateMatcher.reset(); + } + + textureCoordinateMatcher = textureCoordinatePattern.matcher(line); + return textureCoordinateMatcher.matches(); + } + + /*** + * Verifies that the given line from the model file is a valid face that is described by vertices, texture coordinates, and vertex normals + * @param line the line being validated + * @return true if the line is a valid face that matches the format "f v1/vt1/vn1 ..." (with a minimum of 3 points in the face, and a maximum of 4), false otherwise + */ + private static boolean isValidFace_V_VT_VN_Line(String line) + { + if (face_V_VT_VN_Matcher != null) + { + face_V_VT_VN_Matcher.reset(); + } + + face_V_VT_VN_Matcher = face_V_VT_VN_Pattern.matcher(line); + return face_V_VT_VN_Matcher.matches(); + } + + /*** + * Verifies that the given line from the model file is a valid face that is described by vertices and texture coordinates + * @param line the line being validated + * @return true if the line is a valid face that matches the format "f v1/vt1 ..." (with a minimum of 3 points in the face, and a maximum of 4), false otherwise + */ + private static boolean isValidFace_V_VT_Line(String line) + { + if (face_V_VT_Matcher != null) + { + face_V_VT_Matcher.reset(); + } + + face_V_VT_Matcher = face_V_VT_Pattern.matcher(line); + return face_V_VT_Matcher.matches(); + } + + /*** + * Verifies that the given line from the model file is a valid face that is described by vertices and vertex normals + * @param line the line being validated + * @return true if the line is a valid face that matches the format "f v1//vn1 ..." (with a minimum of 3 points in the face, and a maximum of 4), false otherwise + */ + private static boolean isValidFace_V_VN_Line(String line) + { + if (face_V_VN_Matcher != null) + { + face_V_VN_Matcher.reset(); + } + + face_V_VN_Matcher = face_V_VN_Pattern.matcher(line); + return face_V_VN_Matcher.matches(); + } + + /*** + * Verifies that the given line from the model file is a valid face that is described by only vertices + * @param line the line being validated + * @return true if the line is a valid face that matches the format "f v1 ..." (with a minimum of 3 points in the face, and a maximum of 4), false otherwise + */ + private static boolean isValidFace_V_Line(String line) + { + if (face_V_Matcher != null) + { + face_V_Matcher.reset(); + } + + face_V_Matcher = face_V_Pattern.matcher(line); + return face_V_Matcher.matches(); + } + + /*** + * Verifies that the given line from the model file is a valid face of any of the possible face formats + * @param line the line being validated + * @return true if the line is a valid face that matches any of the valid face formats, false otherwise + */ + private static boolean isValidFaceLine(String line) + { + return isValidFace_V_VT_VN_Line(line) || isValidFace_V_VT_Line(line) || isValidFace_V_VN_Line(line) || isValidFace_V_Line(line); + } + + /*** + * Verifies that the given line from the model file is a valid group (or object) + * @param line the line being validated + * @return true if the line is a valid group (or object), false otherwise + */ + private static boolean isValidGroupObjectLine(String line) + { + if (groupObjectMatcher != null) + { + groupObjectMatcher.reset(); + } + + groupObjectMatcher = groupObjectPattern.matcher(line); + return groupObjectMatcher.matches(); + } + + @Override + public String getType() + { + return "obj"; + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/client/model/techne/TechneModel.java b/src/main/java/net/minecraftforge/client/model/techne/TechneModel.java new file mode 100644 index 00000000..da021628 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/techne/TechneModel.java @@ -0,0 +1,363 @@ +package net.minecraftforge.client.model.techne; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.model.ModelBase; +import net.minecraft.client.model.ModelRenderer; +import net.minecraft.client.resources.IResource; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.model.IModelCustom; +import net.minecraftforge.client.model.ModelFormatException; +import net.minecraftforge.fml.common.FMLLog; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.awt.*; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipInputStream; + +/** + * DISCLAIMER: This class has been copied from Minecraftforge for 1.7.10 and will be removed + * once Forge for 1.8 adds support for .obj model files. + * + * Techne model importer, based on iChun's Hats importer + */ +@SideOnly(Side.CLIENT) +public class TechneModel extends ModelBase implements IModelCustom { + public static final List cubeTypes = Arrays.asList( + "d9e621f7-957f-4b77-b1ae-20dcd0da7751", + "de81aa14-bd60-4228-8d8d-5238bcd3caaa" + ); + + private String fileName; + private Map zipContents = new HashMap(); + + private Map parts = new LinkedHashMap(); + private String texture = null; + private Dimension textureDims = null; + private int textureName; + private boolean textureNameSet = false; + + public TechneModel(ResourceLocation resource) throws ModelFormatException + { + this.fileName = resource.toString(); + + try + { + IResource res = Minecraft.getMinecraft().getResourceManager().getResource(resource); + loadTechneModel(res.getInputStream()); + } + catch (IOException e) + { + throw new ModelFormatException("IO Exception reading model format", e); + } + } + + private void loadTechneModel(InputStream stream) throws ModelFormatException + { + try + { + ZipInputStream zipInput = new ZipInputStream(stream); + + ZipEntry entry; + while ((entry = zipInput.getNextEntry()) != null) + { + byte[] data = new byte[(int) entry.getSize()]; + // For some reason, using read(byte[]) makes reading stall upon reaching a 0x1E byte + int i = 0; + while (zipInput.available() > 0 && i < data.length) + { + data[i++] = (byte)zipInput.read(); + } + zipContents.put(entry.getName(), data); + } + + byte[] modelXml = zipContents.get("model.xml"); + if (modelXml == null) + { + throw new ModelFormatException("Model " + fileName + " contains no model.xml file"); + } + + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document document = documentBuilder.parse(new ByteArrayInputStream(modelXml)); + + NodeList nodeListTechne = document.getElementsByTagName("Techne"); + if (nodeListTechne.getLength() < 1) + { + throw new ModelFormatException("Model " + fileName + " contains no Techne tag"); + } + + NodeList nodeListModel = document.getElementsByTagName("Model"); + if (nodeListModel.getLength() < 1) + { + throw new ModelFormatException("Model " + fileName + " contains no Model tag"); + } + + NamedNodeMap modelAttributes = nodeListModel.item(0).getAttributes(); + if (modelAttributes == null) + { + throw new ModelFormatException("Model " + fileName + " contains a Model tag with no attributes"); + } + + Node modelTexture = modelAttributes.getNamedItem("texture"); + if (modelTexture != null) + { + texture = modelTexture.getTextContent(); + } + + NodeList textureDim = document.getElementsByTagName("TextureSize"); + if (textureDim.getLength() > 0) + { + try + { + String[] tmp = textureDim.item(0).getTextContent().split(","); + if (tmp.length == 2) + { + this.textureDims = new Dimension(Integer.parseInt(tmp[0]), Integer.parseInt(tmp[1])); + } + } + catch (NumberFormatException e) + { + throw new ModelFormatException("Model " + fileName + " contains a TextureSize tag with invalid data"); + } + } + + NodeList shapes = document.getElementsByTagName("Shape"); + for (int i = 0; i < shapes.getLength(); i++) + { + Node shape = shapes.item(i); + NamedNodeMap shapeAttributes = shape.getAttributes(); + if (shapeAttributes == null) + { + throw new ModelFormatException("Shape #" + (i + 1) + " in " + fileName + " has no attributes"); + } + + Node name = shapeAttributes.getNamedItem("name"); + String shapeName = null; + if (name != null) + { + shapeName = name.getNodeValue(); + } + if (shapeName == null) + { + shapeName = "Shape #" + (i + 1); + } + + String shapeType = null; + Node type = shapeAttributes.getNamedItem("type"); + if (type != null) + { + shapeType = type.getNodeValue(); + } + if (shapeType != null && !cubeTypes.contains(shapeType)) + { + FMLLog.warning("Model shape [" + shapeName + "] in " + fileName + " is not a cube, ignoring"); + continue; + } + + try + { + boolean mirrored = false; + String[] offset = new String[3]; + String[] position = new String[3]; + String[] rotation = new String[3]; + String[] size = new String[3]; + String[] textureOffset = new String[2]; + + NodeList shapeChildren = shape.getChildNodes(); + for (int j = 0; j < shapeChildren.getLength(); j++) + { + Node shapeChild = shapeChildren.item(j); + + String shapeChildName = shapeChild.getNodeName(); + String shapeChildValue = shapeChild.getTextContent(); + if (shapeChildValue != null) + { + shapeChildValue = shapeChildValue.trim(); + + if (shapeChildName.equals("IsMirrored")) + { + mirrored = !shapeChildValue.equals("False"); + } + else if (shapeChildName.equals("Offset")) + { + offset = shapeChildValue.split(","); + } + else if (shapeChildName.equals("Position")) + { + position = shapeChildValue.split(","); + } + else if (shapeChildName.equals("Rotation")) + { + rotation = shapeChildValue.split(","); + } + else if (shapeChildName.equals("Size")) + { + size = shapeChildValue.split(","); + } + else if (shapeChildName.equals("TextureOffset")) + { + textureOffset = shapeChildValue.split(","); + } + } + } + + // That's what the ModelBase subclassing is needed for + ModelRenderer cube = new ModelRenderer(this, Integer.parseInt(textureOffset[0]), Integer.parseInt(textureOffset[1])); + cube.mirror = mirrored; + cube.addBox(Float.parseFloat(offset[0]), Float.parseFloat(offset[1]), Float.parseFloat(offset[2]), Integer.parseInt(size[0]), Integer.parseInt(size[1]), Integer.parseInt(size[2])); + cube.setRotationPoint(Float.parseFloat(position[0]), Float.parseFloat(position[1]) - 23.4F, Float.parseFloat(position[2])); + + cube.rotateAngleX = (float)Math.toRadians(Float.parseFloat(rotation[0])); + cube.rotateAngleY = (float)Math.toRadians(Float.parseFloat(rotation[1])); + cube.rotateAngleZ = (float)Math.toRadians(Float.parseFloat(rotation[2])); + + if (this.textureDims != null) + { + cube.setTextureSize((int)textureDims.getWidth(), (int)textureDims.getHeight()); + } + + parts.put(shapeName, cube); + } + catch (NumberFormatException e) + { + FMLLog.warning("Model shape [" + shapeName + "] in " + fileName + " contains malformed integers within its data, ignoring"); + e.printStackTrace(); + } + } + } + catch (ZipException e) + { + throw new ModelFormatException("Model " + fileName + " is not a valid zip file"); + } + catch (IOException e) + { + throw new ModelFormatException("Model " + fileName + " could not be read", e); + } + catch (ParserConfigurationException e) + { + // hush + } + catch (SAXException e) + { + throw new ModelFormatException("Model " + fileName + " contains invalid XML", e); + } + } + + private void bindTexture() + { + /* + if (texture != null) + { + if (!textureNameSet) + { + try + { + byte[] textureEntry = zipContents.get(texture); + if (textureEntry == null) + { + throw new ModelFormatException("Model " + fileName + " has no such texture " + texture); + } + + BufferedImage image = ImageIO.read(new ByteArrayInputStream(textureEntry)); + textureName = Minecraft.getMinecraft().renderEngine.allocateAndSetupTexture(image); + textureNameSet = true; + } + catch (ZipException e) + { + throw new ModelFormatException("Model " + fileName + " is not a valid zip file"); + } + catch (IOException e) + { + throw new ModelFormatException("Texture for model " + fileName + " could not be read", e); + } + } + + if (textureNameSet) + { + GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureName); + Minecraft.getMinecraft().renderEngine.resetBoundTexture(); + } + } + */ + } + + @Override + public String getType() + { + return "tcn"; + } + + @Override + public void renderAll() + { + bindTexture(); + + for (ModelRenderer part : parts.values()) + { + part.renderWithRotation(1.0F); + } + } + + @Override + public void renderPart(String partName) + { + ModelRenderer part = parts.get(partName); + if (part != null) + { + bindTexture(); + + part.renderWithRotation(1.0F); + } + } + + @Override + public void renderOnly(String... groupNames) + { + bindTexture(); + for (ModelRenderer part : parts.values()) + { + for (String groupName : groupNames) + { + if (groupName.equalsIgnoreCase(part.boxName)) + { + part.render(1.0f); + } + } + } + } + + @Override + public void renderAllExcept(String... excludedGroupNames) + { + for (ModelRenderer part : parts.values()) + { + boolean skipPart=false; + for (String excludedGroupName : excludedGroupNames) + { + if (excludedGroupName.equalsIgnoreCase(part.boxName)) + { + skipPart=true; + } + } + if(!skipPart) + { + part.render(1.0f); + } + } + } +} diff --git a/src/main/java/net/minecraftforge/client/model/techne/TechneModelLoader.java b/src/main/java/net/minecraftforge/client/model/techne/TechneModelLoader.java new file mode 100644 index 00000000..ad8eff4a --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/techne/TechneModelLoader.java @@ -0,0 +1,33 @@ +package net.minecraftforge.client.model.techne; + +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.model.IModelCustom; +import net.minecraftforge.client.model.IModelCustomLoader; +import net.minecraftforge.client.model.ModelFormatException; + +/** + * DISCLAIMER: This class has been copied from Minecraftforge for 1.7.10 and will be removed + * once Forge for 1.8 adds support for .obj model files. + */ +public class TechneModelLoader implements IModelCustomLoader { + + @Override + public String getType() + { + return "Techne model"; + } + + private static final String[] types = { "tcn" }; + @Override + public String[] getSuffixes() + { + return types; + } + + @Override + public IModelCustom loadInstance(ResourceLocation resource) throws ModelFormatException + { + return new TechneModel(resource); + } + +} \ No newline at end of file