/* jCAE stand for Java Computer Aided Engineering. Features are : Small CAD
modeler, Finite element mesher, Plugin architecture.
Copyright (C) 2004,2005,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.ds;
import gnu.trove.list.TDoubleList;
import gnu.trove.list.TIntList;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.set.hash.TIntHashSet;
import gnu.trove.map.hash.TIntIntHashMap;
import gnu.trove.map.hash.TObjectIntHashMap;
import org.jcae.mesh.amibe.traits.Traits;
import org.jcae.mesh.amibe.traits.MeshTraitsBuilder;
import org.jcae.mesh.amibe.traits.TriangleTraitsBuilder;
import org.jcae.mesh.amibe.metrics.KdTree;
import org.jcae.mesh.amibe.metrics.Matrix3D;
import org.jcae.mesh.amibe.metrics.Metric;
import org.jcae.mesh.amibe.metrics.EuclidianMetric3D;
import org.jcae.mesh.amibe.metrics.Location;
import org.jcae.mesh.amibe.metrics.PoolWorkVectors;
import java.util.Collection;
import java.util.ArrayList;
import java.util.List;
import java.util.LinkedHashSet;
import java.util.LinkedHashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Iterator;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jcae.mesh.amibe.util.HashFactory;
/**
* Mesh data structure.
* A mesh is composed of triangles, edges and vertices. There are
* many data structures to represent meshes, and we focused on the
* following constraints:
* <ul>
* <li>Memory usage must be minimal in order to perform very large
* meshes.</li>
* <li>Mesh traversal must be cheap.</li>
* </ul>
* We decided to implement a triangle-based data structure which is known
* to be more memory efficient. A {@link Triangle} is composed of three
* {@link Vertex}, three links to adjacent triangles, and each vertex
* contains a backward link to one of its incident triangles. It is
* then possible to loop within triangles or around vertices.
*
* But there is also a need for lighter data structures. For instance,
* visualization does not need adjacency relations, we only need to
* store triangle vertices.
*
* {@link Mesh} constructor takes an optional {@link MeshTraitsBuilder}
* argument to fully describe the desired mesh data structure. Once a
* {@link Mesh} instance is created, its features cannot be modified.
* With this argument, it is possible to specify if adjacent relations
* between triangles have to be computed, if an octree is needed to
* locate vertices, if triangles and/or nodes are stored in a list
* or a set, etc. Example:
* <pre>
MeshTraitsBuilder mtb = new MeshTraitsBuilder();
// Store triangles into a set
mtb.addTriangleSet();
TriangleTraitsBuilder ttb = new TriangleTraitsBuilder();
// Store adjacency relations with HalfEdge
ttb.addHalfEdge();
mtb.add(ttb);
// Create a new instance with these features
Mesh mesh = new Mesh(mtb);
// Then each triangle created by mesh.createTriangle
// will contain objects needed to store adjacency relations.
Triangle t = (Triangle) mesh.createTriangle(...);
// Vertices must be created by mesh.createVertex
Vertex v = (Vertex) mesh.createVertex(...);
* </pre>
*
*/
public class Mesh implements Serializable
{
private static final long serialVersionUID = 7130909528217390687L;
private static final Logger logger=Logger.getLogger(Mesh.class.getName());
/**
* User-defined traits builder.
*/
protected final MeshTraitsBuilder traitsBuilder;
/**
* User-defined traits
*/
protected final Traits traits;
/**
* User-defined mesh parameters.
*/
protected final MeshParameters meshParameters;
/**
* Vertex at infinite.
*/
public Vertex outerVertex = new OuterVertex();
// Triangle list
private final Collection<Triangle> triangleList;
// Node list.
private final Collection<Vertex> nodeList;
// Entity factory
protected ElementFactoryInterface factory = null;
// Set to true by Mesh2D, this subclass connects outer triangles
protected boolean outerTrianglesAreConnected = false;
// Set to true if references must be written onto disk
private boolean persistentReferences = false;
private int maxLabel = 0;
// 3D euclidian metric
private final Metric euclidian_metric3d = new EuclidianMetric3D();
// Temporary vectors used as work arrays in HalfEdge
protected final PoolWorkVectors temp = new PoolWorkVectors();
// Complex algorithms require several VirtualHalfEdge, they are
// allocated here to prevent allocation/deallocation overhead.
protected final VirtualHalfEdge[] tempVH = new VirtualHalfEdge[4];
protected final HalfEdge[] tempHE = new HalfEdge[6];
private List<Vertex> beams = new ArrayList<Vertex>();
private TIntArrayList beamGroups = new TIntArrayList();
private Map<Integer, String> groupNames = new HashMap<Integer, String>();
private Map<String, Collection<Vertex>> vertexGroups =
new HashMap<String, Collection<Vertex>>();
// Utility class to improve debugging output
private static class OuterVertex extends Vertex
{
private static final long serialVersionUID = -6535592611308767946L;
public OuterVertex()
{
super(null);
setReadable(false);
setWritable(false);
setMutable(false);
}
@Override
public final String toString()
{
return "outer";
}
}
/**
* Creates an empty mesh. When no <code>MeshTraitsBuilder</code>
* is passed, {@link MeshTraitsBuilder#getDefault3D} is called
* implicitly.
*/
public Mesh()
{
this(MeshTraitsBuilder.getDefault3D(), new MeshParameters());
}
public Mesh(MeshTraitsBuilder builder)
{
this(builder, new MeshParameters());
}
/**
* Creates an empty mesh with specific features.
*
* @param builder mesh traits builder
*/
public Mesh(MeshTraitsBuilder builder, MeshParameters mp)
{
traitsBuilder = builder;
if (builder != null)
traits = builder.createTraits();
else
traits = null;
factory = new ElementFactory(traitsBuilder);
triangleList = traitsBuilder.getTriangles(traits);
nodeList = traitsBuilder.getNodes(traits);
meshParameters = mp;
for (int i = 0; i < tempVH.length; i++)
tempVH[i] = new VirtualHalfEdge();
for (int i = 0; i < tempHE.length; i++)
tempHE[i] = new HalfEdge(traitsBuilder.getHalfEdgeTraitsBuilder(), (TriangleHE) null, (byte) 0, (byte) 0);
}
public MeshTraitsBuilder getBuilder()
{
return traitsBuilder;
}
public final MeshParameters getMeshParameters()
{
return meshParameters;
}
/**
* Adds an existing triangle to triangle list.
*
* @param t triangle being added.
*/
public final void add(Triangle t)
{
assert t!= null;
triangleList.add(t);
}
private int getFreeGroupID()
{
int i = 1;
while(groupNames.get(i) != null)
i++;
return i;
}
public int pushGroup(double[] coordinates, int[] triangles, int[] beams)
{
int groupId = getFreeGroupID();
setGroupName(groupId, Integer.toString(groupId));
return pushGroup(coordinates, triangles, beams, groupId);
}
public int pushGroup(double[] coordinates, int[] triangles, int[] beams, int groupId)
{
Vertex[] vertices = new Vertex[coordinates.length / 3];
int k = 0;
for(int i = 0; i < coordinates.length / 3; i++)
{
vertices[i] = createVertex(
coordinates[k], coordinates[k+1], coordinates[k+2]);
k += 3;
}
if(hasNodes())
{
for(Vertex v:vertices)
add(v);
}
k = 0;
if(triangles != null)
{
for(int i = 0; i < triangles.length / 3; i++)
{
Triangle t = createTriangle(vertices[triangles[k]],
vertices[triangles[k + 1]], vertices[triangles[k + 2]]);
add(t);
t.setGroupId(groupId);
k += 3;
}
}
k = 0;
if(beams != null)
{
for(int i = 0; i < beams.length / 2; i++)
{
addBeam(vertices[beams[k]], vertices[beams[k+1]], groupId);
k += 2;
}
}
return groupId;
}
public void popGroup(TDoubleList coordinates, TIntList triangles, TIntList beams, int group)
{
TObjectIntHashMap<Vertex> map = new TObjectIntHashMap<Vertex>(
100, 0.5f, Integer.MIN_VALUE);
int nextVertexId = 0;
if(triangles != null)
{
Iterator<Triangle> it = getTriangles().iterator();
while(it.hasNext())
{
Triangle t = it.next();
if(t.getGroupId() == group)
{
for(int i = 0; i < 3; i++)
{
Vertex v = t.getV(i);
int vid = map.putIfAbsent(v, nextVertexId);
if(vid == map.getNoEntryValue())
{
vid = nextVertexId;
nextVertexId++;
coordinates.add(v.getX());
coordinates.add(v.getY());
coordinates.add(v.getZ());
}
triangles.add(vid);
}
it.remove();
}
}
}
int nb = getBeams().size() / 2;
if(nb > 0 && beams != null)
{
ArrayList<Vertex> newBeams = new ArrayList<Vertex>(this.beams.size());
TIntArrayList newBeamGroups = new TIntArrayList(beamGroups);
for(int k = 0; k < nb; k++)
{
int g = getBeamGroup(k);
if(g == group)
{
for(int i = 0; i < 2; i++)
{
Vertex v = this.beams.get(2 * k + i);
int vid = map.putIfAbsent(v, nextVertexId);
if(vid == map.getNoEntryValue())
{
vid = nextVertexId;
nextVertexId++;
coordinates.add(v.getX());
coordinates.add(v.getY());
coordinates.add(v.getZ());
}
beams.add(vid);
}
}
else
{
newBeamGroups.add(g);
newBeams.add(this.beams.get(2 * k));
newBeams.add(this.beams.get(2 * k + 1));
}
}
this.beamGroups = newBeamGroups;
this.beams = newBeams;
}
}
/**
* Removes a triangle from triangle list.
*
* @param t triangle being removed.
*/
public final void remove(Triangle t)
{
triangleList.remove(t);
if (!(t instanceof TriangleHE))
return;
TriangleHE that = (TriangleHE) t;
// Remove links to help the garbage collector
HalfEdge e = that.getAbstractHalfEdge();
HalfEdge last = e;
for (int i = 0; i < 3; i++)
{
e = e.next();
e.glue(null);
last.setNext(null);
last = e;
}
}
/**
* Returns triangle list.
*
* @return triangle list.
*/
public final Collection<Triangle> getTriangles()
{
return triangleList;
}
/**
* Resizes internal collections of vertices and triangles.
*
* @param triangles desired number of triangles
*/
public final void ensureCapacity(int triangles)
{
traitsBuilder.ensureCapacity(triangles, traits);
}
/**
* Adds a vertex to vertex list.
*/
public final void add(Vertex vertex)
{
nodeList.add(vertex);
}
/**
* Removes a vertex from vertex list.
*
* @param v vertex being removed.
*/
public final void remove(Vertex v)
{
nodeList.remove(v);
}
/**
* Returns vertex list.
*
* @return vertex list.
*/
public Collection<Vertex> getNodes()
{
return nodeList;
}
/**
* Tells whether nodes are stored.
*
* @return <code>true</code> if mesh was created with a <code>MeshTraitsBuilder</code>
* instance defining nodes, and <code>false</code> otherwise.
*/
public final boolean hasNodes()
{
return traitsBuilder.hasNodes();
}
/**
* Returns the Trace instance associated with this mesh.
*
* @return the Trace instance associated with this mesh.
*/
public final TraceInterface getTrace()
{
return traitsBuilder.getTrace(traits);
}
/**
* Returns the Kd-tree associated with this mesh.
*
* @return the Kd-tree associated with this mesh.
*/
public final KdTree<Vertex> getKdTree()
{
return traitsBuilder.getKdTree(traits);
}
/**
* Initializes Kd-tree with a given bounding box. This method must be called before
* putting any vertex into this Kd-tree.
*
* @param bbmin coordinates of bottom-left vertex
* @param bbmax coordinates of top-right vertex
*/
public final void resetKdTree(double [] bbmin, double [] bbmax)
{
double [] bbox = new double[2*bbmin.length];
for (int i = 0; i < bbmin.length; i++)
{
bbox[i] = bbmin[i];
bbox[i+bbmin.length] = bbmax[i];
}
KdTree kdtree = traitsBuilder.getKdTree(traits);
kdtree.setup(bbox);
}
/**
* Creates a triangle composed of three vertices.
*
* @param v array of three vertices
* @return a new {@link Triangle} instance composed of three vertices
*/
public final Triangle createTriangle(Vertex [] v)
{
assert v.length == 3;
return factory.createTriangle(v[0], v[1], v[2]);
}
/**
* Creates a triangle composed of four vertices, to emulate a tetrahedron.
*
* @param v array of four vertices
* @return a new {@link Triangle} instance composed of four vertices
*/
public final Triangle createTetrahedron(Vertex [] v)
{
assert v.length == 4;
return new Tetrahedron(v);
}
/**
* Creates a triangle composed of three vertices.
*
* @param v0 first vertex
* @param v1 second vertex
* @param v2 third vertex
* @return a new {@link Triangle} instance composed of three vertices
*/
public final Triangle createTriangle(Vertex v0, Vertex v1, Vertex v2)
{
return factory.createTriangle(v0, v1, v2);
}
/**
* Clones a triangle.
*
* @param that triangle to clone
* @return a new {@link Triangle} instance
*/
public final Triangle createTriangle(Triangle that)
{
return factory.createTriangle(that);
}
/**
* Creates a 2D or 3D vertex.
*
* @param p coordinates
* @return a new {@link Vertex} instance with this location.
*/
public final Vertex createVertex(Location p)
{
return factory.createVertex(p);
}
/**
* Creates a 2D vertex.
*
* @param u first coordinate
* @param v second coordinate
* @return a new {@link Vertex} instance with this location.
*/
public final Vertex createVertex(double u, double v)
{
return factory.createVertex(u, v);
}
/**
* Creates a 3D vertex.
*
* @param x first coordinate
* @param y second coordinate
* @param z third coordinate
* @return a new {@link Vertex} instance with this location.
*/
public final Vertex createVertex(double x, double y, double z)
{
return factory.createVertex(x, y, z);
}
public void addBeam(Vertex v1, Vertex v2, int group)
{
addBeam(v1, v2, group, true);
}
public void addBeam(Vertex v1, Vertex v2, int group, boolean addVertices)
{
beams.add(v1);
beams.add(v2);
v1.setMutable(false);
v2.setMutable(false);
if (addVertices && hasNodes())
{
nodeList.add(v1);
nodeList.add(v2);
}
beamGroups.add(group);
}
public List<Vertex> getBeams()
{
return Collections.unmodifiableList(beams);
}
public void setBeam(int i, Vertex v)
{
beams.set(i, v);
}
public int getBeamGroup(int i)
{
return beamGroups.get(i);
}
public void setBeamGroup(int i, int gid)
{
beamGroups.set(i, gid);
}
public void resetBeams()
{
beams.clear();
beamGroups.clear();
}
public void setGroupName(int id, String name)
{
if(name == null)
groupNames.remove(id);
else
groupNames.put(id, name);
}
public String getGroupName(int id)
{
return groupNames.get(id);
}
public int[] getGroupIDs(String ... names)
{
Map<String, Integer> invertMap = new HashMap<String, Integer>();
for(Entry<Integer, String> e:groupNames.entrySet())
invertMap.put(e.getValue(), e.getKey());
int[] groupIds = new int[names.length];
int i = 0;
for(String name:names)
groupIds[i++] = invertMap.get(name);
return groupIds;
}
public void setVertexGroup(Vertex v, String group)
{
Collection<Vertex> l = vertexGroups.get(group);
if(l == null)
{
l = new ArrayList<Vertex>();
vertexGroups.put(group, l);
}
l.add(v);
}
public Map<String, Collection<Vertex>> getVertexGroup()
{
return vertexGroups;
}
public int getNumberOfGroups()
{
return groupNames.size();
}
public boolean hasPersistentReferences()
{
return persistentReferences;
}
public void setPersistentReferences(boolean persistentReferences)
{
this.persistentReferences = persistentReferences;
}
/**
* Tells whether mesh contains adjacency relations.
* @return <code>true</code> if mesh contains adjacency relations,
* <code>false</code> otherwise.
*/
public final boolean hasAdjacency()
{
return factory.hasAdjacency();
}
/**
* Build adjacency relations between triangles.
*/
public final void buildAdjacency()
{
buildAdjacency(0);
}
public final void buildAdjacency(int currentMaxLabel)
{
// Connect all edges together
logger.fine("Connect triangles");
ArrayList<Triangle> newTri = new ArrayList<Triangle>();
// For each vertex, build the list of triangles
// connected to this vertex.
Map<Vertex, Collection<Triangle>> tVertList = getMapVertexLinks();
// Connect all edges together
glueSymmetricHalfEdges(tVertList, newTri);
// Mark boundary edges and bind them to virtual triangles.
logger.fine("Connect boundary triangles");
connectBoundaryTriangles(newTri);
// Fix links for junctions
logger.fine("Fix vertex links");
rebuildVertexLinks(tVertList);
// Find the list of vertices which are on mesh boundary
logger.fine("Build the list of nodes on boundaries and non-manifold edges");
LinkedHashSet<Vertex> bndNodes = new LinkedHashSet<Vertex>();
maxLabel = currentMaxLabel;
AbstractHalfEdge ot = null;
for (Triangle t: triangleList)
{
ot = t.getAbstractHalfEdge(ot);
for (int i = 0; i < 3; i++)
{
ot = ot.next();
if (ot.hasAttributes(AbstractHalfEdge.BOUNDARY | AbstractHalfEdge.NONMANIFOLD))
{
bndNodes.add(ot.origin());
maxLabel = Math.max(maxLabel, ot.origin().getRef());
bndNodes.add(ot.destination());
maxLabel = Math.max(maxLabel, ot.destination().getRef());
}
}
}
logger.fine("Found "+bndNodes.size()+" nodes on boundaries and nonmanifold edges");
// Set reference on boundary nodes if missing
for (Vertex v : bndNodes)
{
if (0 == v.getRef())
setRefVertexOnBoundary(v);
}
int nrJunctionPoints = 0;
Collection<Vertex> freeVertices = new HashSet<Vertex>();
for (Vertex v: tVertList.keySet())
{
if (bndNodes.contains(v))
continue;
if (v.getLink() instanceof Triangle[])
{
nrJunctionPoints++;
if (v.getRef() == 0)
{
maxLabel++;
v.setRef(maxLabel);
}
}
else if(v.getLink() == null)
freeVertices.add(v);
}
freeVertices.removeAll(beams);
if (nrJunctionPoints > 0)
logger.info("Found "+nrJunctionPoints+" junction points");
if (!freeVertices.isEmpty() && hasNodes())
{
logger.log(Level.INFO, "Removed "+freeVertices.size()+" free points");
nodeList.removeAll(freeVertices);
freeVertices = null;
}
if (maxLabel != currentMaxLabel)
logger.fine("Created "+(maxLabel - currentMaxLabel)+" more references");
// Remove all references to help the garbage collector.
for (Collection<Triangle> list : tVertList.values())
list.clear();
// Add outer triangles
triangleList.addAll(newTri);
if (traitsBuilder.hasTrace())
{
traitsBuilder.getTrace(traits).println("self.m.buildAdjacency()");
traitsBuilder.getTrace(traits).addAdjacentTriangles(this);
traitsBuilder.getTrace(traits).println("# End: self.m.buildAdjacency()");
}
}
private Map<Vertex, Collection<Triangle>> getMapVertexLinks()
{
Collection<Vertex> vertices;
if (nodeList == null)
{
vertices = new LinkedHashSet<Vertex>(triangleList.size()/2);
for (Triangle t: triangleList)
{
if (!t.isWritable())
continue;
t.addVertexTo(vertices);
}
}
else
{
vertices = nodeList;
}
Map<Vertex, Collection<Triangle>> tVertList = new LinkedHashMap<Vertex, Collection<Triangle>>(vertices.size());
for (Vertex v: vertices)
tVertList.put(v, new ArrayList<Triangle>(10));
for (Triangle t: triangleList)
{
if (t.hasAttributes(AbstractHalfEdge.OUTER))
continue;
for (int i = 0; i < 3; i++)
{
Vertex v = t.getV(i);
if (v.isReadable())
{
Collection<Triangle> list = tVertList.get(v);
list.add(t);
}
v.setLink(t);
}
}
return tVertList;
}
private void rebuildVertexLinks()
{
rebuildVertexLinks(getMapVertexLinks());
}
public static void rebuildVertexLinks(Map<Vertex, Collection<Triangle>> tVertList)
{
for (Map.Entry<Vertex, Collection<Triangle>> entry : tVertList.entrySet())
{
Vertex v = entry.getKey();
Collection<Triangle> list = entry.getValue();
int cnt = 0;
AbstractHalfEdge ot = null;
if (null == v.getLink())
continue;
assert v.getLink() instanceof Triangle: v;
ot = v.getIncidentAbstractHalfEdge((Triangle) v.getLink(), ot);
Vertex d = ot.destination();
do
{
if (!ot.hasAttributes(AbstractHalfEdge.OUTER))
cnt++;
if (ot.hasAttributes(AbstractHalfEdge.NONMANIFOLD))
{
cnt = 0;
break;
}
ot = ot.nextOriginLoop();
}
while (ot.destination() != d);
if (cnt == list.size())
continue;
// Non-manifold vertex
LinkedHashSet<Triangle> neighbours = new LinkedHashSet<Triangle>(list);
ArrayList<Triangle> fans = new ArrayList<Triangle>();
while (!neighbours.isEmpty())
{
ot = v.getIncidentAbstractHalfEdge(neighbours.iterator().next(), ot);
d = ot.destination();
fans.add(ot.getTri());
do
{
if (!ot.hasAttributes(AbstractHalfEdge.OUTER))
neighbours.remove(ot.getTri());
ot = ot.nextOriginLoop();
}
while (ot.destination() != d);
}
Triangle[] links = new Triangle[fans.size()];
fans.toArray(links);
v.setLink(links);
logger.fine("Non-manifold vertex has "+fans.size()+" fans");
}
}
private void connectBoundaryTriangles(ArrayList<Triangle> newTri)
{
AbstractHalfEdge ot = null;
AbstractHalfEdge sym = null;
for (Triangle t: triangleList)
{
ot = t.getAbstractHalfEdge(ot);
for (int i = 0; i < 3; i++)
{
ot = ot.next();
if (!ot.hasSymmetricEdge())
{
ot.setAttributes(AbstractHalfEdge.BOUNDARY);
Triangle adj = factory.createTriangle(outerVertex, ot.destination(), ot.origin());
newTri.add(adj);
adj.setAttributes(AbstractHalfEdge.OUTER);
adj.setReadable(false);
adj.setWritable(false);
sym = adj.getAbstractHalfEdge(sym);
sym.setAttributes(AbstractHalfEdge.BOUNDARY);
ot.glue(sym);
}
else if (ot.hasAttributes(AbstractHalfEdge.OUTER))
{
if (sym == null)
sym = t.getAbstractHalfEdge(sym);
sym = ot.sym(sym);
if (!sym.hasAttributes(AbstractHalfEdge.OUTER))
{
ot.setAttributes(AbstractHalfEdge.BOUNDARY);
sym.setAttributes(AbstractHalfEdge.BOUNDARY);
}
}
}
}
}
public void glueSymmetricHalfEdges(Map<Vertex,
Collection<Triangle>> tVertList, Collection<Triangle> newTri)
{
Triangle.List markedTri = new Triangle.List();
AbstractHalfEdge ot = null;
AbstractHalfEdge ot2 = null;
AbstractHalfEdge [] work = new AbstractHalfEdge[3];
for (Map.Entry<Vertex, Collection<Triangle>> e: tVertList.entrySet())
{
// Mark all triangles having v as vertex
Vertex v = e.getKey();
Collection<Triangle> neighTriList = e.getValue();
if (neighTriList == null)
continue;
for (Triangle t: neighTriList)
markedTri.add(t);
// Loop on all edges incident to v
for (Triangle t: neighTriList)
{
ot = v.getIncidentAbstractHalfEdge(t, ot);
// Skip this edge if adjacency relations already exist,
if (ot.hasSymmetricEdge())
continue;
Vertex v2 = ot.destination();
Collection<Triangle> neighTriList2 = tVertList.get(v2);
if (neighTriList2 == null)
continue;
// Edge (v,v2) has not yet been processed.
// List of triangles incident to v2.
boolean manifold = true;
// Ensure that work[0] and work[1] are non null to avoid
// tests in glueNonManifoldHalfEdges
if (work[0] == null)
work[0] = t.getAbstractHalfEdge(work[0]);
if (work[1] == null)
work[1] = t.getAbstractHalfEdge(work[1]);
for (Triangle t2: neighTriList2)
{
if (t == t2 || !markedTri.contains(t2))
continue;
// t2 contains v and v2, we now look for an edge
// (v,v2) or (v2,v)
ot2 = v2.getIncidentAbstractHalfEdge(t2, ot2);
if (manifold && ot2.destination() == v && !ot.hasSymmetricEdge() && !ot2.hasSymmetricEdge())
{
// This edge seems to be manifold.
// It may become non manifold later when
// other neighbours are processed.
ot.glue(ot2);
continue;
}
manifold = false;
if (ot2.destination() != v)
ot2 = ot2.prev();
// We are sure now that ot2 == (v,v2) or (v2,v)
glueNonManifoldHalfEdges(v, v2, ot, ot2, work, newTri);
}
if (logger.isLoggable(Level.FINE) && !manifold)
{
int cnt = 0;
for (Iterator<AbstractHalfEdge> it = ot.fanIterator(); it.hasNext(); it.next())
cnt++;
logger.fine("Non-manifold edge: "+v+" "+v2+" "+" connected to "+cnt+" fans");
}
}
// Unmark adjacent triangles
markedTri.clear();
}
}
public void glueNonManifoldHalfEdges(Vertex v, Vertex v2, AbstractHalfEdge ot,
AbstractHalfEdge ot2, AbstractHalfEdge [] work, Collection<Triangle> newTri)
{
assert v == ot.origin() && v2 == ot.destination();
assert (v == ot2.origin() && v2 == ot2.destination()) || (v2 == ot2.origin() && v == ot2.destination());
if (!ot.hasSymmetricEdge())
{
// ot has no symmetric edge yet; this happens only if this edge has
// not been processed yet and ot and ot2 have incompatible orientations,
// i.e. ot2 = (v, v2)
assert v == ot2.origin() && v2 == ot2.destination(): "Wrong configuration around "+v+" and "+v2;
// Link ot to a virtual triangle.
work[0] = bindToVirtualTriangle(ot, work[0]);
newTri.add(work[0].getTri());
// Create an empty cycle
work[1] = work[0].prev(work[1]);
work[0].next().glue(work[1]);
}
else if (!ot.hasAttributes(AbstractHalfEdge.NONMANIFOLD))
{
// ot was already linked to another edge, but it is in fact a
// non-manifold edge. Previous adjacency relation is removed
// and both triangles are linked to virtual triangles.
work[0] = ot.sym(work[0]);
bindSymEdgesToVirtualTriangles(ot, work[0], work[1], work[2], newTri);
}
assert !ot2.hasSymmetricEdge(): ot2+"\n"+ot2.sym();
// Link ot2 to a virtual triangle
work[0] = bindToVirtualTriangle(ot2, work[0]);
newTri.add(work[0].getTri());
// Add ot2 to existing cycle
work[0] = ot.sym(work[0]);
work[0] = work[0].next();
work[1] = work[0].sym(work[1]);
ot2 = ot2.sym();
ot2 = ot2.prev();
ot2.glue(work[0]);
ot2 = ot2.prev();
ot2.glue(work[1]);
}
private AbstractHalfEdge bindToVirtualTriangle(AbstractHalfEdge ot, AbstractHalfEdge sym)
{
Triangle t = factory.createTriangle(outerVertex, ot.destination(), ot.origin());
t.setAttributes(AbstractHalfEdge.OUTER);
t.setReadable(false);
t.setWritable(false);
sym = t.getAbstractHalfEdge(sym);
ot.glue(sym);
ot.setAttributes(AbstractHalfEdge.NONMANIFOLD);
sym.setAttributes(AbstractHalfEdge.NONMANIFOLD);
return sym;
}
private void bindSymEdgesToVirtualTriangles(AbstractHalfEdge ot, AbstractHalfEdge sym,
AbstractHalfEdge temp0, AbstractHalfEdge temp1, Collection<Triangle> newTriangles)
{
// Link ot to a virtual triangle
temp0 = bindToVirtualTriangle(ot, temp0);
newTriangles.add(temp0.getTri());
// Link sym to another virtual triangle
temp1 = bindToVirtualTriangle(sym, temp1);
newTriangles.add(temp1.getTri());
// Create a cycle
temp1 = temp1.next();
temp0 = temp0.prev();
temp0.glue(temp1);
temp1 = temp1.next();
temp0 = temp0.prev();
temp0.glue(temp1);
}
/**
* Add {@link AbstractHalfEdge#SHARP} attribute to sharp edges.
*
* @param coplanarity when dot product of normals adjacent to an edge is
* lower than this value, it is considered as a ridge and its endpoints
* are treated as if they belong to a CAD edge.
*/
public final int buildRidges(double coplanarity)
{
int toReturn = 0;
if (coplanarity < -1.0 || triangleList.isEmpty())
return toReturn;
double [][] temp = new double[4][3];
AbstractHalfEdge ot = null;
AbstractHalfEdge sym = triangleList.iterator().next().getAbstractHalfEdge();
ArrayList<Triangle> newTriangles = new ArrayList<Triangle>();
AbstractHalfEdge temp0 = null;
AbstractHalfEdge temp1 = null;
for (Triangle t: triangleList)
{
ot = t.getAbstractHalfEdge(ot);
if (t.hasAttributes(AbstractHalfEdge.OUTER))
continue;
for (int i = 0; i < 3; i++)
{
ot = ot.next();
if (ot.hasAttributes(AbstractHalfEdge.BOUNDARY | AbstractHalfEdge.NONMANIFOLD | AbstractHalfEdge.SHARP))
continue;
sym = ot.sym(sym);
Matrix3D.computeNormal3D(ot.origin(), ot.destination(), ot.apex(), temp[0], temp[1], temp[2]);
Matrix3D.computeNormal3D(ot.destination(), ot.origin(), sym.apex(), temp[0], temp[1], temp[3]);
if (Matrix3D.prodSca(temp[2], temp[3]) <= coplanarity)
{
ot.setAttributes(AbstractHalfEdge.SHARP);
sym.setAttributes(AbstractHalfEdge.SHARP);
if (ot.origin().getRef() == 0)
setRefVertexOnInnerBoundary(ot.origin());
if (ot.destination().getRef() == 0)
setRefVertexOnInnerBoundary(ot.destination());
bindSymEdgesToVirtualTriangles(ot, sym, temp0, temp1, newTriangles);
}
}
}
toReturn = newTriangles.size() / 2;
triangleList.addAll(newTriangles);
rebuildVertexLinks();
if (toReturn > 0 && logger.isLoggable(Level.CONFIG))
logger.log(Level.CONFIG, "Add virtual boundaries for "+toReturn+" sharp edges");
if (traitsBuilder.hasTrace())
traitsBuilder.getTrace(traits).println("self.m.buildRidges("+coplanarity+")");
return toReturn;
}
public void createRidgesGroup(String groupName)
{
int ridgeGroup = 0;
if (!beamGroups.isEmpty())
ridgeGroup = beamGroups.max() + 1;
boolean found = false;
for (Map.Entry<Integer, String> entry : groupNames.entrySet())
{
if (entry.getValue().compareTo(groupName) == 0)
{
ridgeGroup = entry.getKey();
found = true;
break;
}
}
if (found)
{
// FIXME: We cannot remove previous ridges from beams and
// beamGroups, so clear them.
beams.clear();
beamGroups.clear();
}
else
{
groupNames.put(ridgeGroup, groupName);
}
AbstractHalfEdge ot = null;
for (Triangle t: triangleList)
{
ot = t.getAbstractHalfEdge(ot);
if (t.hasAttributes(AbstractHalfEdge.OUTER))
continue;
for (int i = 0; i < 3; i++)
{
ot = ot.next();
if (ot.hasAttributes(AbstractHalfEdge.SHARP))
{
beams.add(ot.origin());
beams.add(ot.destination());
beamGroups.add(ridgeGroup);
}
}
}
}
/**
* Build group boundaries.
*/
public final int buildGroupBoundaries()
{
return buildGroupBoundaries(null);
}
public final int buildGroupBoundaries(int[] groups)
{
if (triangleList.isEmpty())
return 0;
TIntHashSet groupSet = new TIntHashSet();
if (null != groups)
groupSet.addAll(groups);
ArrayList<Triangle> newTriangles = new ArrayList<Triangle>();
AbstractHalfEdge ot = null;
AbstractHalfEdge sym = triangleList.iterator().next().getAbstractHalfEdge();
AbstractHalfEdge temp0 = null;
AbstractHalfEdge temp1 = null;
for (Triangle t: triangleList)
{
ot = t.getAbstractHalfEdge(ot);
if (t.hasAttributes(AbstractHalfEdge.OUTER))
continue;
int groupId = t.getGroupId();
if (!groupSet.isEmpty() && !groupSet.contains(groupId))
continue;
for (int i = 0; i < 3; i++)
{
ot = ot.next();
if (ot.hasAttributes(AbstractHalfEdge.BOUNDARY | AbstractHalfEdge.NONMANIFOLD))
continue;
sym = ot.sym(sym);
int symGroupId = sym.getTri().getGroupId();
if (groupId != symGroupId && (groupSet.isEmpty() || groupSet.contains(groupId)))
{
bindSymEdgesToVirtualTriangles(ot, sym, temp0, temp1, newTriangles);
if (0 == ot.origin().getRef())
setRefVertexOnBoundary(ot.origin());
if (0 == ot.destination().getRef())
setRefVertexOnBoundary(ot.destination());
}
}
}
int toReturn = newTriangles.size() / 2;
triangleList.addAll(newTriangles);
if (toReturn > 0 && logger.isLoggable(Level.CONFIG))
logger.log(Level.CONFIG, "Add virtual boundaries for "+toReturn+" edges");
rebuildVertexLinks();
if (traitsBuilder.hasTrace())
{
traitsBuilder.getTrace(traits).println("groups = []");
if (null != groups)
{
for (int i : groups)
traitsBuilder.getTrace(traits).println("groups.append("+i+")");
}
traitsBuilder.getTrace(traits).println("self.m.buildGroupBoundaries(groups)");
traitsBuilder.getTrace(traits).addAdjacentTriangles(this);
traitsBuilder.getTrace(traits).println("# End: self.m.buildGroupBoundaries()");
}
return toReturn;
}
// This routine can be called when inverting triangles to have consistent normals
final int scratchVirtualBoundaries()
{
if (triangleList.isEmpty())
return 0;
ArrayList<Triangle> removedTriangles = new ArrayList<Triangle>();
AbstractHalfEdge ot = null;
AbstractHalfEdge sym = triangleList.iterator().next().getAbstractHalfEdge();
AbstractHalfEdge temp0 = null;
AbstractHalfEdge temp1 = null;
for (Triangle t: triangleList)
{
ot = t.getAbstractHalfEdge(ot);
if (t.hasAttributes(AbstractHalfEdge.OUTER))
continue;
for (int i = 0; i < 3; i++)
{
ot = ot.next();
if (!ot.hasAttributes(AbstractHalfEdge.NONMANIFOLD))
continue;
Iterator<AbstractHalfEdge> it = ot.fanIterator();
if (!it.hasNext())
continue;
temp0 = it.next();
if (!it.hasNext())
continue;
temp1 = it.next();
// If there are more than 2 fans, this edge is really
// non-manifold
if (it.hasNext())
continue;
if (temp0.origin() != temp1.destination() || temp0.destination() != temp1.origin())
continue;
// Remove inner boundary
sym = temp0.sym(sym);
removedTriangles.add(sym.getTri());
sym = temp1.sym(sym);
removedTriangles.add(sym.getTri());
temp0.glue(temp1);
ot.clearAttributes(AbstractHalfEdge.NONMANIFOLD);
sym.clearAttributes(AbstractHalfEdge.NONMANIFOLD);
}
}
if (removedTriangles.isEmpty())
return 0;
int toReturn = removedTriangles.size() / 2;
if (triangleList instanceof Set)
triangleList.removeAll(removedTriangles);
else
{
// removeAll may be very slow on large lists
ArrayList<Triangle> savedList = new ArrayList<Triangle>(triangleList);
Set<Triangle> removedSet = HashFactory.createSet(removedTriangles);
triangleList.clear();
for (Triangle t : savedList)
{
if (!removedSet.contains(t))
triangleList.add(t);
}
}
if (logger.isLoggable(Level.CONFIG))
logger.log(Level.CONFIG, "Remove virtual boundaries for "+toReturn+" edges");
// Rebuild list of vertex links
rebuildVertexLinks();
// Make vertex manifold
for (Triangle t : removedTriangles)
{
ot = t.getAbstractHalfEdge(ot);
Vertex o = ot.origin();
if (o.isManifold())
continue;
Triangle [] list = (Triangle[]) o.getLink();
if (list.length == 1)
{
// Check that there is no non-manifold
// incident edge
Vertex d = ot.destination();
boolean manifold = true;
do
{
if (ot.hasAttributes(AbstractHalfEdge.NONMANIFOLD))
{
manifold = false;
break;
}
ot = ot.nextOriginLoop();
}
while (ot.destination() != d);
if (manifold)
o.setLink(list[0]);
}
}
return toReturn;
}
/**
* }
* Sets an unused boundary reference on a vertex.
*/
public final void setRefVertexOnBoundary(Vertex v)
{
maxLabel++;
v.setRef(maxLabel);
}
/**
* }
* Sets an unused boundary reference on a vertex.
*/
public final void setRefVertexOnInnerBoundary(Vertex v)
{
maxLabel++;
v.setRef(-maxLabel);
}
/** Tag Vertices and HalfEdges which are in a give list of groups */
public final void tagGroups(Collection<String> names, int attr)
{
boolean fixVertex = (attr & AbstractHalfEdge.IMMUTABLE) != 0;
if(fixVertex)
{
//node groups
for(String gn:names)
{
Collection<Vertex> l = vertexGroups.get(gn);
if(l != null)
for(Vertex v:l)
v.setMutable(false);
}
}
//triangle groups
if (hasAdjacency() && !triangleList.isEmpty())
{
names = new HashSet<String>(names);
TIntHashSet groupIds = new TIntHashSet(names.size());
for(Entry<Integer, String> e:groupNames.entrySet())
{
if(names.contains(e.getValue()))
groupIds.add(e.getKey());
}
for (Triangle t: triangleList)
{
AbstractHalfEdge ot = t.getAbstractHalfEdge();
if (t.hasAttributes(AbstractHalfEdge.OUTER))
continue;
if(groupIds.contains(t.getGroupId()))
{
for (int i = 0; i < 3; i++)
{
ot.setAttributes(attr);
if (fixVertex)
{
ot.sym().setAttributes(attr);
ot.origin().setMutable(false);
ot.destination().setMutable(false);
}
ot = ot.next();
}
}
}
}
}
/**
* Tag all edges on group boundaries with a given attribute.
*/
public final int tagGroupBoundaries(int attr)
{
if (triangleList.isEmpty())
return 0;
if (!hasAdjacency())
throw new RuntimeException("tagGroupBoundaries called on a mesh without adjacency relations");
AbstractHalfEdge ot = null;
AbstractHalfEdge sym = triangleList.iterator().next().getAbstractHalfEdge();
boolean fixVertex = (attr & AbstractHalfEdge.IMMUTABLE) != 0;
int toReturn = 0;
for (Triangle t: triangleList)
{
ot = t.getAbstractHalfEdge(ot);
if (t.hasAttributes(AbstractHalfEdge.OUTER))
continue;
int groupId = t.getGroupId();
for (int i = 0; i < 3; i++)
{
ot = ot.next();
if (ot.hasAttributes(AbstractHalfEdge.BOUNDARY | AbstractHalfEdge.NONMANIFOLD))
continue;
sym = ot.sym(sym);
if (groupId != sym.getTri().getGroupId())
{
ot.setAttributes(attr);
if (fixVertex)
{
ot.origin().setMutable(false);
ot.destination().setMutable(false);
}
toReturn++;
}
}
}
toReturn /= 2;
if (toReturn > 0 && logger.isLoggable(Level.CONFIG))
logger.log(Level.CONFIG, "Number of edges on group boundaries: "+toReturn);
return toReturn;
}
/**
* Tag all free edges with a given attribute.
*/
public final int tagFreeEdges(int attr)
{
return tagIf(AbstractHalfEdge.BOUNDARY, attr);
}
public final int tagIf(int criterion, int attr)
{
if (triangleList.isEmpty())
return 0;
if (!hasAdjacency())
throw new RuntimeException("tagIf called on a mesh without adjacency relations");
AbstractHalfEdge ot = null;
boolean pinVertices = (attr & AbstractHalfEdge.IMMUTABLE) != 0;
int toReturn = 0;
for (Triangle t: triangleList)
{
ot = t.getAbstractHalfEdge(ot);
if (t.hasAttributes(AbstractHalfEdge.OUTER))
continue;
for (int i = 0; i < 3; i++)
{
ot = ot.next();
if (ot.hasAttributes(criterion))
{
ot.setAttributes(attr);
if (pinVertices)
{
ot.origin().setMutable(false);
ot.destination().setMutable(false);
}
toReturn++;
}
}
}
if (toReturn > 0 && logger.isLoggable(Level.CONFIG))
logger.log(Level.CONFIG, "Number of tagged edges: "+toReturn);
return toReturn;
}
public Metric getMetric(Location pt)
{
return euclidian_metric3d;
}
/**
* Returns square distance between 2 vertices.
*/
public final double distance2(double [] x1, double [] x2)
{
assert x1.length == 3;
double dx = x1[0] - x2[0];
double dy = x1[1] - x2[1];
double dz = x1[2] - x2[2];
return dx*dx + dy*dy + dz*dz;
}
/**
* Perform an automatic partitioning based on festure edges.
*
* @return number of partitions.
*/
public final int buildPartition()
{
int countPart = 0;
if (!hasAdjacency())
{
logger.severe("Mesh data structure does not contain adjacency relations");
return countPart;
}
Set<Triangle> unprocessedTriangles = new LinkedHashSet<Triangle>(triangleList);
Set<Triangle> partTriangles = new LinkedHashSet<Triangle>();
Triangle.List processedTriangles = new Triangle.List();
if (unprocessedTriangles.isEmpty())
return countPart;
for (Triangle t : triangleList)
t.setGroupId(0);
TIntIntHashMap countTrianglesByPart = new TIntIntHashMap();
AbstractHalfEdge ot = unprocessedTriangles.iterator().next().getAbstractHalfEdge();
AbstractHalfEdge sym = unprocessedTriangles.iterator().next().getAbstractHalfEdge();
while(!unprocessedTriangles.isEmpty())
{
Triangle t1 = unprocessedTriangles.iterator().next();
unprocessedTriangles.remove(t1);
if (t1.hasAttributes(AbstractHalfEdge.OUTER))
continue;
partTriangles.clear();
partTriangles.add(t1);
countPart++;
countTrianglesByPart.put(countPart, 0);
while(!partTriangles.isEmpty())
{
Triangle t2 = partTriangles.iterator().next();
partTriangles.remove(t2);
if (t2.getGroupId() != 0 && countPart != t2.getGroupId())
{
logger.severe("Unexpected error");
}
if (processedTriangles.contains(t2))
continue;
t2.setGroupId(countPart);
countTrianglesByPart.increment(countPart);
unprocessedTriangles.remove(t2);
processedTriangles.add(t2);
ot = t2.getAbstractHalfEdge(ot);
if (t2.hasAttributes(AbstractHalfEdge.OUTER))
continue;
for (int i = 0; i < 3; i++)
{
ot = ot.next();
if (!(ot.hasAttributes(AbstractHalfEdge.BOUNDARY | AbstractHalfEdge.NONMANIFOLD | AbstractHalfEdge.SHARP)))
{
sym = ot.sym(sym);
Triangle symTri = sym.getTri();
if (!processedTriangles.contains(symTri))
partTriangles.add(symTri);
}
}
}
}
if (countPart > 0 && logger.isLoggable(Level.INFO))
{
logger.log(Level.INFO, "Found "+countPart+" part components");
if (logger.isLoggable(Level.CONFIG))
{
for (int key : countTrianglesByPart.keys())
logger.log(Level.CONFIG, " * Part id "+key+": "+countTrianglesByPart.get(key)+" triangles");
}
}
if (traitsBuilder.hasTrace())
{
traitsBuilder.getTrace(traits).println("self.m.buildPartition()");
traitsBuilder.getTrace(traits).addAdjacentTriangles(this);
traitsBuilder.getTrace(traits).println("# End: self.m.buildPartition()");
}
return countPart;
}
/**
* Checks whether an edge can be contracted.
*
* @param e edge to be checked
* @param v the resulting vertex
* @return <code>true</code> if this edge can be contracted into the single vertex n, <code>false</code> otherwise.
*/
public final boolean canCollapseEdge(AbstractHalfEdge e, Location v)
{
return e.canCollapse(this, v);
}
/**
* Contracts an edge.
*
* @param e edge to contract
* @param v the resulting vertex
* @return edge starting from <code>n</code> and with the same apex
* @throws IllegalArgumentException if edge belongs to an outer triangle,
* because there would be no valid return value. User must then run this
* method against symmetric edge, this is not done automatically.
*/
public final AbstractHalfEdge edgeCollapse(AbstractHalfEdge e, Vertex v)
{
if (traitsBuilder.hasTrace())
traitsBuilder.getTrace(traits).edgeCollapse(e, v);
return e.collapse(this, v);
}
/**
* Splits an edge. This is the opposite of {@link #edgeCollapse}.
*
* @param e edge being splitted
* @param v the resulting vertex
*/
public final AbstractHalfEdge vertexSplit(AbstractHalfEdge e, Vertex v)
{
if (traitsBuilder.hasTrace())
traitsBuilder.getTrace(traits).vertexSplitBefore(e, v);
AbstractHalfEdge ret = e.split(this, v);
if (traitsBuilder.hasTrace())
traitsBuilder.getTrace(traits).vertexSplitAfter(ret, v);
return ret;
}
/**
* Swaps an edge.
*
* @return swapped edge, origin and apical vertices are the same as in original edge
* @throws IllegalArgumentException if edge is on a boundary or belongs
* to an outer triangle.
*/
public final AbstractHalfEdge edgeSwap(AbstractHalfEdge e)
{
if (traitsBuilder.hasTrace())
traitsBuilder.getTrace(traits).edgeSwap(e);
return e.swap(this);
}
/**
* Checks whether origin point can be moved without introducing degenerated triangles.
*
* @param e edge to be checked
* @param v the destination vertex
* @return <code>true</code> if origin of this edge can be moved, false otherwise.
*/
public final boolean canMoveOrigin(AbstractHalfEdge e, Location pt)
{
return e.canMoveOrigin(this, pt);
}
/**
* Checks whether origin of an edge can be moved without inverting triangles.
*
* @param e edge to be checked
* @param pt coordinates where edge origin is to be moved
* @return <code>true</code> if edge origin can be moved without producing
* inverted triangles, <code>false</code> otherwise.
*/
public final boolean checkNewRingNormals(AbstractHalfEdge e, Location pt)
{
return e.checkNewRingNormals(this, pt);
}
/**
* Checks whether this mesh is valid.
* This routine returns <code>isValid(true)</code>.
*
* @see #isValid(boolean)
*/
public final boolean isValid()
{
return isValid(true);
}
/**
* Checks whether this mesh is valid.
* This routine can be called at any stage, even before boundary
* edges have been enforced. In this case, some tests must be
* removed because they do not make sense.
*
* @param constrained <code>true</code> if mesh is constrained.
*/
protected boolean isValid(boolean constrained)
{
for (Triangle t: triangleList)
{
if (t.getV0() == t.getV1() || t.getV1() == t.getV2() || t.getV2() == t.getV0())
{
logger.severe("Duplicate vertices: "+t);
return false;
}
if (t.getV0() == outerVertex || t.getV1() == outerVertex || t.getV2() == outerVertex)
{
if (constrained && factory.hasAdjacency())
{
if (!t.hasAttributes(AbstractHalfEdge.OUTER))
{
logger.severe("Triangle should be outer: "+t);
return false;
}
}
}
for (int i = 0; i < 3; i++)
{
Vertex v = t.getV(i);
if (v.getLink() == null)
continue;
if (v.isManifold())
{
Triangle t2 = (Triangle) v.getLink();
if (t2.getV0() != v && t2.getV1() != v && t2.getV2() != v)
{
logger.severe("Vertex "+v+" linked to "+t2);
return false;
}
if (!triangleList.contains(t2))
{
logger.severe("Vertex "+v+" linked to removed triangle: "+t2);
return false;
}
}
else
{
// Check that all linked triangles are
// still present in mesh
Triangle [] links = (Triangle []) v.getLink();
for (Triangle t2: links)
{
if (!triangleList.contains(t2))
{
logger.severe("Vertex "+v+" linked to removed triangle: "+t2);
return false;
}
}
}
}
if (!checkVirtualHalfEdges(t))
return false;
if (!checkHalfEdges(t))
return false;
}
return true;
}
private boolean checkVirtualHalfEdges(Triangle t)
{
if (!traitsBuilder.getTriangleTraitsBuilder().hasCapability(TriangleTraitsBuilder.VIRTUALHALFEDGE))
return true;
VirtualHalfEdge ot = new VirtualHalfEdge();
VirtualHalfEdge sym = new VirtualHalfEdge();
ot.bind((TriangleVH) t);
boolean isOuter = ot.hasAttributes(AbstractHalfEdge.OUTER);
for (int i = 0; i < 3; i++)
{
ot.next();
if (isOuter != ot.hasAttributes(AbstractHalfEdge.OUTER))
{
logger.severe("Inconsistent outer state: "+ot);
return false;
}
if (!ot.hasSymmetricEdge())
continue;
sym = ot.sym(sym);
sym.sym();
if (sym.getTri() != ot.getTri())
{
logger.severe("Wrong adjacency relation: ");
logger.severe(" adj1: "+ot);
logger.severe(" adj2: "+sym);
return false;
}
sym.sym();
if (ot.hasAttributes(AbstractHalfEdge.BOUNDARY) != sym.hasAttributes(AbstractHalfEdge.BOUNDARY) || ot.hasAttributes(AbstractHalfEdge.NONMANIFOLD) != sym.hasAttributes(AbstractHalfEdge.NONMANIFOLD))
{
logger.severe("Inconsistent boundary or nonmanifold flags");
logger.severe(" "+ot);
logger.severe(" "+sym);
return false;
}
if (ot.hasAttributes(AbstractHalfEdge.BOUNDARY | AbstractHalfEdge.NONMANIFOLD) && sym.hasAttributes(AbstractHalfEdge.OUTER) != !isOuter)
{
logger.severe("Inconsistent outer flags");
logger.severe(" "+ot);
logger.severe(" "+sym);
return false;
}
if (!triangleList.contains(sym.getTri()))
{
logger.severe("Triangle not present in mesh: "+sym.getTri());
logger.severe("Linked from "+ot);
return false;
}
Vertex v1 = ot.origin();
Vertex v2 = ot.destination();
if (!isOuter)
{
if (sym.origin() != v2 || sym.destination() != v1)
{
logger.severe("Vertex mismatch in adjacency relation: ");
logger.severe(" "+ot);
logger.severe(" "+sym);
return false;
}
continue;
}
// triangle is outer
if (ot.hasAttributes(AbstractHalfEdge.BOUNDARY) && !outerTrianglesAreConnected)
{
// Edge is manifold
// next() and prev() must not be linked to other edges
ot.next();
if (ot.hasSymmetricEdge())
{
logger.severe("Outer edge: should not be linked to another edge: "+ot);
return false;
}
ot.next();
if (ot.hasSymmetricEdge())
{
logger.severe("Outer edge: should not be linked to another edge: "+ot);
return false;
}
ot.next();
}
else if (ot.hasAttributes(AbstractHalfEdge.NONMANIFOLD))
{
if (v1.isManifold())
{
logger.severe("Multiple edges: endpoint must be non-manifold: "+v1);
logger.severe(" "+ot);
return false;
}
if (v2.isManifold())
{
logger.severe("Multiple edges: endpoint must be non-manifold: "+v2);
logger.severe(" "+ot);
return false;
}
// next() and prev() must point to other non-manifold edges
ot.next();
if (!ot.hasSymmetricEdge())
{
logger.severe("Multiple edge: must be linked to another edge: "+ot);
return false;
}
VirtualHalfEdge.symOTri(ot, sym);
sym.next();
if (!sym.hasAttributes(AbstractHalfEdge.NONMANIFOLD))
{
logger.severe("Multiple edges: linked to a non-manifold edge");
logger.severe(" "+ot);
logger.severe(" "+sym);
return false;
}
if (!ot.hasAttributes(AbstractHalfEdge.OUTER) || !sym.hasAttributes(AbstractHalfEdge.OUTER))
{
logger.severe("Multiple edges: linked to an inner edge");
logger.severe(" "+ot);
logger.severe(" "+sym);
return false;
}
if (!triangleList.contains(sym.getTri()))
{
logger.severe("Multiple edges: Triangle not present in mesh: "+sym.getTri());
logger.severe("Linked from "+this);
return false;
}
if (!((sym.origin() == v1 && sym.destination() == v2) || (sym.origin() == v2 && sym.destination() == v1)))
{
logger.severe("Multiple edges: vertex mismatch in adjacency relation: ");
logger.severe(" "+ot);
logger.severe(" "+sym);
return false;
}
ot.next();
if (!ot.hasSymmetricEdge())
{
logger.severe("Multiple edge: must be linked to another edge: "+ot);
return false;
}
VirtualHalfEdge.symOTri(ot, sym);
sym.prev();
if (!sym.hasAttributes(AbstractHalfEdge.NONMANIFOLD))
{
logger.severe("Multiple edges: linked to a non-manifold edge");
logger.severe(" "+ot);
logger.severe(" "+sym);
return false;
}
if (!ot.hasAttributes(AbstractHalfEdge.OUTER) || !sym.hasAttributes(AbstractHalfEdge.OUTER))
{
logger.severe("Multiple edges: linked to an inner edge");
logger.severe(" "+ot);
logger.severe(" "+sym);
return false;
}
if (!triangleList.contains(sym.getTri()))
{
logger.severe("Multiple edges: Triangle not present in mesh: "+sym.getTri());
logger.severe("Linked from "+this);
return false;
}
if (!((sym.origin() == v1 && sym.destination() == v2) || (sym.origin() == v2 && sym.destination() == v1)))
{
logger.severe("Multiple edges: vertex mismatch in adjacency relation: ");
logger.severe(" "+ot);
logger.severe(" "+sym);
return false;
}
}
}
return true;
}
private boolean checkHalfEdges(Triangle t)
{
if (!traitsBuilder.getTriangleTraitsBuilder().hasCapability(TriangleTraitsBuilder.HALFEDGE))
return true;
HalfEdge e = (HalfEdge) t.getAbstractHalfEdge();
boolean isOuter = e.hasAttributes(AbstractHalfEdge.OUTER);
for (int i = 0; i < 3; i++)
{
e = e.next();
if (isOuter != e.hasAttributes(AbstractHalfEdge.OUTER))
{
logger.severe("Inconsistent outer state: "+e);
return false;
}
if (!e.hasSymmetricEdge())
continue;
HalfEdge f = e.sym();
if (f.sym() != e)
{
logger.severe("Wrong adjacency relation: ");
logger.severe(" adj1: "+e);
logger.severe(" adj2: "+f);
return false;
}
if (f.hasAttributes(AbstractHalfEdge.BOUNDARY) != e.hasAttributes(AbstractHalfEdge.BOUNDARY) || f.hasAttributes(AbstractHalfEdge.NONMANIFOLD) != e.hasAttributes(AbstractHalfEdge.NONMANIFOLD))
{
logger.severe("Inconsistent boundary or nonmanifold flags");
logger.severe(" "+e);
logger.severe(" "+f);
return false;
}
if (e.hasAttributes(AbstractHalfEdge.BOUNDARY | AbstractHalfEdge.NONMANIFOLD) && f.hasAttributes(AbstractHalfEdge.OUTER) != !isOuter)
{
logger.severe("Inconsistent outer flags");
logger.severe(" "+e);
logger.severe(" "+f);
return false;
}
if (!triangleList.contains(f.getTri()))
{
logger.severe("Triangle not present in mesh: "+f.getTri());
logger.severe("Linked from "+e);
return false;
}
Vertex v1 = e.origin();
Vertex v2 = e.destination();
if (!isOuter)
{
if (f.origin() != v2 || f.destination() != v1)
{
logger.severe("Vertex mismatch in adjacency relation: ");
logger.severe(" "+e);
logger.severe(" "+f);
return false;
}
continue;
}
// triangle is outer
if (e.hasAttributes(AbstractHalfEdge.BOUNDARY) && !outerTrianglesAreConnected)
{
// Edge e is manifold
// next() and prev() must not be linked to other edges
AbstractHalfEdge g = e.next();
if (g.hasSymmetricEdge())
{
logger.severe("Outer edge: should not be linked to another edge: "+g);
return false;
}
g = e.prev();
if (g.hasSymmetricEdge())
{
logger.severe("Outer edge: should not be linked to another edge: "+g);
return false;
}
}
else if (e.hasAttributes(AbstractHalfEdge.NONMANIFOLD))
{
if (v1.isManifold())
{
logger.severe("Multiple edges: endpoint must be non-manifold: "+v1);
logger.severe(" "+e);
return false;
}
if (v2.isManifold())
{
logger.severe("Multiple edges: endpoint must be non-manifold: "+v2);
logger.severe(" "+e);
return false;
}
// next() and prev() must point to other non-manifold edges
AbstractHalfEdge g = e.next();
if (!g.hasSymmetricEdge())
{
logger.severe("Multiple edge: must be linked to another edge: "+g);
return false;
}
AbstractHalfEdge h = e.prev();
if (!h.hasSymmetricEdge())
{
logger.severe("Multiple edge: must be linked to another edge: "+h);
return false;
}
g = g.sym().next();
h = h.sym().prev();
if (!g.hasAttributes(AbstractHalfEdge.NONMANIFOLD) || !h.hasAttributes(AbstractHalfEdge.NONMANIFOLD))
{
logger.severe("Multiple edges: linked to a non-manifold edge");
logger.severe(" "+f);
logger.severe(" "+g);
logger.severe(" "+h);
return false;
}
if (!g.hasAttributes(AbstractHalfEdge.OUTER) || !h.hasAttributes(AbstractHalfEdge.OUTER))
{
logger.severe("Multiple edges: linked to an inner edge");
logger.severe(" "+f);
logger.severe(" "+g);
logger.severe(" "+h);
return false;
}
if (!triangleList.contains(g.getTri()))
{
logger.severe("Multiple edges: Triangle not present in mesh: "+g.getTri());
logger.severe("Linked from "+f);
return false;
}
if (!triangleList.contains(h.getTri()))
{
logger.severe("Multiple edges: Triangle not present in mesh: "+h.getTri());
logger.severe("Linked from "+f);
return false;
}
if (!((g.origin() == v1 && g.destination() == v2) || (g.origin() == v2 && g.destination() == v1)))
{
logger.severe("Multiple edges: vertex mismatch in adjacency relation: ");
logger.severe(" "+e);
logger.severe(" "+g);
return false;
}
if (!((h.origin() == v1 && h.destination() == v2) || (h.origin() == v2 && h.destination() == v1)))
{
logger.severe("Multiple edges: vertex mismatch in adjacency relation: ");
logger.severe(" "+e);
logger.severe(" "+h);
return false;
}
for (Iterator<AbstractHalfEdge> it = e.fanIterator(); it.hasNext(); )
{
h = it.next();
if (h.hasAttributes(AbstractHalfEdge.OUTER))
{
logger.severe("Multiple edges: fan iterator error: ");
logger.severe(" "+e);
logger.severe(" "+h);
return false;
}
}
}
}
return true;
}
public final boolean checkNoInvertedTriangles()
{
AbstractHalfEdge ot = null;
AbstractHalfEdge sym = null;
double [][] temp = new double[4][3];
for (Triangle t : triangleList)
{
if (t.hasAttributes(AbstractHalfEdge.OUTER))
continue;
ot = t.getAbstractHalfEdge(ot);
sym = t.getAbstractHalfEdge(sym);
for (int i = 0; i < 3; i++)
{
ot = ot.next();
if (ot.hasAttributes(AbstractHalfEdge.SHARP | AbstractHalfEdge.BOUNDARY | AbstractHalfEdge.NONMANIFOLD))
continue;
if (!ot.hasSymmetricEdge())
continue;
sym = ot.sym(sym);
Vertex o = ot.origin();
Vertex d = ot.destination();
Vertex a = ot.apex();
Vertex n = sym.apex();
Matrix3D.computeNormal3D(o, d, a, temp[0], temp[1], temp[2]);
Matrix3D.computeNormal3D(d, o, n, temp[0], temp[1], temp[3]);
if (Matrix3D.prodSca(temp[2], temp[3]) < -0.6)
{
System.err.println("ERROR: dot product of normals of triangles below is: "+Matrix3D.prodSca(temp[2], temp[3]));
System.err.println("T1: "+t);
System.err.println("group name: "+getGroupName(t.getGroupId()));
System.err.println("T2: "+sym.getTri());
System.err.println("group name: "+getGroupName(sym.getTri().getGroupId()));
return false;
}
}
}
return true;
}
public final boolean checkNoDegeneratedTriangles()
{
for (Triangle t : triangleList)
{
if (t.hasAttributes(AbstractHalfEdge.OUTER))
continue;
Vertex t0 = t.getV0();
Vertex t1 = t.getV1();
Vertex t2 = t.getV2();
double a = t.getV0().sqrDistance3D(t.getV1());
double c = t.getV0().sqrDistance3D(t.getV2());
double b =
(t1.getX() - t0.getX()) * (t2.getX() - t0.getX()) +
(t1.getY() - t0.getY()) * (t2.getY() - t0.getY()) +
(t1.getZ() - t0.getZ()) * (t2.getZ() - t0.getZ());
if (a*c == b*b)
{
System.err.println("ERROR: degenerated triangle: "+t);
System.err.println("group name: "+getGroupName(t.getGroupId()));
return false;
}
}
return true;
}
public final boolean checkVertexLinks()
{
AbstractHalfEdge ot = null;
if (nodeList == null)
return true;
for (Vertex v : nodeList)
{
if (v.isManifold())
continue;
Triangle [] links = (Triangle []) v.getLink();
// Fail gracefully if called before buildAdjacency()
if (links == null)
continue;
HashSet triangles = new HashSet();
for (Triangle t : links)
triangles.add(t);
for (Triangle t : links)
{
ot = t.getAbstractHalfEdge(ot);
if (ot.destination() == v)
ot = ot.next();
else if (ot.apex() == v)
ot = ot.next().next();
Vertex d = ot.destination();
do
{
if (ot.getTri() != t && triangles.contains(ot.getTri()))
{
System.err.println("ERROR: Triangles on the same fan found for vertex "+v);
System.err.println(" Triangle "+t);
System.err.println(" Triangle "+ot.getTri());
return false;
}
ot = ot.nextOriginLoop();
}
while (ot.destination() != d);
}
}
return true;
}
public Iterable<Vertex> getManifoldVertices()
{
return new Iterable<Vertex>(){
public Iterator<Vertex> iterator() {
return new Iterator<Vertex>(){
private Iterator<Vertex> delegateIt = nodeList.iterator();
private Vertex next;
private void nextImpl()
{
if(next == null)
{
while(delegateIt.hasNext())
{
next = delegateIt.next();
if(next.isManifold())
break;
}
}
}
public boolean hasNext() {
nextImpl();
return next != null;
}
public Vertex next() {
nextImpl();
if(next == null)
throw new NoSuchElementException();
Vertex toReturn = next;
next = null;
return toReturn;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
public int getNumberOfTrianglesByGroup(int group)
{
int n = 0;
for(Triangle t:getTriangles())
{
if(t.getGroupId() == group)
n ++;
}
return n;
}
public void checkVerticesRef()
{
for(Triangle t:getTriangles())
{
for(int i = 0; i < 3; i++)
{
Vertex v = t.getV(i);
Iterator<AbstractHalfEdge> it = v.getNeighbourIteratorAbstractHalfEdge();
boolean nonManifold = false;
while(it.hasNext())
{
AbstractHalfEdge e = it.next();
nonManifold = nonManifold || e.hasAttributes(
AbstractHalfEdge.BOUNDARY |
AbstractHalfEdge.NONMANIFOLD |
AbstractHalfEdge.SHARP);
}
if((v.getRef() == 0) == nonManifold)
throw new IllegalStateException("Invalid ref "+v.getRef()+
" for "+v+". Manifold="+!nonManifold);
}
}
}
/**
*
* @param bounds {xmin, ymin, zmin, xmax, ymax, zmax}
*/
public void getBoundingBox(double[] bounds)
{
bounds[0] = Double.POSITIVE_INFINITY;
bounds[1] = Double.POSITIVE_INFINITY;
bounds[2] = Double.POSITIVE_INFINITY;
bounds[3] = Double.NEGATIVE_INFINITY;
bounds[4] = Double.NEGATIVE_INFINITY;
bounds[5] = Double.NEGATIVE_INFINITY;
if(hasNodes())
{
for(Vertex v:getNodes())
{
bounds[0] = Math.min(bounds[0], v.getX());
bounds[1] = Math.min(bounds[1], v.getY());
bounds[2] = Math.min(bounds[2], v.getZ());
bounds[3] = Math.max(bounds[3], v.getX());
bounds[4] = Math.max(bounds[4], v.getY());
bounds[5] = Math.max(bounds[5], v.getZ());
}
}
else
{
for(Triangle t:getTriangles())
{
for(int i = 0; i < 3; i++)
{
Vertex v = t.getV(i);
bounds[0] = Math.min(bounds[0], v.getX());
bounds[1] = Math.min(bounds[1], v.getY());
bounds[2] = Math.min(bounds[2], v.getZ());
bounds[3] = Math.max(bounds[3], v.getX());
bounds[4] = Math.max(bounds[4], v.getY());
bounds[5] = Math.max(bounds[5], v.getZ());
}
}
}
}
/** Return the number of fan for each neighbour edges */
private List<Integer> getAdjacency(Vertex v)
{
Iterator<AbstractHalfEdge> it = v.getNeighbourIteratorAbstractHalfEdge();
ArrayList<Integer> l = new ArrayList<Integer>();
while(it.hasNext())
{
AbstractHalfEdge e = it.next();
if(!e.hasAttributes(AbstractHalfEdge.OUTER))
{
if(e.hasAttributes(AbstractHalfEdge.BOUNDARY))
{
l.add(1);
}
else if(e.hasAttributes(AbstractHalfEdge.NONMANIFOLD))
{
int n = 0;
Iterator<AbstractHalfEdge> it2 = e.fanIterator();
while(it2.hasNext())
{
it2.next();
n++;
}
l.add(n);
}
else
{
l.add(2);
}
}
}
Collections.sort(l);
return l;
}
/** Clear the adjacency, rebuild it and compare both version */
public boolean checkAdjacency()
{
Map<Vertex, List<Integer>> adjacency = HashFactory.createMap();
for(Triangle t:getTriangles())
{
for(int i = 0; i < 3; i++)
{
Vertex v = t.getV(i);
List<Integer> l = adjacency.get(v);
if(l == null)
adjacency.put(v, getAdjacency(v));
}
}
clearAdjacency();
buildAdjacency();
for(Triangle t:getTriangles())
{
for(int i = 0; i < 3; i++)
{
Vertex v = t.getV(i);
List<Integer> a1 = adjacency.get(v);
List<Integer> a2 = getAdjacency(v);
if(!a1.equals(a2))
{
new IllegalStateException(v + " " + a1 + " " + a2).printStackTrace();
return false;
}
}
}
return true;
}
public void clearAdjacency()
{
Iterator<Triangle> itt = getTriangles().iterator();
while(itt.hasNext())
{
Triangle t = itt.next();
if(t.hasAttributes(AbstractHalfEdge.OUTER))
itt.remove();
else
{
AbstractHalfEdge e = t.getAbstractHalfEdge();
for(int i = 0; i < 3; i++)
{
e.clearAttributes(0xFFFFFFFF);
e.glue(null);
e = e.next();
}
}
}
if(hasNodes())
{
for(Vertex v:getNodes())
v.setLink(null);
}
}
/**
* Merge an other mesh to this mesh.
* This method assuming that there are no group name conflict that is group
* names from the other mesh do not exists in this mesh
*/
public void merge(Mesh toMerge)
{
if(hasNodes())
//I don't need it yet
throw new UnsupportedOperationException();
int groupOffset = getNumberOfGroups();
for(Triangle t: toMerge.getTriangles())
{
add(t);
t.setGroupId(t.getGroupId() + groupOffset);
}
int nbBeams = toMerge.getBeams().size() / 2;
List<Vertex> otherBeams = toMerge.getBeams();
for(int i = 0; i < nbBeams; i++)
{
addBeam(otherBeams.get(i * 2), otherBeams.get(i * 2 + 1),
toMerge.getBeamGroup(i) + groupOffset);
}
for(Entry<Integer, String> e: toMerge.groupNames.entrySet())
setGroupName(e.getKey()+groupOffset, e.getValue());
vertexGroups.putAll(toMerge.getVertexGroup());
}
public void changeGroups(Map<Integer, Integer> map)
{
for(Triangle t: triangleList)
{
Integer newId = map.get(t.getGroupId());
if(newId != null)
t.setGroupId(newId);
}
}
/**
* Return nodes even if hasNodes is false
* @triangles if not null, receive the triangle in the group
*/
public Collection<Vertex> getOrComputeNodes(int group, Collection<Triangle> triangles)
{
if(hasNodes() && group == -1)
return getNodes();
else
{
Set<Vertex> toReturn;
if(group == -1)
toReturn = HashFactory.createSet(triangleList.size() / 2 * 4 / 3);
else
toReturn = HashFactory.createSet();
for(Triangle t: triangleList)
{
if(group == -1 || t.getGroupId() == group)
{
toReturn.add(t.getV0());
toReturn.add(t.getV1());
toReturn.add(t.getV2());
if(triangles != null)
triangles.add(t);
}
}
return toReturn;
}
}
/**
* Apply a geometrical transformation to all vertices of the triangles of
* a given group
*/
public void transform(int group, Matrix3D rotation, double[] translation)
{
double[] tmp1 = new double[3];
double[] tmp2 = new double[3];
for(Vertex v: getOrComputeNodes(group, null))
{
if(rotation != null)
{
v.get(tmp1);
rotation.apply(tmp1, tmp2);
}
else
v.get(tmp2);
if(translation != null)
{
for(int i = 0; i < 3; i++)
tmp2[i] += translation[i];
}
v.moveTo(tmp2[0], tmp2[1], tmp2[2]);
}
}
/**
* Duplicate the vertices and triangles of a given group
*/
public void copy(int group, int toGroup)
{
ArrayList<Triangle> triangles = new ArrayList<Triangle>();
Collection<Vertex> vertices = getOrComputeNodes(group, triangles);
Map<Vertex, Vertex> map = HashFactory.createMap(vertices.size());
for(Vertex v:vertices)
{
map.put(v, createVertex(v));
}
if(hasNodes())
getNodes().addAll(map.values());
for(Triangle t: triangles)
{
Triangle nt = createTriangle(map.get(t.v0), map.get(t.v1), map.get(t.v2));
nt.setGroupId(toGroup);
add(nt);
}
}
public void reverse(int group)
{
for(Triangle t: getTriangles())
{
if(t.getGroupId() == group || group == -1)
{
Vertex v0 = t.getV0();
Vertex v1 = t.getV1();
t.setV(0, v1);
t.setV(1, v0);
}
}
}
// Useful for debugging
/* Following imports must be moved at top.
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.zip.GZIPOutputStream;
import gnu.trove.map.hash.TObjectIntHashMap;
public void printMesh()
{
System.out.println("Mesh:");
for (Triangle t: triangleList)
System.out.println(""+t);
System.out.println("Outer Vertex: "+outerVertex);
}
public void writeUNV(String file)
{
String cr=System.getProperty("line.separator");
PrintWriter out;
try {
if (file.endsWith(".gz") || file.endsWith(".GZ"))
out = new PrintWriter(new GZIPOutputStream(new FileOutputStream(file)));
else
out = new PrintWriter(new FileOutputStream(file));
out.println(" -1"+cr+" 2411");
LinkedHashSet<Vertex> nodeset = new LinkedHashSet<Vertex>();
for (Triangle t: triangleList)
{
if (t.hasAttributes(AbstractHalfEdge.OUTER))
continue;
if (t.getV0() == outerVertex || t.getV1() == outerVertex || t.getV2() == outerVertex)
continue;
nodeset.add(t.getV0());
nodeset.add(t.getV1());
nodeset.add(t.getV2());
}
int count = 0;
TObjectIntHashMap<Vertex> labels = new TObjectIntHashMap<Vertex>(nodeset.size());
for(Vertex node: nodeset)
{
count++;
labels.put(node, count);
double [] uv = node.getUV();
out.println(count+" 1 1 1");
if (uv.length == 2)
out.println(""+uv[0]+" "+uv[1]+" 0.0");
else
out.println(""+uv[0]+" "+uv[1]+" "+uv[2]);
}
out.println(" -1");
out.println(" -1"+cr+" 2412");
count = 0;
for (Triangle t: triangleList)
{
if (t.hasAttributes(AbstractHalfEdge.OUTER))
continue;
if (t.getV0() == outerVertex || t.getV1() == outerVertex || t.getV2() == outerVertex)
continue;
count++;
out.println(""+count+" 91 1 1 1 3");
for(int i = 0; i < 3; i++)
{
int nodelabel = labels.get(t.getV(i));
out.print(" "+nodelabel);
}
out.println("");
}
out.println(" -1");
out.close();
} catch (FileNotFoundException e)
{
logger.severe(e.toString());
e.printStackTrace();
} catch (IOException e)
{
logger.severe(e.toString());
e.printStackTrace();
}
}
public void writeMesh(String file)
{
String cr=System.getProperty("line.separator");
PrintWriter out;
try {
if (file.endsWith(".gz") || file.endsWith(".GZ"))
out = new PrintWriter(new GZIPOutputStream(new FileOutputStream(file)));
else
out = new PrintWriter(new FileOutputStream(file));
out.println("MeshVersionFormatted 1"+cr+"Dimension"+cr+"3");
LinkedHashSet<Vertex> nodeset = new LinkedHashSet<Vertex>();
for(Triangle t: triangleList)
{
if (t.hasAttributes(AbstractHalfEdge.OUTER))
continue;
if (t.getV0() == outerVertex || t.getV1() == outerVertex || t.getV2() == outerVertex)
continue;
nodeset.add(t.getV0());
nodeset.add(t.getV1());
nodeset.add(t.getV2());
}
int count = 0;
TObjectIntHashMap<Vertex> labels = new TObjectIntHashMap<Vertex>(nodeset.size());
out.println("Vertices"+cr+nodeset.size());
for(Vertex node: nodeset)
{
count++;
labels.put(node, count);
double [] uv = node.getUV();
if (uv.length == 2)
out.println(""+uv[0]+" "+uv[1]+" 0.0 0");
else
out.println(""+uv[0]+" "+uv[1]+" "+uv[2]+" 0");
}
count = 0;
for(Triangle t: triangleList)
{
if (t.hasAttributes(AbstractHalfEdge.OUTER))
continue;
if (t.getV0() == outerVertex || t.getV1() == outerVertex || t.getV2() == outerVertex)
continue;
count++;
}
out.println(cr+"Triangles"+cr+count);
count = 0;
for(Triangle t: triangleList)
{
if (t.hasAttributes(AbstractHalfEdge.OUTER))
continue;
if (t.getV0() == outerVertex || t.getV1() == outerVertex || t.getV2() == outerVertex)
continue;
count++;
for(int i = 0; i < 3; i++)
{
int nodelabel = labels.get(t.getV(i));
out.print(nodelabel+" ");
}
out.println("1");
}
out.println(cr+"End");
out.close();
} catch (FileNotFoundException e)
{
logger.severe(e.toString());
e.printStackTrace();
} catch (IOException e)
{
logger.severe(e.toString());
e.printStackTrace();
}
}
*/
}