/* jCAE stand for Java Computer Aided Engineering. Features are : Small CAD modeler, Finite element mesher, Plugin architecture. Copyright (C) 2005, 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.AbstractHalfEdge; import org.jcae.mesh.amibe.projection.MeshLiaison; import org.jcae.mesh.xmldata.MeshReader; import org.jcae.mesh.xmldata.MeshWriter; import java.io.IOException; import java.util.Map; import java.util.HashMap; import java.util.logging.Level; import java.util.logging.Logger; import org.jcae.mesh.amibe.ds.AbstractHalfEdge.Quality; import org.jcae.mesh.amibe.ds.Vertex; /** * Laplacian smoothing. */ public class SwapEdge extends AbstractAlgoHalfEdge { private static final Logger LOGGER=Logger.getLogger(SwapEdge.class.getName()); private int counter = 0; /** Swap only if the quality is improved by at least this factory */ private double minQualityFactor; private double minCosAfterSwap = -2; private boolean expectInsert = true; /** * if -(swapedAngle-angle)/(swapedQuality-quality) is higher than this value * the edge is not swapped. */ private double angleQualityRatio = Double.POSITIVE_INFINITY; private double sqrDeflection = Double.POSITIVE_INFINITY; private double swapVolume = Double.POSITIVE_INFINITY; private final Quality quality = new Quality(); /** * Creates a <code>SwapEdge</code> instance. * * @param m the <code>Mesh</code> instance to modify * @param options map containing key-value pairs to modify algorithm * behaviour. Valid key is <code>coplanarity</code>. */ public SwapEdge(final Mesh m, final Map<String, String> options) { this(m, null, options); } public SwapEdge(final MeshLiaison liaison, final Map<String, String> options) { this(liaison.getMesh(), liaison, options); } private SwapEdge(final Mesh m, final MeshLiaison meshLiaison, final Map<String, String> options) { super(m, meshLiaison); for (final Map.Entry<String, String> opt: options.entrySet()) { final String key = opt.getKey(); final String val = opt.getValue(); if (key.equals("coplanarity")) { minCos = Double.parseDouble(val); LOGGER.fine("Minimum dot product of face normals allowed for swapping an edge: "+minCos); } else if(key.equals("minCosAfterSwap")) { minCosAfterSwap = Double.parseDouble(val); } else if(key.equals("minQualityFactor")) { minQualityFactor = Double.parseDouble(val); } else if(key.equals("expectInsert")) { expectInsert = Boolean.parseBoolean(val); } else throw new RuntimeException("Unknown option: "+key); } if (meshLiaison == null) mesh.buildRidges(minCos); counter = m.getTriangles().size() * 3; setNoSwapAfterProcessing(true); quality.setLiaison(liaison); } @Override public Logger thisLogger() { return LOGGER; } /** * if -(swapedAngle-angle)/(swapedQuality-quality) is higher than this value * the edge is not swapped. * Then angle si between -1 and 1 and the quality between 0 and 1/(12*sqrt(3)). * A good value is 100. The default value is infinity which disable this * feature. * Hopefully this parameter will give better results than minQualityFactor * and minCosAfterSwap. */ public void setAngleQualityRatio(double angleQualityRatio) { this.angleQualityRatio = angleQualityRatio; } public void setDeflection(double deflection) { this.sqrDeflection = deflection * deflection; } public void setMaxSwapVolume(double volume) { this.swapVolume = volume; } @Override public void preProcessAllHalfEdges() { } @Override public double cost(final HalfEdge e) { double coplanarity = minCos; if (liaison != null) { double[] tNormal = liaison.getBackgroundNormal(e.origin()); if (e.checkSwapNormal(mesh, minCos, tNormal, expectInsert) < -1.0) return Double.MAX_VALUE; // Triangle normals have been checked, let checkSwap3D // only check triangle quality coplanarity = -2.0; if(!Double.isInfinite(sqrDeflection)) { quality.setEdge(e); if(quality.sqrSwappedDeflection(-1) > sqrDeflection) return Double.MAX_VALUE; } } if(!Double.isInfinite(swapVolume)) { quality.setEdge(e); if(quality.swappedVolume() > swapVolume) return Double.MAX_VALUE; } double toReturn = - e.checkSwap3D(mesh, coplanarity, 0, minQualityFactor, expectInsert, minCosAfterSwap, -2.0); if(!Double.isInfinite(angleQualityRatio) && toReturn < tolerance) { quality.setEdge(e); double dq = quality.getSwappedQuality() - quality.getQuality(); double da = quality.getSwappedAngle() - quality.getAngle(); if(-da / dq > angleQualityRatio) return Double.POSITIVE_INFINITY; } return toReturn; } @Override public boolean canProcessEdge(HalfEdge current) { boolean attr = current.hasAttributes(AbstractHalfEdge.IMMUTABLE | AbstractHalfEdge.OUTER | AbstractHalfEdge.SHARP | AbstractHalfEdge.BOUNDARY | AbstractHalfEdge.NONMANIFOLD); return counter > 0 && !attr && current.canSwapTopology(); } @Override public HalfEdge processEdge(HalfEdge current, double costCurrent) { if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine("Swap edge: "+current+" cost="+costCurrent); counter --; for (int i = 0; i < 3; i++) { removeOneFromTree(current).clearAttributes(AbstractHalfEdge.MARKED); current = current.next(); } HalfEdge sym = current.sym(); for (int i = 0; i < 2; i++) { sym = sym.next(); removeOneFromTree(sym).clearAttributes(AbstractHalfEdge.MARKED); } current = (HalfEdge) mesh.edgeSwap(current); // Update edge costs for (int i = 0; i < 2; i++) { addToTree(uniqueOrientation(current)); current = current.prev(); } current = current.sym(); for (int i = 0; i < 2; i++) { current = current.next(); addToTree(uniqueOrientation(current)); } return current.prev(); } @Override public void postProcessAllHalfEdges() { LOGGER.info("Number of swapped edges: "+processed); super.postProcessAllHalfEdges(); } private final static String usageString = "<xmlDir> <coplanarity> <outputDir>"; /** * * @param args xmlDir, -t tolerance | -n triangle, brepFile, output */ public static void main(final String[] args) { if(args.length != 3) { System.out.println(usageString); return; } LOGGER.info("Load mesh"); final Mesh mesh = new Mesh(); try { MeshReader.readObject3D(mesh, args[0]); } catch (IOException ex) { ex.printStackTrace(); throw new RuntimeException(ex); } final HashMap<String, String> options = new HashMap<String, String>(); options.put("coplanarity", args[1]); new SwapEdge(mesh, options).compute(); try { MeshWriter.writeObject3D(mesh, args[2], null); } catch (IOException ex) { ex.printStackTrace(); throw new RuntimeException(ex); } } }