/*
* 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 2015, by Airbus France
*/
package org.jcae.mesh.amibe.projection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.jcae.mesh.amibe.algos2d.Initial;
import org.jcae.mesh.amibe.algos3d.HoleCutter;
import org.jcae.mesh.amibe.ds.AbstractHalfEdge;
import org.jcae.mesh.amibe.ds.Mesh;
import org.jcae.mesh.amibe.ds.MeshParameters;
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.patch.Mesh2D;
import org.jcae.mesh.amibe.patch.Vertex2D;
import org.jcae.mesh.amibe.traits.MeshTraitsBuilder;
import org.jcae.mesh.amibe.traits.TriangleTraitsBuilder;
import org.jcae.mesh.amibe.util.HashFactory;
/**
* Cut a 2D polyline in a 3D mesh using a delaunay 2D algorithm
* @author Jerome Robert
*/
abstract public class Delaunay2DProjector {
private final Mesh mesh;
private final EdgeTrianglesLocator edgeTrianglesLocator;
private Collection<Triangle> trianglesToRemove;
private final Collection<Triangle> trianglesToAdd = new ArrayList<Triangle>();
private boolean swapOrientation;
public Delaunay2DProjector(Mesh mesh, TriangleKdTree kdTree) {
this.mesh = mesh;
this.edgeTrianglesLocator = new EdgeTrianglesLocator(kdTree);
}
private static abstract class LoopBuilder {
public List<AbstractHalfEdge> build(Vertex cutterVertex) {
Iterator<AbstractHalfEdge> it = cutterVertex.getNeighbourIteratorAbstractHalfEdge();
while(it.hasNext()) {
AbstractHalfEdge e = it.next();
if(isValid(e) && !e.hasAttributes(AbstractHalfEdge.OUTER))
return build(e);
}
return null;
}
public List<AbstractHalfEdge> build(AbstractHalfEdge e) {
Vertex start = e.origin();
ArrayList<AbstractHalfEdge> loop = new ArrayList<AbstractHalfEdge>();
loop.add(e);
while(e.destination() != start) {
while(!isValid(e.next()))
e = e.next().sym();
e = e.next();
loop.add(e);
}
return loop;
}
abstract protected boolean isValid(AbstractHalfEdge edge);
}
private double loopLength(List<AbstractHalfEdge> loop) {
double l = 0;
for(AbstractHalfEdge e: loop)
l += e.origin().distance3D(e.destination());
return l;
}
/** Return the border of the domain cuttee triangle set to cut */
private List<AbstractHalfEdge> cutteeLoop(Collection<AbstractHalfEdge> cutterEdges, double tolerance) {
assert !cutterEdges.isEmpty();
final Collection<Triangle> toRemove = HashFactory.createSet();
final Collection<AbstractHalfEdge> toRemoveEdges = HashFactory.createSet();
for(AbstractHalfEdge e: cutterEdges) {
edgeTrianglesLocator.locate(e.origin(), e.destination(), -1, tolerance);
toRemove.addAll(edgeTrianglesLocator.getResult());
for(Triangle t: edgeTrianglesLocator.getResult()) {
AbstractHalfEdge ee = t.getAbstractHalfEdge();
for(int i = 0; i < 3; i++) {
toRemoveEdges.add(ee);
ee = ee.next();
}
}
}
LoopBuilder loopBuilder = new LoopBuilder() {
@Override
protected boolean isValid(AbstractHalfEdge edge) {
return !toRemove.contains(edge.sym().getTri());
}
};
List<AbstractHalfEdge> bestLoop = null;
double bestLoopLength = 0;
while(true) {
AbstractHalfEdge ae = null;
// search a border edge of the toRemove set
for(AbstractHalfEdge e: toRemoveEdges) {
if(!toRemove.contains(e.sym().getTri())) {
ae = e;
break;
}
}
if(ae == null) {
break;
} else {
List<AbstractHalfEdge> loop = loopBuilder.build(ae);
toRemoveEdges.removeAll(loop);
// TODO this is not a good criteria. An internal loop may be
// longer than an external loop. We do not want the longest but
// the most external loop.
double l = loopLength(loop);
if(l > bestLoopLength) {
bestLoop = loop;
bestLoopLength = l;
}
}
}
assert bestLoop != null;
return bestLoop;
}
public void cut(Mesh cutter, Vertex cutterVertex, int cutteeGroup, double tolerance) {
List<AbstractHalfEdge> freeEdges = new LoopBuilder(){
@Override
protected boolean isValid(AbstractHalfEdge edge) {
return edge.hasAttributes(AbstractHalfEdge.BOUNDARY);
}
}.build(cutterVertex);
if(freeEdges == null)
throw new IllegalArgumentException(
"The cutter vertices must be on the boundary. "+cutterVertex+" is not.");
List<AbstractHalfEdge> cutteeEdges = cutteeLoop(freeEdges, tolerance);
HoleCutter hc = new HoleCutter() {
@Override
protected boolean isNormalCut(AbstractHalfEdge edge) {
return false;
}
};
int group = cutteeEdges.get(0).getTri().getGroupId();
trianglesToRemove = hc.cut(cutteeEdges, false);
Map<Vertex, Vertex> v2dTov3d = HashFactory.createMap();
Mesh2D m2d = createMesh2D();
int k = 0;
Vertex2D[] border = new Vertex2D[freeEdges.size() + 1 + cutteeEdges.size() + 1];
for(AbstractHalfEdge e: cutteeEdges) {
Vertex2D v2d = (Vertex2D) m2d.createVertex(0, 0);
transformTo2D(e.origin(), v2d);
v2dTov3d.put(v2d, e.origin());
border[k++] = v2d;
}
border[k++] = border[0];
int start2 = k;
Collections.reverse(freeEdges);
for(AbstractHalfEdge e: freeEdges) {
Vertex2D v2d = (Vertex2D) m2d.createVertex(0, 0);
transformTo2D(e.origin(), v2d);
v2dTov3d.put(v2d, e.origin());
border[k++] = v2d;
}
border[k] = border[start2];
new Initial(m2d, m2d.getBuilder(), border, null).compute();
trianglesToAdd.clear();
for(Triangle t:m2d.getTriangles())
{
if(t.hasAttributes(AbstractHalfEdge.OUTER))
continue;
Triangle t3d = mesh.createTriangle(
v2dTov3d.get(swapOrientation ? t.getV2() : t.getV0()),
v2dTov3d.get(t.getV1()),
v2dTov3d.get(swapOrientation ? t.getV0() : t.getV2()));
t3d.setGroupId(group);
trianglesToAdd.add(t3d);
}
}
private Mesh2D createMesh2D() {
TriangleTraitsBuilder ttb = new TriangleTraitsBuilder();
ttb.addVirtualHalfEdge();
MeshTraitsBuilder mtb = new MeshTraitsBuilder();
mtb.addKdTree(2);
mtb.add(ttb);
return new Mesh2D(mtb, new MeshParameters(), null);
}
public Collection<Triangle> getTrianglesToAdd() {
return trianglesToAdd;
}
public Collection<Triangle> getTrianglesToRemove() {
return trianglesToRemove;
}
abstract protected void transformTo2D(Location location, Vertex2D v2d);
public void setSwapOrientation(boolean swapOrientation) {
this.swapOrientation = swapOrientation;
}
}