• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
equals(Object obj)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
hashCode()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 
ObjectGroup(String objectName)137         public ObjectGroup(String objectName){
138             this.objectName = objectName;
139         }
140 
createGeometry()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 
reset()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 
findVertexIndex(Vertex vert)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 
quadToTriangle(Face f)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 
readFace()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 
readVector3()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 
readVector2()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 
loadMtlLib(String name)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 
nextStatement()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 
readLine()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 
createGeometry(ArrayList<Face> faceList, String matName)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 
constructMesh(ArrayList<Face> faceList)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")
load(AssetInfo info)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