/* jCAE stand for Java Computer Aided Engineering. Features are : Small CAD
modeler, Finite element mesher, Plugin architecture.
Copyright (C) 2007,2008, 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.oemm;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.IntBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.iterator.TIntObjectIterator;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.set.hash.TIntHashSet;
import org.jcae.mesh.amibe.ds.Mesh;
import org.jcae.mesh.amibe.ds.Vertex;
import org.jcae.mesh.amibe.ds.Triangle;
import org.jcae.mesh.amibe.traits.MeshTraitsBuilder;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class builds a mesh from disk.
*/
public class MeshReader extends Storage
{
private static final Logger logger=Logger.getLogger(MeshReader.class.getName());
protected final OEMM oemm;
// Map between octant index and Mesh instance.
protected TIntObjectHashMap<Mesh> mapNodeToMesh = null;
// Map between octant index and a list of vertices from adjacent triangles so that all triangles are readable
protected TIntObjectHashMap<List<FakeNonReadVertex>> mapNodeToNonReadVertexList = null;
/**
* Buffer size. Vertices and triangles are read through buffers to improve
* efficiency, buffer size must be a multiple of {@link #TRIANGLE_SIZE} and
* {@link #VERTEX_SIZE}.
*/
protected static final int BUFFER_SIZE = 24 * VERTEX_SIZE * TRIANGLE_SIZE;
// BUFFER_SIZE = 16128
/**
* Buffer to improve I/O efficiency.
*/
protected static final ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
/**
* Constructor.
*
* @param o OEMM instance
*/
public MeshReader(OEMM o)
{
oemm = o;
}
/**
* Allows reading non-readable triangles.
* On disk, a triangle is contained in one single leaf. If it contains vertices from
* octants which are not loaded, this triangle can not be drawn and is said to be
* non-readable. If argument is <code>true</code>, such vertices are loaded from disk
* to make these triangles as being readable.
*
* @param loadNonReadableTriangles behavior expected.
*/
public void setLoadNonReadableTriangles(boolean loadNonReadableTriangles)
{
if (loadNonReadableTriangles)
mapNodeToNonReadVertexList = new TIntObjectHashMap<List<FakeNonReadVertex>>();
else
mapNodeToNonReadVertexList = null;
}
/**
* Builds meshes for all octants. This method maintains a map in memory of meshes
* from all octants; each mesh can be retrieved by {@link #getMesh}.
*
* @param mtb mesh traits builder used to create <code>Mesh</code> instances
*/
public void buildMeshes(MeshTraitsBuilder mtb)
{
if (!mtb.hasNodes())
mtb.addNodeList();
if (!mtb.hasTriangles())
mtb.addTriangleList();
mapNodeToMesh = new TIntObjectHashMap<Mesh>(oemm.getNumberOfLeaves());
for(OEMM.Node current: oemm.leaves)
mapNodeToMesh.put(current.leafIndex, new Mesh(mtb));
mapNodeToNonReadVertexList = new TIntObjectHashMap<List<FakeNonReadVertex>>();
TIntHashSet loadedLeaves = new TIntHashSet();
for(OEMM.Node current: oemm.leaves)
{
Mesh mesh = mapNodeToMesh.get(current.leafIndex);
loadedLeaves.clear();
loadedLeaves.add(current.leafIndex);
TIntObjectHashMap<Vertex> vertMap = new TIntObjectHashMap<Vertex>();
readVertices(loadedLeaves, mesh, vertMap, current);
readTriangles(loadedLeaves, mesh, vertMap, current);
if (mesh.hasAdjacency())
mesh.buildAdjacency();
}
if (mapNodeToNonReadVertexList != null) {
loadVerticesFromUnloadedNodes();
}
}
/**
* Gets the mesh contained in an octant.
*
* @param leafIndex octant index
* @return mesh contained in these octants
*/
public Mesh getMesh(int leafIndex)
{
if (mapNodeToMesh == null)
throw new RuntimeException("Error: buildMeshes() must be called first!");
return mapNodeToMesh.get(leafIndex);
}
/**
* Builds a mesh composed of all octants.
* @return mesh contained in all octants
*/
public final Mesh buildWholeMesh()
{
TIntHashSet leaves = new TIntHashSet(oemm.getNumberOfLeaves());
for(OEMM.Node current: oemm.leaves)
leaves.add(current.leafIndex);
return buildMesh(leaves);
}
/**
* Builds a mesh composed of specified octants.
* @param leaves set of selected octants
* @return mesh contained in these octants
*/
public final Mesh buildMesh(TIntHashSet leaves)
{
return buildMesh(MeshTraitsBuilder.getDefault3D(), leaves);
}
/**
* Builds a mesh composed of specified octants.
* @param mtb mesh traits builder used to create <code>Mesh</code> instances
* @param leaves set of selected octants
* @return mesh contained in these octants
*/
public final Mesh buildMesh(MeshTraitsBuilder mtb, TIntHashSet leaves)
{
if (mapNodeToMesh != null)
throw new RuntimeException("Error: buildMesh() cannot be called after buildMeshes()!");
// Mesh needs triangle and vertex collections, otherwise it cannot be stored back on disk
if (!mtb.hasTriangles())
mtb.addTriangleList();
if (!mtb.hasNodes())
mtb.addNodeList();
Mesh ret = new Mesh(mtb);
appendMesh(ret, leaves);
return ret;
}
private void appendMesh(Mesh mesh, TIntHashSet leaves)
{
logger.fine("Loading nodes");
// Reset maps between consecutive calls to buildMesh()
if (mapNodeToMesh != null)
mapNodeToMesh.clear();
if (mapNodeToNonReadVertexList != null)
mapNodeToNonReadVertexList.clear();
TIntObjectHashMap<Vertex> vertMap = new TIntObjectHashMap<Vertex>();
TIntArrayList sortedLeaves = new TIntArrayList(leaves.toArray());
sortedLeaves.sort();
for (int i = 0, n = sortedLeaves.size(); i < n; i++) {
readVertices(leaves, mesh, vertMap, oemm.leaves[sortedLeaves.get(i)]);
}
for (int i = 0, n = sortedLeaves.size(); i < n; i++) {
readTriangles(leaves, mesh, vertMap, oemm.leaves[sortedLeaves.get(i)]);
}
if (mapNodeToNonReadVertexList != null) {
loadVerticesFromUnloadedNodes();
for (TIntObjectIterator<List<FakeNonReadVertex>> it = mapNodeToNonReadVertexList.iterator(); it.hasNext(); )
{
it.advance();
for (FakeNonReadVertex vertex: it.value())
mesh.add(vertex);
}
}
if (mesh.hasAdjacency())
mesh.buildAdjacency();
int cnt = 0;
for (Triangle af: mesh.getTriangles())
{
if (af.isWritable())
cnt++;
}
logger.info("Nr. of triangles: "+cnt);
}
/**
* Reads vertex coordinates, create Vertex instances and store them into a map.
*/
private void readVertices(TIntHashSet leaves, Mesh mesh, TIntObjectHashMap<Vertex> vertMap, OEMM.Node current)
{
try
{
logger.fine("Reading "+current.vn+" vertices from "+getVerticesFile(oemm, current));
mesh.ensureCapacity(2*current.vn);
Vertex [] vert = new Vertex[current.vn];
double [] xyz = new double[3];
List<TIntArrayList> listAdjacentLeaves = readAdjacencyFile(oemm, current, leaves);
FileChannel fc = new FileInputStream(getVerticesFile(oemm, current)).getChannel();
buffer.clear();
DoubleBuffer bbD = buffer.asDoubleBuffer();
int remaining = current.vn;
int index = 0;
for (int nblock = (remaining * VERTEX_SIZE) / BUFFER_SIZE; nblock >= 0; --nblock)
{
buffer.rewind();
fc.read(buffer);
bbD.rewind();
int nf = BUFFER_SIZE / VERTEX_SIZE;
if (remaining < nf)
nf = remaining;
remaining -= nf;
for(int nr = 0; nr < nf; nr ++)
{
bbD.get(xyz);
vert[index] = mesh.createVertex(xyz[0], xyz[1], xyz[2]);
vert[index].setLabel(current.minIndex + index);
vert[index].setReadable(true);
boolean writable = listAdjacentLeaves.get(index).isEmpty();
vert[index].setWritable(writable);
vertMap.put(current.minIndex + index, vert[index]);
mesh.add(vert[index]);
index++;
}
}
fc.close();
}
catch (IOException ex)
{
logger.severe("I/O error when reading file "+getVerticesFile(oemm, current));
ex.printStackTrace();
throw new RuntimeException(ex);
}
}
/**
* Reads triangle file, create Triangle instances and store them into mesh.
*/
private void readTriangles(TIntHashSet leaves, Mesh mesh, TIntObjectHashMap<Vertex> vertMap, OEMM.Node current)
{
try
{
logger.fine("Reading "+current.tn+" triangles from "+getTrianglesFile(oemm, current));
FileChannel fc = new FileInputStream(getTrianglesFile(oemm, current)).getChannel();
Vertex [] vert = new Vertex[3];
TIntHashSet processedNode = new TIntHashSet();
int [] leaf = new int[3];
int [] pointIndex = new int[3];
mesh.ensureCapacity(current.tn);
int remaining = current.tn;
buffer.clear();
IntBuffer bbI = buffer.asIntBuffer();
for (int nblock = (remaining * TRIANGLE_SIZE) / BUFFER_SIZE; nblock >= 0; --nblock)
{
buffer.rewind();
fc.read(buffer);
bbI.rewind();
int nf = BUFFER_SIZE / TRIANGLE_SIZE;
if (remaining < nf)
nf = remaining;
remaining -= nf;
for(int nr = 0; nr < nf; nr ++)
{
boolean readable = true;
boolean writable = true;
bbI.get(leaf);
bbI.get(pointIndex);
for (int j = 0; j < 3; j++)
{
int globalIndex = oemm.leaves[leaf[j]].minIndex + pointIndex[j];
if (leaves.contains(leaf[j]))
{
vert[j] = vertMap.get(globalIndex);
assert vert[j] != null;
}
else
{
writable = false;
vert[j] = vertMap.get(globalIndex);
if (vert[j] == null) {
vert[j] = new FakeNonReadVertex(oemm, leaf[j], pointIndex[j]);
vertMap.put(globalIndex, vert[j]);
if (mapNodeToNonReadVertexList != null)
{
FakeNonReadVertex vertex = (FakeNonReadVertex) vert[j];
List<FakeNonReadVertex> vertices = mapNodeToNonReadVertexList.get(leaf[j]);
if (vertices == null) {
vertices = new ArrayList<FakeNonReadVertex>();
mapNodeToNonReadVertexList.put(leaf[j], vertices);
}
vertices.add(vertex);
}
}
}
}
// group number
int groupId = bbI.get();
createTriangle(groupId, vert, readable, writable, mesh);
// When called from buildMeshes(), cross boundary triangles are put into
// all crossed octants.
if (mapNodeToMesh != null && mapNodeToNonReadVertexList != null)
{
processedNode.clear();
for (int j = 0; j < 3; j++) {
if (vert[j] instanceof FakeNonReadVertex) {
FakeNonReadVertex fnrVertex = (FakeNonReadVertex) vert[j];
int leafIndex = fnrVertex.getOEMMIndex();
if (!processedNode.contains(leafIndex)) {
Mesh altMesh = mapNodeToMesh.get(leafIndex);
createTriangle(-leafIndex, vert, false, false, altMesh);
processedNode.add(leafIndex);
}
}
}
}
}
}
fc.close();
}
catch (IOException ex)
{
logger.severe("I/O error when reading indexed file "+getTrianglesFile(oemm, current));
ex.printStackTrace();
throw new RuntimeException(ex);
}
}
private static void createTriangle(int groupId, Vertex[] vert, boolean readable, boolean writable, Mesh mesh)
{
Triangle t = mesh.createTriangle(vert[0], vert[1], vert[2]);
t.setGroupId(groupId);
vert[0].setLink(t);
vert[1].setLink(t);
vert[2].setLink(t);
t.setReadable(readable);
t.setWritable(writable);
mesh.add(t);
}
/**
* This method reads coordinates of non-readable vertices. These vertices are
* set as readable after that.
*/
private void loadVerticesFromUnloadedNodes()
{
assert mapNodeToNonReadVertexList != null;
int lastLimit = buffer.limit();
double[] vertexBuffer = new double[3];
try {
buffer.limit(VERTEX_SIZE);
buffer.rewind();
DoubleBuffer dbb = buffer.asDoubleBuffer();
for (TIntObjectIterator<List<FakeNonReadVertex>> it = mapNodeToNonReadVertexList.iterator(); it.hasNext(); )
{
it.advance();
OEMM.Node node = oemm.leaves[it.key()];
List<FakeNonReadVertex> list = it.value();
sortFakeNonReadVertexList(list);
FileChannel fch = null;
try {
fch = new FileInputStream(getVerticesFile(oemm, node)).getChannel();
for (FakeNonReadVertex vertex: list) {
fch.position(VERTEX_SIZE * vertex.getLocalNumber());
buffer.rewind();
fch.read(buffer);
dbb.rewind();
dbb.get(vertexBuffer);
vertex.moveTo(vertexBuffer[0], vertexBuffer[1], vertexBuffer[2]);
vertex.setReadable(true);
}
} catch (FileNotFoundException e) {
logger.log(Level.SEVERE, "Cannot find file: " + getTrianglesFile(oemm, node).getAbsolutePath(), e);
throw new RuntimeException(e);
} catch (IOException e) {
logger.log(Level.SEVERE, "Cannot operate with file: " + getTrianglesFile(oemm, node).getAbsolutePath(), e);
throw new RuntimeException(e);
} finally {
if (fch != null) {
try {
fch.close();
} catch (IOException e) {
//ignore this
}
}
}
}
} finally {
buffer.limit(lastLimit);
}
}
/**
* Sorts Vertex according to their label.
*/
private static void sortFakeNonReadVertexList(List<FakeNonReadVertex> list)
{
Collections.sort(list, new Comparator<FakeNonReadVertex>()
{
public int compare(FakeNonReadVertex o1, FakeNonReadVertex o2) {
return (o1.getLabel()<o2.getLabel() ? -1 : (o1.getLabel()==o2.getLabel() ? 0 : 1));
}
});
}
}