/* jCAE stand for Java Computer Aided Engineering. Features are : Small CAD modeler, Finite element mesher, Plugin architecture. Copyright (C) 2006, by EADS CRC Copyright (C) 2007-2011, by EADS France This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.jcae.mesh.amibe.algos3d; import org.jcae.mesh.amibe.ds.Mesh; import org.jcae.mesh.amibe.ds.HalfEdge; import org.jcae.mesh.amibe.ds.TriangleHE; import org.jcae.mesh.amibe.ds.AbstractHalfEdge; import org.jcae.mesh.amibe.ds.Triangle; import org.jcae.mesh.amibe.ds.Vertex; import org.jcae.mesh.amibe.projection.MeshLiaison; import org.jcae.mesh.amibe.util.QSortedTree; import org.jcae.mesh.amibe.util.PAVLSortedTree; import java.util.Iterator; import java.io.ObjectOutputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Collection; import java.util.logging.Level; import java.util.logging.Logger; import org.jcae.mesh.amibe.util.HashFactory; public abstract class AbstractAlgoHalfEdge { Mesh mesh; MeshLiaison liaison; int nrFinal = 0; int nrTriangles = 0; double tolerance = 0.0; int processed = 0; int swapped = 0; int notProcessed = 0; private int notInTree = 0; private int progressBarStatus = 10000; private boolean noSwapAfterProcessing = false; double minCos = 0.95; boolean moreTriangles = false; private QSortedTree<HalfEdge> tree = new PAVLSortedTree<HalfEdge>(); private final Collection<HalfEdge> notProcessedObjects = HashFactory.createSet(); protected abstract void preProcessAllHalfEdges(); protected void postProcessAllHalfEdges() { //LOGGER.info("Number of edges which were not in the binary tree before being removed: "+notInTree); thisLogger().info("Number of edges still present in the binary tree: "+tree.size()); } protected abstract boolean canProcessEdge(HalfEdge e); protected abstract HalfEdge processEdge(HalfEdge e, double cost); protected abstract double cost(HalfEdge e); protected abstract Logger thisLogger(); private static final String dumpFile = "/tmp/jcae.dump"; AbstractAlgoHalfEdge(final Mesh m) { this(m, null); } AbstractAlgoHalfEdge(final Mesh m, final MeshLiaison meshLiaison) { mesh = m; liaison = meshLiaison; } protected void preCheck() { assert mesh.checkNoDegeneratedTriangles(); assert mesh.checkNoInvertedTriangles(); } protected void postCheck() { assert mesh.checkNoDegeneratedTriangles(); assert mesh.checkNoInvertedTriangles(); } public final void compute() { long startTime = System.nanoTime(); preCheck(); thisLogger().info("Run "+getClass().getName()); mesh.getTrace().println("# Begin "+getClass().getName()); preProcessAllHalfEdges(); thisLogger().info("Compute initial tree"); computeTree(); postComputeTree(); thisLogger().info("Initial number of triangles: "+countInnerTriangles(mesh)); processAllHalfEdges(); thisLogger().info("Final number of triangles: "+countInnerTriangles(mesh)); mesh.getTrace().println("# End "+getClass().getName()); postCheck(); long endTime = System.nanoTime(); thisLogger().log(Level.INFO, "Computation time: {0}ms", Double.toString((endTime - startTime)/1E6)); } public void setProgressBarStatus(int n) { progressBarStatus = n; } public static int countInnerTriangles(final Mesh mesh) { int ret = 0; for (Triangle af: mesh.getTriangles()) { if (af.isWritable()) ret++; } return ret; } /** * Ensure that edge orientation is fixed and does not depend on hashcodes. This method * must be used when entering canProcessEdge() and processEdge(). */ static HalfEdge uniqueOrientation(HalfEdge current) { if (current.hasAttributes(AbstractHalfEdge.MARKED) || !current.hasSymmetricEdge()) return current; if (current.sym().hasAttributes(AbstractHalfEdge.MARKED) || current.hasAttributes(AbstractHalfEdge.OUTER)) return current.sym(); return current; } private void computeTree() { // Remove all MARKED attributes for (Triangle af: mesh.getTriangles()) { TriangleHE f = (TriangleHE) af; HalfEdge e = f.getAbstractHalfEdge(); for (int i = 0; i < 3; i++) { e = e.next(); e.clearAttributes(AbstractHalfEdge.MARKED); } } // Compute edge cost nrTriangles = 0; for (Triangle af: mesh.getTriangles()) { if (!af.isWritable()) continue; TriangleHE f = (TriangleHE) af; nrTriangles++; HalfEdge e = f.getAbstractHalfEdge(); for (int i = 0; i < 3; i++) { e = e.next(); HalfEdge h = uniqueOrientation(e); if (!tree.contains(h)) addToTree(h); } } } void postComputeTree() { } // This routine is needed by DecimateHalfedge. void preProcessEdge() { } protected void addToTreeIfNot(HalfEdge current) { HalfEdge h = uniqueOrientation(current.next()); if (!h.hasAttributes( AbstractHalfEdge.IMMUTABLE | AbstractHalfEdge.OUTER | AbstractHalfEdge.SHARP | AbstractHalfEdge.BOUNDARY | AbstractHalfEdge.NONMANIFOLD) && !tree.contains(h)) { double val = cost(h); if (val <= tolerance) { // the edge has changed so we want canProcessEdge to be // re-evaluated, so we remove the edge from the // notProcessObjects set notProcessedObjects.remove(h); tree.insert(h, val); h.setAttributes(AbstractHalfEdge.MARKED); } } } final void addToTree(final HalfEdge e) { if (!e.origin().isReadable() || !e.destination().isReadable()) return; if (e.hasAttributes(AbstractHalfEdge.IMMUTABLE)) return; double val = cost(e); // If an edge will not be processed because of its cost, it is // better to not put it in the tree. One drawback though is // that tree.size() is not equal to the total number of edges, // and output displayed by postProcessAllHalfEdges() may thus // not be very useful. if (nrFinal != 0 || val <= tolerance) { // the edge has changed so we want canProcessEdge to be // re-evaluated, so we remove the edge from the notProcessObjects // set notProcessedObjects.remove(e); tree.insert(e, val); e.setAttributes(AbstractHalfEdge.MARKED); } } final void removeFromTree(final HalfEdge e) { for (Iterator<AbstractHalfEdge> it = e.fanIterator(); it.hasNext(); ) { HalfEdge f = (HalfEdge) it.next(); HalfEdge h = uniqueOrientation(f); if(notProcessedObjects.remove(h)) assert !tree.contains(h); if (!tree.remove(h)) notInTree++; h.clearAttributes(AbstractHalfEdge.MARKED); assert !tree.contains(h); } } private boolean processAllHalfEdges() { double cost = -1.0; while (!tree.isEmpty() && (nrFinal == 0 || (moreTriangles && nrTriangles < nrFinal) || (!moreTriangles && nrTriangles > nrFinal))) { preProcessEdge(); HalfEdge current = null; Iterator<QSortedTree.Node<HalfEdge>> itt = tree.iterator(); if (processed > 0 && (processed % progressBarStatus) == 0) thisLogger().info("Edges processed: "+processed); while (itt.hasNext()) { QSortedTree.Node<HalfEdge> q = itt.next(); current = q.getData(); assert current == uniqueOrientation(current); cost = q.getValue(); if (nrFinal == 0 && cost > tolerance) break; if (canProcessEdge(current)) break; if (thisLogger().isLoggable(Level.FINE)) thisLogger().fine("Edge not processed: "+current); notProcessed++; tree.remove(current); notProcessedObjects.add(current); current = null; } if ((nrFinal == 0 && cost > tolerance) || current == null) break; // Update costs for edges which were not contracted because // canProcessEdge returned false if(tree.isEmpty()) { for(HalfEdge e: notProcessedObjects) tree.insert(e, cost(e)); notProcessedObjects.clear(); } current = processEdge(current, cost); afterProcessHook(); processed++; if (noSwapAfterProcessing || minCos < -1.0) continue; // Loop around current.apex with // current = current.nextApexLoop(); // to check all edges which have current.apex // as apical vertex and swap them if this improves // mesh quality. Vertex o = current.origin(); boolean redo = true; while(redo) { redo = false; while(true) { if (current.checkSwap3D(mesh, minCos) >= 0.0 && current.canSwapTopology()) { // Swap edge for (int i = 0; i < 3; i++) { current = current.next(); removeFromTree(current); } HalfEdge sym = current.sym(); for (int i = 0; i < 2; i++) { sym = sym.next(); removeFromTree(sym); } Vertex a = current.apex(); current = (HalfEdge) mesh.edgeSwap(current); swapped++; redo = true; // Now current = (ona) assert a == current.apex(); for (int i = 0; i < 3; i++) { current = current.next(); for (Iterator<AbstractHalfEdge> it = current.fanIterator(); it.hasNext(); ) { HalfEdge e = uniqueOrientation((HalfEdge) it.next()); addToTree(e); } } sym = current.next().sym(); for (int i = 0; i < 2; i++) { sym = sym.next(); for (Iterator<AbstractHalfEdge> it = sym.fanIterator(); it.hasNext(); ) { HalfEdge e = uniqueOrientation((HalfEdge) it.next()); addToTree(e); } } } else { current = current.nextApexLoop(); if (current.origin() == o) break; } } } afterSwapHook(); } postProcessAllHalfEdges(); return processed > 0; } public void setNoSwapAfterProcessing(boolean noSwapAfterProcessing) { this.noSwapAfterProcessing = noSwapAfterProcessing; } final void dumpState() { ObjectOutputStream out = null; try { out = new ObjectOutputStream(new FileOutputStream(dumpFile)); out.writeObject(mesh); out.writeObject(tree); appendDumpState(out); out.close(); } catch (Exception ex) { ex.printStackTrace(); System.exit(-1); } } void appendDumpState(ObjectOutputStream out) throws IOException { } @SuppressWarnings("unchecked") final boolean restoreState() { try { FileInputStream istream = new FileInputStream(dumpFile); ObjectInputStream q = new ObjectInputStream(istream); System.out.println("Loading restored state"); mesh = (Mesh) q.readObject(); tree = (QSortedTree<HalfEdge>) q.readObject(); appendRestoreState(q); System.out.println("... Done."); q.close(); assert mesh.isValid(); } catch (FileNotFoundException ex) { return false; } catch (Exception ex) { ex.printStackTrace(); System.exit(-1); } return true; } void appendRestoreState(ObjectInputStream q) throws IOException { } protected void afterProcessHook() { } protected void afterSwapHook() { } protected void updateCost(HalfEdge f) { updateCost(f, cost(f)); } protected void updateCost(HalfEdge f, double newCost) { HalfEdge h = uniqueOrientation(f); // the edge has changed so we want canProcessEdge to be re-evaluated, so // we remove the edge from the notProcessObjects set notProcessedObjects.remove(h); if (tree.contains(h)) tree.update(h, newCost); else { tree.insert(h, newCost); h.setAttributes(AbstractHalfEdge.MARKED); } } protected HalfEdge removeOneFromTree(HalfEdge e) { HalfEdge h = uniqueOrientation(e); if(notProcessedObjects.remove(h)) { // and edge cannot be in tree and in notProcessedObjects at the same // time assert !tree.contains(h); return h; } if (!tree.remove(h)) notInTree++; assert !tree.contains(h); return h; } }