Browse Source

OBJLoader enhancement: named groups support

kkolyan 5 years ago
parent
commit
0f06c14c28

+ 4 - 0
jme3-core/build.gradle

@@ -17,6 +17,10 @@ sourceSets {
     }
 }
 
+dependencies {
+    testCompile project(':jme3-testdata')
+}
+
 task updateVersionPropertiesFile {
     def versionFile = file('src/main/resources/com/jme3/system/version.properties')
     def versionFileText = "# THIS IS AN AUTO-GENERATED FILE..\n" +

+ 1 - 0
jme3-core/src/plugins/java/com/jme3/scene/plugins/MTLLoader.java

@@ -152,6 +152,7 @@ public class MTLLoader implements AssetLoader {
             material.setFloat("AlphaDiscardThreshold", 0.01f);
         }
         
+        material.setName(matName);
         matList.put(matName, material);
     }
 

+ 64 - 29
jme3-core/src/plugins/java/com/jme3/scene/plugins/OBJLoader.java

@@ -66,10 +66,9 @@ public final class OBJLoader implements AssetLoader {
     protected final ArrayList<Vector3f> verts = new ArrayList<Vector3f>();
     protected final ArrayList<Vector2f> texCoords = new ArrayList<Vector2f>();
     protected final ArrayList<Vector3f> norms = new ArrayList<Vector3f>();
-    
-    protected final ArrayList<Face> faces = new ArrayList<Face>();
-    protected final HashMap<String, ArrayList<Face>> matFaces = new HashMap<String, ArrayList<Face>>();
-    
+
+    private final ArrayList<Group> groups = new ArrayList<Group>();
+
     protected String currentMatName;
     protected String currentObjectName;
 
@@ -87,6 +86,16 @@ public final class OBJLoader implements AssetLoader {
     protected String objName;
     protected Node objNode;
 
+    private static class Group {
+        private String name;
+        private final ArrayList<Face> faces = new ArrayList<Face>();
+        private final HashMap<String, ArrayList<Face>> matFaces = new HashMap<String, ArrayList<Face>>();
+
+        public Group(final String name) {
+            this.name = name;
+        }
+    }
+
     protected static class Vertex {
 
         Vector3f v;
@@ -164,8 +173,7 @@ public final class OBJLoader implements AssetLoader {
         verts.clear();
         texCoords.clear();
         norms.clear();
-        faces.clear();
-        matFaces.clear();
+        groups.clear();
 
         vertIndexMap.clear();
         indexVertMap.clear();
@@ -289,10 +297,17 @@ public final class OBJLoader implements AssetLoader {
             f.verticies[i] = vertList.get(i);
         }
 
-        if (matList != null && matFaces.containsKey(currentMatName)){
-            matFaces.get(currentMatName).add(f);
+        Group group = groups.get(groups.size() - 1);
+
+        if (currentMatName != null && matList != null && matList.containsKey(currentMatName)){
+            ArrayList<Face> matFaces = group.matFaces.get(currentMatName);
+            if (matFaces == null) {
+                matFaces = new ArrayList<Face>();
+                group.matFaces.put(currentMatName, matFaces);
+            }
+            matFaces.add(f);
         }else{
-            faces.add(f); // faces that belong to the default material
+            group.faces.add(f); // faces that belong to the default material
         }
     }
 
@@ -337,13 +352,6 @@ public final class OBJLoader implements AssetLoader {
         } catch (AssetNotFoundException ex){
             logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{name, key});
         }
-
-        if (matList != null){
-            // create face lists for every material
-            for (String matName : matList.keySet()){
-                matFaces.put(matName, new ArrayList<Face>());
-            }
-        }
     }
 
     protected boolean nextStatement(){
@@ -387,8 +395,14 @@ public final class OBJLoader implements AssetLoader {
             // specify MTL lib to use for this OBJ file
             String mtllib = scan.nextLine().trim();
             loadMtlLib(mtllib);
-        }else if (cmd.equals("s") || cmd.equals("g")){
+        }else if (cmd.equals("s")) {
+            logger.log(Level.WARNING, "smoothing groups are not supported, statement ignored: {0}", cmd);
+            return nextStatement();
+        }else if (cmd.equals("mg")) {
+            logger.log(Level.WARNING, "merge groups are not supported, statement ignored: {0}", cmd);
             return nextStatement();
+        }else if (cmd.equals("g")) {
+            groups.add(new Group(scan.nextLine().trim()));
         }else{
             // skip entire command until next line
             logger.log(Level.WARNING, "Unknown statement in OBJ! {0}", cmd);
@@ -565,11 +579,14 @@ public final class OBJLoader implements AssetLoader {
         }
 
         objNode = new Node(objName + "-objnode");
+    
+        Group defaultGroupStub = new Group(null);
+        groups.add(defaultGroupStub);
 
         if (!(info.getKey() instanceof ModelKey))
             throw new IllegalArgumentException("Model assets must be loaded using a ModelKey");
 
-        InputStream in = null; 
+        InputStream in = null;
         try {
             in = info.openStream();
             
@@ -583,25 +600,43 @@ public final class OBJLoader implements AssetLoader {
             }
         }
         
-        if (matFaces.size() > 0){
-            for (Entry<String, ArrayList<Face>> entry : matFaces.entrySet()){
-                ArrayList<Face> materialFaces = entry.getValue();
-                if (materialFaces.size() > 0){
-                    Geometry geom = createGeometry(materialFaces, entry.getKey());
+        for (Group group : groups) {
+            if (group == defaultGroupStub) {
+                materializeGroup(group, objNode);
+            } else {
+                Node groupNode = new Node(group.name);
+                materializeGroup(group, groupNode);
+                if (groupNode.getQuantity() == 1) {
+                    Spatial geom = groupNode.getChild(0);
+                    geom.setName(groupNode.getName());
                     objNode.attachChild(geom);
+                } else if (groupNode.getQuantity() > 1) {
+                    objNode.attachChild(groupNode);
                 }
             }
-        }else if (faces.size() > 0){
-            // generate final geometry
-            Geometry geom = createGeometry(faces, null);
-            objNode.attachChild(geom);
         }
 
         if (objNode.getQuantity() == 1)
             // only 1 geometry, so no need to send node
-            return objNode.getChild(0); 
+            return objNode.getChild(0);
         else
             return objNode;
     }
- 
+    
+    private void materializeGroup(Group group, Node container) throws IOException {
+        if (group.matFaces.size() > 0) {
+            for (Entry<String, ArrayList<Face>> entry : group.matFaces.entrySet()){
+                ArrayList<Face> materialFaces = entry.getValue();
+                if (materialFaces.size() > 0){
+                    Geometry geom = createGeometry(materialFaces, entry.getKey());
+                    container.attachChild(geom);
+                }
+            }
+        } else if (group.faces.size() > 0) {
+            // generate final geometry
+            Geometry geom = createGeometry(group.faces, null);
+            container.attachChild(geom);
+        }
+    }
+    
 }

+ 106 - 0
jme3-core/src/test/java/com/jme3/scene/plugins/OBJLoaderTest.java

@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2009-2020 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetLoader;
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.ModelKey;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.system.TestUtil;
+import com.jme3.texture.Image;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class OBJLoaderTest {
+    private AssetManager assetManager;
+    
+    @Before
+    public void init() {
+        assetManager = TestUtil.createAssetManager();
+        // texture loaders are outside of core, so creating stub
+        assetManager.registerLoader(PngLoaderStub.class, "png");
+        
+    }
+    
+    @Test
+    public void testHappyPath() {
+        Node scene = (Node) assetManager.loadModel(new ModelKey("OBJLoaderTest/TwoChairs.obj"));
+        String sceneAsString = toDiffFriendlyString("", scene);
+        System.out.println(sceneAsString);
+        String expectedText = "" +
+            // generated root name (as before named groups support)
+            "TwoChairs-objnode\n" +
+            // unnamed geometry with generated name (as before named groups support).
+            // actually it's partially smoothed, but this fact is ignored.
+            "  TwoChairs-geom-0 (material: dot_purple)\n" +
+            // named group as Geometry
+            "  Chair 2 (material: dot_purple)\n" +
+            // named group as Geometry
+            "  Pillow 2 (material: dot_red)\n" +
+            // named group as node with two dufferent Geometry instances,
+            // because two materials are used (as before named groups support)
+            "  Podium\n" +
+            "    TwoChairs-geom-3 (material: dot_red)\n" +
+            "    TwoChairs-geom-4 (material: dot_blue)\n" +
+            // named group as Geometry
+            "  Pillow 1 (material: dot_green)";
+        assertEquals(expectedText, sceneAsString.trim());
+    }
+    
+    private static String toDiffFriendlyString(String indent, Spatial spatial) {
+        if (spatial instanceof Geometry) {
+            return indent + spatial.getName() + " (material: "+((Geometry) spatial).getMaterial().getName()+")\n";
+        }
+        if (spatial instanceof Node) {
+            StringBuilder s = new StringBuilder();
+            s.append(indent).append(spatial.getName()).append("\n");
+            Node node = (Node) spatial;
+            for (final Spatial child : node.getChildren()) {
+                s.append(toDiffFriendlyString(indent + "  ", child));
+            }
+            return s.toString();
+        }
+        return indent + spatial + "\n";
+    }
+    
+    public static class PngLoaderStub implements AssetLoader {
+        @Override
+        public Object load(final AssetInfo assetInfo) {
+            return new Image();
+        }
+    }
+}

+ 119 - 0
jme3-examples/src/main/java/jme3test/model/TestObjGroupsLoading.java

@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2009-2020 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package jme3test.model;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.ModelKey;
+import com.jme3.collision.CollisionResult;
+import com.jme3.collision.CollisionResults;
+import com.jme3.font.BitmapFont;
+import com.jme3.font.BitmapText;
+import com.jme3.font.Rectangle;
+import com.jme3.light.AmbientLight;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Ray;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+
+public class TestObjGroupsLoading extends SimpleApplication {
+    
+    public static void main(String[] args) {
+        TestObjGroupsLoading app = new TestObjGroupsLoading();
+        app.start();
+    }
+    
+    private BitmapText pointerDisplay;
+    
+    @Override
+    public void simpleInitApp() {
+    
+        // load scene with following structure:
+        // Chair 1 (just mesh without name) and named groups: Chair 2, Pillow 2, Podium
+        Spatial scene = assetManager.loadModel(new ModelKey("OBJLoaderTest/TwoChairs.obj"));
+        // add light to make it visible
+        scene.addLight(new AmbientLight(ColorRGBA.White));
+        // attach scene to the root
+        rootNode.attachChild(scene);
+        
+        // configure camera for best scene viewing
+        cam.setLocation(new Vector3f(-3, 4, 3));
+        cam.lookAtDirection(new Vector3f(0, -0.5f, -1), Vector3f.UNIT_Y);
+        flyCam.setMoveSpeed(10);
+        
+        // create display to indicate pointed geometry name
+        pointerDisplay = new BitmapText(guiFont);
+        pointerDisplay.setBox(new Rectangle(0, settings.getHeight(), settings.getWidth(), settings.getHeight()/2));
+        pointerDisplay.setAlignment(BitmapFont.Align.Center);
+        pointerDisplay.setVerticalAlignment(BitmapFont.VAlign.Center);
+        guiNode.attachChild(pointerDisplay);
+        
+        initCrossHairs();
+    }
+    
+    @Override
+    public void simpleUpdate(final float tpf) {
+        
+        // ray to the center of the screen from the camera
+        Ray ray = new Ray(cam.getLocation(), cam.getDirection());
+        
+        // find object at the center of the screen
+    
+        final CollisionResults results = new CollisionResults();
+        rootNode.collideWith(ray, results);
+        
+        CollisionResult result = results.getClosestCollision();
+        if (result == null) {
+            pointerDisplay.setText("");
+        } else {
+            // display pointed geometry and it's parents names
+            StringBuilder sb = new StringBuilder();
+            for (Spatial node = result.getGeometry(); node != null; node = node.getParent()) {
+                if (sb.length() > 0) {
+                    sb.append(" < ");
+                }
+                sb.append(node.getName());
+            }
+            pointerDisplay.setText(sb);
+        }
+    }
+    
+    private void initCrossHairs() {
+        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
+        BitmapText ch = new BitmapText(guiFont, false);
+        ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
+        ch.setText("+"); // crosshairs
+        ch.setLocalTranslation( // center
+            settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
+            settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
+        guiNode.attachChild(ch);
+    }
+}

+ 20 - 0
jme3-testdata/src/main/resources/OBJLoaderTest/TwoChairs.mtl

@@ -0,0 +1,20 @@
+newmtl dot_purple
+map_Kd -o 0 0 -s 1 1 dot_purple.png
+Kd 1 1 1
+d 1
+
+newmtl dot_red
+map_Kd -o 0 0 -s 1 1 dot_red.png
+Kd 1 1 1
+d 1
+
+newmtl dot_blue
+map_Kd -o 0 0 -s 1 1 dot_blue.png
+Kd 1 1 1
+d 1
+
+newmtl dot_green
+map_Kd -o 0 0 -s 1 1 dot_green.png
+Kd 1 1 1
+d 1
+

+ 219 - 0
jme3-testdata/src/main/resources/OBJLoaderTest/TwoChairs.obj

@@ -0,0 +1,219 @@
+# ProBuilder 4.2.3
+# https://unity3d.com/unity/features/worldbuilding/probuilder
+# 5/10/2020 4:30:31 PM
+
+mtllib ./TwoChairs.mtl
+o TwoChairs
+
+v -4 1 0
+v -5 1 0
+v -4 2 0
+v -5 2 0
+v -5 1 -1
+v -5 2 -1
+v -5 1 -1.25
+v -4 1 -1.25
+
+s 1
+v -5 2 -1.25
+v -4 2 -1.25
+v -4 1 -1
+v -4 2 -1
+v -4 3 -1
+v -5 3 -1
+v -4 3 -1.25
+v -5 3 -1.25
+s off
+
+vt 0 0
+vt -1 0
+vt 0 1
+vt -1 1
+vt 1 0
+vt 1 1
+vt -1 -1
+vt 0 -1
+vt -1 -1.25
+vt 0 -1.25
+vt -1.25 1
+vt -1.25 0
+vt 1.25 0
+vt 1.25 1
+vt 1 -1
+vt 1 -1.25
+vt 0 2
+vt -1 2
+vt 1.25 2
+vt 1 2
+vt -1.25 2
+
+vn 0 0 1
+vn -1 0 0
+vn 0 0 -1
+vn 1 0 0
+vn 0 -1 0
+vn 0 1 0
+
+usemtl dot_purple
+f 3/3/1 4/4/1 2/2/1 1/1/1
+f 4/3/2 6/4/2 5/2/2 2/1/2
+f 9/6/3 10/3/3 8/1/3 7/5/3
+f 12/6/4 3/3/4 1/1/4 11/5/4
+f 7/9/5 8/10/5 11/8/5 5/7/5
+f 9/11/2 7/12/2 5/2/2 6/4/2
+f 8/13/4 10/14/4 12/6/4 11/5/4
+f 15/10/6 16/16/6 14/15/6 13/8/6
+f 13/17/1 14/18/1 6/4/1 12/3/1
+f 15/19/4 13/20/4 12/6/4 10/14/4
+f 14/18/2 16/21/2 9/11/2 6/4/2
+f 16/20/3 15/17/3 10/3/3 9/6/3
+
+g Chair 2
+v -2 1 0
+v -3 1 0
+v -2 2 0
+v -3 2 0
+v -3 1 -1
+v -3 2 -1
+v -3 1 -1.25
+v -2 1 -1.25
+v -3 2 -1.25
+v -2 2 -1.25
+v -2 1 -1
+v -2 2 -1
+v -2 3 -1
+v -3 3 -1
+v -2 3 -1.25
+v -3 3 -1.25
+
+vt 0 0
+vt -1 0
+vt 0 1
+vt -1 1
+vt 1 0
+vt 1 1
+vt -1 -1
+vt 0 -1
+vt -1 -1.25
+vt 0 -1.25
+vt -1.25 1
+vt -1.25 0
+vt 1.25 0
+vt 1.25 1
+vt 1 -1
+vt 1 -1.25
+vt 0 2
+vt -1 2
+vt 1.25 2
+vt 1 2
+vt -1.25 2
+
+vn 0 0 1
+vn -1 0 0
+vn 0 0 -1
+vn 1 0 0
+vn 0 -1 0
+vn 0 1 0
+
+usemtl dot_purple
+f 19/24/7 20/25/7 18/23/7 17/22/7
+f 20/24/8 22/25/8 21/23/8 18/22/8
+f 25/27/9 26/24/9 24/22/9 23/26/9
+f 28/27/10 19/24/10 17/22/10 27/26/10
+f 23/30/11 24/31/11 27/29/11 21/28/11
+f 25/32/8 23/33/8 21/23/8 22/25/8
+f 24/34/10 26/35/10 28/27/10 27/26/10
+f 31/31/12 32/37/12 30/36/12 29/29/12
+f 29/38/7 30/39/7 22/25/7 28/24/7
+f 31/40/10 29/41/10 28/27/10 26/35/10
+f 30/39/8 32/42/8 25/32/8 22/25/8
+f 32/41/9 31/38/9 26/24/9 25/27/9
+
+g Pillow 2
+v -2 2 0
+v -3 2 0
+v -2 2 -1
+v -3 2 -1
+
+vt 0 0
+vt 1 0
+vt 0 -1
+vt 1 -1
+
+vn 0 1 0
+
+usemtl dot_red
+f 35/45/13 36/46/13 34/44/13 33/43/13
+
+g Podium
+v -1 0 1.5
+v -6 0 1.5
+v -1 1 1.5
+v -6 1 1.5
+v -6 0 -2
+v -6 1 -2
+v -1 0 -2
+v -1 1 -2
+
+vt -1 0
+vt -6 0
+vt -1 1
+vt -6 1
+vt 1.5 0
+vt -2 0
+vt 1.5 1
+vt -2 1
+vt 6 0
+vt 1 0
+vt 6 1
+vt 1 1
+
+s 1
+vt 2 0
+vt -1.5 0
+vt 2 1
+vt -1.5 1
+vt 1 1.5
+vt 6 1.5
+vt 1 -2
+vt 6 -2
+vt -1 -2
+vt -6 -2
+vt -1 1.5
+vt -6 1.5
+s off
+
+vn 0 0 1
+vn 0 0.7071068 0.7071068
+vn -1 0 0
+vn 0 0 -1
+vn 1 0 0
+vn 0 1 0
+vn 0 -1 0
+
+usemtl dot_red
+f 39/49/15 40/50/15 38/48/14 37/47/14
+f 40/53/16 42/54/16 41/52/16 38/51/16
+f 42/57/17 44/58/17 43/56/17 41/55/17
+f 44/61/18 39/62/18 37/60/18 43/59/18
+f 37/69/20 38/70/20 41/68/20 43/67/20
+
+usemtl dot_blue
+f 44/65/19 42/66/19 40/64/15 39/63/15
+
+g Pillow 1
+v -4 2 0
+v -5 2 0
+v -4 2 -1
+v -5 2 -1
+
+vt 0 0
+vt 1 0
+vt 0 -1
+vt 1 -1
+
+vn 0 1 0
+
+usemtl dot_green
+f 47/73/21 48/74/21 46/72/21 45/71/21
+

BIN
jme3-testdata/src/main/resources/OBJLoaderTest/dot_blue.png


BIN
jme3-testdata/src/main/resources/OBJLoaderTest/dot_green.png


BIN
jme3-testdata/src/main/resources/OBJLoaderTest/dot_purple.png


BIN
jme3-testdata/src/main/resources/OBJLoaderTest/dot_red.png