/*
* Project Info: http://jcae.sourceforge.net
*
* This program 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 program 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 program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* (C) Copyright 2013, by EADS France
*/
package org.jcae.mesh.stitch;
import gnu.trove.list.array.TDoubleArrayList;
import gnu.trove.list.array.TIntArrayList;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jcae.mesh.amibe.algos3d.Fuse;
import org.jcae.mesh.amibe.algos3d.Skeleton;
import org.jcae.mesh.amibe.ds.AbstractHalfEdge;
import org.jcae.mesh.amibe.ds.Mesh;
import org.jcae.mesh.amibe.ds.Triangle;
import org.jcae.mesh.amibe.ds.Vertex;
import org.jcae.mesh.amibe.metrics.Location;
import org.jcae.mesh.amibe.projection.TriangleKdTree;
import org.jcae.mesh.amibe.traits.MeshTraitsBuilder;
import org.jcae.mesh.amibe.util.HashFactory;
import org.jcae.mesh.xmldata.MeshReader;
/**
*
* @author Jerome Robert
*/
public class NonManifoldStitch {
private final static Logger LOGGER = Logger.getLogger(
NonManifoldStitch.class.getName());
private final Mesh mesh;
private final TriangleKdTree kdTree;
private int workingGroup;
private int nbInsertedBeams;
private double maxDistance = 10.0, tolerance = 1;
public NonManifoldStitch(Mesh mesh) {
this.mesh = mesh;
checkNonManifold();
kdTree = new TriangleKdTree(mesh);
workingGroup = mesh.getNumberOfGroups();
while(mesh.getGroupName(workingGroup) != null)
workingGroup ++;
}
private void checkNonManifold()
{
Map<Integer, Collection<AbstractHalfEdge>> edges =
new HashMap<Integer, Collection<AbstractHalfEdge>>();
for(Triangle t:mesh.getTriangles())
{
AbstractHalfEdge e = t.getAbstractHalfEdge();
for(int i = 0; i < 3; i ++)
{
if(e.hasAttributes(AbstractHalfEdge.NONMANIFOLD))
{
int gid = e.getTri().getGroupId();
Collection<AbstractHalfEdge> l = edges.get(gid);
if(l == null)
{
l = new ArrayList<AbstractHalfEdge>();
edges.put(gid, l);
}
l.add(e);
}
e = e.next();
}
}
if(!edges.isEmpty())
{
final StringBuilder sb = new StringBuilder(
"Cannot stitch non-manifold or badly oriented surfaces.");
for(Map.Entry<Integer, Collection<AbstractHalfEdge>> e: edges.entrySet())
{
sb.append("\ngroup: "+mesh.getGroupName(e.getKey())+" / "+e.getKey());
for(AbstractHalfEdge ee: e.getValue())
sb.append("\n\t"+new Location(ee.origin())+"-"+new Location(ee.destination()));
}
throw new IllegalArgumentException()
{
// localized message so netbeans plaform open a popup
@Override
public String getLocalizedMessage() {
return sb.toString();
}
};
}
}
public double getMaxDistance() {
return maxDistance;
}
public void setMaxDistance(double maxDistance) {
this.maxDistance = maxDistance;
}
public double getTolerance() {
return tolerance;
}
public void setTolerance(double tolerance) {
this.tolerance = tolerance;
}
private void stitchBeams(List<Vertex> beams, int triaGroup, double weight,
double maxDistance, double tolerance)
{
final Vertex dummyVertex = mesh.createVertex(0, 0, 0);
int nbBeams = beams.size() / 2;
Collection<AbstractHalfEdge> edgesToProject = new ArrayList<AbstractHalfEdge>(nbBeams);
for(int i = 0; i < nbBeams; i++)
{
Vertex v1 = beams.get(2 * i);
Vertex v2 = beams.get(2 * i + 1);
assert v1 != v2;
v1.setMutable(true);
v2.setMutable(true);
Triangle t = mesh.createTriangle(dummyVertex, v1, v2);
edgesToProject.add(t.getAbstractHalfEdge());
t.setGroupId(workingGroup);
mesh.add(t);
}
//TODO do not clear adjacency, this is time consuming.
mesh.clearAdjacency();
mesh.buildAdjacency();
EdgeProjector edgeProjector = new EdgeProjector(mesh, kdTree,
edgesToProject, triaGroup, maxDistance, tolerance, weight)
{
@Override
protected boolean isToProject(AbstractHalfEdge edge) {
return edge.origin() != dummyVertex && edge.destination() != dummyVertex;
}
};
edgeProjector.checkMerge = false;
edgeProjector.project();
nbInsertedBeams += nbBeams;
}
private Collection<AbstractHalfEdge> getBorder(int group)
{
ArrayList<AbstractHalfEdge> toReturn = new ArrayList<AbstractHalfEdge>();
for(Triangle t:mesh.getTriangles())
{
if(t.getGroupId() == group)
{
AbstractHalfEdge e = t.getAbstractHalfEdge();
for(int i = 0; i < 3; i++)
{
if(e.hasAttributes(AbstractHalfEdge.BOUNDARY))
toReturn.add(e);
e = e.next();
}
}
}
return toReturn;
}
public void stitch(int group1, double weight, boolean boundaryOnly) {
Collection<AbstractHalfEdge> set1 = getBorder(group1);
EdgeProjector edgeProjector = new EdgeProjector(mesh, kdTree, set1,
group1, maxDistance, tolerance, weight);
edgeProjector.setIgnoreGroup(true);
edgeProjector.setBoundaryOnly(boundaryOnly);
edgeProjector.project();
}
/**
* Stitch all borders of groups in the given mesh.
* This implementation is very slow because it recreate a new KdTree for
* each pair of groups. A faster implementation would require that
* EdgeProjector support non-manifold edges which is not yet available.
*/
public static void stitch(Mesh mesh, double maxDist, double cleanTol)
{
int nbGroup = mesh.getNumberOfGroups();
TDoubleArrayList tmpCoords = new TDoubleArrayList();
TIntArrayList tmpTria = new TIntArrayList();
for(int gid1 = 1; gid1 < nbGroup; gid1++)
{
Mesh workingMesh = new Mesh(MeshTraitsBuilder.getDefault3D());
tmpTria.clear();
tmpCoords.clear();
mesh.popGroup(tmpCoords, tmpTria, null, gid1);
workingMesh.pushGroup(tmpCoords.toArray(), tmpTria.toArray(), null, gid1);
for(int gid2 = gid1+1; gid2 <= nbGroup; gid2++)
{
tmpTria.clear();
tmpCoords.clear();
mesh.popGroup(tmpCoords, tmpTria, null, gid2);
workingMesh.pushGroup(tmpCoords.toArray(), tmpTria.toArray(), null, gid2);
workingMesh.clearAdjacency();
workingMesh.buildAdjacency();
NonManifoldStitch nms = new NonManifoldStitch(workingMesh);
nms.setMaxDistance(maxDist);
nms.setTolerance(cleanTol);
EdgeProjector.saveAsVTK(workingMesh);
nms.stitchBoth(gid1, gid2, 0);
tmpTria.clear();
tmpCoords.clear();
workingMesh.popGroup(tmpCoords, tmpTria, null, gid2);
mesh.pushGroup(tmpCoords.toArray(), tmpTria.toArray(), null, gid2);
}
tmpTria.clear();
tmpCoords.clear();
workingMesh.popGroup(tmpCoords, tmpTria, null, gid1);
mesh.pushGroup(tmpCoords.toArray(), tmpTria.toArray(), null, gid1);
}
new Fuse(mesh, cleanTol).compute();
}
public void stitch(int group1, final int group2, double weight, boolean boundaryOnly) {
Collection<AbstractHalfEdge> set1 = getBorder(group1);
EdgeProjector edgeProjector = new EdgeProjector(mesh, kdTree, set1,
group2, maxDistance, tolerance, weight);
edgeProjector.setBoundaryOnly(boundaryOnly);
edgeProjector.project();
}
private void stitchBoth(int group1, int group2, double weight)
{
stitch(group1, group2, weight, false);
stitch(group2, group1, 1 - weight, false);
EdgeProjector.saveAsVTK(mesh);
finish();
EdgeProjector.saveAsVTK(mesh);
}
public void intersect(int group1, int group2)
{
Intersection inter = new Intersection(mesh, kdTree);
List<Vertex> beams = inter.intersect(group1, group2, tolerance);
//copy the beam to insert because stitchBeams will move them.
List<Vertex> beams2 = new ArrayList<Vertex>(beams.size());
Map<Vertex, Vertex> copyMap = HashFactory.createMap(beams.size() / 2 + 5);
for(Vertex b: beams)
{
Vertex copy = copyMap.get(b);
if(copy == null)
{
copy = mesh.createVertex(b);
copyMap.put(b, copy);
}
beams2.add(copy);
}
stitchBeams(beams, group1, 0, Double.MAX_VALUE, tolerance);
stitchBeams(beams2, group2, 0, Double.MAX_VALUE, tolerance);
}
/** Actually fissure the surface and convert the working group to beams */
public void finish()
{
new NonManifoldSplitter(mesh).compute();
if(nbInsertedBeams > 0)
{
ArrayList<Triangle> toRemove = new ArrayList<Triangle>(nbInsertedBeams * 2);
for(Triangle t: mesh.getTriangles())
{
if(t.getGroupId() == workingGroup)
toRemove.add(t);
}
mesh.getTriangles().removeAll(toRemove);
}
}
private static void test6(Mesh mesh)
{
long t1 = System.nanoTime();
NonManifoldStitch nms = new NonManifoldStitch(mesh);
long t2 = System.nanoTime();
System.err.println("kdTree: "+(t2-t1)/1E9);
nms.setMaxDistance(1.0);
nms.setTolerance(0.01);
// increase number of iteration to test perfs
for(int k = 0; k < 1; k++)
{
for(int i = 1; i <= 4; i++)
for(int j = 1; j <= 4; j++)
{
if(i != j)
nms.stitch(i, j, 1.0, false);
}
}
long t3 = System.nanoTime();
System.err.println("projection: "+(t3-t2)/1E9);
}
/** Intersection of 4 groups with adjacent triangles */
private static void test8(String data) throws IOException
{
Mesh mesh = new Mesh(MeshTraitsBuilder.getDefault3D());
MeshReader.readObject3D(mesh, data+"case8.amibe");
EdgeProjector.saveAsVTK(mesh);
NonManifoldStitch nms = new NonManifoldStitch(mesh);
nms.setTolerance(0.01);
for(int i = 1; i <= mesh.getNumberOfGroups(); i++)
System.err.println(i+" => "+mesh.getGroupName(i));
for(int i = 1; i <= mesh.getNumberOfGroups()-1; i++)
for(int j = i+1; j <= mesh.getNumberOfGroups(); j++)
{
System.err.println("intersecting "+i+" "+j);
EdgeProjector.saveVTK = true;
EdgeProjector.saveAsVTK(mesh);
EdgeProjector.saveVTK = false;
nms.intersect(i, j);
}
new NonManifoldSplitter(mesh).compute();
mesh.popGroup(null, null, null, nms.workingGroup);
EdgeProjector.saveVTK = true;
EdgeProjector.saveAsVTK(mesh);
EdgeProjector.saveVTK = false;
}
/**
* Functional tests
* @param args
*/
public static void main(final String[] args) {
try {
String data = args[0];
Mesh mesh = new Mesh(MeshTraitsBuilder.getDefault3D());
MeshReader.readObject3D(mesh, data+"case1.amibe");
NonManifoldStitch nms = new NonManifoldStitch(mesh);
EdgeProjector.saveVTK = true;
nms.setMaxDistance(100);
nms.stitchBoth(1, 2, 1.0);
mesh = new Mesh(MeshTraitsBuilder.getDefault3D());
MeshReader.readObject3D(mesh, data+"case3.amibe");
nms = new NonManifoldStitch(mesh);
nms.stitchBoth(1, 2, 1.0);
mesh = new Mesh(MeshTraitsBuilder.getDefault3D());
MeshReader.readObject3D(mesh, data+"case4.amibe");
nms = new NonManifoldStitch(mesh);
nms.setMaxDistance(50);
nms.stitchBoth(1, 2, 1.0);
mesh = new Mesh(MeshTraitsBuilder.getDefault3D());
MeshReader.readObject3D(mesh, data+"case6.amibe");
EdgeProjector.saveVTK = false;
test6(mesh);
EdgeProjector.saveVTK = true;
EdgeProjector.saveAsVTK(mesh);
test8(data);
mesh = new Mesh(MeshTraitsBuilder.getDefault3D());
MeshReader.readObject3D(mesh, data+"case7.amibe");
nms = new NonManifoldStitch(mesh);
nms.setMaxDistance(40);
nms.setTolerance(0.1);
EdgeProjector.saveVTK = false;
nms.stitch(1, 2, 1.0, false);
EdgeProjector.saveVTK = true;
EdgeProjector.saveAsVTK(mesh);
} catch (Exception ex) {
Logger.getLogger(NonManifoldStitch.class.getName()).log(
Level.SEVERE, null, ex);
}
}
}