    001package net.minecraftforge.client.model.obj;
    002
    003import java.io.BufferedReader;
    004import java.io.IOException;
    005import java.io.InputStream;
    006import java.io.InputStreamReader;
    007import java.net.URL;
    008import java.util.ArrayList;
    009import java.util.regex.Matcher;
    010import java.util.regex.Pattern;
    011
    012import net.minecraft.client.renderer.Tessellator;
    013import net.minecraftforge.client.model.IModelCustom;
    014import net.minecraftforge.client.model.ModelFormatException;
    015
    016import org.lwjgl.opengl.GL11;
    017
    018import cpw.mods.fml.relauncher.Side;
    019import cpw.mods.fml.relauncher.SideOnly;
    020
    021/**
    022 *  Wavefront Object importer
    023 *  Based heavily off of the specifications found at http://en.wikipedia.org/wiki/Wavefront_.obj_file
    024 */
    025@SideOnly(Side.CLIENT)
    026public class WavefrontObject implements IModelCustom
    027{
    028
    029    private static Pattern vertexPattern = Pattern.compile("(v( (\\-){0,1}\\d+\\.\\d+){3,4} *\\n)|(v( (\\-){0,1}\\d+\\.\\d+){3,4} *$)");
    030    private static Pattern vertexNormalPattern = Pattern.compile("(vn( (\\-){0,1}\\d+\\.\\d+){3,4} *\\n)|(vn( (\\-){0,1}\\d+\\.\\d+){3,4} *$)");
    031    private static Pattern textureCoordinatePattern = Pattern.compile("(vt( (\\-){0,1}\\d+\\.\\d+){2,3} *\\n)|(vt( (\\-){0,1}\\d+\\.\\d+){2,3} *$)");
    032    private static Pattern face_V_VT_VN_Pattern = Pattern.compile("(f( \\d+/\\d+/\\d+){3,4} *\\n)|(f( \\d+/\\d+/\\d+){3,4} *$)");
    033    private static Pattern face_V_VT_Pattern = Pattern.compile("(f( \\d+/\\d+){3,4} *\\n)|(f( \\d+/\\d+){3,4} *$)");
    034    private static Pattern face_V_VN_Pattern = Pattern.compile("(f( \\d+//\\d+){3,4} *\\n)|(f( \\d+//\\d+){3,4} *$)");
    035    private static Pattern face_V_Pattern = Pattern.compile("(f( \\d+){3,4} *\\n)|(f( \\d+){3,4} *$)");
    036    private static Pattern groupObjectPattern = Pattern.compile("([go]( [\\w\\d]+) *\\n)|([go]( [\\w\\d]+) *$)");
    037
    038    private static Matcher vertexMatcher, vertexNormalMatcher, textureCoordinateMatcher;
    039    private static Matcher face_V_VT_VN_Matcher, face_V_VT_Matcher, face_V_VN_Matcher, face_V_Matcher;
    040    private static Matcher groupObjectMatcher;
    041
    042    public ArrayList<Vertex> vertices = new ArrayList<Vertex>();
    043    public ArrayList<Vertex> vertexNormals = new ArrayList<Vertex>();
    044    public ArrayList<TextureCoordinate> textureCoordinates = new ArrayList<TextureCoordinate>();
    045    public ArrayList<GroupObject> groupObjects = new ArrayList<GroupObject>();
    046    private GroupObject currentGroupObject;
    047    private String fileName;
    048
    049    public WavefrontObject(String fileName, URL resource) throws ModelFormatException
    050    {
    051        this.fileName = fileName;
    052        loadObjModel(resource);
    053    }
    054
    055    private void loadObjModel(URL fileURL) throws ModelFormatException
    056    {
    057        BufferedReader reader = null;
    058        InputStream inputStream = null;
    059
    060        String currentLine = null;
    061        int lineCount = 0;
    062
    063        try
    064        {
    065            inputStream = fileURL.openStream();
    066            reader = new BufferedReader(new InputStreamReader(inputStream));
    067
    068            while ((currentLine = reader.readLine()) != null)
    069            {
    070                lineCount++;
    071                currentLine = currentLine.replaceAll("\\s+", " ").trim();
    072
    073                if (currentLine.startsWith("#") || currentLine.length() == 0)
    074                {
    075                    continue;
    076                }
    077                else if (currentLine.startsWith("v "))
    078                {
    079                    Vertex vertex = parseVertex(currentLine, lineCount);
    080                    if (vertex != null)
    081                    {
    082                        vertices.add(vertex);
    083                    }
    084                }
    085                else if (currentLine.startsWith("vn "))
    086                {
    087                    Vertex vertex = parseVertexNormal(currentLine, lineCount);
    088                    if (vertex != null)
    089                    {
    090                        vertexNormals.add(vertex);
    091                    }
    092                }
    093                else if (currentLine.startsWith("vt "))
    094                {
    095                    TextureCoordinate textureCoordinate = parseTextureCoordinate(currentLine, lineCount);
    096                    if (textureCoordinate != null)
    097                    {
    098                        textureCoordinates.add(textureCoordinate);
    099                    }
    100                }
    101                else if (currentLine.startsWith("f "))
    102                {
    103
    104                    if (currentGroupObject == null)
    105                    {
    106                        currentGroupObject = new GroupObject("Default");
    107                    }
    108
    109                    Face face = parseFace(currentLine, lineCount);
    110
    111                    if (face != null)
    112                    {
    113                        currentGroupObject.faces.add(face);
    114                    }
    115                }
    116                else if (currentLine.startsWith("g ") | currentLine.startsWith("o "))
    117                {
    118                    GroupObject group = parseGroupObject(currentLine, lineCount);
    119
    120                    if (group != null)
    121                    {
    122                        if (currentGroupObject != null)
    123                        {
    124                            groupObjects.add(currentGroupObject);
    125                        }
    126                    }
    127
    128                    currentGroupObject = group;
    129                }
    130            }
    131
    132            groupObjects.add(currentGroupObject);
    133        }
    134        catch (IOException e)
    135        {
    136            throw new ModelFormatException("IO Exception reading model format", e);
    137        }
    138        finally
    139        {
    140            try
    141            {
    142                reader.close();
    143            }
    144            catch (IOException e)
    145            {
    146                // hush
    147            }
    148
    149            try
    150            {
    151                inputStream.close();
    152            }
    153            catch (IOException e)
    154            {
    155                // hush
    156            }
    157        }
    158    }
    159
    160    public void renderAll()
    161    {
    162        Tessellator tessellator = Tessellator.instance;
    163
    164        if (currentGroupObject != null)
    165        {
    166            tessellator.startDrawing(currentGroupObject.glDrawingMode);
    167        }
    168        else
    169        {
    170            tessellator.startDrawing(GL11.GL_TRIANGLES);
    171        }
    172
    173        for (GroupObject groupObject : groupObjects)
    174        {
    175            groupObject.render(tessellator);
    176        }
    177
    178        tessellator.draw();
    179    }
    180
    181    public void renderOnly(String... groupNames)
    182    {
    183        for (GroupObject groupObject : groupObjects)
    184        {
    185            for (String groupName : groupNames)
    186            {
    187                if (groupName.equalsIgnoreCase(groupObject.name))
    188                {
    189                    groupObject.render();
    190                }
    191            }
    192        }
    193    }
    194
    195    public void renderPart(String partName)
    196    {
    197        for (GroupObject groupObject : groupObjects)
    198        {
    199            if (partName.equalsIgnoreCase(groupObject.name))
    200            {
    201                groupObject.render();
    202            }
    203        }
    204    }
    205
    206    public void renderAllExcept(String... excludedGroupNames)
    207    {
    208        for (GroupObject groupObject : groupObjects)
    209        {
    210            for (String excludedGroupName : excludedGroupNames)
    211            {
    212                if (!excludedGroupName.equalsIgnoreCase(groupObject.name))
    213                {
    214                    groupObject.render();
    215                }
    216            }
    217        }
    218    }
    219
    220    private Vertex parseVertex(String line, int lineCount) throws ModelFormatException
    221    {
    222        Vertex vertex = null;
    223
    224        if (isValidVertexLine(line))
    225        {
    226            line = line.substring(line.indexOf(" ") + 1);
    227            String[] tokens = line.split(" ");
    228
    229            try
    230            {
    231                if (tokens.length == 2)
    232                {
    233                    return new Vertex(Float.parseFloat(tokens[0]), Float.parseFloat(tokens[1]));
    234                }
    235                else if (tokens.length == 3)
    236                {
    237                    return new Vertex(Float.parseFloat(tokens[0]), Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2]));
    238                }
    239            }
    240            catch (NumberFormatException e)
    241            {
    242                throw new ModelFormatException(String.format("Number formatting error at line %d",lineCount), e);
    243            }
    244        }
    245        else
    246        {
    247            throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Incorrect format");
    248        }
    249
    250        return vertex;
    251    }
    252
    253    private Vertex parseVertexNormal(String line, int lineCount) throws ModelFormatException
    254    {
    255        Vertex vertexNormal = null;
    256
    257        if (isValidVertexNormalLine(line))
    258        {
    259            line = line.substring(line.indexOf(" ") + 1);
    260            String[] tokens = line.split(" ");
    261
    262            try
    263            {
    264                if (tokens.length == 3)
    265                    return new Vertex(Float.parseFloat(tokens[0]), Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2]));
    266            }
    267            catch (NumberFormatException e)
    268            {
    269                throw new ModelFormatException(String.format("Number formatting error at line %d",lineCount), e);
    270            }
    271        }
    272        else
    273        {
    274            throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Incorrect format");
    275        }
    276
    277        return vertexNormal;
    278    }
    279
    280    private TextureCoordinate parseTextureCoordinate(String line, int lineCount) throws ModelFormatException
    281    {
    282        TextureCoordinate textureCoordinate = null;
    283
    284        if (isValidTextureCoordinateLine(line))
    285        {
    286            line = line.substring(line.indexOf(" ") + 1);
    287            String[] tokens = line.split(" ");
    288
    289            try
    290            {
    291                if (tokens.length == 2)
    292                    return new TextureCoordinate(Float.parseFloat(tokens[0]), 1 - Float.parseFloat(tokens[1]));
    293                else if (tokens.length == 3)
    294                    return new TextureCoordinate(Float.parseFloat(tokens[0]), 1 - Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2]));
    295            }
    296            catch (NumberFormatException e)
    297            {
    298                throw new ModelFormatException(String.format("Number formatting error at line %d",lineCount), e);
    299            }
    300        }
    301        else
    302        {
    303            throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Incorrect format");
    304        }
    305
    306        return textureCoordinate;
    307    }
    308
    309    private Face parseFace(String line, int lineCount) throws ModelFormatException
    310    {
    311        Face face = null;
    312
    313        if (isValidFaceLine(line))
    314        {
    315            face = new Face();
    316
    317            String trimmedLine = line.substring(line.indexOf(" ") + 1);
    318            String[] tokens = trimmedLine.split(" ");
    319            String[] subTokens = null;
    320
    321            if (tokens.length == 3)
    322            {
    323                if (currentGroupObject.glDrawingMode == -1)
    324                {
    325                    currentGroupObject.glDrawingMode = GL11.GL_TRIANGLES;
    326                }
    327                else if (currentGroupObject.glDrawingMode != GL11.GL_TRIANGLES)
    328                {
    329                    throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Invalid number of points for face (expected 4, found " + tokens.length + ")");
    330                }
    331            }
    332            else if (tokens.length == 4)
    333            {
    334                if (currentGroupObject.glDrawingMode == -1)
    335                {
    336                    currentGroupObject.glDrawingMode = GL11.GL_QUADS;
    337                }
    338                else if (currentGroupObject.glDrawingMode != GL11.GL_QUADS)
    339                {
    340                    throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Invalid number of points for face (expected 3, found " + tokens.length + ")");
    341                }
    342            }
    343
    344            // f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 ...
    345            if (isValidFace_V_VT_VN_Line(line))
    346            {
    347                face.vertices = new Vertex[tokens.length];
    348                face.textureCoordinates = new TextureCoordinate[tokens.length];
    349                face.vertexNormals = new Vertex[tokens.length];
    350
    351                for (int i = 0; i < tokens.length; ++i)
    352                {
    353                    subTokens = tokens[i].split("/");
    354
    355                    face.vertices[i] = vertices.get(Integer.parseInt(subTokens[0]) - 1);
    356                    face.textureCoordinates[i] = textureCoordinates.get(Integer.parseInt(subTokens[1]) - 1);
    357                    face.vertexNormals[i] = vertexNormals.get(Integer.parseInt(subTokens[2]) - 1);
    358                }
    359
    360                face.faceNormal = face.calculateFaceNormal();
    361            }
    362            // f v1/vt1 v2/vt2 v3/vt3 ...
    363            else if (isValidFace_V_VT_Line(line))
    364            {
    365                face.vertices = new Vertex[tokens.length];
    366                face.textureCoordinates = new TextureCoordinate[tokens.length];
    367
    368                for (int i = 0; i < tokens.length; ++i)
    369                {
    370                    subTokens = tokens[i].split("/");
    371
    372                    face.vertices[i] = vertices.get(Integer.parseInt(subTokens[0]) - 1);
    373                    face.textureCoordinates[i] = textureCoordinates.get(Integer.parseInt(subTokens[1]) - 1);
    374                }
    375
    376                face.faceNormal = face.calculateFaceNormal();
    377            }
    378            // f v1//vn1 v2//vn2 v3//vn3 ...
    379            else if (isValidFace_V_VN_Line(line))
    380            {
    381                face.vertices = new Vertex[tokens.length];
    382                face.vertexNormals = new Vertex[tokens.length];
    383
    384                for (int i = 0; i < tokens.length; ++i)
    385                {
    386                    subTokens = tokens[i].split("//");
    387
    388                    face.vertices[i] = vertices.get(Integer.parseInt(subTokens[0]) - 1);
    389                    face.vertexNormals[i] = vertexNormals.get(Integer.parseInt(subTokens[1]) - 1);
    390                }
    391
    392                face.faceNormal = face.calculateFaceNormal();
    393            }
    394            // f v1 v2 v3 ...
    395            else if (isValidFace_V_Line(line))
    396            {
    397                face.vertices = new Vertex[tokens.length];
    398
    399                for (int i = 0; i < tokens.length; ++i)
    400                {
    401                    face.vertices[i] = vertices.get(Integer.parseInt(tokens[i]) - 1);
    402                }
    403
    404                face.faceNormal = face.calculateFaceNormal();
    405            }
    406            else
    407            {
    408                throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Incorrect format");
    409            }
    410        }
    411        else
    412        {
    413            throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Incorrect format");
    414        }
    415
    416        return face;
    417    }
    418
    419    private GroupObject parseGroupObject(String line, int lineCount) throws ModelFormatException
    420    {
    421        GroupObject group = null;
    422
    423        if (isValidGroupObjectLine(line))
    424        {
    425            String trimmedLine = line.substring(line.indexOf(" ") + 1);
    426
    427            if (trimmedLine.length() > 0)
    428            {
    429                group = new GroupObject(trimmedLine);
    430            }
    431        }
    432        else
    433        {
    434            throw new ModelFormatException("Error parsing entry ('" + line + "'" + ", line " + lineCount + ") in file '" + fileName + "' - Incorrect format");
    435        }
    436
    437        return group;
    438    }
    439
    440    /***
    441     * Verifies that the given line from the model file is a valid vertex
    442     * @param line the line being validated
    443     * @return true if the line is a valid vertex, false otherwise
    444     */
    445    private static boolean isValidVertexLine(String line)
    446    {
    447        if (vertexMatcher != null)
    448        {
    449            vertexMatcher.reset();
    450        }
    451
    452        vertexMatcher = vertexPattern.matcher(line);
    453        return vertexMatcher.matches();
    454    }
    455
    456    /***
    457     * Verifies that the given line from the model file is a valid vertex normal
    458     * @param line the line being validated
    459     * @return true if the line is a valid vertex normal, false otherwise
    460     */
    461    private static boolean isValidVertexNormalLine(String line)
    462    {
    463        if (vertexNormalMatcher != null)
    464        {
    465            vertexNormalMatcher.reset();
    466        }
    467
    468        vertexNormalMatcher = vertexNormalPattern.matcher(line);
    469        return vertexNormalMatcher.matches();
    470    }
    471
    472    /***
    473     * Verifies that the given line from the model file is a valid texture coordinate
    474     * @param line the line being validated
    475     * @return true if the line is a valid texture coordinate, false otherwise
    476     */
    477    private static boolean isValidTextureCoordinateLine(String line)
    478    {
    479        if (textureCoordinateMatcher != null)
    480        {
    481            textureCoordinateMatcher.reset();
    482        }
    483
    484        textureCoordinateMatcher = textureCoordinatePattern.matcher(line);
    485        return textureCoordinateMatcher.matches();
    486    }
    487
    488    /***
    489     * Verifies that the given line from the model file is a valid face that is described by vertices, texture coordinates, and vertex normals
    490     * @param line the line being validated
    491     * @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
    492     */
    493    private static boolean isValidFace_V_VT_VN_Line(String line)
    494    {
    495        if (face_V_VT_VN_Matcher != null)
    496        {
    497            face_V_VT_VN_Matcher.reset();
    498        }
    499
    500        face_V_VT_VN_Matcher = face_V_VT_VN_Pattern.matcher(line);
    501        return face_V_VT_VN_Matcher.matches();
    502    }
    503
    504    /***
    505     * Verifies that the given line from the model file is a valid face that is described by vertices and texture coordinates
    506     * @param line the line being validated
    507     * @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
    508     */
    509    private static boolean isValidFace_V_VT_Line(String line)
    510    {
    511        if (face_V_VT_Matcher != null)
    512        {
    513            face_V_VT_Matcher.reset();
    514        }
    515
    516        face_V_VT_Matcher = face_V_VT_Pattern.matcher(line);
    517        return face_V_VT_Matcher.matches();
    518    }
    519
    520    /***
    521     * Verifies that the given line from the model file is a valid face that is described by vertices and vertex normals
    522     * @param line the line being validated
    523     * @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
    524     */
    525    private static boolean isValidFace_V_VN_Line(String line)
    526    {
    527        if (face_V_VN_Matcher != null)
    528        {
    529            face_V_VN_Matcher.reset();
    530        }
    531
    532        face_V_VN_Matcher = face_V_VN_Pattern.matcher(line);
    533        return face_V_VN_Matcher.matches();
    534    }
    535
    536    /***
    537     * Verifies that the given line from the model file is a valid face that is described by only vertices
    538     * @param line the line being validated
    539     * @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
    540     */
    541    private static boolean isValidFace_V_Line(String line)
    542    {
    543        if (face_V_Matcher != null)
    544        {
    545            face_V_Matcher.reset();
    546        }
    547
    548        face_V_Matcher = face_V_Pattern.matcher(line);
    549        return face_V_Matcher.matches();
    550    }
    551
    552    /***
    553     * Verifies that the given line from the model file is a valid face of any of the possible face formats
    554     * @param line the line being validated
    555     * @return true if the line is a valid face that matches any of the valid face formats, false otherwise
    556     */
    557    private static boolean isValidFaceLine(String line)
    558    {
    559        return isValidFace_V_VT_VN_Line(line) || isValidFace_V_VT_Line(line) || isValidFace_V_VN_Line(line) || isValidFace_V_Line(line);
    560    }
    561
    562    /***
    563     * Verifies that the given line from the model file is a valid group (or object)
    564     * @param line the line being validated
    565     * @return true if the line is a valid group (or object), false otherwise
    566     */
    567    private static boolean isValidGroupObjectLine(String line)
    568    {
    569        if (groupObjectMatcher != null)
    570        {
    571            groupObjectMatcher.reset();
    572        }
    573
    574        groupObjectMatcher = groupObjectPattern.matcher(line);
    575        return groupObjectMatcher.matches();
    576    }
    577
    578    @Override
    579    public String getType()
    580    {
    581        return "obj";
    582    }
    583}