/* 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.EOFException;
import java.io.File;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.BufferedInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.ObjectInputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.RandomAccessFile;
import java.nio.IntBuffer;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.Comparator;
import java.util.List;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.set.hash.TIntHashSet;
import gnu.trove.iterator.TIntIterator;
import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.iterator.TIntObjectIterator;
import gnu.trove.map.hash.TObjectIntHashMap;
import gnu.trove.map.hash.TIntIntHashMap;
import gnu.trove.map.hash.TIntByteHashMap;
import org.jcae.mesh.amibe.ds.Mesh;
import org.jcae.mesh.amibe.ds.Triangle;
import org.jcae.mesh.amibe.ds.Vertex;
import org.jcae.mesh.amibe.ds.AbstractHalfEdge;
import org.jcae.mesh.oemm.OEMM.Node;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jcae.mesh.amibe.metrics.Location;
/**
* This class converts between disk and memory formats.
* {@link org.jcae.mesh.MeshOEMMIndex} is an example to show how to
* convert a triangle soup into an out-of-core mesh data structure.
* When an OEMM is generated on disk, octants can be loaded and unloaded
* on demand. An {@link OEMM} instance is first created by calling
* {@link #readOEMMStructure}. Then {@link MeshReader#buildMesh} can be called
* to select which octants are loaded into memory.
*
* As can be seen in {@link #readOEMMStructure}, {@link OEMM} instances
* are serialized on disk. But this is different with partial mesh data
* structure. Each child octant has a local number between 0 and 7 depending
* on its spatial localization. A similar structure is kept on disk, each
* child octant is put into a seperate directory named after its number.
* Octant leaves contain vertex and triangle data. A file with a "v" suffix
* contains coordinates of vertices stored as 3 double values. A file with
* a "a" suffix contains for each vertex the list of leaves which are
* connected to this vertex. This allows setting <code>readable</code> and
* <code>writable</code> attributes, as explained in original paper.
* A file with a "t" suffix contains for each triangle 6 int values, the first
* 3 are leaf numbers for its 3 vertices, and last 3 are local vertex number
* in their respective leaf.
*/
public class Storage
{
private static final Logger logger=Logger.getLogger(Storage.class.getName());
/**
* Number of bytes per triangle. On disk a triangle is represented by
* <ul>
* <li>3 int : leaf numbers for each vertex</li>
* <li>3 int : local vertex indices</li>
* <li>1 int : group number</li>
* </ul>
*/
protected static final int TRIANGLE_SIZE = 28;
/**
* Number of bytes per vertex. On disk a vertex is represented by 3 double
* (coordinates).
*/
protected static final int VERTEX_SIZE = 24;
/**
* Creates an {@link OEMM} instance from its disk representation.
*
* @param dir directory containing disk representation
* @return an {@link OEMM} instance
*/
public static OEMM readOEMMStructure(String dir)
{
OEMM ret = new OEMM(dir);
logger.info("Build an OEMM from "+ret.getFileName());
try
{
ObjectInputStream os = new ObjectInputStream(new FileInputStream(new File(ret.getFileName())));
ret = (OEMM) os.readObject();
// Reset nr_leaves and nr_cells because they are
// incremented by OEMM.insert()
int nrl = ((Integer) os.readObject()).intValue();
ret.clearNodes();
ret.leaves = new OEMM.Node[nrl];
for (int i = 0; i < nrl; i++)
{
OEMM.Node n = (OEMM.Node) os.readObject();
ret.insert(n);
ret.leaves[i] = n;
}
os.close();
ret.setDirectory(dir);
ret.printInfos();
}
catch (IOException ex)
{
logger.severe("I/O error when reading indexed file in "+dir);
ex.printStackTrace();
throw new RuntimeException(ex);
}
catch (ClassNotFoundException ex)
{
logger.severe("I/O error when reading indexed file in "+dir);
ex.printStackTrace();
throw new RuntimeException();
}
return ret;
}
/**
* Stores an {@link OEMM} instance to its disk representation.
*
* @param oemm stored object
*/
private static void storeOEMMStructure(OEMM oemm)
{
if (logger.isLoggable(Level.INFO)) {
logger.info("storeOEMMStructure");
}
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(new File(oemm.getFileName()))));
oos.writeObject(oemm);
oos.writeObject(Integer.valueOf(oemm.leaves.length));
for (OEMM.Node node : oemm.leaves) {
oos.writeObject(node);
}
} catch (IOException e) {
logger.severe("I/O error when reading indexed file in " + oemm.getDirectory());
e.printStackTrace();
throw new RuntimeException(e);
} finally {
if (oos != null) {
try {
oos.close();
} catch(IOException e) {
//ignore this
}
}
}
}
/**
* Saves mesh on the disk into octree structure. The o-nodes, that
* will be saved, are deduced from mesh (from position of vertices).
*
* @param oemm OEMM instance
* @param mesh mesh to be stored onto disk
* @param storedLeaves set of leaves to store
* TODO There is no support for moving vertices into another octree node.
*/
public static void saveNodes(OEMM oemm, Mesh mesh, TIntHashSet storedLeaves)
{
logger.fine("saveNodes started");
removeNonReferencedVertices(mesh);
// For each Vertex, find its enclosing octant leaf.
// Side-effect: storedLeaves may be modified if new leaves have to be added.
TObjectIntHashMap<Vertex> mapVertexToLeafindex = getMapVertexToLeafindex(oemm, mesh, storedLeaves);
storeVertices(oemm, mesh, storedLeaves, mapVertexToLeafindex);
storeTriangles(oemm, mesh, storedLeaves, mapVertexToLeafindex);
storeOEMMStructure(oemm);
logger.fine("saveNodes ended");
}
/**
* Removes vertices from mesh that are not in any triangle of mesh except vertices
* that are read only.
* @param mesh
*/
private static void removeNonReferencedVertices(Mesh mesh)
{
List<Vertex> tempCollection = new ArrayList<Vertex>();
tempCollection.addAll(mesh.getNodes());
mesh.getNodes().clear();
//actually we do not need create map (set is enough) but we index with label of vertex
// - it should be faster. Also, we could control that there is
// no different vertices with the same label.
TIntObjectHashMap<Vertex> referencedVertices = getMapLabelToVertex(mesh);
TIntHashSet processedVertIndex = new TIntHashSet(referencedVertices.size());
for (Vertex v: referencedVertices.valueCollection())
{
mesh.add(v);
processedVertIndex.add(v.getLabel());
}
for (Vertex v: tempCollection)
{
int label = v.getLabel();
if (processedVertIndex.contains(label))
continue;
processedVertIndex.add(label);
if (!v.isWritable()) {
mesh.add(v);
}
}
}
/**
* Returns a map of vertex label into vertex.
*
* @param mesh mesh
* @return a map of vertex label into vertex.
* @throws RuntimeException There are different vertices with the same label in the mesh.
*/
private static TIntObjectHashMap<Vertex> getMapLabelToVertex(Mesh mesh)
{
TIntObjectHashMap<Vertex> referencedVertices = new TIntObjectHashMap<Vertex>(mesh.getTriangles().size() / 2);
for(Triangle tr: mesh.getTriangles())
{
for (int i = 0; i < 3; i++)
{
Vertex vertex = tr.getV(i);
if (!vertex.isReadable() || vertex instanceof FakeNonReadVertex) {
continue;
}
//check that there are no different vertices with the same label
int label = vertex.getLabel();
Vertex oldVertex = referencedVertices.get(label);
if (oldVertex != vertex)
{
if (oldVertex != null)
throw new RuntimeException("There are different vertices with the same label!");
referencedVertices.put(label, vertex);
}
}
}
return referencedVertices;
}
/**
* Locates mesh vertices.
*
* @param oemm
* @param mesh
* @param storedLeaves
* @param
*/
private static TObjectIntHashMap<Vertex> getMapVertexToLeafindex(OEMM oemm, Mesh mesh, TIntHashSet storedLeaves)
{
int positions[] = new int[3];
TObjectIntHashMap<Vertex> ret = new TObjectIntHashMap<Vertex>(mesh.getNodes().size());
for(Vertex v: mesh.getNodes())
{
oemm.double2int(v, positions);
Node n = null;
try {
n = oemm.search(positions);
} catch (IllegalArgumentException e) {
//ingore this - try move vertex more closer
logger.warning("A vertex has moved and requires a new leaf to be created");
n = createNewNode(oemm, positions);
storedLeaves.add(n.leafIndex);
}
ret.put(v, n.leafIndex);
}
return ret;
}
/**
* Stores vertices of the mesh into oemm structure on the disk.
* @param oemm
* @param mesh
* @param storedLeaves set of leaves to store
*/
private static void storeVertices(OEMM oemm, Mesh mesh, TIntHashSet storedLeaves, TObjectIntHashMap<Vertex> mapVertexToLeafindex)
{
TIntObjectHashMap<ArrayList<Vertex>> mapLeafindexToVertexList = new TIntObjectHashMap<ArrayList<Vertex>>(storedLeaves.size());
byte[] byteBuffer = new byte[256];
TIntObjectHashMap<VertexIndexHolder> old2newIndex = new TIntObjectHashMap<VertexIndexHolder>();
TIntIntHashMap new2oldIndex = new TIntIntHashMap();
TIntHashSet nodes4Update = new TIntHashSet();
TIntHashSet addedNeighbour = new TIntHashSet();
TIntHashSet movedVertices = new TIntHashSet();
int[] positions = new int[3];
for (TIntIterator it = storedLeaves.iterator(); it.hasNext(); )
{
int leafIndex = it.next();
mapLeafindexToVertexList.put(leafIndex, new ArrayList<Vertex>());
}
collectAllVertices(oemm, mesh, mapVertexToLeafindex, mapLeafindexToVertexList, movedVertices);
for (TIntObjectIterator<ArrayList<Vertex>> it = mapLeafindexToVertexList.iterator(); it.hasNext();)
{
it.advance();
Node node = oemm.leaves[it.key()];
List<Vertex> vertexList = it.value();
sortVertexList(vertexList);
reindexVerticesInNode(node, vertexList, old2newIndex, new2oldIndex);
List<TIntArrayList> adjacencyFileWithoutLoadedNodes = readAdjacencyFile(oemm, node, storedLeaves);
removeLoadedAdjacentNodes(node, storedLeaves);
TIntByteHashMap nodeIndex2adjIndex = makeNodeIndex2adjIndexMap(node);
if (vertexList.size() > node.vn) {
throw new RuntimeException("Cannot add/delete vertex yet");
}
// Write vertex coordinates
DataOutputStream fc;
try {
fc = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(getVerticesFile(oemm, node))));
} catch (FileNotFoundException e) {
logger.severe("I/O error when writing file "+getVerticesFile(oemm, node));
e.printStackTrace();
throw new RuntimeException(e);
}
try {
for (Vertex vertex: vertexList)
writeDoubleArray(fc, vertex);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
try {
fc.close();
} catch (IOException ex) {
//ignore this
}
}
// Write adjacency
DataOutputStream afc = null;
try {
afc = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(getAdjacencyFile(oemm, node))));
} catch (FileNotFoundException e) {
logger.severe("I/O error when writing file "+getAdjacencyFile(oemm, node));
e.printStackTrace();
throw new RuntimeException(e);
}
try {
int counter = 0;
for (Vertex vertex: vertexList) {
assert ((counter + node.minIndex) == vertex.getLabel());
int neighbours = 0;
addedNeighbour.clear();
for (Iterator<Vertex> itnv = vertex.getNeighbourIteratorVertex(); itnv.hasNext(); )
{
Vertex neighbour = itnv.next();
if (neighbour == mesh.outerVertex)
continue;
int nodeNumber;
if (mapVertexToLeafindex.containsKey(neighbour))
nodeNumber = mapVertexToLeafindex.get(neighbour);
else
nodeNumber = searchNode(oemm, neighbour, positions);
if (nodeNumber != node.leafIndex && !addedNeighbour.contains(nodeNumber))
{
if (!nodeIndex2adjIndex.containsKey(nodeNumber))
{
//throw new UnsupportedOperationException ("Add adjacent nodes not implemented yet");
byte index = (byte) (node.adjLeaves.size() & 0xff);
node.adjLeaves.add(nodeNumber);
nodeIndex2adjIndex.put(nodeNumber, index);
}
byteBuffer[neighbours] = nodeIndex2adjIndex.get(nodeNumber);
neighbours++;
addedNeighbour.add(nodeNumber);
}
}
//add adjacent non-loaded nodes
int label = vertex.getLabel();
int lastIndex = getLaterIndex(new2oldIndex, label);
int localIndex = lastIndex - node.minIndex;
if (!movedVertices.contains(lastIndex) && localIndex < adjacencyFileWithoutLoadedNodes.size() ) {
TIntArrayList list = adjacencyFileWithoutLoadedNodes.get(localIndex);
for (int i = 0, n = list.size(); i < n; i++)
{
int adjacent = list.get(i);
if (!addedNeighbour.contains(adjacent)) {
addedNeighbour.add(adjacent);
byteBuffer[neighbours] = nodeIndex2adjIndex.get(adjacent);
neighbours++;
}
}
if (new2oldIndex.containsKey(label)) {
addRequiredNodes4Update(node, storedLeaves, nodes4Update, byteBuffer, neighbours);
}
}
afc.writeByte(neighbours);
afc.write(byteBuffer, 0, neighbours);
counter++;
}
node.vn = vertexList.size();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
try {
afc.close();
} catch (IOException ex) {
//ignore this
}
}
}
updateNonReadNodes(oemm, nodes4Update, old2newIndex);
}
/**
* It browses all nodes of the mesh and it fills map of node index to list
* of contained vertices.
* @param oemm
* @param mesh
* @param mapVertexToLeafindex
* @param mapLeafindexToVertexList
* @param movedVertices
*/
private static void collectAllVertices(OEMM oemm, Mesh mesh, TObjectIntHashMap<Vertex> mapVertexToLeafindex, TIntObjectHashMap<ArrayList<Vertex>> mapLeafindexToVertexList, TIntHashSet movedVertices)
{
for(Vertex vertex: mesh.getNodes())
{
assert mapVertexToLeafindex.containsKey(vertex);
int index = mapVertexToLeafindex.get(vertex);
assert mapLeafindexToVertexList.contains(index);
List<Vertex> vertices = mapLeafindexToVertexList.get(index);
if (vertices == null) {
throw new UnsupportedOperationException("Cannot put vertex into octree node: "+index+". Node is not loaded!");
}
Node n = oemm.leaves[index];
int label = vertex.getLabel();
if (label >= n.minIndex && label <= n.maxIndex && (label - n.minIndex + 1) > n.vn ) {
System.out.println("Vertex: " + label + " added!!!");
}
if (label < n.minIndex || label > n.maxIndex) {
//throw new RuntimeException("Cannot move vertex between leafs");
//experimental implementation
if (n.getMaxIndex() - n.minIndex < n.vn) {
throw new UnsupportedOperationException("Cannot put vertex into octree node: "
+n.leafIndex + ". It contains " + n.vn + " of vertices.");
}
if (!vertex.isWritable()) {
throw new UnsupportedOperationException("Cannot move non-writable vertices!!");
}
int newLabel = n.minIndex + n.vn;
n.vn++;
vertex.setLabel(newLabel);
movedVertices.add(newLabel);
}
vertices.add(vertex);
}
}
/**
* It makes ascending sort of list of vertices in respect of their label.
* @param list
*/
private static void sortVertexList(List<Vertex> list)
{
Collections.sort(list, new Comparator<Vertex>() {
public int compare(Vertex o1, Vertex o2) {
return (o1.getLabel()<o2.getLabel() ? -1 : (o1.getLabel()==o2.getLabel() ? 0 : 1));
}
});
}
/**
* Reindexes vertices in given oemm node. List of vertices must satisfy
* for every n in <0,n-1> this condition: label(v_n) + 1 = label(v_n + 1).
* @param node node being reindexed
* @param vertices list of vertices contained in the node
* @param old2newIndex map of old label to new label
* @param new2oldIndex map of new label to old label
*/
private static void reindexVerticesInNode(Node node, List<Vertex> vertices,
TIntObjectHashMap<VertexIndexHolder> old2newIndex,
TIntIntHashMap new2oldIndex)
{
//assert node.vn == vertices.size();
Vertex[] values = vertices.toArray(new Vertex[vertices.size()]);
vertices.clear();
int upperBound = values.length - 1;
for (int i = 0; i <= upperBound; i++) {
//there is hole in labeling. We move vertex from the tail and reindex it
while (values[i].getLabel() > node.minIndex + vertices.size())
{
Vertex moved = values[upperBound];
upperBound--;
int oldIndex = moved.getLabel();
//new index is next empty index
int newIndex = vertices.size() + node.minIndex;
old2newIndex.put(oldIndex, new VertexIndexHolder(node, newIndex));
new2oldIndex.put(newIndex, oldIndex);
moved.setLabel(newIndex);
vertices.add(moved);
}
if (i > upperBound) {
break;
}
vertices.add(values[i]);
}
}
/**
* Remove adjacent nodes for given node. It removes nodes that will be stored, because
* adjacency will be constructed again.
*
* @param node
* @param storedLeaves
*/
private static void removeLoadedAdjacentNodes(Node node, TIntHashSet storedLeaves)
{
TIntArrayList list = new TIntArrayList(node.adjLeaves);
node.adjLeaves.clear();
for (int nodeValue: list.toArray()) {
if (!storedLeaves.contains(nodeValue)) {
node.adjLeaves.add(nodeValue);
}
}
}
/**
* It add leafIndex of nodes that are not loaded but it is necessary update
* index of vertices in triangles.
* @param node
* @param storedLeaves
* @param nodes4Update
* @param byteBuffer
* @param neighbours
*/
private static void addRequiredNodes4Update(OEMM.Node node, TIntHashSet storedLeaves,
TIntHashSet nodes4Update, byte[] byteBuffer, int neighbours)
{
for (int i = 0; i < neighbours; i++) {
int nodeNumber = node.adjLeaves.get(byteBuffer[i]);
if (!storedLeaves.contains(nodeNumber)) {
nodes4Update.add(nodeNumber);
}
}
}
/**
* It updates labels of reindexed vertices in the triangle file of the non-loaded nodes.
*
* @param oemm
* @param nodes4Update
* @param old2newIndex
*/
private static void updateNonReadNodes(OEMM oemm, TIntHashSet nodes4Update, TIntObjectHashMap<VertexIndexHolder> old2newIndex)
{
int[] leaf = new int[3];
int[] localIndices = new int[3];
int maxTn = 0;
for (TIntIterator it = nodes4Update.iterator(); it.hasNext();)
{
int tn = oemm.leaves[it.next()].tn;
if (tn > maxTn)
maxTn = tn;
}
ByteBuffer bb = ByteBuffer.allocate(maxTn * TRIANGLE_SIZE);
for (TIntIterator it = nodes4Update.iterator(); it.hasNext();)
{
OEMM.Node node = oemm.leaves[it.next()];
FileChannel fc = null;
try {
fc = new RandomAccessFile(getTrianglesFile(oemm, node),"rw").getChannel();
} catch (FileNotFoundException e) {
logger.log(Level.SEVERE, "Couldn't find file " + getTrianglesFile(oemm, node), e);
throw new RuntimeException(e);
}
bb.clear();
IntBuffer bbI = bb.asIntBuffer();
try {
fc.read(bb);
} catch (IOException e) {
logger.log(Level.SEVERE, "I/O error in operation with file " + getTrianglesFile(oemm, node), e);
throw new RuntimeException(e);
}
bb.flip();
boolean fileModified = false;
for (int i = 0; i < node.tn; i++)
{
boolean modified_triangle = false;
bbI.mark();
bbI.get(leaf);
bbI.get(localIndices);
int group = bbI.get();
for (int ii = 0; ii < 3; ii++)
{
OEMM.Node oldNode = oemm.leaves[leaf[ii]];
int globalIndexOfNode = oldNode.minIndex + localIndices[ii];
if (!old2newIndex.containsKey(globalIndexOfNode)) {
assert 0 <= localIndices[ii] && localIndices[ii] <= oldNode.vn;
continue;
}
modified_triangle = true;
VertexIndexHolder newIndex = old2newIndex.get(globalIndexOfNode);
leaf[ii] = newIndex.getContainedNode().leafIndex;
localIndices[ii] = newIndex.getLocalIndex();
assert 0 <= localIndices[ii] && localIndices[ii] <= newIndex.containedNode.vn;
}
if (modified_triangle)
{
fileModified = true;
bbI.reset();
bbI.put(leaf);
bbI.put(localIndices);
bbI.put(group);
}
}
if (fileModified)
{
try {
fc.position(0L);
fc.write(bb);
} catch (IOException e) {
logger.log(Level.SEVERE, "I/O error in operation with file " + getTrianglesFile(oemm, node), e);
throw new RuntimeException(e);
}
}
try {
fc.close();
} catch (IOException e) {
//ignore this
}
}
}
/**
* Get label of vertex before reindexing. It means old label whether was reindexed,
* or present label otherwise.
* @param new2oldIndex
* @param counter new label of vertex
* @return
*/
private static int getLaterIndex(TIntIntHashMap new2oldIndex, int counter)
{
if (new2oldIndex.containsKey(counter))
return new2oldIndex.get(counter);
return counter;
}
/**
* Creates map of leafIndex of adjacent node to local index. This is done
* for specific node.
* @param node
* @return
*/
private static TIntByteHashMap makeNodeIndex2adjIndexMap(Node node)
{
TIntByteHashMap nodeIndex2adjIndex = new TIntByteHashMap(node.adjLeaves.size());
for(int i = 0; i < node.adjLeaves.size(); i++) {
nodeIndex2adjIndex.put(node.adjLeaves.get(i), (byte)(0xff & i));
}
return nodeIndex2adjIndex;
}
/**
* Stores triangles of the mesh into oemm structure on the disk.
*
* @param oemm
* @param mesh
* @param storedLeaves
*/
private static void storeTriangles(OEMM oemm, Mesh mesh, TIntHashSet storedLeaves, TObjectIntHashMap<Vertex> mapVertexToLeafindex)
{
TIntObjectHashMap<ArrayList<Triangle>> mapLeafindexToTriangleList = new TIntObjectHashMap<ArrayList<Triangle>>();
int[] leaf = new int[3];
int[] pointIndex = new int[3];
int[] positions = new int[3];
for (TIntIterator it = storedLeaves.iterator(); it.hasNext(); )
{
int leafIndex = it.next();
mapLeafindexToTriangleList.put(leafIndex, new ArrayList<Triangle>());
}
collectAllTriangles(oemm, mesh, mapVertexToLeafindex, mapLeafindexToTriangleList);
for (TIntObjectIterator<ArrayList<Triangle>> it = mapLeafindexToTriangleList.iterator(); it.hasNext();)
{
it.advance();
Node node = oemm.leaves[it.key()];
List<Triangle> triangleList = it.value();
DataOutputStream fc;
try {
fc = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(getTrianglesFile(oemm, node))));
} catch (FileNotFoundException e1) {
logger.severe("I/O error when reading indexed file "+getTrianglesFile(oemm, node));
e1.printStackTrace();
throw new RuntimeException(e1);
}
try {
for (Triangle triangle: triangleList)
{
for (int i = 0; i < 3; i++)
{
Vertex v = triangle.getV(i);
Node foundNode = oemm.leaves[searchNode(oemm, v, positions)];
leaf[i] = foundNode.leafIndex;
assert leaf[i] < oemm.leaves.length;
pointIndex[i] = v.getLabel() - foundNode.minIndex;
assert pointIndex[i] < oemm.leaves[leaf[i]].vn;
}
writeIntArray(fc, leaf);
writeIntArray(fc, pointIndex);
fc.writeInt(triangle.getGroupId());
}
node.tn = triangleList.size();
} catch (IOException e) {
logger.log(Level.SEVERE, "Error in saving to " + getTrianglesFile(oemm, node), e);
e.printStackTrace();
throw new RuntimeException(e);
} finally {
try {
fc.close();
} catch (IOException e) {
//ignore this
}
}
}
}
/**
* It browses all triangle of the mesh and it fills map of node index to list
* of contained vertices.
* @param oemm
* @param mesh
* @param mapLeafindexToTriangleList
*/
private static void collectAllTriangles(OEMM oemm, Mesh mesh, TObjectIntHashMap<Vertex> mapVertexToLeafindex, TIntObjectHashMap<ArrayList<Triangle>> mapLeafindexToTriangleList)
{
int positions[] = new int[3];
for(Triangle tr: mesh.getTriangles())
{
boolean hasOuterEdge =
mesh.outerVertex == tr.getV0() ||
mesh.outerVertex == tr.getV1() ||
mesh.outerVertex == tr.getV2();
assert hasOuterEdge == tr.hasAttributes(AbstractHalfEdge.OUTER);
if (tr.hasAttributes(AbstractHalfEdge.OUTER))
continue;
// By convention, if T=(V1,V2,V3) and each Vi is contained in node Ni,
// then T belongs to min(Ni)
int nodeNumber = Integer.MAX_VALUE;
for(int i = 0; i < tr.vertexNumber(); i++)
{
Vertex v = tr.getV(i);
int n;
if (mapVertexToLeafindex.containsKey(v))
n = mapVertexToLeafindex.get(v);
else
n = searchNode(oemm, v, positions);
if (n < nodeNumber)
nodeNumber = n;
}
List<Triangle> triangles = mapLeafindexToTriangleList.get(nodeNumber);
if (triangles == null) {
throw new UnsupportedOperationException("Cannot put triangle into octree node: "
+nodeNumber + ". Node is not loaded!");
}
triangles.add(tr);
}
}
private static File getAdjacencyFile(OEMM oemm, Node node)
{
return new File(oemm.getDirectory(), node.file+"a");
}
protected static File getTrianglesFile(OEMM oemm, Node node)
{
return new File(oemm.getDirectory(), node.file+"t");
}
protected static File getVerticesFile(OEMM oemm, OEMM.Node current)
{
return new File(oemm.getDirectory(), current.file+"v");
}
/**
* Read adjacency file and returns List of adjacent nodes without nodes
* that are loaded.
* @param oemm OEMM instance
* @param node OEMM node
* @param storedLeaves set of node indices being loaded
* @return a <code>List<TIntArrayList></code> instance; for each vertex, returns indices of adjacent
* and non-loaded nodes
*/
static List<TIntArrayList> readAdjacencyFile(OEMM oemm, Node node, TIntHashSet storedLeaves)
{
List<TIntArrayList> result = new ArrayList<TIntArrayList>();
TIntArrayList nullList = new TIntArrayList();
DataInputStream dis = null;
try {
dis= new DataInputStream(new BufferedInputStream(new FileInputStream(getAdjacencyFile(oemm, node))));
} catch (FileNotFoundException e) {
logger.log(Level.SEVERE, "Problem with opening " + getAdjacencyFile(oemm, node).getPath() + ". It may be new node.", e);
return result;
}
try {
while (true)
{
int count;
try {
count = dis.readByte();
} catch (EOFException e) {
break;
}
if (count == 0)
result.add(nullList);
else
{
TIntArrayList row = null;
for (int i = 0; i < count; i++)
{
byte adjacentLeave = dis.readByte();
int leafIndex = node.adjLeaves.get(adjacentLeave);
if (storedLeaves == null || !storedLeaves.contains(leafIndex))
{
if (row == null)
row = new TIntArrayList(count-i);
row.add(leafIndex);
}
}
if (row == null)
row = nullList;
result.add(row);
}
}
} catch (IOException e) {
logger.log(Level.SEVERE, "Problem with reading " + getAdjacencyFile(oemm, node).getPath(), e);
throw new RuntimeException(e);
} finally {
try {
dis.close();
} catch (IOException e) {
///ignore this
}
}
return result;
}
private static void getFile(OEMM oemm, Node n, StringBuilder sb)
{
if (n.parent == null) {
return;
}
getFile(oemm, n.parent, sb);
int octant = -1;
for (int i = 0; i < n.parent.child.length; i++) {
if (n == n.parent.child[i]) {
octant = i;
}
}
assert octant != -1;
sb.append(File.separatorChar).append(octant);
if (!n.isLeaf) {
new File(oemm.getDirectory() + File.separator + sb).mkdir();
}
}
private static Node createNewNode(OEMM oemm, int[] positions)
{
Node n;
logger.info("Creating new leaf node.");
n = oemm.build(positions);
n.adjLeaves = new TIntArrayList();
n.leafIndex = oemm.leaves.length;
Node prev = oemm.leaves[oemm.leaves.length - 1];
n.minIndex = prev.minIndex - prev.getMaxIndex() + prev.minIndex - 1;
n.maxIndex = n.minIndex - prev.getMaxIndex() + prev.minIndex;
StringBuilder sb = new StringBuilder();
getFile(oemm,n, sb);
n.file = sb.toString();
Node [] newLeaves = new Node[oemm.leaves.length + 1];
System.arraycopy(oemm.leaves, 0, newLeaves, 0, oemm.leaves.length + 1);
newLeaves[n.leafIndex] = n;
return n;
}
private static int searchNode(OEMM oemm, Location coords, int[] positions)
{
if (coords instanceof FakeNonReadVertex) {
return ((FakeNonReadVertex) coords).getOEMMIndex();
}
oemm.double2int(coords, positions);
return oemm.search(positions).leafIndex;
}
public static void readIntArray(DataInputStream fc, int[] buffer) throws IOException
{
for(int i = 0 ; i < buffer.length ; ++i)
buffer[i] = fc.readInt();
}
private static void writeIntArray(DataOutputStream fc, int[] pointIndex) throws IOException
{
for(int val: pointIndex)
fc.writeInt(val);
}
private static void writeDoubleArray(DataOutputStream fc, Location uv) throws IOException
{
fc.writeDouble(uv.getX());
fc.writeDouble(uv.getY());
fc.writeDouble(uv.getZ());
}
/**
* Class that helps to hold global index of vertex in local index way -
* <containing_node, local_index_in_node>
* @author koz01
*
*/
private static class VertexIndexHolder
{
OEMM.Node containedNode;
private int localIndex;
public VertexIndexHolder(OEMM.Node node, int globalIndex) {
assert node.minIndex <= globalIndex && globalIndex <= node.getMaxIndex();
containedNode = node;
localIndex = globalIndex - node.minIndex;
assert localIndex >= 0 && localIndex < node.vn;
}
public final OEMM.Node getContainedNode() {
return containedNode;
}
public final int getLocalIndex() {
return localIndex;
}
}
}