changeset 4258:c5348406f107

Extend support for smoothing groups Add generalized class to generate smoothing groups (SG) from normals. Add support for SG import (into TriangleMesh or PolygonMesh) in obj files both from SG or normals. Change SG import in maya files to use the new SG class. Add support for using SG when rendering PolygonMesh.
author Alex X. Lee
date Wed, 10 Jul 2013 19:30:56 -0700
parents 24eb6c608b31
children f385ea72b01c
files apps/experiments/3DViewer/src/main/java/com/javafx/experiments/importers/SmoothingGroups.java apps/experiments/3DViewer/src/main/java/com/javafx/experiments/importers/maya/Loader.java apps/experiments/3DViewer/src/main/java/com/javafx/experiments/importers/maya/SmoothGroups.java apps/experiments/3DViewer/src/main/java/com/javafx/experiments/importers/obj/ObjImporter.java apps/experiments/3DViewer/src/main/java/com/javafx/experiments/importers/obj/PolyObjImporter.java apps/experiments/3DViewer/src/main/java/com/javafx/experiments/shape3d/PolygonMesh.java apps/experiments/3DViewer/src/main/java/com/javafx/experiments/shape3d/PolygonMeshView.java apps/experiments/3DViewer/src/main/java/com/javafx/experiments/shape3d/SkinningMesh.java
diffstat 8 files changed, 519 insertions(+), 382 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/apps/experiments/3DViewer/src/main/java/com/javafx/experiments/importers/SmoothingGroups.java	Wed Jul 10 19:30:56 2013 -0700
@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2010, 2013 Oracle and/or its affiliates.
+ * All rights reserved. Use is subject to license terms.
+ *
+ * This file is available and licensed under the following license:
+ *
+ * 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 Oracle Corporation 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.javafx.experiments.importers;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import com.sun.javafx.geom.Vec3f;
+import static javafx.scene.shape.TriangleMesh.*;
+
+/** Util for converting Normals to Smoothing Groups */
+public class SmoothingGroups {
+    private BitSet visited, notVisited;
+    private Queue<Integer> q;
+
+    private int[][] faces;
+    private int[][] faceNormals;
+    private float[] normals;
+    
+    private Edge[][] faceEdges;
+
+    public SmoothingGroups(int faces[][], int[][] faceNormals, float[] normals) {
+        this.faces = faces;
+        this.faceNormals = faceNormals;
+        this.normals = normals;
+        visited = new BitSet(faces.length);
+        notVisited = new BitSet(faces.length);
+        notVisited.set(0, faces.length, true);
+        q = new LinkedList<Integer>();
+    }
+
+    // edge -> [faces]
+    private List<Integer> getNextConnectedComponent(Map<Edge, List<Integer>> adjacentFaces) {
+        int index = notVisited.previousSetBit(faces.length - 1);
+        q.add(index);
+        visited.set(index);
+        notVisited.set(index, false);
+        List<Integer> res = new ArrayList<Integer>();
+        while (!q.isEmpty()) {
+            Integer faceIndex = q.remove();
+            res.add(faceIndex);
+            for (Edge edge : faceEdges[faceIndex]) {
+                List<Integer> adjFaces = adjacentFaces.get(edge);
+                if (adjFaces == null) {
+                    continue;
+                }
+                Integer adjFaceIndex = adjFaces.get(adjFaces.get(0).equals(faceIndex) ? 1 : 0);
+                if (!visited.get(adjFaceIndex)) {
+                    q.add(adjFaceIndex);
+                    visited.set(adjFaceIndex);
+                    notVisited.set(adjFaceIndex, false);
+                }
+            }
+        }
+        return res;
+    }
+
+    private boolean hasNextConnectedComponent() {
+        return !notVisited.isEmpty();
+    }
+
+    private void computeFaceEdges() {
+        faceEdges = new Edge[faces.length][];
+        for (int f = 0; f < faces.length; f++) {
+            int[] face = faces[f];
+            int[] faceNormal = faceNormals[f];
+            int n = face.length/2;
+            faceEdges[f] = new Edge[n];
+            int from = face[(n-1) * 2];
+            int fromNormal = faceNormal[n-1];
+            for (int i = 0; i < n; i++) {
+                int to = face[i * 2];
+                int toNormal = faceNormal[i];
+                Edge edge = new Edge(from, to, fromNormal, toNormal);
+                faceEdges[f][i] = edge;
+                from = to;
+                fromNormal = toNormal;
+            }
+        }
+    }
+    
+    private Map<Edge, List<Integer>> getAdjacentFaces() {
+        Map<Edge, List<Integer>> adjacentFaces = new HashMap<Edge, List<Integer>>();
+        for (int f = 0; f < faceEdges.length; f++) {
+            for (Edge edge : faceEdges[f]) {
+                if (!adjacentFaces.containsKey(edge)) {
+                    adjacentFaces.put(edge, new ArrayList<Integer>());
+                }
+                adjacentFaces.get(edge).add(f);
+            }
+        }
+        for (Iterator<Map.Entry<Edge, List<Integer>>> it = adjacentFaces.entrySet().iterator(); it.hasNext(); ) {
+            Map.Entry<Edge, List<Integer>> e = it.next();
+            if (e.getValue().size() != 2) {
+                // just skip them
+                it.remove();
+            }
+        }
+        return adjacentFaces;
+    }
+
+    Vec3f getNormal(int index) {
+        return new Vec3f(normals[index * 3], normals[index * 3 + 1], normals[index * 3 + 2]);
+    }
+    
+    private static final float normalAngle = 0.9994f; // cos(2)
+
+    private static boolean isNormalsEqual(Vec3f n1, Vec3f n2) {
+        if (n1.x == 1.0e20f || n1.y == 1.0e20f || n1.z == 1.0e20f
+                || n2.x == 1.0e20f || n2.y == 1.0e20f || n2.z == 1.0e20f) {
+            //System.out.println("unlocked normal found, skipping");
+            return false;
+        }
+        Vec3f myN1 = new Vec3f(n1);
+        myN1.normalize();
+        Vec3f myN2 = new Vec3f(n2);
+        myN2.normalize();
+        return myN1.dot(myN2) >= normalAngle;
+    }
+
+    private Map<Edge, List<Integer>> getSmoothEdges(Map<Edge, List<Integer>> adjacentFaces) {
+        Map<Edge, List<Integer>> smoothEdges = new HashMap<Edge, List<Integer>>();
+
+        for (int face = 0; face < faceEdges.length; face++) {
+            for (Edge edge : faceEdges[face]) {
+                List<Integer> adjFaces = adjacentFaces.get(edge);
+                if (adjFaces == null || adjFaces.size() != 2) {
+                    // could happen when we skip edges!
+                    continue;
+                }
+                int adjFace = adjFaces.get(adjFaces.get(0) == face ? 1 : 0);
+                Edge[] adjFaceEdges = faceEdges[adjFace];
+                int adjEdgeInd = Arrays.asList(adjFaceEdges).indexOf(edge);
+                if (adjEdgeInd == -1) {
+                    System.out.println("Can't find edge " + edge + " in face " + adjFace);
+                    System.out.println(Arrays.asList(adjFaceEdges));
+                    continue;
+                }
+                Edge adjEdge = adjFaceEdges[adjEdgeInd];
+
+                if (edge.isSmooth(adjEdge)) {
+                    if (!smoothEdges.containsKey(edge)) {
+                        smoothEdges.put(edge, adjFaces);
+                    }
+                }
+            }
+        }
+        return smoothEdges;
+    }
+
+    private List<List<Integer>> calcConnComponents(Map<Edge, List<Integer>> smoothEdges) {
+        //System.out.println("smoothEdges = " + smoothEdges);
+        List<List<Integer>> groups = new ArrayList<List<Integer>>();
+        while (hasNextConnectedComponent()) {
+            List<Integer> smoothGroup = getNextConnectedComponent(smoothEdges);
+            groups.add(smoothGroup);
+        }
+        return groups;
+    }
+
+    private int[] generateSmGroups(List<List<Integer>> groups) {
+        int[] smGroups = new int[faceNormals.length];
+        int curGroup = 0;
+        for (int i = 0; i < groups.size(); i++) {
+            List<Integer> list = groups.get(i);
+            if (list.size() == 1) {
+                smGroups[list.get(0)] = 0;
+            } else {
+                for (int j = 0; j < list.size(); j++) {
+                    Integer faceIndex = list.get(j);
+                    smGroups[faceIndex] = 1 << curGroup;
+                }
+                if (curGroup++ == 31) {
+                    curGroup = 0;
+                }
+            }
+        }
+        return smGroups;
+    }
+
+    private int[] calcSmoothGroups() {
+        computeFaceEdges();
+        
+        // edge -> [faces]
+        Map<Edge, List<Integer>> adjacentFaces = getAdjacentFaces();
+
+        // smooth edge -> [faces]
+        Map<Edge, List<Integer>> smoothEdges = getSmoothEdges(adjacentFaces);
+
+        //System.out.println("smoothEdges = " + smoothEdges);
+        List<List<Integer>> groups = calcConnComponents(smoothEdges);
+
+        return generateSmGroups(groups);
+    }
+    
+    private class Edge {
+        int from, to;
+        int fromNormal, toNormal;
+
+        public Edge(int from, int to, int fromNormal, int toNormal) {
+            this.from = Math.min(from, to);
+            this.to = Math.max(from, to);
+            this.fromNormal = Math.min(fromNormal, toNormal);
+            this.toNormal = Math.max(fromNormal, toNormal);
+        }
+        
+        public boolean isSmooth(Edge edge) {
+            boolean smooth = (isNormalsEqual(getNormal(fromNormal), getNormal(edge.fromNormal)) && isNormalsEqual(getNormal(toNormal), getNormal(edge.toNormal))) ||
+                    (isNormalsEqual(getNormal(fromNormal), getNormal(edge.toNormal)) && isNormalsEqual(getNormal(toNormal), getNormal(edge.fromNormal)));
+            return smooth;
+        }
+
+        @Override
+        public int hashCode() {
+            int hash = 7;
+            hash = 41 * hash + this.from;
+            hash = 41 * hash + this.to;
+            return hash;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            final Edge other = (Edge) obj;
+            if (this.from != other.from) {
+                return false;
+            }
+            if (this.to != other.to) {
+                return false;
+            }
+            return true;
+        }
+    }
+
+    /**
+     * Calculates smoothing groups for data formatted in PolygonMesh style
+     * @param faces An array of faces, where each face consists of an array of vertex and uv indices
+     * @param faceNormals An array of face normals, where each face normal consists of an array of normal indices
+     * @param normals The array of normals
+     * @return An array of smooth groups, where the length of the array is the number of faces
+     */
+    public static int[] calcSmoothGroups(int[][] faces, int[][] faceNormals, float[] normals) {
+        SmoothingGroups smoothGroups = new SmoothingGroups(faces, faceNormals, normals);
+        return smoothGroups.calcSmoothGroups();
+    }
+    
+    /**
+     * Calculates smoothing groups for data formatted in TriangleMesh style
+     * @param flatFaces An array of faces, where each triangle face is represented by 6 (vertex and uv) indices
+     * @param flatFaceNormals An array of face normals, where each triangle face is represented by 3 normal indices
+     * @param normals The array of normals
+     * @return An array of smooth groups, where the length of the array is the number of faces
+     */
+    public static int[] calcSmoothGroups(int[] flatFaces, int[] flatFaceNormals, float[] normals) {
+        int[][] faces = new int[flatFaces.length/NUM_COMPONENTS_PER_FACE][NUM_COMPONENTS_PER_FACE];
+        for (int f = 0; f < faces.length; f++) {
+            for (int e = 0; e < NUM_COMPONENTS_PER_FACE; e++) {
+                faces[f][e] = flatFaces[f*NUM_COMPONENTS_PER_FACE + e];
+            }
+        }
+        int[][] faceNormals = new int[flatFaceNormals.length/NUM_COMPONENTS_PER_POINT][NUM_COMPONENTS_PER_POINT];
+        for (int f = 0; f < faceNormals.length; f++) {
+            for (int e = 0; e < NUM_COMPONENTS_PER_POINT; e++) {
+                faceNormals[f][e] = flatFaceNormals[f*NUM_COMPONENTS_PER_POINT + e];
+            }
+        }
+        SmoothingGroups smoothGroups = new SmoothingGroups(faces, faceNormals, normals);
+        return smoothGroups.calcSmoothGroups();
+    }
+}
--- a/apps/experiments/3DViewer/src/main/java/com/javafx/experiments/importers/maya/Loader.java	Wed Jul 10 17:07:14 2013 -0700
+++ b/apps/experiments/3DViewer/src/main/java/com/javafx/experiments/importers/maya/Loader.java	Wed Jul 10 19:30:56 2013 -0700
@@ -1,5 +1,6 @@
 package com.javafx.experiments.importers.maya;
 
+import com.javafx.experiments.importers.SmoothingGroups;
 import java.io.File;
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -49,6 +50,7 @@
 import com.sun.javafx.geom.Vec3f;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.Arrays;
 import javafx.animation.AnimationTimer;
 import javafx.beans.value.ChangeListener;
 import javafx.beans.value.ObservableValue;
@@ -1617,8 +1619,6 @@
         // copy UV as-is (if any)
         float[] texCoords = getTexCoords(uvChannel);
 
-        int[] smGroups = SmoothGroups.calcSmoothGroups(faces, normals);
-
         if (asPolygonMesh) {
             List<int[]> ff = new ArrayList<int[]>();
             for (int f = 0; f < faces.size(); f++) {
@@ -1637,13 +1637,37 @@
                     ff.add(polyFace);
                 }
             }
-            PolygonMesh mesh = new PolygonMesh(points, texCoords, ff.toArray(new int[ff.size()][]));
+            int[][] facesArray = ff.toArray(new int[ff.size()][]);
+            
+            int[][] faceNormals = new int[facesArray.length][];
+            int normalInd = 0;
+            for (int f = 0; f < faceNormals.length; f++) {
+                faceNormals[f] = new int[facesArray[f].length/2];
+                for (int e = 0; e < faceNormals[f].length; e++) {
+                    faceNormals[f][e] = normalInd++;
+                }
+            }
+            int[] smGroups;
+            // we can only figure out faces' normal indices if the faces' normal indices have a one-to-one ordered correspondence with the normals
+            if (normalInd == normals.getSize()) {
+                smGroups = SmoothingGroups.calcSmoothGroups(facesArray, faceNormals, normals.get());
+            } else {
+                smGroups = new int[facesArray.length];
+                Arrays.fill(smGroups, 1);
+            }
+
+            PolygonMesh mesh = new PolygonMesh();
+            mesh.getPoints().setAll(points);
+            mesh.getTexCoords().setAll(texCoords);
+            mesh.faces = facesArray;
+            mesh.getFaceSmoothingGroups().setAll(smGroups);
             return mesh;
         } else {
             // Split the polygonal faces into triangle faces
             List<Integer> ff = new ArrayList<Integer>();
-            List<Integer> sg = new ArrayList<Integer>();
-
+            List<Integer> nn = new ArrayList<Integer>();
+            int nIndex = 0;
+            
             for (int f = 0; f < faces.size(); f++) {
                 MPolyFace.FaceData faceData = faces.get(f);
                 int[] faceEdges = faceData.getFaceEdges();
@@ -1654,13 +1678,16 @@
                     // Generate triangle fan about the first vertex
                     int vIndex0 = edgeStart(faceEdges[0]);
                     int uvIndex0 = uvIndices == null ? 0 : uvIndices[0];
+                    int nIndex0 = nIndex++;
 
                     int vIndex1 = edgeStart(faceEdges[1]);
                     int uvIndex1 = uvIndices == null ? 0 : uvIndices[1];
+                    int nIndex1 = nIndex++;
 
                     for (int i = 2; i < faceEdges.length; i++) {
                         int vIndex2 = edgeStart(faceEdges[i]);
                         int uvIndex2 = uvIndices == null ? 0 : uvIndices[i];
+                        int nIndex2 = nIndex++;
 
                         ff.add(vIndex0);
                         ff.add(uvIndex0);
@@ -1668,7 +1695,9 @@
                         ff.add(uvIndex1);
                         ff.add(vIndex2);
                         ff.add(uvIndex2);
-                        sg.add(smGroups[f]);
+                        nn.add(nIndex0);
+                        nn.add(nIndex1);
+                        nn.add(nIndex2);
 
                         vIndex1 = vIndex2;
                         uvIndex1 = uvIndex2;
@@ -1679,16 +1708,25 @@
             for (int i = 0; i < fff.length; i++) {
                 fff[i] = ff.get(i);
             }
-            int[] sgsg = new int[sg.size()];
-            for (int i = 0; i < sgsg.length; i++) {
-                sgsg[i] = sg.get(i);
+            
+            int[] smGroups;
+            // we can only figure out faces' normal indices if the faces' normal indices have a one-to-one ordered correspondence with the normals
+            if (nIndex == normals.getSize()) {
+                int[] faceNormals = new int[nn.size()];
+                for (int i = 0; i < faceNormals.length; i++) {
+                    faceNormals[i] = nn.get(i);
+                }
+                smGroups = SmoothingGroups.calcSmoothGroups(fff, faceNormals, normals.get());
+            } else {
+                smGroups = new int[fff.length];
+                Arrays.fill(smGroups, 1);
             }
-
+            
             TriangleMesh mesh = new TriangleMesh();
             mesh.getPoints().setAll(points);
             mesh.getTexCoords().setAll(texCoords);
             mesh.getFaces().setAll(fff);
-            mesh.getFaceSmoothingGroups().setAll(sgsg);
+            mesh.getFaceSmoothingGroups().setAll(smGroups);
             return mesh;
         }
     }
--- a/apps/experiments/3DViewer/src/main/java/com/javafx/experiments/importers/maya/SmoothGroups.java	Wed Jul 10 17:07:14 2013 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,346 +0,0 @@
-/*
- * Copyright (c) 2010, 2013 Oracle and/or its affiliates.
- * All rights reserved. Use is subject to license terms.
- *
- * This file is available and licensed under the following license:
- *
- * 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 Oracle Corporation 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.javafx.experiments.importers.maya;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.BitSet;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
-import com.javafx.experiments.importers.maya.values.MFloat3Array;
-import com.javafx.experiments.importers.maya.values.MPolyFace;
-import com.sun.javafx.geom.Vec3f;
-
-/** Util for converting Normals to Smoothing Groups */
-public class SmoothGroups {
-    private BitSet visited, notVisited;
-    private Queue<Integer> q;
-
-    private List<MPolyFace.FaceData> faces;
-    MFloat3Array normals;
-    // offset in normals array for starting edge of each face
-    private int normalsOffsets[] = null;
-
-    private static boolean trace = false;
-    private static boolean verbose = false;
-
-    public SmoothGroups(List<MPolyFace.FaceData> faces, MFloat3Array normals) {
-        this.faces = faces;
-        this.normals = normals;
-        visited = new BitSet(faces.size());
-        notVisited = new BitSet(faces.size());
-        notVisited.set(0, faces.size(), true);
-        q = new LinkedList<Integer>();
-    }
-
-    // edge -> [faces]
-    private List<Integer> getNextConnectedComponent(Map<Integer, List<Integer>> adjacentFaces) {
-        int index = notVisited.previousSetBit(faces.size() - 1);
-        q.add(index);
-        visited.set(index);
-        notVisited.set(index, false);
-        List<Integer> res = new ArrayList<Integer>();
-        while (!q.isEmpty()) {
-            Integer faceIndex = q.remove();
-            res.add(faceIndex);
-            MPolyFace.FaceData faceData = faces.get(faceIndex);
-            int[] faceEdges = faceData.getFaceEdges();
-            for (int e = 0; e < faceEdges.length; e++) {
-                int edge = reverse(faceEdges[e]);
-                List<Integer> adjFaces = adjacentFaces.get(edge);
-                if (adjFaces == null) {
-                    continue;
-                }
-                // double get always, get(0); if(==) get(1) would be better?
-                Integer adjFaceIndex = adjFaces.get(adjFaces.get(0).equals(faceIndex) ? 1 : 0);
-                if (!visited.get(adjFaceIndex)) {
-                    q.add(adjFaceIndex);
-                    visited.set(adjFaceIndex);
-                    notVisited.set(adjFaceIndex, false);
-                }
-            }
-        }
-        return res;
-    }
-
-    private boolean hasNextConnectedComponent() {
-        return !notVisited.isEmpty();
-    }
-
-    private Map<Integer, List<Integer>> getAdjacentFaces() {
-        Map<Integer, List<Integer>> adjacentFaces = new HashMap();
-        for (int f = 0; f < faces.size(); f++) {
-            MPolyFace.FaceData faceData = faces.get(f);
-            int[] faceEdges = faceData.getFaceEdges();
-            for (int i = 0; i < faceEdges.length; i++) {
-                int edge = reverse(faceEdges[i]);
-                Integer key = Integer.valueOf(edge);
-                if (!adjacentFaces.containsKey(key)) {
-                    adjacentFaces.put(key, new ArrayList<Integer>());
-                }
-                adjacentFaces.get(key).add(Integer.valueOf(f));
-            }
-        }
-        //System.out.println("adjacentFaces = " + adjacentFaces);
-        for (Iterator<Map.Entry<Integer, List<Integer>>> it = adjacentFaces.entrySet().iterator(); it.hasNext(); ) {
-            Map.Entry<Integer, List<Integer>> e = it.next();
-            if (e.getValue().size() != 2) {
-                // just skip them
-                //System.out.println("edge " + e.getKey() + " has too many adjacent faces: " + e.getValue());
-                it.remove();
-            }
-        }
-        return adjacentFaces;
-    }
-
-    private void calcNormalsOffsets() {
-        normalsOffsets = new int[faces.size()];
-        int offset = 0;
-        for (int face = 0; face < faces.size(); face++) {
-            normalsOffsets[face] = offset;
-            MPolyFace.FaceData faceData = faces.get(face);
-            offset += faceData.getFaceEdges().length;
-        }
-    }
-
-    private int getNormalIndex(int face, int edge) {
-        return normalsOffsets[face] + edge;
-    }
-
-    private static int reverse(int edge) {
-        if (edge < 0) {
-            return -edge - 1;
-        }
-        return edge;
-    }
-
-    private static int findEdge(int faceEdges[], int edge) {
-        for (int i = 0; i < faceEdges.length; i++) {
-            if (reverse(faceEdges[i]) == edge) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    Vec3f getNormal(int index) {
-        float normalsData[] = normals.get();
-        return new Vec3f(normalsData[index * 3], normalsData[index * 3 + 1], normalsData[index * 3 + 2]);
-    }
-
-    private static final float normalAngle = 0.9994f; // cos(2)
-
-    private static boolean isNormalsEqual(Vec3f n1, Vec3f n2) {
-        if (n1.x == 1.0e20f || n1.y == 1.0e20f || n1.z == 1.0e20f
-                || n2.x == 1.0e20f || n2.y == 1.0e20f || n2.z == 1.0e20f) {
-            //System.out.println("unlocked normal found, skipping");
-            return false;
-        }
-        Vec3f myN1 = new Vec3f(n1);
-        myN1.normalize();
-        Vec3f myN2 = new Vec3f(n2);
-        myN2.normalize();
-        return myN1.dot(myN2) >= normalAngle;
-    }
-
-    private static void dumpNormals(MFloat3Array normals) {
-        System.out.println("normals size = " + normals.getSize());
-        float data[] = normals.get();
-        for (int i = 0; i < normals.getSize(); i++) {
-            String out = String.format("%3d: %8.5f %8.5f %8.5f", i, data[i * 3], data[i * 3 + 1], data[i * 3 + 2]);
-            System.out.println(out);
-        }
-    }
-
-    private static int getEdgeSubindex(int[] faceEdges, int edge, int n) {
-        if (faceEdges[edge] < 0) {
-            n = 1 - n;
-        }
-        return edge + n < faceEdges.length ? edge + n : 0;
-    }
-
-    private Map<Integer, List<Integer>> getSmoothEdges(Map<Integer, List<Integer>> adjacentFaces) {
-        Map<Integer, List<Integer>> smoothEdges = new HashMap<Integer, List<Integer>>();
-
-        for (int face = 0; face < faces.size(); face++) {
-            MPolyFace.FaceData faceData = faces.get(face);
-            int[] faceEdges = faceData.getFaceEdges();
-            //System.out.println("face = " + face + ", edges = " + Arrays.toString(faceEdges));
-            for (int e = 0; e < faceEdges.length; e++) {
-                int edge = reverse(faceEdges[e]);
-                List<Integer> adjFaces = adjacentFaces.get(edge);
-                if (adjFaces == null || adjFaces.size() != 2) {
-                    // should never happen
-                    // actually could happen when we skip edges!
-                    //System.out.println("adjacent faces for edge " + faceEdges[e] + " : " + adjFaces);
-                    continue;
-                }
-                int adjFace = adjFaces.get(adjFaces.get(0) == face ? 1 : 0);
-                MPolyFace.FaceData adjFaceData = faces.get(adjFace);
-                int e2 = findEdge(adjFaceData.getFaceEdges(), edge);
-                if (e2 == -1) {
-                    System.out.println("Can't find edge " + edge + " in face " + adjFace);
-                    System.out.println(Arrays.asList(adjFaceData.getFaceEdges()));
-                    continue;
-                }
-                boolean smooth = true;
-                //TODO: loop through normals themselves instead of edges
-                for (int n = 0; n < 2; n++) {
-                    int n1 = getNormalIndex(face, getEdgeSubindex(faceEdges, e, n));
-                    int n2 = getNormalIndex(adjFace, getEdgeSubindex(adjFaceData.getFaceEdges(), e2, n));
-                    boolean eq = isNormalsEqual(getNormal(n1), getNormal(n2));
-                    //System.out.println("edge " + edge + ", v" + n + ": " + n1 + (eq ? " == " : " != ") + n2 + " faces: " + adjFaces);
-                    smooth &= eq;
-                }
-                if (smooth) {
-                    if (!smoothEdges.containsKey(edge)) {
-                        smoothEdges.put(edge, adjFaces);
-                    }
-                }
-            }
-        }
-        return smoothEdges;
-    }
-
-    private List<List<Integer>> calcConnComponents(Map<Integer, List<Integer>> smoothEdges) {
-        //System.out.println("smoothEdges = " + smoothEdges);
-        List<List<Integer>> groups = new ArrayList<List<Integer>>();
-        while (hasNextConnectedComponent()) {
-            List<Integer> smoothGroup = getNextConnectedComponent(smoothEdges);
-            groups.add(smoothGroup);
-        }
-        if (verbose) {
-            dumpConnComponents(groups);
-        }
-        return groups;
-    }
-
-    private void dumpConnComponents(List<List<Integer>> groups) {
-        System.out.println("Connected components size = " + groups.size());
-        for (int i = 0; i < groups.size(); i++) {
-            List<Integer> group = groups.get(i);
-            System.out.format("%2d(size = %d): %s", i, group.size(), group).println();
-        }
-    }
-
-    private void dumpSmGroups(int[] smGroups) {
-        System.out.println("smGroups size = " + smGroups.length);
-        for (int i = 0; i < smGroups.length; i++) {
-            System.out.format("%3d: %d", i, smGroups[i]).println();
-        }
-    }
-
-    private int[] generateSmGroups(List<List<Integer>> groups) {
-        int[] smGroups = new int[faces.size()];
-        int curGroup = 0;
-        int nonEmptyGroupsCount = 0;
-        for (int i = 0; i < groups.size(); i++) {
-            List<Integer> list = groups.get(i);
-            if (list.size() == 1) {
-                smGroups[list.get(0)] = 0;
-            } else {
-                for (int j = 0; j < list.size(); j++) {
-                    Integer faceIndex = list.get(j);
-                    smGroups[faceIndex] = 1 << curGroup;
-                }
-                if (curGroup++ == 31) {
-                    curGroup = 0;
-                }
-                nonEmptyGroupsCount++;
-            }
-        }
-        if (verbose) {
-            dumpSmGroups(smGroups);
-        }
-        if (trace || verbose) {
-            System.out.println("Smoothing groups count = " + nonEmptyGroupsCount);
-        }
-        return smGroups;
-    }
-
-    private static int calcLockedNormals(List<MPolyFace.FaceData> faces) {
-        int offset = 0;
-        for (MPolyFace.FaceData faceData : faces) {
-            offset += faceData.getFaceEdges().length;
-        }
-        return offset;
-    }
-
-    private static boolean canCalcSmoothGroups(List<MPolyFace.FaceData> faces, MFloat3Array normals) {
-        if (normals != null) {
-            int lockedNormalsCount = calcLockedNormals(faces);
-            int normalsCount = normals.getSize();
-            if (lockedNormalsCount == normalsCount) {
-                return true;
-            }
-            if (trace || verbose) {
-                System.out.println(
-                        "Can't generate smoothing groups, normalsCount = "
-                                + normalsCount + ", lockedNormalsCount = " + lockedNormalsCount);
-            }
-        }
-        return false;
-    }
-
-    private int[] calcSmoothGroups() {
-        if (verbose) {
-            dumpNormals(normals);
-        }
-
-        calcNormalsOffsets();
-
-        // edge -> [faces]
-        Map<Integer, List<Integer>> adjacentFaces = getAdjacentFaces();
-
-        // smooth edge -> [faces]
-        Map<Integer, List<Integer>> smoothEdges = getSmoothEdges(adjacentFaces);
-
-        //System.out.println("smoothEdges = " + smoothEdges);
-        List<List<Integer>> groups = calcConnComponents(smoothEdges);
-
-        return generateSmGroups(groups);
-    }
-
-    public static int[] calcSmoothGroups(List<MPolyFace.FaceData> faces, MFloat3Array normals) {
-        if (canCalcSmoothGroups(faces, normals)) {
-            SmoothGroups smoothGroups = new SmoothGroups(faces, normals);
-            return smoothGroups.calcSmoothGroups();
-        }
-        int[] smGroups = new int[faces.size()];
-        Arrays.fill(smGroups, 1);
-        return smGroups;
-    }
-}
--- a/apps/experiments/3DViewer/src/main/java/com/javafx/experiments/importers/obj/ObjImporter.java	Wed Jul 10 17:07:14 2013 -0700
+++ b/apps/experiments/3DViewer/src/main/java/com/javafx/experiments/importers/obj/ObjImporter.java	Wed Jul 10 19:30:56 2013 -0700
@@ -31,6 +31,7 @@
  */
 package com.javafx.experiments.importers.obj;
 
+import com.javafx.experiments.importers.SmoothingGroups;
 import java.io.BufferedReader;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -69,6 +70,14 @@
             return uvIndex - 1;
         }
     }
+    
+    private int normalIndex(int normalIndex) {
+        if (normalIndex < 0) {
+            return normalIndex + normals.size() / 3;
+        } else {
+            return normalIndex - 1;
+        }
+    }
 
     private static boolean debug = false;
     private static float scale = 1;
@@ -136,8 +145,11 @@
     private FloatArrayList uvs = new FloatArrayList();
     private IntegerArrayList faces = new IntegerArrayList();
     private IntegerArrayList smoothingGroups = new IntegerArrayList();
+    private FloatArrayList normals = new FloatArrayList();
+    private IntegerArrayList faceNormals = new IntegerArrayList();
     private Material material = new PhongMaterial(Color.WHITE);
     private int facesStart = 0;
+    private int facesNormalStart = 0;
     private int smoothingGroupsStart = 0;
 
     private void read(InputStream inputStream) throws IOException {
@@ -180,11 +192,15 @@
                     String[] split = line.substring(2).trim().split(" +");
                     int[][] data = new int[split.length][];
                     boolean uvProvided = true;
+                    boolean normalProvided = true;
                     for (int i = 0; i < split.length; i++) {
                         String[] split2 = split[i].split("/");
                         if (split2.length < 2) {
                             uvProvided = false;
                         }
+                        if (split2.length < 3) {
+                            normalProvided = false;
+                        }
                         data[i] = new int[split2.length];
                         for (int j = 0; j < split2.length; j++) {
                             if (split2[j].length() == 0) {
@@ -192,6 +208,9 @@
                                 if (j == 1) {
                                     uvProvided = false;
                                 }
+                                if (j == 2) {
+                                    normalProvided = false;
+                                }
                             } else {
                                 data[i][j] = Integer.parseInt(split2[j]);
                             }
@@ -199,22 +218,33 @@
                     }
                     int v1 = vertexIndex(data[0][0]);
                     int uv1 = -1;
+                    int n1 = -1;
                     if (uvProvided && !flatXZ) {
                         uv1 = uvIndex(data[0][1]);
                         if (uv1 < 0) {
                             uvProvided = false;
                         }
                     }
+                    if (normalProvided) {
+                        n1 = normalIndex(data[0][2]);
+                        if (n1 < 0) {
+                            normalProvided = false;
+                        }
+                    }
                     for (int i = 1; i < data.length - 1; i++) {
                         int v2 = vertexIndex(data[i][0]);
                         int v3 = vertexIndex(data[i + 1][0]);
                         int uv2 = -1;
                         int uv3 = -1;
+                        int n2 = -1;
+                        int n3 = -1;
                         if (uvProvided && !flatXZ) {
                             uv2 = uvIndex(data[i][1]);
                             uv3 = uvIndex(data[i + 1][1]);
-                        } else {
-                            //                            System.out.println("uvProvided = " + uvProvided);
+                        }
+                        if (normalProvided) {
+                            n2 = normalIndex(data[i][2]);
+                            n3 = normalIndex(data[i + 1][2]);
                         }
 
                         //                    log("v1 = " + v1 + ", v2 = " + v2 + ", v3 = " + v3);
@@ -226,6 +256,10 @@
                         faces.add(uv2);
                         faces.add(v3);
                         faces.add(uv3);
+                        faceNormals.add(n1);
+                        faceNormals.add(n2);
+                        faceNormals.add(n3);
+                        
                         smoothingGroups.add(currentSmoothGroup);
                     }
                 } else if (line.startsWith("s ")) {
@@ -255,7 +289,13 @@
                 } else if (line.isEmpty() || line.startsWith("#")) {
                     // comments and empty lines are ignored
                 } else if (line.startsWith("vn ")) {
-                    // normals are ignored
+                    String[] split = line.substring(2).trim().split(" +");
+                    float x = Float.parseFloat(split[0]);
+                    float y = Float.parseFloat(split[1]);
+                    float z = Float.parseFloat(split[2]);
+                    normals.add(x);
+                    normals.add(y);
+                    normals.add(z);
                 } else {
                     log("line skipped: " + line);
                 }
@@ -280,8 +320,11 @@
         }
         Map<Integer, Integer> vertexMap = new HashMap<>(vertexes.size() / 2);
         Map<Integer, Integer> uvMap = new HashMap<>(uvs.size() / 2);
+        Map<Integer, Integer> normalMap = new HashMap<>(normals.size() / 2);
         FloatArrayList newVertexes = new FloatArrayList(vertexes.size() / 2);
         FloatArrayList newUVs = new FloatArrayList(uvs.size() / 2);
+        FloatArrayList newNormals = new FloatArrayList(normals.size() / 2);
+        boolean useNormals = true;
 
         for (int i = facesStart; i < faces.size(); i += 2) {
             int vi = faces.get(i);
@@ -309,14 +352,43 @@
                 }
             }
             faces.set(i + 1, nuvi);
+            
+            if (useNormals) {
+                int ni = faceNormals.get(i/2);
+                Integer nni = normalMap.get(ni);
+                if (nni == null) {
+                    nni = newNormals.size() / 3;
+                    normalMap.put(ni, nni);
+                    if (ni >= 0 && normals.size() >= (ni+1)*3) {
+                        newNormals.add(normals.get(ni * 3));
+                        newNormals.add(normals.get(ni * 3 + 1));
+                        newNormals.add(normals.get(ni * 3 + 2));
+                    } else {
+                        useNormals = false;
+                        newNormals.add(0f);
+                        newNormals.add(0f);
+                        newNormals.add(0f);
+                    }
+                }
+                faceNormals.set(i/2, nni);
+            }
         }
 
         TriangleMesh mesh = new TriangleMesh();
         mesh.getPoints().setAll(newVertexes.toFloatArray());
         mesh.getTexCoords().setAll(newUVs.toFloatArray());
         mesh.getFaces().setAll(((IntegerArrayList) faces.subList(facesStart, faces.size())).toIntArray());
-        mesh.getFaceSmoothingGroups().setAll(((IntegerArrayList) smoothingGroups.subList(smoothingGroupsStart, smoothingGroups.size())).toIntArray());
-
+        
+        // Use normals if they are provided
+        if (useNormals) {
+            int[] newFaces = ((IntegerArrayList) faces.subList(facesStart, faces.size())).toIntArray();
+            int[] newFaceNormals = ((IntegerArrayList) faceNormals.subList(facesNormalStart, faceNormals.size())).toIntArray();
+            int[] smGroups = SmoothingGroups.calcSmoothGroups(newFaces, newFaceNormals, newNormals.toFloatArray());
+            mesh.getFaceSmoothingGroups().setAll(smGroups);
+        } else {
+            mesh.getFaceSmoothingGroups().setAll(((IntegerArrayList) smoothingGroups.subList(smoothingGroupsStart, smoothingGroups.size())).toIntArray());
+        }
+       
         int keyIndex = 2;
         String keyBase = key;
         while (meshes.get(key) != null) {
@@ -334,6 +406,7 @@
         log("material diffuse map = " + ((PhongMaterial) material).getDiffuseMap());
 
         facesStart = faces.size();
+        facesNormalStart = faceNormals.size();
         smoothingGroupsStart = smoothingGroups.size();
     }
 
--- a/apps/experiments/3DViewer/src/main/java/com/javafx/experiments/importers/obj/PolyObjImporter.java	Wed Jul 10 17:07:14 2013 -0700
+++ b/apps/experiments/3DViewer/src/main/java/com/javafx/experiments/importers/obj/PolyObjImporter.java	Wed Jul 10 19:30:56 2013 -0700
@@ -32,6 +32,7 @@
 package com.javafx.experiments.importers.obj;
 
 
+import com.javafx.experiments.importers.SmoothingGroups;
 import javafx.scene.paint.Color;
 import javafx.scene.paint.Material;
 import javafx.scene.paint.PhongMaterial;
@@ -65,6 +66,14 @@
             return uvIndex - 1;
         }
     }
+    
+    private int normalIndex(int normalIndex) {
+        if (normalIndex < 0) {
+            return normalIndex + normals.size() / 3;
+        } else {
+            return normalIndex - 1;
+        }
+    }
 
     private static boolean debug = false;
     private static float scale = 1;
@@ -132,8 +141,11 @@
     private FloatArrayList uvs = new FloatArrayList();
     private List<int[]> faces = new ArrayList<>();
     private IntegerArrayList smoothingGroups = new IntegerArrayList();
+    private FloatArrayList normals = new FloatArrayList();
+    private List<int[]> faceNormals = new ArrayList<>();
     private Material material = new PhongMaterial(Color.WHITE);
     private int facesStart = 0;
+    private int facesNormalStart = 0;
     private int smoothingGroupsStart = 0;
     
     private void read(InputStream inputStream) throws IOException {
@@ -175,13 +187,16 @@
                 } else if (line.startsWith("f ")) {
                     String[] split = line.substring(2).trim().split(" +");
                     int[] faceIndexes = new int[split.length*2];
+                    int[] faceNormalIndexes = new int[split.length];
                     for (int i = 0; i < split.length; i++) {
                         String[] split2 = split[i].split("/");
                         faceIndexes[i*2] = vertexIndex(Integer.parseInt(split2[0]));
-                        faceIndexes[(i*2)+1] = (split2.length > 1) ? uvIndex(Integer.parseInt(split2[1])) : 0;
+                        faceIndexes[(i*2)+1] = (split2.length > 1 && split2[1].length()>0) ? uvIndex(Integer.parseInt(split2[1])) : -1;
+                        faceNormalIndexes[i] = (split2.length > 2 && split2[2].length()>0) ? normalIndex(Integer.parseInt(split2[2])) : -1;
                     }
                     faces.add(faceIndexes);
-//                        smoothingGroups.add(currentSmoothGroup); TODO
+                    faceNormals.add(faceNormalIndexes);
+                    smoothingGroups.add(currentSmoothGroup);
                 } else if (line.startsWith("s ")) {
                     if (line.substring(2).equals("off")) {
                         currentSmoothGroup = 0;
@@ -209,7 +224,13 @@
                 } else if (line.isEmpty() || line.startsWith("#")) {
                     // comments and empty lines are ignored
                 } else if (line.startsWith("vn ")) {
-                    // normals are ignored
+                    String[] split = line.substring(2).trim().split(" +");
+                    float x = Float.parseFloat(split[0]);
+                    float y = Float.parseFloat(split[1]);
+                    float z = Float.parseFloat(split[2]);
+                    normals.add(x);
+                    normals.add(y);
+                    normals.add(z);
                 } else {
                     log("line skipped: " + line);
                 }
@@ -233,13 +254,18 @@
         }
         Map<Integer, Integer> vertexMap = new HashMap<>(vertexes.size() / 2);
         Map<Integer, Integer> uvMap = new HashMap<>(uvs.size() / 2);
+        Map<Integer, Integer> normalMap = new HashMap<>(normals.size() / 2);
         FloatArrayList newVertexes = new FloatArrayList(vertexes.size() / 2);
         FloatArrayList newUVs = new FloatArrayList(uvs.size() / 2);
+        FloatArrayList newNormals = new FloatArrayList(normals.size() / 2);
+        boolean useNormals = true;
 
         int[][] faceArrays = new int[faces.size()-facesStart][];
+        int[][] faceNormalArrays = new int[faceNormals.size()-facesNormalStart][];
         
         for (int i = facesStart; i < faces.size();i++) {
             int[] faceIndexes = faces.get(i);
+            int[] faceNormalIndexes = faceNormals.get(i);
             for (int j=0;j<faceIndexes.length;j+=2){
                 int vi = faceIndexes[j];
                 Integer nvi = vertexMap.get(vi);
@@ -267,22 +293,43 @@
                 }
                 faceIndexes[j+1] = nuvi;
 //                faces.set(i + 1, nuvi);
+                
+                int ni = faceNormalIndexes[j/2];
+                Integer nni = normalMap.get(ni);
+                if (nni == null) {
+                    nni = newNormals.size() / 3;
+                    normalMap.put(ni, nni);
+                    if (ni >= 0 && normals.size() >= (ni+1)*3) {
+                        newNormals.add(normals.get(ni * 3));
+                        newNormals.add(normals.get(ni * 3 + 1));
+                        newNormals.add(normals.get(ni * 3 + 2));
+                    } else {
+                        useNormals = false;
+                        newNormals.add(0f);
+                        newNormals.add(0f);
+                        newNormals.add(0f);
+                    }
+                }
+                faceNormalIndexes[j/2] = nni;
             }
             faceArrays[i-facesStart] = faceIndexes;
+            faceNormalArrays[i-facesNormalStart] = faceNormalIndexes;
         }
 
-        // TODO
-//        mesh.setPoints(newVertexes.toFloatArray());
-//        mesh.setTexCoords(newUVs.toFloatArray());
-//        mesh.setFaces(((IntegerArrayList) faces.subList(facesStart, faces.size())).toIntArray());
-//        mesh.setFaceSmoothingGroups(((IntegerArrayList) smoothingGroups.subList(smoothingGroupsStart, smoothingGroups.size())).toIntArray());
-
-
         PolygonMesh mesh = new PolygonMesh(
                 newVertexes.toFloatArray(),
                 newUVs.toFloatArray(),
                 faceArrays
         );
+        
+        // Use normals if they are provided
+        if (useNormals) {
+            int[] smGroups = SmoothingGroups.calcSmoothGroups(faceArrays, faceNormalArrays, newNormals.toFloatArray());
+            mesh.getFaceSmoothingGroups().setAll(smGroups);
+        } else {
+            mesh.getFaceSmoothingGroups().setAll(((IntegerArrayList) smoothingGroups.subList(smoothingGroupsStart, smoothingGroups.size())).toIntArray());
+        }
+        
         if (debug) {
             System.out.println("mesh.points = " + mesh.getPoints());
             System.out.println("mesh.texCoords = " + mesh.getTexCoords());
@@ -308,6 +355,7 @@
         log("material diffuse map = " + ((PhongMaterial) material).getDiffuseMap());
         
         facesStart = faces.size();
+        facesNormalStart = faceNormals.size();
         smoothingGroupsStart = smoothingGroups.size();
     }
 
--- a/apps/experiments/3DViewer/src/main/java/com/javafx/experiments/shape3d/PolygonMesh.java	Wed Jul 10 17:07:14 2013 -0700
+++ b/apps/experiments/3DViewer/src/main/java/com/javafx/experiments/shape3d/PolygonMesh.java	Wed Jul 10 19:30:56 2013 -0700
@@ -2,17 +2,19 @@
 
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableFloatArray;
+import javafx.collections.ObservableIntegerArray;
 
 /**
  * A Mesh where each face can be a Polygon
  *
- * TODO convert to using ObservableFloatArray and ObservableIntegerArray
+ * TODO convert to using ObservableIntegerArray
  */
 public class PolygonMesh {
     private final ObservableFloatArray points = FXCollections.observableFloatArray();
     private final ObservableFloatArray texCoords = FXCollections.observableFloatArray();
-    public int[][] faces;
-    protected int numEdgesInFaces = -1;
+    public int[][] faces = new int[0][0];
+    private final ObservableIntegerArray faceSmoothingGroups = FXCollections.observableIntegerArray();
+    protected int numEdgesInFaces = -1; // TODO invalidate automatically by listening to faces (whenever it is an observable)
 
     public PolygonMesh() {}
 
@@ -30,6 +32,10 @@
         return texCoords;
     }
     
+    public ObservableIntegerArray getFaceSmoothingGroups() {
+        return faceSmoothingGroups;
+    }
+     
     public int getNumEdgesInFaces() {
         if (numEdgesInFaces == -1) {
             numEdgesInFaces = 0;
--- a/apps/experiments/3DViewer/src/main/java/com/javafx/experiments/shape3d/PolygonMeshView.java	Wed Jul 10 17:07:14 2013 -0700
+++ b/apps/experiments/3DViewer/src/main/java/com/javafx/experiments/shape3d/PolygonMeshView.java	Wed Jul 10 19:30:56 2013 -0700
@@ -284,6 +284,7 @@
                     }
                 }
                 triangleMesh.getFaces().setAll(facesArray);
+                triangleMesh.getFaceSmoothingGroups().clear();
             }
             if (texCoordsDirty) {
                 texCoordsDirty = false;
@@ -334,8 +335,11 @@
                 final int numOfFacesBefore = pmesh.faces.length;
                 final int numOfFacesAfter = pmesh.getNumEdgesInFaces() - 2*numOfFacesBefore;
                 int [] facesArray = new int [numOfFacesAfter * NUM_COMPONENTS_PER_FACE];
+                int [] smoothingGroupsArray = new int [pmesh.getNumEdgesInFaces()];
                 int facesInd = 0;
-                for(int[] face: pmesh.faces) {
+                for(int f = 0; f < pmesh.faces.length; f++) {
+                    int[] face = pmesh.faces[f];
+                    int currentSmoothGroup = pmesh.getFaceSmoothingGroups().get(f);
                     if (DEBUG) System.out.println("face.length = " + face.length+"  -- "+Arrays.toString(face));
                     int firstPointIndex = face[0];
                     int firstTexIndex = face[1];
@@ -344,17 +348,20 @@
                     for (int p=4;p<face.length;p+=2) {
                         int pointIndex = face[p];
                         int texIndex = face[p+1];
-                        facesArray[facesInd++] = firstPointIndex;
-                        facesArray[facesInd++] = firstTexIndex;
-                        facesArray[facesInd++] = lastPointIndex;
-                        facesArray[facesInd++] = lastTexIndex;
-                        facesArray[facesInd++] = pointIndex;
-                        facesArray[facesInd++] = texIndex;
+                        facesArray[facesInd * NUM_COMPONENTS_PER_FACE] = firstPointIndex;
+                        facesArray[facesInd * NUM_COMPONENTS_PER_FACE + 1] = firstTexIndex;
+                        facesArray[facesInd * NUM_COMPONENTS_PER_FACE + 2] = lastPointIndex;
+                        facesArray[facesInd * NUM_COMPONENTS_PER_FACE + 3] = lastTexIndex;
+                        facesArray[facesInd * NUM_COMPONENTS_PER_FACE + 4] = pointIndex;
+                        facesArray[facesInd * NUM_COMPONENTS_PER_FACE + 5] = texIndex;
+                        smoothingGroupsArray[facesInd] = currentSmoothGroup;
+                        facesInd++;
                         lastPointIndex = pointIndex;
                         lastTexIndex = texIndex;
                     }
                 }
                 triangleMesh.getFaces().setAll(facesArray);
+                triangleMesh.getFaceSmoothingGroups().setAll(smoothingGroupsArray);
             }
             if (texCoordsDirty) {
                 texCoordsDirty = false;
--- a/apps/experiments/3DViewer/src/main/java/com/javafx/experiments/shape3d/SkinningMesh.java	Wed Jul 10 17:07:14 2013 -0700
+++ b/apps/experiments/3DViewer/src/main/java/com/javafx/experiments/shape3d/SkinningMesh.java	Wed Jul 10 19:30:56 2013 -0700
@@ -46,6 +46,7 @@
         this.getPoints().addAll(mesh.getPoints());
         this.getTexCoords().addAll(mesh.getTexCoords());
         this.faces = mesh.faces;
+        this.getFaceSmoothingGroups().addAll(mesh.getFaceSmoothingGroups());
         
         this.weights = weights;