Home | History | Annotate | Download | only in plugins
      1 /*
      2  * Copyright (c) 2009-2010 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 package com.jme3.scene.plugins;
     34 
     35 import com.jme3.asset.*;
     36 import com.jme3.material.Material;
     37 import com.jme3.material.MaterialList;
     38 import com.jme3.math.Vector2f;
     39 import com.jme3.math.Vector3f;
     40 import com.jme3.renderer.queue.RenderQueue.Bucket;
     41 import com.jme3.scene.Mesh.Mode;
     42 import com.jme3.scene.*;
     43 import com.jme3.scene.VertexBuffer.Type;
     44 import com.jme3.scene.mesh.IndexBuffer;
     45 import com.jme3.scene.mesh.IndexIntBuffer;
     46 import com.jme3.scene.mesh.IndexShortBuffer;
     47 import com.jme3.util.BufferUtils;
     48 import com.jme3.util.IntMap;
     49 import java.io.File;
     50 import java.io.IOException;
     51 import java.io.InputStream;
     52 import java.nio.FloatBuffer;
     53 import java.nio.IntBuffer;
     54 import java.nio.ShortBuffer;
     55 import java.util.Map.Entry;
     56 import java.util.*;
     57 import java.util.logging.Level;
     58 import java.util.logging.Logger;
     59 
     60 /**
     61  * Reads OBJ format models.
     62  */
     63 public final class OBJLoader implements AssetLoader {
     64 
     65     private static final Logger logger = Logger.getLogger(OBJLoader.class.getName());
     66 
     67     protected final ArrayList<Vector3f> verts = new ArrayList<Vector3f>();
     68     protected final ArrayList<Vector2f> texCoords = new ArrayList<Vector2f>();
     69     protected final ArrayList<Vector3f> norms = new ArrayList<Vector3f>();
     70 
     71     protected final ArrayList<Face> faces = new ArrayList<Face>();
     72     protected final HashMap<String, ArrayList<Face>> matFaces = new HashMap<String, ArrayList<Face>>();
     73 
     74     protected String currentMatName;
     75     protected String currentObjectName;
     76 
     77     protected final HashMap<Vertex, Integer> vertIndexMap = new HashMap<Vertex, Integer>(100);
     78     protected final IntMap<Vertex> indexVertMap = new IntMap<Vertex>(100);
     79     protected int curIndex    = 0;
     80     protected int objectIndex = 0;
     81     protected int geomIndex   = 0;
     82 
     83     protected Scanner scan;
     84     protected ModelKey key;
     85     protected AssetManager assetManager;
     86     protected MaterialList matList;
     87 
     88     protected String objName;
     89     protected Node objNode;
     90 
     91     protected static class Vertex {
     92 
     93         Vector3f v;
     94         Vector2f vt;
     95         Vector3f vn;
     96         int index;
     97 
     98         @Override
     99         public boolean equals(Object obj) {
    100             if (obj == null) {
    101                 return false;
    102             }
    103             if (getClass() != obj.getClass()) {
    104                 return false;
    105             }
    106             final Vertex other = (Vertex) obj;
    107             if (this.v != other.v && (this.v == null || !this.v.equals(other.v))) {
    108                 return false;
    109             }
    110             if (this.vt != other.vt && (this.vt == null || !this.vt.equals(other.vt))) {
    111                 return false;
    112             }
    113             if (this.vn != other.vn && (this.vn == null || !this.vn.equals(other.vn))) {
    114                 return false;
    115             }
    116             return true;
    117         }
    118 
    119         @Override
    120         public int hashCode() {
    121             int hash = 5;
    122             hash = 53 * hash + (this.v != null ? this.v.hashCode() : 0);
    123             hash = 53 * hash + (this.vt != null ? this.vt.hashCode() : 0);
    124             hash = 53 * hash + (this.vn != null ? this.vn.hashCode() : 0);
    125             return hash;
    126         }
    127     }
    128 
    129     protected static class Face {
    130         Vertex[] verticies;
    131     }
    132 
    133     protected class ObjectGroup {
    134 
    135         final String objectName;
    136 
    137         public ObjectGroup(String objectName){
    138             this.objectName = objectName;
    139         }
    140 
    141         public Spatial createGeometry(){
    142             Node groupNode = new Node(objectName);
    143 
    144 //            if (matFaces.size() > 0){
    145 //                for (Entry<String, ArrayList<Face>> entry : matFaces.entrySet()){
    146 //                    ArrayList<Face> materialFaces = entry.getValue();
    147 //                    if (materialFaces.size() > 0){
    148 //                        Geometry geom = createGeometry(materialFaces, entry.getKey());
    149 //                        objNode.attachChild(geom);
    150 //                    }
    151 //                }
    152 //            }else if (faces.size() > 0){
    153 //                // generate final geometry
    154 //                Geometry geom = createGeometry(faces, null);
    155 //                objNode.attachChild(geom);
    156 //            }
    157 
    158             return groupNode;
    159         }
    160     }
    161 
    162     public void reset(){
    163         verts.clear();
    164         texCoords.clear();
    165         norms.clear();
    166         faces.clear();
    167         matFaces.clear();
    168 
    169         vertIndexMap.clear();
    170         indexVertMap.clear();
    171 
    172         currentMatName = null;
    173         matList = null;
    174         curIndex = 0;
    175         geomIndex = 0;
    176         scan = null;
    177     }
    178 
    179     protected void findVertexIndex(Vertex vert){
    180         Integer index = vertIndexMap.get(vert);
    181         if (index != null){
    182             vert.index = index.intValue();
    183         }else{
    184             vert.index = curIndex++;
    185             vertIndexMap.put(vert, vert.index);
    186             indexVertMap.put(vert.index, vert);
    187         }
    188     }
    189 
    190     protected Face[] quadToTriangle(Face f){
    191         assert f.verticies.length == 4;
    192 
    193         Face[] t = new Face[]{ new Face(), new Face() };
    194         t[0].verticies = new Vertex[3];
    195         t[1].verticies = new Vertex[3];
    196 
    197         Vertex v0 = f.verticies[0];
    198         Vertex v1 = f.verticies[1];
    199         Vertex v2 = f.verticies[2];
    200         Vertex v3 = f.verticies[3];
    201 
    202         // find the pair of verticies that is closest to each over
    203         // v0 and v2
    204         // OR
    205         // v1 and v3
    206         float d1 = v0.v.distanceSquared(v2.v);
    207         float d2 = v1.v.distanceSquared(v3.v);
    208         if (d1 < d2){
    209             // put an edge in v0, v2
    210             t[0].verticies[0] = v0;
    211             t[0].verticies[1] = v1;
    212             t[0].verticies[2] = v3;
    213 
    214             t[1].verticies[0] = v1;
    215             t[1].verticies[1] = v2;
    216             t[1].verticies[2] = v3;
    217         }else{
    218             // put an edge in v1, v3
    219             t[0].verticies[0] = v0;
    220             t[0].verticies[1] = v1;
    221             t[0].verticies[2] = v2;
    222 
    223             t[1].verticies[0] = v0;
    224             t[1].verticies[1] = v2;
    225             t[1].verticies[2] = v3;
    226         }
    227 
    228         return t;
    229     }
    230 
    231     private ArrayList<Vertex> vertList = new ArrayList<Vertex>();
    232 
    233     protected void readFace(){
    234         Face f = new Face();
    235         vertList.clear();
    236 
    237         String line = scan.nextLine().trim();
    238         String[] verticies = line.split("\\s");
    239         for (String vertex : verticies){
    240             int v = 0;
    241             int vt = 0;
    242             int vn = 0;
    243 
    244             String[] split = vertex.split("/");
    245             if (split.length == 1){
    246                 v = Integer.parseInt(split[0].trim());
    247             }else if (split.length == 2){
    248                 v = Integer.parseInt(split[0].trim());
    249                 vt = Integer.parseInt(split[1].trim());
    250             }else if (split.length == 3 && !split[1].equals("")){
    251                 v = Integer.parseInt(split[0].trim());
    252                 vt = Integer.parseInt(split[1].trim());
    253                 vn = Integer.parseInt(split[2].trim());
    254             }else if (split.length == 3){
    255                 v = Integer.parseInt(split[0].trim());
    256                 vn = Integer.parseInt(split[2].trim());
    257             }
    258 
    259             Vertex vx = new Vertex();
    260             vx.v = verts.get(v - 1);
    261 
    262             if (vt > 0)
    263                 vx.vt = texCoords.get(vt - 1);
    264 
    265             if (vn > 0)
    266                 vx.vn = norms.get(vn - 1);
    267 
    268             vertList.add(vx);
    269         }
    270 
    271         if (vertList.size() > 4 || vertList.size() <= 2)
    272             logger.warning("Edge or polygon detected in OBJ. Ignored.");
    273 
    274         f.verticies = new Vertex[vertList.size()];
    275         for (int i = 0; i < vertList.size(); i++){
    276             f.verticies[i] = vertList.get(i);
    277         }
    278 
    279         if (matList != null && matFaces.containsKey(currentMatName)){
    280             matFaces.get(currentMatName).add(f);
    281         }else{
    282             faces.add(f); // faces that belong to the default material
    283         }
    284     }
    285 
    286     protected Vector3f readVector3(){
    287         Vector3f v = new Vector3f();
    288 
    289         v.set(Float.parseFloat(scan.next()),
    290               Float.parseFloat(scan.next()),
    291               Float.parseFloat(scan.next()));
    292 
    293         return v;
    294     }
    295 
    296     protected Vector2f readVector2(){
    297         Vector2f v = new Vector2f();
    298 
    299         String line = scan.nextLine().trim();
    300         String[] split = line.split("\\s");
    301         v.setX( Float.parseFloat(split[0].trim()) );
    302         v.setY( Float.parseFloat(split[1].trim()) );
    303 
    304 //        v.setX(scan.nextFloat());
    305 //        if (scan.hasNextFloat()){
    306 //            v.setY(scan.nextFloat());
    307 //            if (scan.hasNextFloat()){
    308 //                scan.nextFloat(); // ignore
    309 //            }
    310 //        }
    311 
    312         return v;
    313     }
    314 
    315     protected void loadMtlLib(String name) throws IOException{
    316         if (!name.toLowerCase().endsWith(".mtl"))
    317             throw new IOException("Expected .mtl file! Got: " + name);
    318 
    319         // NOTE: Cut off any relative/absolute paths
    320         name = new File(name).getName();
    321         AssetKey mtlKey = new AssetKey(key.getFolder() + name);
    322         try {
    323             matList = (MaterialList) assetManager.loadAsset(mtlKey);
    324         } catch (AssetNotFoundException ex){
    325             logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{name, key});
    326         }
    327 
    328         if (matList != null){
    329             // create face lists for every material
    330             for (String matName : matList.keySet()){
    331                 matFaces.put(matName, new ArrayList<Face>());
    332             }
    333         }
    334     }
    335 
    336     protected boolean nextStatement(){
    337         try {
    338             scan.skip(".*\r{0,1}\n");
    339             return true;
    340         } catch (NoSuchElementException ex){
    341             // EOF
    342             return false;
    343         }
    344     }
    345 
    346     protected boolean readLine() throws IOException{
    347         if (!scan.hasNext()){
    348             return false;
    349         }
    350 
    351         String cmd = scan.next();
    352         if (cmd.startsWith("#")){
    353             // skip entire comment until next line
    354             return nextStatement();
    355         }else if (cmd.equals("v")){
    356             // vertex position
    357             verts.add(readVector3());
    358         }else if (cmd.equals("vn")){
    359             // vertex normal
    360             norms.add(readVector3());
    361         }else if (cmd.equals("vt")){
    362             // texture coordinate
    363             texCoords.add(readVector2());
    364         }else if (cmd.equals("f")){
    365             // face, can be triangle, quad, or polygon (unsupported)
    366             readFace();
    367         }else if (cmd.equals("usemtl")){
    368             // use material from MTL lib for the following faces
    369             currentMatName = scan.next();
    370 //            if (!matList.containsKey(currentMatName))
    371 //                throw new IOException("Cannot locate material " + currentMatName + " in MTL file!");
    372 
    373         }else if (cmd.equals("mtllib")){
    374             // specify MTL lib to use for this OBJ file
    375             String mtllib = scan.nextLine().trim();
    376             loadMtlLib(mtllib);
    377         }else if (cmd.equals("s") || cmd.equals("g")){
    378             return nextStatement();
    379         }else{
    380             // skip entire command until next line
    381             logger.log(Level.WARNING, "Unknown statement in OBJ! {0}", cmd);
    382             return nextStatement();
    383         }
    384 
    385         return true;
    386     }
    387 
    388     protected Geometry createGeometry(ArrayList<Face> faceList, String matName) throws IOException{
    389         if (faceList.isEmpty())
    390             throw new IOException("No geometry data to generate mesh");
    391 
    392         // Create mesh from the faces
    393         Mesh mesh = constructMesh(faceList);
    394 
    395         Geometry geom = new Geometry(objName + "-geom-" + (geomIndex++), mesh);
    396 
    397         Material material = null;
    398         if (matName != null && matList != null){
    399             // Get material from material list
    400             material = matList.get(matName);
    401         }
    402         if (material == null){
    403             // create default material
    404             material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
    405             material.setFloat("Shininess", 64);
    406         }
    407         geom.setMaterial(material);
    408         if (material.isTransparent())
    409             geom.setQueueBucket(Bucket.Transparent);
    410         else
    411             geom.setQueueBucket(Bucket.Opaque);
    412 
    413         if (material.getMaterialDef().getName().contains("Lighting")
    414           && mesh.getFloatBuffer(Type.Normal) == null){
    415             logger.log(Level.WARNING, "OBJ mesh {0} doesn't contain normals! "
    416                                     + "It might not display correctly", geom.getName());
    417         }
    418 
    419         return geom;
    420     }
    421 
    422     protected Mesh constructMesh(ArrayList<Face> faceList){
    423         Mesh m = new Mesh();
    424         m.setMode(Mode.Triangles);
    425 
    426         boolean hasTexCoord = false;
    427         boolean hasNormals  = false;
    428 
    429         ArrayList<Face> newFaces = new ArrayList<Face>(faceList.size());
    430         for (int i = 0; i < faceList.size(); i++){
    431             Face f = faceList.get(i);
    432 
    433             for (Vertex v : f.verticies){
    434                 findVertexIndex(v);
    435 
    436                 if (!hasTexCoord && v.vt != null)
    437                     hasTexCoord = true;
    438                 if (!hasNormals && v.vn != null)
    439                     hasNormals = true;
    440             }
    441 
    442             if (f.verticies.length == 4){
    443                 Face[] t = quadToTriangle(f);
    444                 newFaces.add(t[0]);
    445                 newFaces.add(t[1]);
    446             }else{
    447                 newFaces.add(f);
    448             }
    449         }
    450 
    451         FloatBuffer posBuf  = BufferUtils.createFloatBuffer(vertIndexMap.size() * 3);
    452         FloatBuffer normBuf = null;
    453         FloatBuffer tcBuf   = null;
    454 
    455         if (hasNormals){
    456             normBuf = BufferUtils.createFloatBuffer(vertIndexMap.size() * 3);
    457             m.setBuffer(VertexBuffer.Type.Normal, 3, normBuf);
    458         }
    459         if (hasTexCoord){
    460             tcBuf = BufferUtils.createFloatBuffer(vertIndexMap.size() * 2);
    461             m.setBuffer(VertexBuffer.Type.TexCoord, 2, tcBuf);
    462         }
    463 
    464         IndexBuffer indexBuf = null;
    465         if (vertIndexMap.size() >= 65536){
    466             // too many verticies: use intbuffer instead of shortbuffer
    467             IntBuffer ib = BufferUtils.createIntBuffer(newFaces.size() * 3);
    468             m.setBuffer(VertexBuffer.Type.Index, 3, ib);
    469             indexBuf = new IndexIntBuffer(ib);
    470         }else{
    471             ShortBuffer sb = BufferUtils.createShortBuffer(newFaces.size() * 3);
    472             m.setBuffer(VertexBuffer.Type.Index, 3, sb);
    473             indexBuf = new IndexShortBuffer(sb);
    474         }
    475 
    476         int numFaces = newFaces.size();
    477         for (int i = 0; i < numFaces; i++){
    478             Face f = newFaces.get(i);
    479             if (f.verticies.length != 3)
    480                 continue;
    481 
    482             Vertex v0 = f.verticies[0];
    483             Vertex v1 = f.verticies[1];
    484             Vertex v2 = f.verticies[2];
    485 
    486             posBuf.position(v0.index * 3);
    487             posBuf.put(v0.v.x).put(v0.v.y).put(v0.v.z);
    488             posBuf.position(v1.index * 3);
    489             posBuf.put(v1.v.x).put(v1.v.y).put(v1.v.z);
    490             posBuf.position(v2.index * 3);
    491             posBuf.put(v2.v.x).put(v2.v.y).put(v2.v.z);
    492 
    493             if (normBuf != null){
    494                 if (v0.vn != null){
    495                     normBuf.position(v0.index * 3);
    496                     normBuf.put(v0.vn.x).put(v0.vn.y).put(v0.vn.z);
    497                     normBuf.position(v1.index * 3);
    498                     normBuf.put(v1.vn.x).put(v1.vn.y).put(v1.vn.z);
    499                     normBuf.position(v2.index * 3);
    500                     normBuf.put(v2.vn.x).put(v2.vn.y).put(v2.vn.z);
    501                 }
    502             }
    503 
    504             if (tcBuf != null){
    505                 if (v0.vt != null){
    506                     tcBuf.position(v0.index * 2);
    507                     tcBuf.put(v0.vt.x).put(v0.vt.y);
    508                     tcBuf.position(v1.index * 2);
    509                     tcBuf.put(v1.vt.x).put(v1.vt.y);
    510                     tcBuf.position(v2.index * 2);
    511                     tcBuf.put(v2.vt.x).put(v2.vt.y);
    512                 }
    513             }
    514 
    515             int index = i * 3; // current face * 3 = current index
    516             indexBuf.put(index,   v0.index);
    517             indexBuf.put(index+1, v1.index);
    518             indexBuf.put(index+2, v2.index);
    519         }
    520 
    521         m.setBuffer(VertexBuffer.Type.Position, 3, posBuf);
    522         // index buffer and others were set on creation
    523 
    524         m.setStatic();
    525         m.updateBound();
    526         m.updateCounts();
    527         //m.setInterleaved();
    528 
    529         // clear data generated face statements
    530         // to prepare for next mesh
    531         vertIndexMap.clear();
    532         indexVertMap.clear();
    533         curIndex = 0;
    534 
    535         return m;
    536     }
    537 
    538     @SuppressWarnings("empty-statement")
    539     public Object load(AssetInfo info) throws IOException{
    540         reset();
    541 
    542         key = (ModelKey) info.getKey();
    543         assetManager = info.getManager();
    544         objName    = key.getName();
    545 
    546         String folderName = key.getFolder();
    547         String ext        = key.getExtension();
    548         objName = objName.substring(0, objName.length() - ext.length() - 1);
    549         if (folderName != null && folderName.length() > 0){
    550             objName = objName.substring(folderName.length());
    551         }
    552 
    553         objNode = new Node(objName + "-objnode");
    554 
    555         if (!(info.getKey() instanceof ModelKey))
    556             throw new IllegalArgumentException("Model assets must be loaded using a ModelKey");
    557 
    558         InputStream in = null;
    559         try {
    560             in = info.openStream();
    561 
    562             scan = new Scanner(in);
    563             scan.useLocale(Locale.US);
    564 
    565             while (readLine());
    566         } finally {
    567             if (in != null){
    568                 in.close();
    569             }
    570         }
    571 
    572         if (matFaces.size() > 0){
    573             for (Entry<String, ArrayList<Face>> entry : matFaces.entrySet()){
    574                 ArrayList<Face> materialFaces = entry.getValue();
    575                 if (materialFaces.size() > 0){
    576                     Geometry geom = createGeometry(materialFaces, entry.getKey());
    577                     objNode.attachChild(geom);
    578                 }
    579             }
    580         }else if (faces.size() > 0){
    581             // generate final geometry
    582             Geometry geom = createGeometry(faces, null);
    583             objNode.attachChild(geom);
    584         }
    585 
    586         if (objNode.getQuantity() == 1)
    587             // only 1 geometry, so no need to send node
    588             return objNode.getChild(0);
    589         else
    590             return objNode;
    591     }
    592 
    593 }
    594