/* jCAE stand for Java Computer Aided Engineering. Features are : Small CAD
modeler, Finite element mesher, Plugin architecture.
Copyright (C) 2003,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.AbstractHalfEdge;
import org.jcae.mesh.amibe.ds.Mesh;
import org.jcae.mesh.amibe.ds.HalfEdge;
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.File;
import java.io.IOException;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Split long edges. Edges are sorted and splitted in turn, the longest edge
* being processed first.
* Example: if a mesh has already been performed with a target size
* of discr, the following commands will split edges longer then discr/2
* <pre>
* SplitEdge split = new SplitEdge(m, 0.5*discr);
* split.compute();
* </pre>
* TODO: currently edges longer than sqrt(2)*discr/2 will be splitted
*
* When an interior edge is splitted, its midpoint is projected onto
* the surface by {@link org.jcae.mesh.amibe.projection.QuadricProjection}; if this
* projection fails, this edge is not splitted. It is removed from
* the tree because there are few chances that this projection works
* later. This means in particular that an interior edge whose
* endpoints are both on boundaries cannot be splitted, because
* discrete projection cannot be performed on boundary nodes.
* As for now, boundary edges are always splitted, and the new point
* is in the middle of this edge.
*
* In all cases, the distance between the newly inserted point and
* apical vertices is computed; if it is too low, the edge is not
* splitted to avoid bad triangles.
* TODO: edges should be swapped too to improve triangle quality.
*/
public class SplitEdge extends AbstractAlgoHalfEdge
{
private static final Logger LOGGER=Logger.getLogger(SplitEdge.class.getName());
private Vertex insertedVertex = null;
/**
* Creates a <code>SplitEdge</code> instance.
*
* @param m the <code>Mesh</code> instance to refine.
* @param options map containing key-value pairs to modify algorithm
* behaviour. Valid keys are <code>size</code> and
* <code>maxtriangles</code>.
*/
public SplitEdge(final Mesh m, final Map<String, String> options)
{
this(m, null, options);
}
public SplitEdge(final MeshLiaison liaison, final Map<String, String> options)
{
this(liaison.getMesh(), liaison, options);
}
public SplitEdge(final Mesh m, final MeshLiaison meshLiaison, final Map<String, String> options)
{
super(m, meshLiaison);
moreTriangles = true;
for (final Map.Entry<String, String> opt: options.entrySet())
{
final String key = opt.getKey();
final String val = opt.getValue();
if (key.equals("size"))
{
double sizeTarget = Double.valueOf(val).doubleValue();
tolerance = - (sizeTarget * sizeTarget);
}
else if (key.equals("maxtriangles"))
nrFinal = Integer.valueOf(val).intValue();
else if (key.equals("coplanarity"))
{
minCos = Double.parseDouble(val);
LOGGER.fine("Minimum dot product of face normals allowed for swapping an edge: "+minCos);
}
else
throw new RuntimeException("Unknown option: "+key);
}
if (tolerance == 0.0 && nrFinal == 0)
throw new RuntimeException("Either 'size' or 'maxtriangles' must be specified");
if (meshLiaison == null)
mesh.buildRidges(minCos);
}
@Override
public Logger thisLogger()
{
return LOGGER;
}
@Override
public void preProcessAllHalfEdges()
{
}
@Override
protected final double cost(final HalfEdge e)
{
return - e.origin().sqrDistance3D(e.destination());
}
@Override
public boolean canProcessEdge(HalfEdge current)
{
current = uniqueOrientation(current);
if (current.hasAttributes(AbstractHalfEdge.IMMUTABLE))
return false;
insertedVertex = mesh.createVertex(0, 0, 0);
insertedVertex.middle(current.origin(), current.destination());
return true;
}
private void updateTree(HalfEdge current)
{
if (!current.origin().isReadable() || !current.destination().isReadable())
return;
double newCost = cost(current);
if (nrFinal == 0 && newCost > tolerance)
return;
updateCost(current, newCost);
}
@Override
public HalfEdge processEdge(HalfEdge current, double costCurrent)
{
current = uniqueOrientation(current);
if (LOGGER.isLoggable(Level.FINE))
{
LOGGER.fine("Split edge: "+current+" by "+insertedVertex+" cost="+costCurrent);
if (current.hasAttributes(AbstractHalfEdge.NONMANIFOLD))
{
LOGGER.fine("Non-manifold edge:");
for (Iterator<AbstractHalfEdge> it = current.fanIterator(); it.hasNext(); )
LOGGER.fine(" --> "+it.next());
}
}
if (current.hasAttributes(AbstractHalfEdge.NONMANIFOLD))
{
for (Iterator<AbstractHalfEdge> it = current.fanIterator(); it.hasNext(); )
removeOneFromTree((HalfEdge) it.next());
}
else
removeOneFromTree(current);
current.clearAttributes(AbstractHalfEdge.MARKED);
mesh.vertexSplit(current, insertedVertex);
nrTriangles += 2;
assert current.destination() == insertedVertex : insertedVertex+" "+current;
assert mesh.isValid();
if (liaison != null)
{
liaison.addVertex(insertedVertex, current.origin());
}
HalfEdge ret = current.prev();
// Update edge lengths
if (current.hasAttributes(AbstractHalfEdge.NONMANIFOLD))
{
for (Iterator<AbstractHalfEdge> it = current.fanIterator(); it.hasNext(); )
{
HalfEdge f = (HalfEdge) it.next();
f = f.next();
updateTree(f);
}
updateTree(current);
current = current.next();
current = current.sym();
current = current.next();
updateTree(current);
}
else
{
current = current.sym();
Vertex d = current.destination();
do
{
if (current.destination() != mesh.outerVertex)
updateTree(current);
current = current.nextOriginLoop();
}
while (current.destination() != d);
}
return ret;
}
@Override
public void postProcessAllHalfEdges()
{
LOGGER.info("Number of splitted edges: "+processed);
LOGGER.info("Total number of edges not splitted during processing: "+notProcessed);
LOGGER.info("Total number of edges swapped to increase quality: "+swapped);
super.postProcessAllHalfEdges();
}
private final static String usageString = "<xmlDir> <-t maxLength | -n nrTriangles> <brepFile> <outputDir>";
/**
*
* @param args xmlDir, -t tolerance | -n triangle, brepFile, output
*/
public static void main(String[] args)
{
HashMap<String, String> options = new HashMap<String, String>();
if(args.length != 5)
{
System.out.println(usageString);
return;
}
if(args[1].equals("-n"))
options.put("maxtriangles", args[2]);
else if(args[1].equals("-t"))
options.put("size", args[2]);
else
{
System.out.println(usageString);
return;
}
LOGGER.info("Load geometry file");
org.jcae.mesh.amibe.traits.MeshTraitsBuilder mtb = org.jcae.mesh.amibe.traits.MeshTraitsBuilder.getDefault3D();
mtb.addTriangleSet();
Mesh mesh = new Mesh(mtb);
try
{
MeshReader.readObject3D(mesh, args[0]);
}
catch (IOException ex)
{
ex.printStackTrace();
throw new RuntimeException(ex);
}
new SplitEdge(mesh, options).compute();
File brepFile=new File(args[3]);
try
{
MeshWriter.writeObject3D(mesh, args[4], brepFile.getName());
}
catch (IOException ex)
{
ex.printStackTrace();
throw new RuntimeException(ex);
}
}
}