From e0e2a181fa2c42d87248c2d4367e0ad93e15c4c6 Mon Sep 17 00:00:00 2001 From: Marek Zuzi Date: Sat, 8 Oct 2016 12:26:20 +0200 Subject: [PATCH 1/2] Work in progress for mesh decimation --- Processing/nbproject/project.xml | 1 + .../fidentis/processing/decimation/Edge.java | 53 +++ .../processing/decimation/EdgeComparator.java | 40 ++ .../decimation/QuadricEdgeCollapse.java | 433 ++++++++++++++++++ 4 files changed, 527 insertions(+) create mode 100644 Processing/src/cz/fidentis/processing/decimation/Edge.java create mode 100644 Processing/src/cz/fidentis/processing/decimation/EdgeComparator.java create mode 100644 Processing/src/cz/fidentis/processing/decimation/QuadricEdgeCollapse.java diff --git a/Processing/nbproject/project.xml b/Processing/nbproject/project.xml index 55f4895f..d2b92ee4 100644 --- a/Processing/nbproject/project.xml +++ b/Processing/nbproject/project.xml @@ -115,6 +115,7 @@ cz.fidentis.gui cz.fidentis.processing.comparison.surfaceComparison + cz.fidentis.processing.decimation cz.fidentis.processing.exportProcessing cz.fidentis.processing.featurePoints cz.fidentis.processing.fileUtils diff --git a/Processing/src/cz/fidentis/processing/decimation/Edge.java b/Processing/src/cz/fidentis/processing/decimation/Edge.java new file mode 100644 index 00000000..ae14b79b --- /dev/null +++ b/Processing/src/cz/fidentis/processing/decimation/Edge.java @@ -0,0 +1,53 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package cz.fidentis.processing.decimation; + +/** + * Simple wrapper that identifies a valid pair for edge collapse. + * @author Marek Zuzi + */ +public class Edge { + private int from; + private int to; + + public Edge(int from, int to) { + this.from = from; + this.to = to; + } + + public int getFrom() { + return from; + } + + public int getTo() { + return to; + } + + @Override + public int hashCode() { + int hash = 5; + int addition = from + to; + hash = 73 * hash + addition; + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Edge other = (Edge) obj; + return (this.from == other.from && this.to == other.to) || (this.from == other.to && this.to == other.from); + } + + +} diff --git a/Processing/src/cz/fidentis/processing/decimation/EdgeComparator.java b/Processing/src/cz/fidentis/processing/decimation/EdgeComparator.java new file mode 100644 index 00000000..26a843fa --- /dev/null +++ b/Processing/src/cz/fidentis/processing/decimation/EdgeComparator.java @@ -0,0 +1,40 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package cz.fidentis.processing.decimation; + +import java.util.Comparator; +import java.util.Map; + +/** + * + * @author Marek Zuzi + */ +public class EdgeComparator implements Comparator { + private static final double EPS = 0.00001d; + private Map weights; + + public EdgeComparator(Map weights) { + this.weights = weights; + } + + + + @Override + public int compare(Edge o1, Edge o2) { + double w1 = weights.get(o1); + double w2 = weights.get(o2); + + if(Math.abs(w1 - w2) < EPS) { + return 0; + } + + if(w1 < w1) { + return 1; + } else { + return -1; + } + } +} diff --git a/Processing/src/cz/fidentis/processing/decimation/QuadricEdgeCollapse.java b/Processing/src/cz/fidentis/processing/decimation/QuadricEdgeCollapse.java new file mode 100644 index 00000000..5f89332b --- /dev/null +++ b/Processing/src/cz/fidentis/processing/decimation/QuadricEdgeCollapse.java @@ -0,0 +1,433 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package cz.fidentis.processing.decimation; + +import Jama.Matrix; +import cz.fidentis.model.Model; +import cz.fidentis.model.corner_table.Corner; +import cz.fidentis.model.corner_table.CornerTable; +import cz.fidentis.utils.MathUtils; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.PriorityQueue; +import javax.vecmath.Vector3f; +import javax.vecmath.Vector4d; + +/** + * + * @author Marek Zuzi + */ +public class QuadricEdgeCollapse { + private final Model model; + private float threshold = -1; + + private CornerTable mesh; + + private HashMap quadrics; + + private HashSet pairs; + private HashMap pairTargets; + private HashMap pairErrors; + + private PriorityQueue queue; + private HashSet removedFaces; + private HashSet removedVerts; + + private HashMap> vertsToFaces; + private HashMap indexReorientations; + + public QuadricEdgeCollapse(Model model) { + this.model = model; + } + + public void decimate(int targetVertexCount) { + while(decimateWhilePossible(targetVertexCount)) { + + } + } + + public boolean decimateWhilePossible(int targetVertexCount) { + mesh = new CornerTable(model); + removedFaces = new HashSet<>(); + removedVerts = new HashSet<>(); + indexReorientations = new HashMap<>(); + + // TEST + vertsToFaces = new HashMap<>(); + for(int i=0;i()); + } + } + for(int triangleIdx=0;triangleIdx 0; i--) { + Edge edgeToContract = getEdgeToContract(); + if(edgeToContract == null) { + break; + } + contractEdge(edgeToContract); + } + + // 6. clean up model to remove detached verices and faces + cleanupModel(); + + return queue.isEmpty(); + } + + public void setThreshold(float t) { + this.threshold = t; + } + + public Model getModel() { + return model; + } + + /** + * First it is needed to initialize error quadric for each vertex by looking + * at its incident triangles. + */ + private void initializeVertexQuadrics() { + quadrics = new HashMap<>(model.getVerts().size()); + + for(int i=0;i(); + + // add a pair for each edge in model, make sure there are no duplicates + for(int triangleIdx=0;triangleIdx 0.0f) { + for(int i=0;i(pairs.size()); + + for(Edge e : pairs) { + pairTargets.put(e, computeTargetV(e)); + } + } + + /** + * Finally, initialised pairs should be sorted by the cost of contraction so + * that the least errors would be produced. + */ + private void initializeQueue() { + pairErrors = new HashMap<>(pairs.size()); + EdgeComparator c = new EdgeComparator(pairErrors); + queue = new PriorityQueue<>(pairs.size(), c); + + for(Edge e : pairs) { + // get weight of this pair + double weight = computeWeight(e); + pairErrors.put(e, weight); + + // add it to priority queue keyed by weight + queue.add(e); + } + } + + /** + * Removes all vertices and faces that were detached during collapses. Also + * recompute normals. + */ + private void cleanupModel() { + // TODO do this + ArrayList newVerts = new ArrayList<>(); + + for(int i=0;i Date: Wed, 12 Oct 2016 20:58:07 +0200 Subject: [PATCH 2/2] Completing decimation algorithm --- .../decimation/QuadricEdgeCollapse.java | 109 ++++++++++++------ 1 file changed, 73 insertions(+), 36 deletions(-) diff --git a/Processing/src/cz/fidentis/processing/decimation/QuadricEdgeCollapse.java b/Processing/src/cz/fidentis/processing/decimation/QuadricEdgeCollapse.java index 5f89332b..a6d577ce 100644 --- a/Processing/src/cz/fidentis/processing/decimation/QuadricEdgeCollapse.java +++ b/Processing/src/cz/fidentis/processing/decimation/QuadricEdgeCollapse.java @@ -6,6 +6,7 @@ package cz.fidentis.processing.decimation; import Jama.Matrix; +import cz.fidentis.model.Faces; import cz.fidentis.model.Model; import cz.fidentis.model.corner_table.Corner; import cz.fidentis.model.corner_table.CornerTable; @@ -25,20 +26,18 @@ public class QuadricEdgeCollapse { private final Model model; private float threshold = -1; - private CornerTable mesh; + private final HashMap quadrics = new HashMap<>(); - private HashMap quadrics; - - private HashSet pairs; - private HashMap pairTargets; - private HashMap pairErrors; + private final HashSet pairs = new HashSet<>(); + private final HashMap pairTargets = new HashMap<>(); + private final HashMap pairErrors = new HashMap<>(); private PriorityQueue queue; - private HashSet removedFaces; - private HashSet removedVerts; + private final HashSet removedFaces = new HashSet<>(); + private final HashSet removedVerts = new HashSet<>(); - private HashMap> vertsToFaces; - private HashMap indexReorientations; + private final HashMap> vertsToFaces = new HashMap<>(); + private final HashMap indexReorientations = new HashMap<>(); public QuadricEdgeCollapse(Model model) { this.model = model; @@ -51,24 +50,9 @@ public void decimate(int targetVertexCount) { } public boolean decimateWhilePossible(int targetVertexCount) { - mesh = new CornerTable(model); - removedFaces = new HashSet<>(); - removedVerts = new HashSet<>(); - indexReorientations = new HashMap<>(); - - // TEST - vertsToFaces = new HashMap<>(); - for(int i=0;i()); - } - } - for(int triangleIdx=0;triangleIdx()); + } + } + for(int triangleIdx=0;triangleIdx(model.getVerts().size()); + quadrics.clear(); for(int i=0;i(); + pairs.clear(); // add a pair for each edge in model, make sure there are no duplicates for(int triangleIdx=0;triangleIdx(pairs.size()); + pairTargets.clear(); for(Edge e : pairs) { pairTargets.put(e, computeTargetV(e)); @@ -169,7 +179,7 @@ private void initializeTargets() { * that the least errors would be produced. */ private void initializeQueue() { - pairErrors = new HashMap<>(pairs.size()); + pairErrors.clear(); EdgeComparator c = new EdgeComparator(pairErrors); queue = new PriorityQueue<>(pairs.size(), c); @@ -188,14 +198,41 @@ private void initializeQueue() { * recompute normals. */ private void cleanupModel() { - // TODO do this - ArrayList newVerts = new ArrayList<>(); + ArrayList newVerts = new ArrayList<>(model.getVerts().size() - removedVerts.size()); + HashMap newMapping = new HashMap<>(model.getVerts().size() - removedVerts.size()); + // copy only vertices that were not removed for(int i=0;i> newFaces = new ArrayList<>(model.getFaces().getNumFaces() - removedFaces.size()); + for(int i=0;i newFace = new ArrayList<>(triangle.length); + for(int j=0;j