/*
* 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.set.hash.THashSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jcae.mesh.amibe.algos3d.VertexSwapper;
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.TriangleHE;
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.stitch.TriangleProjector.ProjectionType;
import org.jcae.mesh.xmldata.Amibe2VTK;
import org.jcae.mesh.xmldata.MeshWriter;
/**
*
* @author Jerome Robert
*/
public class EdgeProjector {
private interface Pool<E> extends Collection<E>
{
E get();
E pop();
}
private static <E> Pool<E> createPool()
{
return new DebugPool<E>();
}
private static class TPool<E> extends THashSet<E> implements Pool<E>
{
public E get()
{
int n = _set.length;
for(int i = 0; i < n; i++)
{
if(_set[i] != FREE && _set[i] != REMOVED)
return (E) _set[i];
}
throw new NoSuchElementException();
}
public E pop()
{
int n = _set.length;
for(int i = 0; i < n; i++)
{
if(_set[i] != FREE && _set[i] != REMOVED)
{
E toReturn = (E) _set[i];
removeAt(i);
return toReturn;
}
}
throw new NoSuchElementException();
}
}
private static class DebugPool<E> extends LinkedHashSet<E> implements Pool<E>
{
public E get()
{
return iterator().next();
}
public E pop()
{
Iterator<E> it = iterator();
E toReturn = it.next();
it.remove();
return toReturn;
}
@Override
public boolean add(E e) {
assert e != null;
return super.add(e);
}
}
private final static Logger LOGGER = Logger.getLogger(EdgeProjector.class.getName());
private final Pool<Triangle> triangles = createPool();
//projectors for origin vertices
private final List<TriangleProjector> triangleProjectors1 = new ArrayList<TriangleProjector>();
//the current projector for origin vertices
private TriangleProjector triangleProjector1;
private boolean projector1Valid;
//projectors for origin vertices
private final List<TriangleProjector> triangleProjectors2 = new ArrayList<TriangleProjector>();
//the current projector for origin vertices
private TriangleProjector triangleProjector2;
private boolean projector2Valid;
private Integer[] triangleProjectorOrder;
/** A projector which will alway be too far to be concidered */
private TriangleProjector farProjector = new TriangleProjector();
private final double[] aabb = new double[6];
private final Mesh mesh;
private final TriangleKdTree kdTree;
private final Pool<AbstractHalfEdge> toProject = createPool();
private final Pool<AbstractHalfEdge> halfInserted = createPool();
private final int group;
private boolean ignoreGroup;
private final Collection<Triangle> splittedTriangle = new ArrayList<Triangle>(3);
private final List<TriangleHelper> triangleHelpers = new ArrayList<TriangleHelper>();
private Vertex lastMergeSource;
private Vertex lastMergeTarget;
private AbstractHalfEdge lastSplitted1;
private AbstractHalfEdge lastSplitted2;
private AbstractHalfEdge edgeToCollapse;
private double weight;
private final TriangleSplitter triangleSplitter = new TriangleSplitter();
private final VertexMerger vertexMerger = new VertexMerger();
private final VertexSwapper vertexSwapper;
public boolean checkMerge = true, boundaryOnly;
private final double maxSqrDist, sqrTol;
public EdgeProjector(Mesh mesh, TriangleKdTree kdTree,
Collection<AbstractHalfEdge> edges, int group, double maxDist,
double tol, double weight) {
this.mesh = mesh;
this.kdTree = kdTree;
final double tol3 = tol * tol * tol;
vertexSwapper = new VertexSwapper(mesh)
{
@Override
protected boolean isQualityImproved(AbstractHalfEdge.Quality quality) {
return quality.getSwappedQuality()> quality.getQuality() &&
quality.getSwappedAngle() > 0 &&
quality.swappedVolume() < tol3;
}
};
this.toProject.addAll(edges);
for(AbstractHalfEdge e: toProject)
{
e.origin().setMutable(true);
e.destination().setMutable(true);
}
vertexSwapper.setKdTree(kdTree);
this.group = group;
maxSqrDist = maxDist * maxDist;
sqrTol = tol * tol;
this.weight = weight;
}
/**
* When true project on all groups but the specified one, instead of
* projecting to the specified group
*/
public void setIgnoreGroup(boolean ignore)
{
ignoreGroup = ignore;
}
public void setBoundaryOnly(boolean b)
{
boundaryOnly = b;
}
public void project() {
while (true) {
AbstractHalfEdge tp;
if (!halfInserted.isEmpty()) {
tp = halfInserted.pop();
} else if (!toProject.isEmpty()) {
tp = toProject.pop();
if(!tp.hasAttributes(AbstractHalfEdge.BOUNDARY))
//previous merge has render this edge not boundary so
//we skip it
continue;
} else {
break;
}
projectEdge(tp);
}
}
private Vertex splitTriangle(Triangle t, TriangleProjector tp) {
Vertex toInsert = mesh.createVertex(tp.getProjection());
kdTree.remove(t);
((TriangleHE) t).split(mesh, toInsert, splittedTriangle);
for(Triangle tt: splittedTriangle)
kdTree.addTriangle(tt);
splittedTriangle.clear();
vertexSwapper.swap(toInsert);
return toInsert;
}
private boolean canMerge(Vertex source, Location target) {
assert source != null;
assert target != null;
if(source == target)
return false;
Location realPosition = null;
if(checkMerge)
realPosition = new Location(weight * source.getX() + (1 - weight) * target.getX(),
weight * source.getY() + (1 - weight) * target.getY(),
weight * source.getZ() + (1 - weight) * target.getZ());
Iterator<AbstractHalfEdge> it = source.getNeighbourIteratorAbstractHalfEdge();
edgeToCollapse = null;
while (it.hasNext()) {
AbstractHalfEdge e = it.next();
assert e.origin() == source;
assert e.origin().isMutable(): e;
if (checkMerge && (!e.origin().isManifold() || !mesh.canMoveOrigin(e, realPosition))) {
LOGGER.info("Cannot move " + source + " to " + realPosition +
". distance=" + source.distance3D(realPosition));
return false;
}
boolean boundary = e.hasAttributes(AbstractHalfEdge.BOUNDARY) ||
(e.sym() != null && e.sym().hasAttributes(AbstractHalfEdge.BOUNDARY));
if(boundaryOnly)
{
// check that we do not create non-manifold edges in boundary
// only mode (manifold stitch).
Iterator<AbstractHalfEdge> itt = e.destination().getNeighbourIteratorAbstractHalfEdge();
while(itt.hasNext())
{
AbstractHalfEdge ee = itt.next();
if(ee.destination() == target &&
(!ee.hasAttributes(AbstractHalfEdge.BOUNDARY) &&
!ee.sym().hasAttributes(AbstractHalfEdge.BOUNDARY) || !boundary))
return false;
}
}
if(e.destination() == target)
{
if(e.hasAttributes(AbstractHalfEdge.OUTER))
e = e.sym();
if(boundary && mesh.canCollapseEdge(e, target))
{
// target has alread been projected to the target mesh so we
// collapse the edge of the source mesh instead of merging vertices.
// Merging vertices would create degenerated triangle.
edgeToCollapse = e;
return true;
}
else
{
LOGGER.info(
"Cannot collapse " + source + " to " + target +
". boundary=" + e.hasAttributes(AbstractHalfEdge.BOUNDARY) +
" canCollapse=" + mesh.canCollapseEdge(e, target));
return false;
}
}
}
return true;
}
/** @return true if the merge was possible */
private void mergeVertices(Vertex source, Vertex target) {
saveAsVTK(mesh);
assert source != target;
assert !Double.isNaN(source.getX());
assert !Double.isNaN(target.getX());
assert !(source.getLink() instanceof Triangle[]): source;
assert canMerge(source, target);
target.moveTo(weight * source.getX() + (1 - weight) * target.getX(),
weight * source.getY() + (1 - weight) * target.getY(),
weight * source.getZ() + (1 - weight) * target.getZ());
if(edgeToCollapse == null)
vertexMerger.merge(mesh, target, source, target);
else
{
assert (edgeToCollapse.origin() == source && edgeToCollapse.destination() == target) ||
(edgeToCollapse.origin() == target && edgeToCollapse.destination() == source);
Triangle t = edgeToCollapse.getTri();
if(lastSplitted1 != null && lastSplitted1.getTri() == t)
lastSplitted1 = null;
if(lastSplitted2 != null && lastSplitted2 .getTri() == t)
lastSplitted2 = null;
toProject.remove(edgeToCollapse);
toProject.remove(edgeToCollapse.next());
toProject.remove(edgeToCollapse.prev());
mesh.edgeCollapse(edgeToCollapse, target);
kdTree.remove(t);
edgeToCollapse = null;
}
target.setMutable(false);
saveAsVTK(mesh);
}
private void splitEdge(AbstractHalfEdge toSplit, Vertex v) {
assert v.sqrDistance3D(toSplit.origin()) > 1E-24: toSplit+" "+v;
assert v.sqrDistance3D(toSplit.destination()) > 1E-24: toSplit+" "+v;
if (toSplit.hasAttributes(AbstractHalfEdge.BOUNDARY))
{
Triangle t = toSplit.getTri();
kdTree.remove(t);
mesh.vertexSplit(toSplit, v);
AbstractHalfEdge newEdge = toSplit.next().sym().next();
addTriangleToKdTree(toSplit.getTri());
addTriangleToKdTree(newEdge.getTri());
}
else
{
Triangle t1 = toSplit.getTri();
Triangle t2 = toSplit.sym().getTri();
kdTree.remove(t1);
kdTree.remove(t2);
mesh.vertexSplit(toSplit, v);
AbstractHalfEdge newEdge = toSplit.next().sym().next();
assert toSplit.destination() == v;
assert newEdge.origin() == v;
addTriangleToKdTree(toSplit.getTri());
addTriangleToKdTree(toSplit.sym().getTri());
addTriangleToKdTree(newEdge.getTri());
addTriangleToKdTree(newEdge.sym().getTri());
}
vertexSwapper.swap(v);
}
private void addTriangleToKdTree(Triangle triangle) {
if (!triangle.hasAttributes(AbstractHalfEdge.OUTER)) {
kdTree.addTriangle(triangle);
}
}
private Vertex splitEdge(TriangleProjector tp) {
Vertex v = mesh.createVertex(tp.getProjection());
AbstractHalfEdge toSplit = tp.getEdge();
if (toSplit.hasAttributes(AbstractHalfEdge.NONMANIFOLD)) {
LOGGER.info(
"I will not split a non-manifold edge as there should not be "+
"any non-manifold edges here: "+toSplit);
return null;
} else {
splitEdge(toSplit, v);
}
return v;
}
/**
* Split 2 edges
* @param edge1 an edge to be splitted with a copy of vertexToInsert
* @param edge2 an edge to be splitted with vertexToInsert
* @param vertexToInsert the point to insert on edge2
* @return The vertex splitting edge1
*/
private void split2Edges(Vertex v1, AbstractHalfEdge edge1, Vertex v2,
AbstractHalfEdge edge2) {
assert v1 != null;
assert v1 != edge1.origin();
assert v1 != edge1.destination();
assert TriangleHelper.isOnEdge(v1, edge1.origin(), edge1.destination(),
triangleProjector1.sqrMaxDistance);
// I duplicate the vertex because I'm almost sure that
// vertexSplit doesn't support inserted vertex which are
// already in the mesh
check(mesh);
splitEdge(edge1, v1);
check(mesh);
if (edge2 != null) {
assert v2 != edge2.origin();
assert v2 != edge2.destination();
assert TriangleHelper.isOnEdge(v2, edge2.origin(),
edge2.destination(), triangleProjector1.sqrTolerance);
splitEdge(edge2, v2);
check(mesh);
}
}
public static boolean checkMesh = false;
private static void check(Mesh mesh) {
if(checkMesh)
{
assert mesh.isValid();
assert mesh.checkNoDegeneratedTriangles();
assert mesh.checkNoInvertedTriangles();
}
}
/**
* Find the border edge adjacent to the given edge and sharing its
* destination vertex
*/
private AbstractHalfEdge getNextBorderEdge(AbstractHalfEdge edge) {
assert edge.hasAttributes(AbstractHalfEdge.BOUNDARY): "boundary edge expected: "+edge;
AbstractHalfEdge toReturn = edge.next();
int gid = edge.getTri().getGroupId();
while (toReturn.destination() != mesh.outerVertex &&
!(toReturn.hasAttributes(AbstractHalfEdge.BOUNDARY) &&
toReturn.getTri().getGroupId() == gid)) {
toReturn = toReturn.sym().next();
}
assert toReturn.origin() == edge.destination();
return toReturn.destination() == mesh.outerVertex ? null : toReturn;
}
private AbstractHalfEdge getPreviousBorderEdge(AbstractHalfEdge edge) {
assert edge.hasAttributes(AbstractHalfEdge.BOUNDARY): "boundary edge expected: "+edge;
AbstractHalfEdge toReturn = edge.next().next();
int gid = edge.getTri().getGroupId();
while (toReturn.origin() != mesh.outerVertex &&
!(toReturn.hasAttributes(AbstractHalfEdge.BOUNDARY) &&
toReturn.getTri().getGroupId() == gid)) {
toReturn = toReturn.sym().next().next();
}
assert toReturn.destination() == edge.origin();
return toReturn.origin() == mesh.outerVertex ? null : toReturn;
}
private void handleOutOutCase(AbstractHalfEdge edge) {
lastMergeSource = null;
lastMergeTarget = null;
if (triangleProjector1.getType() == ProjectionType.OUT && triangleProjector2.getType() == ProjectionType.OUT) {
//split and edge of the triangle and split the projected
//edge. The edge may cut 2 edges of the triangle but we will
//insert only one
Location p2 = triangleProjector2.getProjection();
Location p1 = triangleProjector1.getProjection();
triangleSplitter.split(p1, p2, triangleProjector1.sqrTolerance);
lastMergeTarget = triangleSplitter.getSplitVertex(mesh);
if (lastMergeTarget != null) {
lastMergeSource = mesh.createVertex(0, 0, 0);
double l = triangleSplitter.getReverseSplitPoint(p2,
edge.destination(), edge.origin(), lastMergeSource);
if (l > triangleProjector1.sqrMaxDistance) {
lastMergeTarget = null;
}
}
if (lastMergeTarget != null) {
assert lastMergeSource != null : lastMergeTarget;
split2Edges(lastMergeSource, edge, lastMergeTarget,
triangleSplitter.getSplittedEdge());
lastSplitted1 = edge;
lastSplitted2 = edge.next().sym().next();
assert TriangleHelper.isOnEdge(lastMergeSource, edge.origin(),
edge.destination(), triangleProjector1.sqrTolerance);
}
if (lastMergeSource != null && lastMergeTarget != null && !canMerge(lastMergeSource,
lastMergeTarget)) {
lastMergeTarget = null;
}
}
}
/**
* Select the good algorithm, apply it and update lastInserted,
* lastSplitted1 and lastSplitted2.
*/
private void dispatchCases(boolean origin, boolean destination,
AbstractHalfEdge edge, Triangle t) {
lastMergeSource = null;
lastMergeTarget = null;
lastSplitted1 = null;
lastSplitted2 = null;
if (triangleProjector1.getType() == ProjectionType.OUT && !destination) {
//Split and edge of the triangle and split the projected edge
triangleSplitter.splitApex(edge.destination(),
triangleProjector1.getProjection(),
triangleProjector1.sqrTolerance);
lastMergeTarget = triangleSplitter.getSplitVertex(mesh);
if(lastMergeTarget != null && !lastMergeTarget.isMutable())
lastMergeTarget = null;
if (lastMergeTarget != null) {
lastMergeSource = mesh.createVertex(0, 0, 0);
double l = triangleSplitter.getReverseSplitPoint(
triangleProjector1.getProjection(), edge.origin(),
edge.destination(), lastMergeSource);
if (l > triangleProjector1.sqrMaxDistance) {
lastMergeTarget = null;
}
}
if (lastMergeTarget != null && canMerge(lastMergeSource,
lastMergeTarget)) {
split2Edges(lastMergeSource, edge, lastMergeTarget,
triangleSplitter.getSplittedEdge());
lastSplitted1 = edge;
assert TriangleHelper.isOnEdge(lastMergeSource, edge.origin(),
edge.destination(), triangleProjector1.sqrTolerance);
} else {
lastMergeTarget = null;
}
} else if (triangleProjector2.getType() == ProjectionType.OUT && !origin) {
//Split and edge of the triangle and split the projected edge
triangleSplitter.splitApex(edge.origin(),
triangleProjector2.getProjection(),
triangleProjector2.sqrTolerance);
lastMergeTarget = triangleSplitter.getSplitVertex(mesh);
if(lastMergeTarget != null && !lastMergeTarget.isMutable())
lastMergeTarget = null;
if (lastMergeTarget != null) {
lastMergeSource = mesh.createVertex(0, 0, 0);
double l = triangleSplitter.getReverseSplitPoint(
triangleProjector2.getProjection(), edge.destination(),
edge.origin(), lastMergeSource);
if (l > triangleProjector2.sqrMaxDistance) {
lastMergeTarget = null;
}
}
if (lastMergeTarget != null && canMerge(lastMergeSource,
lastMergeTarget)) {
split2Edges(lastMergeSource, edge, lastMergeTarget,
triangleSplitter.getSplittedEdge());
lastSplitted2 = getNextBorderEdge(edge);
assert TriangleHelper.isOnEdge(lastMergeSource, edge.origin(),
edge.destination(), triangleProjector2.sqrTolerance);
} else {
lastMergeTarget = null;
}
} else if (origin && triangleProjector1.getType() == ProjectionType.VERTEX &&
edge.origin() != triangleProjector1.getVertex())
{
lastMergeSource = edge.origin();
lastMergeTarget = triangleProjector1.getVertex();
lastSplitted1 = edge;
lastSplitted2 = getPreviousBorderEdge(edge);
assert TriangleHelper.isOnEdge(lastMergeSource, edge.origin(),
edge.destination(), triangleProjector1.sqrTolerance);
} else if (destination && triangleProjector2.getType() == ProjectionType.VERTEX &&
edge.destination() != triangleProjector2.getVertex())
{
lastMergeSource = edge.destination();
lastMergeTarget = triangleProjector2.getVertex();
lastSplitted1 = edge;
lastSplitted2 = getNextBorderEdge(edge);
assert TriangleHelper.isOnEdge(lastMergeSource, edge.origin(),
edge.destination(), triangleProjector2.sqrTolerance);
} else if (origin && triangleProjector1.getType() == ProjectionType.FACE) {
lastMergeSource = edge.origin();
if (canMerge(lastMergeSource, triangleProjector1.getProjection())) {
lastMergeTarget = splitTriangle(t, triangleProjector1);
lastSplitted1 = edge;
lastSplitted2 = getPreviousBorderEdge(edge);
assert TriangleHelper.isOnEdge(lastMergeSource, edge.origin(),
edge.destination(), triangleProjector1.sqrTolerance);
}
} else if (destination && triangleProjector2.getType() == ProjectionType.FACE) {
lastMergeSource = edge.destination();
if (canMerge(lastMergeSource, triangleProjector2.getProjection())) {
lastMergeTarget = splitTriangle(t, triangleProjector2);
lastSplitted1 = edge;
lastSplitted2 = getNextBorderEdge(edge);
assert TriangleHelper.isOnEdge(lastMergeSource, edge.origin(),
edge.destination(), triangleProjector2.sqrTolerance);
}
} else if (origin && triangleProjector1.getType() == ProjectionType.EDGE) {
lastMergeSource = edge.origin();
if (canMerge(lastMergeSource, triangleProjector1.getProjection())) {
lastMergeTarget = splitEdge(triangleProjector1);
lastSplitted1 = edge;
lastSplitted2 = getPreviousBorderEdge(edge);
assert TriangleHelper.isOnEdge(lastMergeSource, edge.origin(),
edge.destination(), triangleProjector1.sqrTolerance);
}
} else if (destination && triangleProjector2.getType() == ProjectionType.EDGE) {
lastMergeSource = edge.destination();
if (canMerge(lastMergeSource, triangleProjector2.getProjection())) {
lastMergeTarget = splitEdge(triangleProjector2);
lastSplitted1 = edge;
lastSplitted2 = getNextBorderEdge(edge);
assert TriangleHelper.isOnEdge(lastMergeSource, edge.origin(),
edge.destination(), triangleProjector2.sqrTolerance);
}
}
if (lastMergeSource != null && lastMergeTarget != null && !canMerge(lastMergeSource,
lastMergeTarget)) {
lastMergeTarget = null;
}
}
private boolean mergeAndFinish() {
if (lastMergeTarget != null) {
if(lastMergeSource == lastMergeTarget)
{
LOGGER.info("Something strange append around "+lastMergeSource);
return false;
}
assert lastMergeSource.sqrDistance3D(lastMergeTarget) < triangleProjector1.sqrMaxDistance * 2 :
"Vertex is too far: "+
Math.sqrt(lastMergeSource.sqrDistance3D(lastMergeTarget))+
" > "+Math.sqrt(triangleProjector1.sqrMaxDistance);
check(mesh);
mergingVertices(lastMergeSource, lastMergeTarget);
mergeVertices(lastMergeSource, lastMergeTarget);
verticesMerged(lastMergeTarget);
check(mesh);
if (lastSplitted1 != null &&
lastSplitted1.hasAttributes(AbstractHalfEdge.BOUNDARY) &&
isToProject(lastSplitted1))
{
halfInserted.add(lastSplitted1);
}
if (lastSplitted2 != null &&
lastSplitted2.hasAttributes(AbstractHalfEdge.BOUNDARY) &&
isToProject(lastSplitted2))
{
halfInserted.add(lastSplitted2);
}
return true;
}
return false;
}
/**
* Callback called when a vertex is inserted on an existing vertex.
* By default it does nothing
*/
protected void mergingVertices(Vertex source, Vertex target)
{
}
protected void verticesMerged(Vertex target)
{
}
protected boolean isToProject(AbstractHalfEdge edge)
{
return true;
}
/**
* Init the triangles field with triangles where the edge may be
* projected
*/
private void findCandidateTriangles(AbstractHalfEdge edge) {
boolean origin = edge.origin().isMutable();
boolean destination = edge.destination().isMutable();
//if neither origin nor destination both extremities of the edge
//are already projected but the full edge may not be fully projected
//so we will try only the out/out case.
triangles.clear();
boolean notProjected = !origin && !destination &&
edge.hasAttributes(AbstractHalfEdge.BOUNDARY);
int group = this.group;
boolean ignoreGroup = this.ignoreGroup;
if(group == -1)
{
ignoreGroup = true;
group = edge.getTri().getGroupId();
}
if ((origin && destination) || notProjected) {
compteEdgeAABB(edge, aabb);
kdTree.getNearTriangles(aabb, triangles, group, ignoreGroup);
} else {
Vertex vv = origin ? edge.destination() : edge.origin();
Iterator<Triangle> it = vv.getNeighbourIteratorTriangle();
while (it.hasNext()) {
Triangle t = it.next();
if (!t.hasAttributes(AbstractHalfEdge.OUTER) &&
((!ignoreGroup && group >= 0 && t.getGroupId() == group) ||
(ignoreGroup && group >= 0 && t.getGroupId() != group)))
{
triangles.add(t);
}
}
}
}
/**
* Fill triangleProjectors1 and triangleProjectors2, then order them in the
* triangleProjectorOrder array.
* triangleProjectorOrder can be used to iterator over triangleProjectors
* starting with the closest triangle.
* Projecting to the closest triangle instead of a random close triangle is
* more robust.
*/
private void projectToClosest(AbstractHalfEdge edge)
{
boolean origin = edge.origin().isMutable();
boolean destination = edge.destination().isMutable();
findCandidateTriangles(edge);
while(triangleHelpers.size() < triangles.size())
triangleHelpers.add(new TriangleHelper());
int k = 0;
for(Triangle t: triangles)
{
assert t.getAbstractHalfEdge() != null: t;
assert mesh.getTriangles().contains(t): t;
triangleHelpers.get(k++).setTriangle(t);
}
projector1Valid = origin || (!origin && !destination);
projector2Valid = destination || (!origin && !destination);
if (projector1Valid)
projectToClosest(edge.origin(), triangleProjectors1);
if (projector2Valid)
projectToClosest(edge.destination(), triangleProjectors2);
if(triangleProjectorOrder == null || triangleProjectorOrder.length < triangles.size())
triangleProjectorOrder = new Integer[triangles.size()];
for(int i = 0; i < triangleProjectorOrder.length; i++)
triangleProjectorOrder[i] = i;
Arrays.sort(triangleProjectorOrder, 0, triangles.size(), projectorComparator);
}
private final Comparator<Integer> projectorComparator
= new Comparator<Integer>(){
public int compare(Integer o1, Integer o2) {
double v1, v2;
if(projector1Valid && projector2Valid)
{
v1 = Math.min(triangleProjectors1.get(o1).getSqrDistance(),
triangleProjectors2.get(o1).getSqrDistance());
v2 = Math.min(triangleProjectors1.get(o2).getSqrDistance(),
triangleProjectors2.get(o2).getSqrDistance());
}
else if(projector1Valid)
{
v1 = triangleProjectors1.get(o1).getSqrDistance();
v2 = triangleProjectors1.get(o2).getSqrDistance();
}
else //if(projector2Valid)
{
assert projector2Valid;
v1 = triangleProjectors2.get(o1).getSqrDistance();
v2 = triangleProjectors2.get(o2).getSqrDistance();
}
return Double.compare(v1, v2);
}
};
/**
* Project location to the closest triangle available in the triangleHelpers list.
* triangleHelpers must contains TriangleHelper initialised with the triangle
* of the triangles list.
*/
private void projectToClosest(Location location, List<TriangleProjector> projectors)
{
while(projectors.size() < triangles.size())
{
TriangleProjector tp = new TriangleProjector();
tp.sqrMaxDistance = maxSqrDist;
tp.sqrTolerance = sqrTol;
tp.boundaryOnly = boundaryOnly;
projectors.add(tp);
}
int n = triangles.size();
for(int k = 0; k < n; k++)
{
TriangleProjector tp = projectors.get(k);
tp.reset();
tp.project(location, triangleHelpers.get(k));
}
}
/**
* Project the edge on the given group of the mesh
* @param edge the edge to project
* @param halfProjected filled with the list of half projected edges. A
* half projected edges is an edge whose only one vertex is projected or
* both vertex a projected but on different triangles.
*/
private void projectEdge(AbstractHalfEdge edge) {
boolean origin = edge.origin().isMutable();
boolean destination = edge.destination().isMutable();
projectToClosest(edge);
//if neither origin nor destination both extremities of the edge
//are already projected but the full edge may not be fully projected
//so we will try only the out/out case.
int n = triangles.size();
for (int i = 0; i < n; i++) {
int index = triangleProjectorOrder[i];
triangleProjector1 = farProjector;
triangleProjector2 = farProjector;
if(projector1Valid)
triangleProjector1 = triangleProjectors1.get(index);
if(projector2Valid)
triangleProjector2 = triangleProjectors2.get(index);
TriangleHelper th = triangleHelpers.get(index);
triangleSplitter.setTriangle(th);
check(mesh);
dispatchCases(origin, destination, edge, th.getTriangle());
check(mesh);
if (lastMergeTarget != null) {
assert TriangleHelper.isOnEdge(lastMergeSource, edge.origin(),
edge.destination(), triangleProjector1.sqrTolerance) : origin + " " + triangleProjector1 + "\n" + destination + " " + triangleProjector2;
}
if (mergeAndFinish()) {
return;
}
}
// OUT/OUT case have a lower priority over other cases so we process
// it in a separate loop after others.
if (origin && destination) {
for (int i = 0; i < n; i++) {
int index = triangleProjectorOrder[i];
triangleProjector1 = triangleProjectors1.get(index);
triangleProjector2 = triangleProjectors2.get(index);
triangleSplitter.setTriangle(triangleHelpers.get(index));
handleOutOutCase(edge);
if (mergeAndFinish()) {
return;
}
}
}
}
private static void compteEdgeAABB(AbstractHalfEdge edge, double[] aabb)
{
aabb[0] = Math.min(edge.origin().getX(), edge.destination().getX());
aabb[1] = Math.min(edge.origin().getY(), edge.destination().getY());
aabb[2] = Math.min(edge.origin().getZ(), edge.destination().getZ());
aabb[3] = Math.max(edge.origin().getX(), edge.destination().getX());
aabb[4] = Math.max(edge.origin().getY(), edge.destination().getY());
aabb[5] = Math.max(edge.origin().getZ(), edge.destination().getZ());
}
private static int saveVTKCounter = 0;
static boolean saveVTK = false;
public static void saveAsVTK(Mesh mesh)
{
if(saveVTK)
{
try {
String dir = "/tmp/tmp.amibe";
MeshWriter.writeObject3D(mesh, dir, null);
String name = "/tmp/non-manifold-stitch"+(saveVTKCounter++)+".vtp";
System.err.println("saving to "+name);
new Amibe2VTK(dir).write(name);
} catch (Exception ex) {
Logger.getLogger(NonManifoldStitch.class.getName()).log(Level.SEVERE,
null, ex);
}
}
}
}