From 21613c7dd1be071b7a8f6aa4843db1370fcd1c4a Mon Sep 17 00:00:00 2001
From: Radek Oslejsek <oslejsek@fi.muni.cz>
Date: Thu, 8 Apr 2021 08:12:35 +0200
Subject: [PATCH] Automatically removes duplicite vertices when loading new
 HumanFace

---
 .../cz/fidentis/analyst/face/HumanFace.java   |  5 +-
 .../analyst/visitors/mesh/Curvature.java      |  2 -
 .../analyst/tests/EfficiencyTests.java        |  7 ++-
 .../analyst/mesh/core/CornerTable.java        |  7 ++-
 .../analyst/mesh/core/CornerTableRow.java     |  9 ++++
 .../fidentis/analyst/mesh/core/MeshFacet.java | 10 ++++
 .../analyst/mesh/core/MeshFacetImpl.java      | 53 ++++++++++++++++++-
 .../fidentis/analyst/mesh/core/MeshModel.java | 13 ++++-
 .../analyst/mesh/io/MeshObjLoader.java        |  2 +-
 9 files changed, 98 insertions(+), 10 deletions(-)

diff --git a/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFace.java b/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFace.java
index 396f2be6..fa5bf458 100644
--- a/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFace.java
+++ b/Comparison/src/main/java/cz/fidentis/analyst/face/HumanFace.java
@@ -9,6 +9,7 @@ import cz.fidentis.analyst.mesh.io.MeshObjLoader;
 import cz.fidentis.analyst.symmetry.Plane;
 
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 
@@ -50,8 +51,7 @@ public class HumanFace implements MeshListener {
      * @throws IOException on I/O failure
      */
     public HumanFace(File file) throws IOException {
-        meshModel = MeshObjLoader.read(file);
-        meshModel.registerListener(this);
+        this(new FileInputStream(file));
     }
     
     /**
@@ -62,6 +62,7 @@ public class HumanFace implements MeshListener {
      */
     public HumanFace(InputStream is) throws IOException {
         meshModel = MeshObjLoader.read(is);
+        meshModel.simplifyModel();
         meshModel.registerListener(this);
     }
     
diff --git a/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/Curvature.java b/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/Curvature.java
index 536a78e0..7198da9a 100644
--- a/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/Curvature.java
+++ b/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/Curvature.java
@@ -1,6 +1,5 @@
 package cz.fidentis.analyst.visitors.mesh;
 
-import cz.fidentis.analyst.kdtree.KdTreeVisitor;
 import cz.fidentis.analyst.mesh.MeshVisitor;
 import cz.fidentis.analyst.mesh.core.MeshFacet;
 import cz.fidentis.analyst.mesh.core.MeshTriangle;
@@ -13,7 +12,6 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
 import javax.vecmath.Vector3d;
 
 /**
diff --git a/GUI/src/main/java/cz/fidentis/analyst/tests/EfficiencyTests.java b/GUI/src/main/java/cz/fidentis/analyst/tests/EfficiencyTests.java
index aa40e41d..e3d44487 100644
--- a/GUI/src/main/java/cz/fidentis/analyst/tests/EfficiencyTests.java
+++ b/GUI/src/main/java/cz/fidentis/analyst/tests/EfficiencyTests.java
@@ -36,8 +36,11 @@ public class EfficiencyTests {
      * @throws IOException on IO error
      */
     public static void main(String[] args) throws IOException {
-        face1 = new HumanFace(girlFile);
-        face2 = new HumanFace(boyFile);
+        face1 = new HumanFace(faceFile2);
+        face2 = new HumanFace(faceFile4);
+        
+        face1.getMeshModel().simplifyModel();
+        face2.getMeshModel().simplifyModel();
         
         boolean relativeDist = false;
         boolean printDetails = false;
diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/CornerTable.java b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/CornerTable.java
index 045a8666..5157c085 100644
--- a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/CornerTable.java
+++ b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/CornerTable.java
@@ -174,8 +174,13 @@ public class CornerTable {
     public void replaceRow(int index, CornerTableRow row) {
         CornerTableRow oldRow = rows.get(index);
         int oldVertIndex = oldRow.getVertexIndex();
+        
         List<Integer> oldReferences = vertexToRow.get(oldVertIndex);
-        oldReferences.remove(index);
+        
+        oldReferences.remove(oldReferences.indexOf(index)); // !!!!
+        if (oldReferences.isEmpty()) {
+            vertexToRow.remove(oldVertIndex);
+        }
         
         rows.set(index, row);
         
diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/CornerTableRow.java b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/CornerTableRow.java
index 3f2b58d9..761ce9e9 100644
--- a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/CornerTableRow.java
+++ b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/CornerTableRow.java
@@ -62,4 +62,13 @@ public class CornerTableRow {
     public void setOppositeCornerIndex(int index) {
         this.oppositeCornerRow = index;
     }
+    
+    /**
+     * sets index of the vertex
+     * 
+     * @param index New index
+     */
+    public void setVertexIndex(int index) {
+        this.vertexIndex = index;
+    }
 }
diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshFacet.java b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshFacet.java
index 27cfd798..6f3a8f9f 100644
--- a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshFacet.java
+++ b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshFacet.java
@@ -131,4 +131,14 @@ public interface MeshFacet extends Iterable<MeshTriangle> {
      * @return Triangles around the vertex or {@code null}
      */
     TriangleFan getOneRingNeighborhood(int vertexIndex);
+    
+    /**
+     * Removes duplicate vertices that differ only in normal vectors or texture coordinates.
+     * Multiple normals are replaced with the average normal. If the texture coordinate 
+     * differ then randomly selected one is used.
+     * 
+     * @return {@code true} if the mesh was changed, {@code false} if there were
+     * no duplicities.
+     */
+    boolean simplify();
 }
diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshFacetImpl.java b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshFacetImpl.java
index af454d2e..467ce790 100644
--- a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshFacetImpl.java
+++ b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshFacetImpl.java
@@ -26,7 +26,7 @@ public class MeshFacetImpl implements MeshFacet {
     
     private List<MeshPoint> vertices = new ArrayList<>();
     
-    private CornerTable cornerTable;
+    private final CornerTable cornerTable;
     
     /**
      * Constructor of MeshFacet
@@ -228,5 +228,56 @@ public class MeshFacetImpl implements MeshFacet {
         }
         return new TriangleFan(this, vertexIndex);
     }
+    
+    @Override
+    public boolean simplify() {
+        // aggregate duplicates into the map, remember old positions:
+        Map<Vector3d, MeshPoint> mapPoints = new HashMap<>();
+        Map<Vector3d, List<Integer>> mapOrigPositions = new HashMap<>();
+        for (int i = 0; i < this.getNumberOfVertices(); i++) {
+            Vector3d v = this.getVertex(i).getPosition();
+            Vector3d n = this.getVertex(i).getNormal();
+            Vector3d t = this.getVertex(i).getTexCoord();
+            if (!mapPoints.containsKey(v)) {
+                mapPoints.put(v, new MeshPointImpl(v, n, t));
+                mapOrigPositions.put(v, new ArrayList<>());
+            } else if (n != null) {
+                mapPoints.put(v, mapPoints.get(v).addNormal(n));
+            }
+            mapOrigPositions.get(v).add(i);
+        }
+        
+        if (mapPoints.size() == getNumberOfVertices()) {
+            return false;
+        }
+        
+        // create shrinked list of vertices:
+        List<MeshPoint> newVertices = new ArrayList<>(mapPoints.size());
+        Map<Integer, Integer> mapOrigNew = new HashMap<>();
+        for (Vector3d v : mapPoints.keySet()) {
+            MeshPoint p = mapPoints.get(v);
+            if (p.getNormal() != null) {
+                p.getNormal().normalize();
+            }
+            newVertices.add(p);
+            
+            for (Integer pos: mapOrigPositions.get(v)) {
+                mapOrigNew.put(pos, newVertices.size()-1);
+            }
+        }
+       
+        // update corner table:
+        for (int i = 0; i < this.cornerTable.getSize(); i++) {
+            int origIndex = cornerTable.getRow(i).getVertexIndex();
+            CornerTableRow newRow = new CornerTableRow(cornerTable.getRow(i));
+            newRow.setVertexIndex(mapOrigNew.get(origIndex));
+            cornerTable.replaceRow(i, newRow);
+        }
+        
+        this.vertices = newVertices;
+        return true;
+    }
+    
+    
 }
 
diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshModel.java b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshModel.java
index 99e4f36f..de605a83 100644
--- a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshModel.java
+++ b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshModel.java
@@ -105,6 +105,7 @@ public class MeshModel {
             }
         }
     }
+    
     /**
      * Applies the visitor to all mesh facets sequentially.
      * 
@@ -115,7 +116,6 @@ public class MeshModel {
         compute(visitor, false);
     }
     
-    
     /**
      * Registers listeners (objects concerned in the mesh model changes) to receive events.
      * If listener is {@code null}, no exception is thrown and no action is taken.
@@ -135,6 +135,17 @@ public class MeshModel {
         eventBus.unregister(listener);
     }
     
+    /**
+     * Removes duplicate vertices that differ only in normal vectors or texture coordinates.
+     * Multiple normals are replaced with the average normal. If the texture coordinate 
+     * differ then randomly selected one is used.
+     */
+    public void simplifyModel() {
+        for (MeshFacet f : this.facets) {
+            f.simplify();
+        }
+    }
+    
     @Override
     public String toString() {
         int verts = 0;
diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/io/MeshObjLoader.java b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/io/MeshObjLoader.java
index 250dacd9..ab612a92 100644
--- a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/io/MeshObjLoader.java
+++ b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/io/MeshObjLoader.java
@@ -99,7 +99,7 @@ public class MeshObjLoader {
         for (OBJFace face : mesh.getFaces()) {
             processFace(model, face, meshFacet, vertices, edges);
         }
-
+        
         return meshFacet;
     }
 
-- 
GitLab