I thought it would be best to post the code for storing the octree to a texture.
The octree CPU is is made up of a rootNode, which is subdivided into 8 nodes, which are subdivided into 8 nodes etc. Each node has an array of pointers to child nodes. These pointers are initally set to 0, to indicate that the node is not pointing to anything. Each node also has a stored value, which represents the average scalar value of all the scalars within the node (I’m using the octree to subdivide volume data).
It’s a lot of code, but I’m pretty sure it is correct, and the problem is in the shader side and not the texture creation.
void Octree::InitOctreeTexture(GLubyte **octreeData)
{
// Get the number of nodes in the octree
int numNodes = pow(8.0, (double)currentSubDivision) + 1;
// Number of girds stored in u v w
Su = ceil(cube_root(numNodes));
Sv = Su;
Sw = Su;
// The number of indirection grids stored in the pool
S = Vector3<float>(Su, Sv, Sw);
// Number of cells in a grid per dimension
N = 2;
// Index of the current maximum indirection gird
maxI = Vector3<int>(0, 0, 0);
// Size of the array based on S & N
const int arraySize = ( (S.x * N) * (S.y * N) * (S.z * N));// * 4;
// create array and set all values to 0
octreeData = new GLubyte*[arraySize];
for (int i = 0; i < arraySize; i++)
{
octreeData[i] = new GLubyte[4];
for (int j = 0; j < 4; j++)
{
octreeData[i][j] = 0;
}
}
// Start loading the data to the array
FillNode(octreeData, rootNode, Vector3<int>(0, 0, 0));
}
void Octree::FillNode(GLubyte **data, Node *node, Vector3<int> I)
{
Vector3<int> *offsets = new Vector3<int>(0, 0, 0);
int arrayIndex = 0;
if (node->subDivided)
{
for (int i = 0; i < 8; i++)
{
switch(i)
{
case TOP_LEFT_FRONT:
offsets = new Vector3<int>(0, 0, 0);
arrayIndex = GetArrayIndex(I, offsets);
break;
case TOP_LEFT_BACK:
offsets = new Vector3<int>(0, 0, 1);
arrayIndex = GetArrayIndex(I, offsets);
break;
case TOP_RIGHT_BACK:
offsets = new Vector3<int>(1, 0, 1);
arrayIndex = GetArrayIndex(I, offsets);
break;
case TOP_RIGHT_FRONT:
offsets = new Vector3<int>(1, 0, 0);
arrayIndex = GetArrayIndex(I, offsets);
break;
case BOTTOM_LEFT_FRONT:
offsets = new Vector3<int>(0, 1, 0);
arrayIndex = GetArrayIndex(I, offsets);
break;
case BOTTOM_LEFT_BACK:
offsets = new Vector3<int>(0, 1, 1);
arrayIndex = GetArrayIndex(I, offsets);
break;
case BOTTOM_RIGHT_BACK:
offsets = new Vector3<int>(1, 1, 1);
arrayIndex = GetArrayIndex(I, offsets);
break;
case BOTTOM_RIGHT_FRONT:
offsets = new Vector3<int>(1, 1, 0);
arrayIndex = GetArrayIndex(I, offsets);
break;
}
// This child is empty, so set the current cell to be empty
if (node->children[i] == 0)
{
data[arrayIndex][0] = (GLubyte)OCTREE_EMPTY_ALPHA;
data[arrayIndex][1] = (GLubyte)OCTREE_EMPTY_ALPHA;
data[arrayIndex][2] = (GLubyte)OCTREE_EMPTY_ALPHA;
data[arrayIndex][3] = (GLubyte)OCTREE_EMPTY_ALPHA;
}
// Else if child is subdivided, add another indirection grid
else if (node->children[i]->subDivided)
{
Vector3<int> newI = Vector3<int>(maxI.x + 1, maxI.y, maxI.z);
if (newI.x > (Su - 1))
{
newI.x = 0;
newI.y++;
if (newI.y > (Sv - 1))
{
newI.y = 0;
newI.z++;
}
}
maxI = newI;
data[arrayIndex][0] = (GLubyte)newI.x;
data[arrayIndex][1] = (GLubyte)newI.y;
data[arrayIndex][2] = (GLubyte)newI.z;
data[arrayIndex][3] = (GLubyte)OCTREE_NODE_ALPHA;
FillNode(data, node->children[i], newI);
}
// Else this child is a leaf node, add scalar value to array
else
{
data[arrayIndex][0] = (GLubyte)node->children[i]->totalScalarValue;
data[arrayIndex][1] = (GLubyte)OCTREE_EMPTY_ALPHA;
data[arrayIndex][2] = (GLubyte)OCTREE_EMPTY_ALPHA;
data[arrayIndex][3] = (GLubyte)OCTREE_LEAF_ALPHA;
}
}
}
delete offsets;
}
int Octree::GetArrayIndex(Vector3<int> I, Vector3<int> *offsets)
{
int x, y, z;
x = ( I.x * (N) ) + (offsets->x);
y = ( I.y * ( (Su * (N) ) * N) ) + (offsets->y * ( Su * (N) ) );
z = ( I.z * ( ( (Su * (N) ) * N) * Sv) ) + (offsets->z * (Su * (N)) * (Sv * N));
return x + y + z;
}