/* jCAE stand for Java Computer Aided Engineering. Features are : Small CAD
modeler, Finite element mesher, Plugin architecture.
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.AbstractHalfEdge;
import org.jcae.mesh.amibe.ds.HalfEdge;
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.xmldata.MeshReader;
import org.jcae.mesh.xmldata.MeshWriter;
import java.io.IOException;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import gnu.trove.map.hash.TObjectIntHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Swaps edge to try to have all vertices have six neighbors.
*
* TODO:
* In <a href="www.cs.technion.ac.il/~gotsman/AmendedPubl/SGP/SGP03.pdf">Explicit Surface Remeshing</a>, Vitaly Surazhsky and Craig Gotsman proposes an interesting algorithm.
*/
public class ImproveEdgeConnectivity extends AbstractAlgoHalfEdge
{
private static final Logger LOGGER=Logger.getLogger(ImproveEdgeConnectivity.class.getName());
private TObjectIntHashMap<Vertex> map;
/**
* Creates a <code>ImproveConnectivity</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>coplanar</code>.
*/
public ImproveEdgeConnectivity(final Mesh m, final Map<String, String> options)
{
this(m, null, options);
}
public ImproveEdgeConnectivity(final MeshLiaison liaison, final Map<String, String> options)
{
this(liaison.getMesh(), liaison, options);
}
private ImproveEdgeConnectivity(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("Coplanar value: "+minCos);
}
else
throw new RuntimeException("Unknown option: "+key);
}
tolerance = - 0.01;
// Do not let AbstractAlgoHalfEdge swap edges
setNoSwapAfterProcessing(true);
if (meshLiaison == null)
mesh.buildRidges(minCos);
}
@Override
public Logger thisLogger()
{
return LOGGER;
}
@Override
public void preProcessAllHalfEdges()
{
map = new TObjectIntHashMap<Vertex>(mesh.getTriangles().size() / 2);
for (Triangle t: mesh.getTriangles())
{
if (t.hasAttributes(AbstractHalfEdge.OUTER))
continue;
map.adjustOrPutValue(t.getV0(), 1, 1);
map.adjustOrPutValue(t.getV1(), 1, 1);
map.adjustOrPutValue(t.getV2(), 1, 1);
HalfEdge e = (HalfEdge) t.getAbstractHalfEdge();
for (int i = 0; i < 3; i++)
{
e = e.next();
Vertex v = e.origin();
double [] tNormal = liaison.getBackgroundNormal(v);
if (e.checkSwapNormal(mesh, minCos, tNormal) <= -1.0)
{
e.setAttributes(AbstractHalfEdge.MARKED);
e.sym().setAttributes(AbstractHalfEdge.MARKED);
}
}
}
}
@Override
protected final double cost(final HalfEdge e)
{
Vertex o = e.origin();
Vertex d = e.destination();
Vertex a = e.apex();
Vertex n = e.sym().apex();
double before =
vertexQuality(o, 0) +
vertexQuality(d, 0) +
vertexQuality(a, 0) +
vertexQuality(n, 0);
double after =
vertexQuality(o, -1) +
vertexQuality(d, -1) +
vertexQuality(a, 1) +
vertexQuality(n, 1);
return before - after;
}
private double vertexQuality(Vertex v, int delta)
{
if (!v.isManifold())
return 1.0;
if (v.getRef() != 0 || !v.isManifold() || !v.isMutable())
{
int q = map.get(v);
assert q > 0;
if (q + delta <= 1)
return -100.0;
return 0.0;
}
int q = map.get(v) + delta;
return - (q - 6.0) * (q - 6.0);
}
@Override
public boolean canProcessEdge(HalfEdge current)
{
if (current.hasAttributes(AbstractHalfEdge.IMMUTABLE | AbstractHalfEdge.OUTER | AbstractHalfEdge.SHARP | AbstractHalfEdge.BOUNDARY | AbstractHalfEdge.NONMANIFOLD))
return false;
if (current.hasAttributes(AbstractHalfEdge.MARKED) && current.sym().hasAttributes(AbstractHalfEdge.MARKED))
return false;
if(!current.apex().isManifold() && !current.sym().apex().isManifold())
return false;
Vertex v = current.origin();
double [] tNormal = liaison.getBackgroundNormal(v);
double checkNormal = current.checkSwapNormal(mesh, minCos, tNormal);
return (checkNormal > -1.0);
}
@Override
public HalfEdge processEdge(HalfEdge current, double costCurrent)
{
Vertex o = current.origin();
Vertex d = current.destination();
Vertex a = current.apex();
Vertex n = current.sym().apex();
if (LOGGER.isLoggable(Level.FINE))
{
LOGGER.fine("Swap edge: "+current+" cost="+costCurrent);
LOGGER.fine(" Connections before swap: o="+map.get(o)+" d="+map.get(d)+" a="+map.get(a)+" n="+map.get(n));
}
assert costCurrent == cost(current) : costCurrent+" != "+cost(current);
removeAllEdgesIncidentTo(o);
removeAllEdgesIncidentTo(d);
removeAllEdgesIncidentTo(a);
removeAllEdgesIncidentTo(n);
// mesh.edgeSwap() transforms (oda) into (ona)
current = (HalfEdge) mesh.edgeSwap(current);
// Update vertex costs
int oVal = map.get(o);
int dVal = map.get(d);
int aVal = map.get(a);
int nVal = map.get(n);
map.put(o, oVal - 1);
map.put(d, dVal - 1);
map.put(a, aVal + 1);
map.put(n, nVal + 1);
addAllEdgesIncidentTo(o);
addAllEdgesIncidentTo(d);
addAllEdgesIncidentTo(a);
addAllEdgesIncidentTo(n);
return current.prev();
}
private void removeAllEdgesIncidentTo(Vertex v)
{
HalfEdge current;
Vertex d = null;
Iterator<AbstractHalfEdge> it = null;
boolean isManifold = v.isManifold();
if (isManifold)
{
Triangle t = (Triangle) v.getLink();
current = (HalfEdge) t.getAbstractHalfEdge();
if (current.destination() == v)
current = current.next();
else if (current.apex() == v)
current = current.prev();
assert current.origin() == v;
d = current.destination();
}
else
{
it = v.getNeighbourIteratorAbstractHalfEdge();
current = (HalfEdge) it.next();
}
while (true)
{
removeOneFromTree(current);
removeOneFromTree(current.next());
if (isManifold)
{
current = current.nextOriginLoop();
if (current.destination() == d)
break;
}
else
{
if (!it.hasNext())
break;
current = (HalfEdge) it.next();
}
}
}
private void addAllEdgesIncidentTo(Vertex v)
{
HalfEdge current;
Vertex d = null;
Iterator<AbstractHalfEdge> it = null;
boolean isManifold = v.isManifold();
if (isManifold)
{
Triangle t = (Triangle) v.getLink();
current = (HalfEdge) t.getAbstractHalfEdge();
if (current.destination() == v)
current = current.next();
else if (current.apex() == v)
current = current.prev();
assert current.origin() == v;
d = current.destination();
}
else
{
it = v.getNeighbourIteratorAbstractHalfEdge();
current = (HalfEdge) it.next();
}
while (true)
{
addToTreeIfNot(current);
addToTreeIfNot(current.next());
if (isManifold)
{
current = current.nextOriginLoop();
if (current.destination() == d)
break;
}
else
{
if (!it.hasNext())
break;
current = (HalfEdge) it.next();
}
}
}
@Override
public void postProcessAllHalfEdges()
{
LOGGER.info("Number of swapped edges: "+processed);
super.postProcessAllHalfEdges();
}
private static void usage(int rc)
{
System.out.println("Usage: ImproveConnectivity [options] xmlDir outDir");
System.out.println("Options:");
System.out.println(" -h, --help display this message and exit");
System.out.println(" --coplanar <d> adjacent triangles for which dot product of normals > d are coplanar");
System.exit(rc);
}
/**
*
* @param args xmlDir, -t tolerance | -n triangle, brepFile, output
*/
public static void main(final String[] args)
{
final HashMap<String, String> options = new HashMap<String, String>();
int argc = 0;
for (String arg: args)
if (arg.equals("--help") || arg.equals("-h"))
usage(0);
while (argc < args.length-1)
{
if (args[argc].length() < 2 || args[argc].charAt(0) != '-' || args[argc].charAt(1) != '-')
break;
options.put(args[argc].substring(2), args[argc+1]);
argc += 2;
}
if (argc + 2 != args.length)
usage(1);
LOGGER.info("Load geometry file");
final Mesh mesh = new Mesh();
try
{
MeshReader.readObject3D(mesh, args[argc]);
}
catch (IOException ex)
{
ex.printStackTrace();
throw new RuntimeException(ex);
}
new ImproveEdgeConnectivity(mesh, options).compute();
try
{
MeshWriter.writeObject3D(mesh, args[argc+1], null);
}
catch (IOException ex)
{
ex.printStackTrace();
throw new RuntimeException(ex);
}
}
}