/* jCAE stand for Java Computer Aided Engineering. Features are : Small CAD
modeler, Finite element mesher, Plugin architecture.
Copyright (C) 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.amibe.metrics.Metric;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jcae.mesh.amibe.metrics.Location;
import org.jcae.mesh.amibe.traits.MeshTraitsBuilder;
import org.jcae.mesh.amibe.util.HashFactory;
import org.jcae.mesh.amibe.util.QSortedTree.Node;
import org.jcae.mesh.xmldata.MeshReader;
import org.jcae.mesh.xmldata.MeshWriter;
/**
* Remove vertices with low valence, and duplicate vertices with high valence.
*/
public class ImproveVertexValence extends AbstractAlgoVertex
{
private static final Logger LOGGER=Logger.getLogger(ImproveVertexValence.class.getName());
private final Set<Vertex> immutableNodes = HashFactory.createSet();
private int valence3;
private int valence4;
private int inserted;
private int minValence = 1, maxValence = Integer.MAX_VALUE;
private boolean checkNormals = true;
/** valences for alternate pattern */
private int[] valences = new int[6];
private double[] tNormal = new double[3];
/** neighbourgs edges for alternate pattern */
private List<AbstractHalfEdge> aPEdges = new ArrayList<AbstractHalfEdge>(6);
private List<Vertex> aPVertices = new ArrayList<Vertex>(6);
/**
* Level neighbours from a node.
* This is needed to update the tree. Level 1 is not enough because of
* alternate and 55 patterns.
*/
private Collection<Vertex> level2Neighbours = HashFactory.createSet(18);
/**
* 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 ImproveVertexValence(final Mesh m, final Map<String, String> options)
{
this(m, null, options);
}
public ImproveVertexValence(final MeshLiaison liaison, final Map<String, String> options)
{
this(liaison.getMesh(), liaison, options);
}
private ImproveVertexValence(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 if("checkNormals".equals(key))
{
checkNormals = Boolean.parseBoolean(val);
}
else if("minValence".equals(key))
{
minValence = Integer.parseInt(val);
}
else if("maxValence".equals(key))
{
maxValence = Integer.parseInt(val);
}
else
throw new RuntimeException("Unknown option: "+key);
}
// Do not change vertices with a valence of 5,6,7
tolerance = 40.0;
if (meshLiaison == null)
mesh.buildRidges(minCos);
}
@Override
public Logger thisLogger()
{
return LOGGER;
}
@Override
public void preProcessAllVertices()
{
AbstractHalfEdge ot = null;
for (Triangle t: mesh.getTriangles())
{
if (t.hasAttributes(AbstractHalfEdge.OUTER))
continue;
ot = t.getAbstractHalfEdge(ot);
for (int i = 0; i < 3; i++)
{
ot = ot.next();
if (ot.hasAttributes(AbstractHalfEdge.IMMUTABLE | AbstractHalfEdge.BOUNDARY | AbstractHalfEdge.NONMANIFOLD | AbstractHalfEdge.SHARP))
{
immutableNodes.add(ot.origin());
immutableNodes.add(ot.destination());
}
}
}
}
private int getValence(Vertex v)
{
if(v.isManifold())
{
AbstractHalfEdge start = v.getIncidentAbstractHalfEdge((Triangle)v.getLink(), null);
AbstractHalfEdge edge = start;
int r = 0;
do
{
r++;
edge = edge.nextOrigin();
if(edge == null)
//border or non-manifold vertex
return -1;
}
while(edge != start);
return r;
}
else
{
Iterator<AbstractHalfEdge> it = v.getNeighbourIteratorAbstractHalfEdge();
int r = 0;
while(it.hasNext())
{
AbstractHalfEdge e = it.next();
if(!e.hasAttributes(AbstractHalfEdge.OUTER))
r++;
}
return r;
}
}
private int getAPValences(Vertex v, boolean withNeighbours)
{
assert getValence(v) == 6: getValence(v);
AbstractHalfEdge start = v.getIncidentAbstractHalfEdge((Triangle)v.getLink(), null);
AbstractHalfEdge edge = start;
int k = 0;
int min = Integer.MAX_VALUE;
int imin = 0;
if(withNeighbours)
{
aPEdges.clear();
aPVertices.clear();
}
do
{
if(!edge.destination().isManifold())
return -1;
if(withNeighbours)
{
aPEdges.add(edge);
aPVertices.add(edge.destination());
}
int valence = getValence(edge.destination());
if(valence < 0)
return -1;
valences[k] = valence;
if(valence < min)
{
min = valence;
imin = k;
}
k++;
edge = edge.nextOrigin();
}
while(edge != start);
return imin;
}
/**
* Assuming that v have a valence of 6, check that v neighbours have
* >6, <6, >6, <6, >6, <6 as connectivity
*/
private boolean isAlternatePattern(Vertex v)
{
if(!v.isManifold())
return false;
int imin = getAPValences(v, false);
if(imin < 0)
return false;
int currentCost = 0;
int swappedCost = 0;
for(int i = 0; i < 6; i+=2)
{
int valence = valences[(imin + i) % 6];
if(valence > 6)
return false;
currentCost += Math.abs(6 - valence);
swappedCost += Math.abs(6 - (valence + 1));
valence = valences[(imin + i + 1) % 6];
currentCost += Math.abs(valence - 6);
swappedCost += Math.abs(valence - 1 - 6);
}
return swappedCost < currentCost;
}
private boolean processAlternatePattern(Vertex v)
{
getLevel2Neighbors(v);
int imin = getAPValences(v, true);
AbstractHalfEdge he5 = aPEdges.get(imin);
if(!mesh.canCollapseEdge(he5, he5.destination()))
return false;
for(int i = 0; i < 3; i++)
{
AbstractHalfEdge toSwap = aPEdges.get((imin + 2 * i + 1) % 6);
mesh.edgeSwap(toSwap);
}
mesh.edgeCollapse(he5, he5.destination());
tree.remove(v);
for(Vertex mv:level2Neighbours)
{
if(canProcessVertex(mv))
{
if(tree.contains(mv))
tree.update(mv, cost(mv));
else
tree.insert(mv, cost(mv));
}
else
tree.remove(mv);
}
return true;
}
private AbstractHalfEdge get55Pattern(Vertex v)
{
if(!v.isManifold())
return null;
AbstractHalfEdge start = v.getIncidentAbstractHalfEdge((Triangle)v.getLink(), null);
AbstractHalfEdge edge = start;
for(int i = 0; i < 5; i++)
{
if(edge.destination().isManifold() && getValence(edge.destination()) == 5)
{
int v1 = getValence(edge.apex());
int v2 = getValence(edge.sym().apex());
if((v1 > 6 && v2 >= 6) || (v1 >= 6 && v2 > 6))
return edge;
}
edge = edge.nextOrigin();
}
return null;
}
/**
* Assuming that v have a valence of 5 check that is has a neightbour with a
* valence of 5, and that the 2 apex of this edges have valences greater
* than 6
*/
private boolean is55Pattern(Vertex v)
{
return get55Pattern(v) != null;
}
private boolean process55Pattern(Vertex v)
{
AbstractHalfEdge edge = get55Pattern(v);
assert edge.origin() == v;
getLevel2Neighbors(edge.origin());
if(mesh.canCollapseEdge(edge, edge.destination()))
mesh.edgeCollapse(edge, edge.destination());
else
return false;
tree.remove(v);
for(Vertex mv:level2Neighbours)
{
if(canProcessVertex(mv))
{
if(tree.contains(mv))
tree.update(mv, cost(mv));
else
tree.insert(mv, cost(mv));
}
else
tree.remove(mv);
}
return true;
}
private void getLevel2Neighbors(Vertex v)
{
level2Neighbours.clear();
AbstractHalfEdge start = v.getIncidentAbstractHalfEdge((Triangle)v.getLink(), null);
AbstractHalfEdge edge = start;
do
{
Vertex d = edge.destination();
level2Neighbours.add(d);
if(d.isManifold())
{
AbstractHalfEdge start2 = d.getIncidentAbstractHalfEdge((Triangle)d.getLink(), null);
AbstractHalfEdge edge2 = start2;
do
{
level2Neighbours.add(edge2.destination());
edge2 = edge2.nextOrigin();
}
while(edge2 != start2 && edge2 != null);
}
edge = edge.nextOrigin();
}
while(edge != start);
level2Neighbours.remove(v);
}
@Override
protected final double cost(final Vertex v)
{
if (!v.isManifold() || !v.isMutable())
return Double.MAX_VALUE;
int q = getValence(v);
if(q < minValence || q > maxValence)
return Double.MAX_VALUE;
switch(q)
{
case 1:
case 2:
return tolerance + 1;
case 3:
return 4;
case 4:
return 3;
case 5:
return is55Pattern(v) ? 6 : tolerance + 1;
case 6:
return isAlternatePattern(v) ? 5 : tolerance + 1;
case 7:
return tolerance + 1;
default:
return 7 + (tolerance - 7) / (q - 7);
}
}
@Override
public boolean canProcessVertex(Vertex v)
{
if (!v.isManifold() || !v.isMutable() || immutableNodes.contains(v))
return false;
if(checkNormals)
{
double [] tNormal = liaison.getBackgroundNormal(v);
Triangle t = (Triangle) v.getLink();
HalfEdge ot = (HalfEdge) t.getAbstractHalfEdge();
if (ot.destination() == v)
ot = ot.next();
else if (ot.apex() == v)
ot = ot.prev();
assert ot.origin() == v;
double checkNormal = ot.checkSwapNormal(mesh, minCos, tNormal);
return (checkNormal > -1.0);
}
else
return true;
}
@Override
public boolean processVertex(Vertex v, double cost)
{
// FIXME: If a penalty has been added, we do not know what the
// real valence is. Skip this vertex for now
if (cost != cost(v))
return false;
Triangle t = (Triangle) v.getLink();
AbstractHalfEdge ot = t.getAbstractHalfEdge();
if (ot.destination() == v)
ot = ot.next();
else if (ot.apex() == v)
ot = ot.prev();
assert ot.origin() == v;
if(cost == 5)
return processAlternatePattern(v);
else if(cost == 6)
return process55Pattern(v);
else if (cost == 4 || cost == 3)
{
// Very low valence, try to remove vertex
int iVal = (cost == 4 ? 3 : 4);
// For valence 3, the edge to collapse does not matter
ot = iVal == 3 ? ot : checkLowValence(ot, iVal);
if (ot == null)
return false;
//no need to check collapse if the valence is 3 as collapse is
//equivalent to remove 3 triangles and create a new one.
if (iVal != 3 && !mesh.canCollapseEdge(ot, ot.destination()))
return false;
Vertex destination = ot.destination();
Vertex origin = ot.origin();
getLevel2Neighbors(origin);
tree.remove(origin);
ot = mesh.edgeCollapse(ot, destination);
if (iVal == 3)
valence3++;
else
valence4++;
for(Vertex o: level2Neighbours)
{
if (canProcessVertex(o))
{
double val = cost(o);
if (tree.contains(o))
tree.update(o, val);
else
tree.insert(o, val);
}
else
tree.remove(o);
}
}
else if (cost >= 7.0)
{
getLevel2Neighbors(v);
// Valence is 8 or more, try to insert a vertex
ot = checkLargeValence(ot);
if (ot == null)
return false;
Location newPt = new Location();
newPt.middle(ot.destination(), v);
Vertex newV = mesh.createVertex(newPt);
ot = mesh.vertexSplit(ot, newV);
liaison.addVertex(newV, v, tNormal);
// The valence of v has not been changed by
// inserting a Vertex, we now try to swap an edge.
// If we can't, revert this split.
HalfEdge h = (HalfEdge) ot.nextOrigin();
if (h.checkSwapNormal(mesh, minCos, tNormal) < -1.0)
{
h = (HalfEdge) ot.sym().next();
if (h.checkSwapNormal(mesh, minCos, tNormal) < -1.0)
{
mesh.edgeCollapse(ot, ot.origin());
liaison.removeVertex(newV);
thisLogger().warning("ERR "+v);
return false;
}
}
assert h.origin() == v;
mesh.edgeSwap(h);
for(Vertex o: level2Neighbours)
{
if (canProcessVertex(o))
{
double val = cost(o);
if (tree.contains(o))
tree.update(o, val);
else
tree.insert(o, val);
}
else
tree.remove(o);
}
inserted++;
}
return true;
}
private AbstractHalfEdge checkLowValence(AbstractHalfEdge ot, int valence)
{
Vertex o = ot.origin();
Metric mo = mesh.getMetric(o);
Vertex d = ot.destination();
double dMin = Double.MAX_VALUE;
int iMin = -1;
for (int i = valence; i > 0; --i)
{
if (ot == null || ot.hasAttributes(AbstractHalfEdge.OUTER))
return null;
double dist = mo.distance2(o, ot.destination());
if (dist < dMin)
{
dMin = dist;
iMin = i;
}
ot = ot.nextOrigin();
}
if (d != ot.destination())
return null;
for (int i = valence; i > iMin; --i)
ot = ot.nextOrigin();
return ot;
}
private AbstractHalfEdge checkLargeValence(AbstractHalfEdge ot)
{
Vertex o = ot.origin();
Metric mo = mesh.getMetric(o);
Vertex d = ot.destination();
double dMax = Double.MIN_VALUE;
int iMax = -1;
int cnt = 0;
do
{
if (ot == null || ot.hasAttributes(AbstractHalfEdge.OUTER))
return null;
double dist = mo.distance2(o, ot.destination());
if (dist > dMax)
{
dMax = dist;
iMax = cnt;
}
cnt++;
ot = ot.nextOrigin();
}
while (d != ot.destination());
for ( ; iMax > 0; --iMax)
ot = ot.nextOrigin();
return ot;
}
@Override
public void postProcessAllVertices()
{
if (valence3 > 0)
LOGGER.info("Number of removed vertices with a valence 3: "+valence3);
if (valence4 > 0)
LOGGER.info("Number of removed vertices with a valence 4: "+valence4);
if (inserted > 0)
LOGGER.info("Number of inserted vertices: "+inserted);
LOGGER.info("Number of vertices still present in the binary tree: "+tree.size());
}
/** Debugging method to check that the tree has been properly updated */
private void checkTree()
{
Iterator<Node<Vertex>> it = tree.iterator();
while(it.hasNext())
{
Node<Vertex> n = it.next();
if(n.getValue() != cost(n.getData()))
{
System.err.println(n.getData());
System.err.println(n.getValue());
System.err.println(getValence(n.getData()));
System.err.println(cost(n.getData()));
throw new IllegalStateException();
}
}
}
public static void main(final String[] args) {
try {
MeshTraitsBuilder mtb = MeshTraitsBuilder.getDefault3D();
mtb.addNodeSet();
Mesh mesh = new Mesh(mtb);
MeshReader.readObject3D(mesh, "/home/robert/ast-a319-neo/demo-anabelle/demo/AST_mesh.amibe");
MeshLiaison ml = MeshLiaison.create(mesh);
ml.getMesh().buildGroupBoundaries();
HashMap opts = new HashMap();
opts.put("checkNormals", "false");
opts.put("maxValence", "7");
new ImproveVertexValence(ml, opts).compute();
MeshWriter.writeObject3D(ml.getMesh(), "/home/robert/ast-a319-neo/demo-anabelle/demo/AST_mesh3.amibe", null);
} catch (Exception ex) {
Logger.getLogger(VertexInsertion.class.getName()).log(Level.SEVERE,
null, ex);
}
}
}