/* jCAE stand for Java Computer Aided Engineering. Features are : Small CAD
modeler, Finite element mesher, Plugin architecture.
Copyright (C) 2005, by EADS CRC
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.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Merge adjacent octree nodes.
* Children are merged if these two conditions are met: the total number of
* triangles in merged nodes does not exceed a given threshold, and levels of
* adjacent nodes do not differ more than {@link #MAX_DELTA_LEVEL}.
* This process is an optimization to have fewer octree nodes.
*/
public class Aggregate
{
private static final Logger logger=Logger.getLogger(Aggregate.class.getName());
// Maximum level difference between adjacent cells.
// With a difference of N, a node has at most
// (6*(2^N)*(2^N) + 12*(2^N) + 8) = 6*(2^N+1)*(2^N+1)+2 neighbors.
// In her paper, Cignoni takes N=3, but we consider N=2 instead so that
// upper bound is less than 256 and neighbor indices can be stored in
// byte arrays. In practice, N=3 or 4 should also work though.
private static final int MAX_DELTA_LEVEL = 2;
private static final int [] neighborOffset = {
// Face neighbors
1, 0, 0,
0, 1, 0,
0, 0, 1,
// Symmetry
-1, 0, 0,
0, -1, 0,
0, 0, -1,
// Edge neighbors
1, 1, 0,
1, 0, 1,
0, 1, 1,
1, -1, 0,
1, 0, -1,
0, 1, -1,
// Symmetry
-1, -1, 0,
-1, 0, -1,
0, -1, -1,
-1, 1, 0,
-1, 0, 1,
0, -1, 1,
// Vertex neighbors
1, 1, 1,
1, 1, -1,
1, -1, 1,
-1, 1, 1,
// Symmetry
-1, -1, -1,
-1, -1, 1,
-1, 1, -1,
1, -1, -1
};
// Initialize other neighbor-finding related arrays
// Adjacent nodes can be connected by faces (6), edges (12)
// or vertices (8).
private static final int [] neighborMask = new int[26];
private static final int [] neighborValue = new int[26];
static {
for (int i = 0; i < neighborOffset.length/3; i++)
{
neighborMask[i] = 0;
neighborValue[i] = 0;
for (int b = 2; b >= 0; b--)
{
neighborMask[i] <<= 1;
neighborValue[i] <<= 1;
if (neighborOffset[3*i+b] == 1)
neighborMask[i]++;
else if (neighborOffset[3*i+b] == -1)
{
neighborMask[i]++;
neighborValue[i]++;
}
}
}
}
/**
* Merge nodes.
*
* @param oemm OEMM instance
* @param max maximal number of triangles in merged cells
* @return total number of merged nodes
*/
public static int compute(OEMM oemm, int max)
{
logger.info("Merge cells, delta="+MAX_DELTA_LEVEL+" triangles="+max);
// Array of linked lists of non-leaf octree cells
List<OEMM.Node> [] nonLeaves = new ArrayList[OEMM.MAXLEVEL];
for (int i = 0; i < nonLeaves.length; i++)
nonLeaves[i] = new ArrayList<OEMM.Node>();
// A bottom-up traversal is the most efficient way to
// merge nodes when both conditions above are met.
// We first walk through the whole tree to compute total
// number of triangles in non-leaf nodes, and for each
// depth a linked list of non-leaf nodes
PreProcessOEMM st_proc = new PreProcessOEMM(nonLeaves);
oemm.walk(st_proc);
// Exit only after PreProcessOEMM has been called, we have to
// compute oemm.root.tn
if (MAX_DELTA_LEVEL <= 0)
return 0;
// If a cell is smaller than minCellSize() << MAX_DELTA_LEVEL
// depth of adjacent nodes can not differ more than
// MAX_DELTA_LEVEL, and checkLevelNeighbors() can safely
// be skipped. The cellSizeByHeight() method ensures that
// this variable does not overflow.
int minSize = oemm.cellSizeByHeight(MAX_DELTA_LEVEL+1);
// checkLevelNeighbors() needs a stack of OEMM.Node instances,
// allocate it here.
OEMM.Node [] nodeStack = new OEMM.Node[4*OEMM.MAXLEVEL];
int ret = 0;
for (int level = nonLeaves.length - 1; level >= 0; level--)
{
int merged = 0;
logger.fine(" Checking neighbors at level "+level);
for (OEMM.Node current: nonLeaves[level])
{
assert !current.isLeaf;
if (current.tn > max)
continue;
// This node is not a leaf and its children
// can be merged if neighbors have a difference
// level lower than MAX_DELTA_LEVEL
if (current.size < minSize || checkLevelNeighbors(current, nodeStack))
{
for (int ind = 0; ind < 8; ind++)
if (current.child[ind] != null)
merged++;
oemm.mergeChildren(current);
}
}
nonLeaves[level] = null;
logger.fine(" Merged octree cells: "+merged);
ret += merged;
}
logger.info("Octree cells merged: "+ret);
return ret;
}
private static boolean checkLevelNeighbors(OEMM.Node current, OEMM.Node [] nodeStack)
{
// If an adjacent node has a size lower than minSize, children
// nodes must not be merged
int minSize = current.size >> MAX_DELTA_LEVEL;
if (logger.isLoggable(Level.FINE))
logger.fine("Checking neighbors of "+current);
int [] ijk = new int[3];
for (int i = 0; i < neighborOffset.length/3; i++)
{
ijk[0] = current.i0 + neighborOffset[3*i] * current.size;
ijk[1] = current.j0 + neighborOffset[3*i+1] * current.size;
ijk[2] = current.k0 + neighborOffset[3*i+2] * current.size;
OEMM.Node n = OEMM.searchAdjacentNode(current, ijk);
if (n == null || n.isLeaf)
continue;
assert n.size == current.size;
if (logger.isLoggable(Level.FINE))
logger.fine("Node "+n+" contains "+Integer.toHexString(ijk[0])+" "+Integer.toHexString(ijk[1])+" " +Integer.toHexString(ijk[2]));
// We found the adjacent node with same size,
// and have now to find all its children which are
// adjacent to current node.
int pos = 0;
nodeStack[pos] = n;
while (pos >= 0)
{
OEMM.Node c = nodeStack[pos];
pos--;
if (c.tn == 0)
continue;
if (c.isLeaf)
{
if (c.size < minSize)
{
if (logger.isLoggable(Level.FINE))
logger.fine("Found too deep neighbor: "+c+" "+c.tn);
return false;
}
continue;
}
for (int ind = 0; ind < 8; ind++)
{
// Only push children on the "right"
// side, at most 4 nodes are added.
if (c.child[ind] != null && (ind & neighborMask[i]) == neighborValue[i])
{
pos++;
nodeStack[pos] = c.child[ind];
}
}
}
}
return true;
}
private static final class PreProcessOEMM extends TraversalProcedure
{
private int depth = 0;
private final List<OEMM.Node> [] nonLeaves;
public PreProcessOEMM(List<OEMM.Node> [] a)
{
nonLeaves = a;
}
@Override
public final int action(OEMM oemm, OEMM.Node current, int octant, int visit)
{
if (visit == PREORDER)
{
current.tn = 0;
nonLeaves[depth].add(current);
depth++;
}
else if (visit == POSTORDER)
{
depth--;
for (int i = 0; i < 8; i++)
if (current.child[i] != null)
current.tn += current.child[i].tn;
}
return OK;
}
}
}