jCAE
 

Amibe Frequently Asked Questions

Questions

1. Import/export mesh

1.1. Which output formats are supported?

Amibe uses its own file format to store meshes, but they can also be exported into the following formats:

  1. I-deas UNV file
  2. Medit MESH format (in French)
  3. STL format
  4. TetGen POLY format

Here are some sample source code:

// Directory containing Amibe 3D mesh files (jcae3d)
String outputDir3D = "amibe.out";
// Conversion into UNV format
new org.jcae.mesh.xmldata.MeshExporter.UNV(outputDir3D).write("foo.unv");
// Conversion into POLY format
new org.jcae.mesh.xmldata.MeshExporter.POLY(outputDir3D).write("foo.poly");
// Conversion into STL format
new org.jcae.mesh.xmldata.MeshExporter.STL(outputDir3D).write("foo.stl");
// Conversion into MESH format
new org.jcae.mesh.xmldata.MeshExporter.MESH(outputDir3D).write("foo.mesh");
        

These conversions are applied on the whole indexed mesh. It is thus not possible to handle very large meshes, and we have to convert Amibe 2D files instead. An example can be found in org.jcae.mesh.Mesher class.

1.2. Which input formats are supported?

Amibe is mainly targeted at meshing CAD surfaces, but some basic support to read I-deas UNV files have been added.

Mesh mesh = new Mesh();
String filename = "mymesh.unv";
org.jcae.mesh.amibe.util.UNVReader.readMesh(mesh, filename);
        

1.3. Adjacency relations are not computed when importing meshes

When a Mesh instance is created, a list of traits (=features) is passed to the constructor. The list of defined traits cannot be modified afterwards. For instance we can request adjacency relations if needed with the following code:

TriangleTraitsBuilder ttb = new org.jcae.mesh.amibe.traits.TriangleTraitsBuilder();
ttb.addHalfEdge();
// Above line can be replaced by
//   ttb.addShallowHalfEdge();
// if mesh operations can be performed on cheaper VirtualHalfEdge data structure.
MeshTraitsBuilder mtb = new org.jcae.mesh.amibe.traits.MeshTraitsBuilder();
Mesh mesh = new Mesh(mtb);
        

2. Mesh traversal

2.1. Why are there two half-edge implementations?

Our first implementation was designed to be very compact. But under some circumstances we faced problems and had to find an alternative. As we focus on very large meshes, we decided to keep both; org.jcae.mesh.amibe.ds.AbstractHalfEdge is an abstract class, with two derived classes:

  1. org.jcae.mesh.amibe.ds.VirtualHalfEdge is the most compact representation, edges are not stored as separate objects but as handles on triangles (since an edge is only a local number on a given triangle).
  2. org.jcae.mesh.amibe.ds.HalfEdge is very similar, but edges are stored within objects.

You should write generic code wherever possible, unless it is absolutely clear that a given implementation will be used. This means that mesh traversal should be performed only by calling methods from AbstractHalfEdge and not from derived classes.

2.2. How to choose between these two implementations?

It depends on which operations will be performed on the mesh. If edges are going to be sorted according to a given criterion, you must have an object representation and call TriangleTraitsBuilder.addHalfEdge(). Otherwise, it is likely that TriangleTraitsBuilder.addShallowHalfEdge() is a more efficient alternative. If you are writing some new methods to modify mesh topology, it is surely a good idea to begin with HalfEdge instances which are simpler to deal with, and when it is over, implement these methods with VirtualHalfEdge instances.

2.3. How to traverse mesh?

Classes derived from org.jcae.mesh.amibe.ds.AbstractHalfEdge define the following methods:

// Retrieves symmetric edge
public AbstractHalfEdge sym();
// Retrieves next edge on the same triangle
public AbstractHalfEdge next();
// Retrieves previous edge on the same triangle
public AbstractHalfEdge prev();
// Retrieves next edge (on a different triangle) starting from the same vertex
public AbstractHalfEdge nextOrigin();
// Like nextOrigin(), but takes care of mesh boundaries
public AbstractHalfEdge nextOriginLoop();
        

With these methods, it is possible to define all other geometric primitives which are needed to traverse mesh.

Geometric primitives

Edge endpoints can be obtained through origin(), destination() and apex() methods. These identities may help to better understand how all these methods are defined:

e.nextOrigin().origin() == e.prevOrigin().origin() == e.origin() == A
e.nextDest().destination() == e.prevDest().destination() == e.destination() == B
e.nextApex().apex() == e.prevApex().apex() == e.apex() == C
        

2.4. How to loop over edges inside a triangle?

Here is a trivial example:

Triangle f = ...;
AbstractHalfEdge e = f.getAbstractHalfEdge();
for (int i = 0; i < 3; i++)
{
   // do some processing on e
   ...
   e = e.next();
}
        
Warning
The following example does not work, because if triangle adjacency is obtained through VirtualHalfEdge, e and start then reference the exact same object.
Triangle f = ...;
AbstractHalfEdge start = f.getAbstractHalfEdge();
AbstractHalfEdge e = start;
do
{
   // do some processing on e
   ...
   e = e.next();
}
while (e != start);
        

2.5. How to loop around a vertex?

Each vertex has a link to one of its adjacent triangles. We first determine the half-edge in this triangle which starts from this vertex, and we can then use edge pointers to walk through adjacent edges.

Vertex v = ...;
Triangle f = (Triangle) v.getLink();
AbstractHalfEdge e = f.getAbstractHalfEdge();
if (e.destination() == v)
  e = e.next();
else if (e.apex() == v)
  e = e.prev();
assert e.origin() == v;
Vertex d = e.destination();
do
{
   // do some processing on e
   ...
   e = e.nextOriginLoop();
}
while (d != e.destination());
        
Note
In these constructs, we always call nextOriginLoop() method instead of nextOrigin() because the former is designed to gracefully handle mesh boundaries whereas the latter is a geometric primitive.