Code cleanup, change readBitstreamToTree() to support multiple trees, first cut at new loadBitstream()

- some small tweaks to cod to match coding standard for pointers
- changed readBitstreamToTree() to handle multiple trees in a single packet
- first cut at new version of loadBitstream() which is currently not in use
This commit is contained in:
ZappoMan 2013-04-24 23:47:38 -07:00
parent 9bc2f667b6
commit 785ef88820
2 changed files with 362 additions and 41 deletions

View file

@ -5,6 +5,48 @@
// Created by Stephen Birarda on 3/13/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
//
//
// Oct-Tree in bits, and child ordinals
//
// Location Decimal Bit Child in Array
// ------------------- ------- --- ---------------
// Bottom Right Near 128 8th 0
// Bottom Right Far 64 7th 1
// Top Right Near 32 6th 2
// Top Right Far 16 5th 3
// Bottom Left Near 8 4th 4
// Bottom Left Far 4 3rd 5
// Top Left Near 2 2nd 6
// Top Left Far 1 1st 7
//
// ^
// +-------|------+ + Y
// / / | /|
// / 1 / | 16 / |
// /------/-------+ |
// / / | /|16|
// / 2 / 32 | / | |
// / / |/ | /| / +Z
// +--------------+ 32|/ | /
// | | | / | /
// | | | /| | /
// | 2 | 32 | / |64| /
// 4----| | |/ | |/
// +------|------ + | /
// | | | | /
// | | |128|/
// | 8 | 128 | /
// | | | /
// | | | /
// < - - - +------+-------+/ - - - - - - >
// +X (0,0,0) -X
// /|
// / |
// / |
// -Z / V -Y
//
//
#ifdef _WIN32
#define _USE_MATH_DEFINES
@ -117,12 +159,14 @@ VoxelNode * VoxelTree::nodeForOctalCode(VoxelNode *ancestorNode, unsigned char *
return ancestorNode;
}
VoxelNode * VoxelTree::createMissingNode(VoxelNode *lastParentNode, unsigned char *codeToReach) {
// returns the node created!
VoxelNode* VoxelTree::createMissingNode(VoxelNode* lastParentNode, unsigned char* codeToReach) {
int indexOfNewChild = branchIndexWithDescendant(lastParentNode->octalCode, codeToReach);
lastParentNode->addChildAtIndex(indexOfNewChild);
if (*lastParentNode->children[indexOfNewChild]->octalCode == *codeToReach) {
return lastParentNode;
return lastParentNode->children[indexOfNewChild];
} else {
return createMissingNode(lastParentNode->children[indexOfNewChild], codeToReach);
}
@ -134,24 +178,24 @@ VoxelNode * VoxelTree::createMissingNode(VoxelNode *lastParentNode, unsigned cha
int VoxelTree::readNodeData(VoxelNode *destinationNode,
unsigned char * nodeData,
int bytesLeftToRead) {
// instantiate variable for bytes already read
int bytesRead = 1;
for (int i = 0; i < 8; i++) {
// check the colors mask to see if we have a child to color in
if (oneAtBit(*nodeData, i)) {
// create the child if it doesn't exist
if (destinationNode->children[i] == NULL) {
destinationNode->addChildAtIndex(i);
this->voxelsCreated++;
this->voxelsCreatedStats.updateAverage(1);
}
// pull the color for this child
nodeColor newColor;
memcpy(newColor, nodeData + bytesRead, 3);
newColor[3] = 1;
destinationNode->children[i]->setColor(newColor);
this->voxelsColored++;
this->voxelsColoredStats.updateAverage(1);
@ -193,17 +237,33 @@ int VoxelTree::readNodeData(VoxelNode *destinationNode,
}
void VoxelTree::readBitstreamToTree(unsigned char * bitstream, int bufferSizeBytes) {
VoxelNode *bitstreamRootNode = nodeForOctalCode(rootNode, (unsigned char *)bitstream, NULL);
int bytesRead = 0;
// Keep looping through the buffer calling readNodeData() this allows us to pack multiple root-relative Octal codes
// into a single network packet. readNodeData() basically goes down a tree from the root, and fills things in from there
// if there are more bytes after that, it's assumed to be another root relative tree
if (*bitstream != *bitstreamRootNode->octalCode) {
// if the octal code returned is not on the same level as
// the code being searched for, we have VoxelNodes to create
bitstreamRootNode = createMissingNode(bitstreamRootNode, (unsigned char *)bitstream);
while (bytesRead < bufferSizeBytes) {
VoxelNode* bitstreamRootNode = nodeForOctalCode(rootNode, (unsigned char *)bitstream, NULL);
if (*bitstream != *bitstreamRootNode->octalCode) {
// if the octal code returned is not on the same level as
// the code being searched for, we have VoxelNodes to create
// Note: we need to create this node relative to root, because we're assuming that the bitstream for the initial
// octal code is always relative to root!
bitstreamRootNode = createMissingNode(rootNode, (unsigned char *)bitstream);
}
int octalCodeBytes = bytesRequiredForCodeLength(*bitstream);
bytesRead += octalCodeBytes;
bytesRead += readNodeData(bitstreamRootNode, bitstream + octalCodeBytes, bufferSizeBytes - octalCodeBytes);
// skip bitstream to new startPoint
bitstream += bytesRead;
}
int octalCodeBytes = bytesRequiredForCodeLength(*bitstream);
readNodeData(bitstreamRootNode, bitstream + octalCodeBytes, bufferSizeBytes - octalCodeBytes);
this->voxelsBytesRead += bufferSizeBytes;
this->voxelsBytesReadStats.updateAverage(bufferSizeBytes);
}
@ -247,12 +307,11 @@ void VoxelTree::eraseAllVoxels() {
}
void VoxelTree::readCodeColorBufferToTree(unsigned char *codeColorBuffer) {
VoxelNode *lastCreatedNode = nodeForOctalCode(rootNode, codeColorBuffer, NULL);
VoxelNode* lastCreatedNode = nodeForOctalCode(rootNode, codeColorBuffer, NULL);
// create the node if it does not exist
if (*lastCreatedNode->octalCode != *codeColorBuffer) {
VoxelNode *parentNode = createMissingNode(lastCreatedNode, codeColorBuffer);
lastCreatedNode = parentNode->children[branchIndexWithDescendant(parentNode->octalCode, codeColorBuffer)];
lastCreatedNode = createMissingNode(lastCreatedNode, codeColorBuffer);
}
// give this node its color
@ -325,15 +384,13 @@ unsigned char * VoxelTree::loadBitstreamBuffer(unsigned char *& bitstreamBuffer,
bool voxelIsClose = (distanceToVoxelCenter < boundaryDistanceForRenderLevel(*currentVoxelNode->octalCode + 1));
bool sendVoxel = voxelIsClose && voxelInView;
//printf("VoxelTree::loadBitstreamBuffer() sendVoxel=%d, voxelIsClose=%d, voxelInView=%d, viewFrustumCulling=%d\n",
// sendVoxel, voxelIsClose, voxelInView, viewFrustumCulling);
if (sendVoxel) {
// write this voxel's data if we're below or at
// or at the same level as the stopOctalCode
if (*currentVoxelNode->octalCode >= *stopOctalCode) {
if ((bitstreamBuffer - initialBitstreamPos) + MAX_TREE_SLICE_BYTES > MAX_VOXEL_PACKET_SIZE) {
// we can't send this packet, not enough room
// return our octal code as the stop
@ -359,13 +416,11 @@ unsigned char * VoxelTree::loadBitstreamBuffer(unsigned char *& bitstreamBuffer,
unsigned char *colorPointer = bitstreamBuffer + 1;
for (int i = 0; i < 8; i++) {
// Rules for including a child:
// 1) child must exists
if ((currentVoxelNode->children[i] != NULL)) {
// 2) child must have a color...
if (currentVoxelNode->children[i]->isColored()) {
unsigned char* childOctalCode = currentVoxelNode->children[i]->octalCode;
float childPosition[3];
@ -384,9 +439,6 @@ unsigned char * VoxelTree::loadBitstreamBuffer(unsigned char *& bitstreamBuffer,
childBox.setBox(glm::vec3(childPosition[0], childPosition[1], childPosition[2]),
fullChildVoxel, fullChildVoxel, fullChildVoxel);
//printf("VoxelTree::loadBitstreamBuffer() childBox.corner=(%f,%f,%f) x=%f \n",
// childBox.getCorner().x,childBox.getCorner().y,childBox.getCorner().z, childBox.getSize().x);
// XXXBHG - not sure we want to do this "distance/LOD culling" at this level.
//bool childIsClose = (distanceToChildCenter < boundaryDistanceForRenderLevel(*childOctalCode + 1));
@ -401,9 +453,6 @@ unsigned char * VoxelTree::loadBitstreamBuffer(unsigned char *& bitstreamBuffer,
// removed childIsClose - until we determine if we want to include that
bool sendChild = (childInView) || falseColorInsteadOfCulling;
//printf("VoxelTree::loadBitstreamBuffer() childIsClose=%d, childInView=%d\n",
// childIsClose, childInView);
// if we sendAnyway, we'll do false coloring of the voxels based on childIsClose && childInView
if (sendChild) {
@ -449,15 +498,13 @@ unsigned char * VoxelTree::loadBitstreamBuffer(unsigned char *& bitstreamBuffer,
? branchIndexWithDescendant(currentVoxelNode->octalCode, stopOctalCode)
: 0;
}
unsigned char * arrBufferBeforeChild = bitstreamBuffer;
for (int i = firstIndexToCheck; i < 8; i ++) {
// ask the child to load this bitstream buffer
// if they or their descendants fill the MTU we will receive the childStopOctalCode back
if (currentVoxelNode->children[i] != NULL) {
if (!oneAtBit(currentMarkerNode->childrenVisitedMask, i)) {
// create the marker node for this child if it does not yet exist
@ -488,7 +535,7 @@ unsigned char * VoxelTree::loadBitstreamBuffer(unsigned char *& bitstreamBuffer,
}
}
**** disabled *****************************************************************************************/
// ask the child to load the bitstream buffer with their data
childStopOctalCode = loadBitstreamBuffer(bitstreamBuffer,
currentVoxelNode->children[i],
@ -756,3 +803,274 @@ void VoxelTree::createSphere(float r,float xc, float yc, float zc, float s, bool
}
this->reaverageVoxelColors(this->rootNode);
}
int VoxelTree::bhgLoadBitstream(VoxelNode* node, const ViewFrustum& viewFrustum,
unsigned char*& lastOctalCode, bool& startedWriting,
unsigned char*& outputBuffer, int availableBytes) const {
// Some debugging code... where are we in the tree..
AABox box;
node->getAABox(box);
printf("bhgLoadBitstream() box.corner=(%f,%f,%f) size=%f node=",
box.getCorner().x, box.getCorner().y, box.getCorner().z,box.getSize().x);
printOctalCode(node->octalCode);
// How many bytes have we written so far at this level;
int bytesAtThisLevel = 0;
// remember our prior writing state, because we might need to do something special with this information below
bool priorWritingState = startedWriting;
// If we're at a node that is out of view, then we can return, because no nodes below us will be in view!
if (!node->isInView(viewFrustum)) {
printf("bhgLoadBitstream() node NOT IN VIEW\n");
return bytesAtThisLevel;
}
bool keepDiggingDeeper = true; // Assuming we're in view we have a great work ethic, we're always ready for more!
// At any given point in writing the bitstream, the largest minimum we might need to flesh out the current level
// is 1 byte for child colors + 3*8 bytes for the actual colors + 1 byte for child trees. There could be sub trees
// below this point, which might take many more bytes, but that's ok, because we can always mark our subtrees as
// not existing and stop the packet at this point, then start up with a new packet for the remaining sub trees.
const int CHILD_COLOR_MASK_BYTES = 1;
const int MAX_CHILDREN = 8;
const int BYTES_PER_COLOR = 3;
const int CHILD_TREE_EXISTS_BYTES = 1;
const int MAX_LEVEL_BYTES = CHILD_COLOR_MASK_BYTES + MAX_CHILDREN * BYTES_PER_COLOR + CHILD_TREE_EXISTS_BYTES;
// If we haven't yet started writing we may also need to write an octcode for this level. Which we can determine
// the size of by looking at this node's octcode.
int thisLevelOctcodeSize = startedWriting ? 0 : bytesRequiredForCodeLength(*node->octalCode);
// Some degenerate cases... If for some reason, we haven't started writing, and the available bytes left are less
// than what it would take to even write our octcode, we need to bail at this point
if (!startedWriting && (availableBytes < thisLevelOctcodeSize)) {
return bytesAtThisLevel;
}
// Make our local buffer large enough to handle writing at this level in case we need to.
unsigned char thisLevelBuffer[thisLevelOctcodeSize+MAX_LEVEL_BYTES];
unsigned char* writeToThisLevelBuffer = &thisLevelBuffer[0];
// XXXBHG - We are not yet using this. But we plan to soon... this is how we determine if we've previously sent out
// content at this level or not. In theory, if we're more shallow than where we left off, then we don't actually want
// to send data. If we're deeper than where we left off, then we want to be prepared to send data
bool sendData = compareOctalCodes(node->octalCode,lastOctalCode);
VoxelNode* inViewChildren[MAX_CHILDREN];
float distancesToChildren[MAX_CHILDREN];
int positionOfChildren[MAX_CHILDREN];
int inViewCount = 0;
int inViewNotLeafCount = 0;
int inViewWithColorCount = 0;
unsigned char childrenExistBits = 0;
unsigned char childrenColoredBits = 0;
// for each child node, check to see if they exist, are colored, and in view, and if so
// add them to our distance ordered array of children
for (int i = 0; i < MAX_CHILDREN; i++) {
VoxelNode* childNode = node->children[i];
bool childExists = (childNode != NULL);
if (childExists) {
AABox childBox;
childNode->getAABox(childBox);
printf("child[%d] childBox.corner=(%f,%f,%f) size=%f \n", i ,
childBox.getCorner().x, childBox.getCorner().y, childBox.getCorner().z, childBox.getSize().x);
}
bool childIsColored = (childExists && childNode->isColored());
bool childIsInView = (childExists && childNode->isInView(viewFrustum));
bool childIsLeaf = (childExists && childNode->isLeaf());
printf("childExists=%s childIsColored=%s childIsInView=%s childIsLeaf=%s \n",
(childExists ? "yes" : "no"), (childIsColored ? "yes" : "no"),
(childIsInView ? "yes" : "no"), (childIsLeaf ? "yes" : "no") );
// colored not important??
if (childExists && childIsInView) {
// track children in view as existing and not a leaf
if (!childIsLeaf) {
childrenExistBits += (1 << (7 - i));
inViewNotLeafCount++;
}
// track children with actual color
if (childIsColored) {
childrenColoredBits += (1 << (7 - i));
inViewWithColorCount++;
}
float distance = childNode->distanceToCamera(viewFrustum);
printf("child[%d] distance=%f\n",i,distance);
inViewCount = insertIntoSortedArrays((void*)childNode, distance, i,
(void**)&inViewChildren, (float*)&distancesToChildren, (int*)&positionOfChildren,
inViewCount, MAX_CHILDREN);
}
}
printf("bhgLoadBitstream() child nodes in view... inViewCount=%d\n",inViewCount);
printf("bhgLoadBitstream() child nodes in view with color... inViewWithColorCount=%d\n",inViewWithColorCount);
printf("bhgLoadBitstream() child nodes in view NOT LEAF... inViewNotLeafCount=%d\n",inViewNotLeafCount);
// If we have children with color, then we must go ahead and start writing codes,
// we can't dig deeper before starting to write.
bool wroteOctcodeAtThisLevel = false;
if (inViewWithColorCount && !startedWriting) {
// keep track that we've started writing
startedWriting = true;
// write the octal code
int codeLength = bytesRequiredForCodeLength(*node->octalCode);
printf("bhgLoadBitstream() writing my octal code\n");
memcpy(writeToThisLevelBuffer,node->octalCode,codeLength);
writeToThisLevelBuffer += codeLength; // move the pointer
bytesAtThisLevel += codeLength; // keep track of byte count
// remember we wrote our octcode here
wroteOctcodeAtThisLevel = true;
}
// Ok, no matter when we started writing (at this level, or above) if we've got children with color
// we need to write them here.
if (inViewWithColorCount && startedWriting) {
// write the child color bits
printf("bhgLoadBitstream() writing child color bits\n");
*writeToThisLevelBuffer = childrenColoredBits;
writeToThisLevelBuffer += sizeof(childrenColoredBits); // move the pointer
bytesAtThisLevel += sizeof(childrenColoredBits); // keep track of byte count
// write the color data...
for (int i = 0; i < MAX_CHILDREN; i++) {
if (oneAtBit(childrenColoredBits, i)) {
printf("bhgLoadBitstream() writing color for child %d\n",i);
memcpy(writeToThisLevelBuffer,&node->children[i]->getColor(),BYTES_PER_COLOR);
writeToThisLevelBuffer += BYTES_PER_COLOR; // move the pointer for color
bytesAtThisLevel += BYTES_PER_COLOR; // keep track of byte count for color
}
}
}
printf("startedWriting=%s inViewNotLeafCount=%d \n", (startedWriting ? "yes" : "no"), inViewNotLeafCount );
// If all of our IN VIEW children are LEAVES, then we're done
if (startedWriting && (0 == inViewNotLeafCount)) {
printf("bhgLoadBitstream() writing child trees exist bits of 0\n");
// write the child color bits
*writeToThisLevelBuffer = childrenExistBits;
writeToThisLevelBuffer += sizeof(childrenExistBits); // move the pointer
bytesAtThisLevel += sizeof(childrenExistBits); // keep track of byte count
keepDiggingDeeper = false;
}
// at this point we need to do a gut check. If we're writing, then we need to check how many bytes we've
// written at this level, and how many bytes we have available in the outputBuffer. If we can fit what we've got so far
// and room for the no more children terminator, then let's write what we got so far.
// If we plan to keep digging deeper, we will need at least one more byte. Otherwise, we've got all the bytes we
// need to write already written in our thisLevelBuffer. If we have room for this in our outputBuffer, then copy
// it and proceed as expected.
int potentiallyMoreBytes = keepDiggingDeeper ? CHILD_TREE_EXISTS_BYTES : 0;
printf("startedWriting=%s availableBytes=%d bytesAtThisLevel=%d keepDiggingDeeper=%s potentiallyMoreBytes=%d\n",
(startedWriting ? "yes" : "no"), availableBytes, bytesAtThisLevel,
(keepDiggingDeeper ? "yes" : "no"), potentiallyMoreBytes );
if (startedWriting && (availableBytes > (bytesAtThisLevel + potentiallyMoreBytes))) {
memcpy(outputBuffer,&thisLevelBuffer[0],bytesAtThisLevel);
availableBytes -= bytesAtThisLevel;
printf("actually write our local bytes to the outputBuffer bytesAtThisLevel=%d availableBytes=%d\n",
bytesAtThisLevel, availableBytes);
}
if (keepDiggingDeeper) {
printf("keepDiggingDeeper == TRUE... dig deeper!\n");
// at this point, we need to iterate the children who are in view, even if not colored
// and we need to determine if there's a deeper tree below them that we care about. We will iterate
// these based on which tree is closer.
//
// We have potentially a couple of states here, it's possible we haven't started writing at all. In
// which case, the lower level trees may start writing. And so when we get back here, we need to check
// our writing state, if we had not been writing, and we started writing below, then we basically want
// to start a new top level tree. We do that by simply toggling our startedWriting flag, and then call
// the child node.
//
// It's also possible that we did start writing, but we have NOT yet written our childrenExistBits. This
// is because we don't really know how big the child tree will be. What we kinda would like to do is
// write our childExistsBits as a place holder. Then let each potential tree have a go at it. If they
// write something, we keep them in the bits, if they don't, we take them out.
//
unsigned char* childExistsPlaceHolder = NULL;
bool havePlaceHolderChildBits = false;
if (startedWriting) {
// write the child tree exists "placeholder" bits.
childExistsPlaceHolder = outputBuffer;
*outputBuffer = childrenExistBits;
outputBuffer += sizeof(childrenExistBits); // move the pointer
bytesAtThisLevel += sizeof(childrenExistBits); // keep track of byte count
havePlaceHolderChildBits = true;
}
for (int i = 0; i < inViewCount; i++) {
VoxelNode* childNode = inViewChildren[i];
int childNodePosition = positionOfChildren[i];
printf("bhgLoadBitstream() calling inViewChildren[%d] startedWriting=%s\n",i, (startedWriting ? "yes" : "no") );
printf("about to dig deeper, priorWritingState=%s\n",(priorWritingState ? "yes" : "no"));
int childTreeBytesOut = bhgLoadBitstream(childNode,viewFrustum,lastOctalCode,startedWriting,
outputBuffer,availableBytes);
printf("back from dig deeper, priorWritingState=%s startedWriting=%s childTreeBytesOut=%d\n",
(priorWritingState ? "yes" : "no"), (startedWriting ? "yes" : "no"), childTreeBytesOut);
bytesAtThisLevel += childTreeBytesOut;
// If we had previously started writing, and if the child DIDN'T write any bytes,
// then we want to remove their bit from the childExistsPlaceHolder bitmask
if (havePlaceHolderChildBits && (0 == childTreeBytesOut)) {
printf("bhgLoadBitstream() child %d orig %d didn't write bytes, removing from bitmask\n",i, childNodePosition );
// remove this child's bit...
childrenExistBits -= (1 << (7 - childNodePosition));
// repair the child exists mask
*childExistsPlaceHolder = childrenExistBits;
// Note: no need to move the pointer, cause we already stored this
}
}
}
printf("bhgLoadBitstream() at bottom, returning... bytesAtThisLevel=%d\n",bytesAtThisLevel );
// If this was the level that we wrote our octcode at.. AND our prior state was not writing, THEN THIS
// is the level we want to switch back to not writing...
// If before we started this tree, we WEREN'T writing, but after iterating this tree we ARE writing
// then we need to reset writing state, so we'll start a new tree when appropriate.
if (!priorWritingState && startedWriting) {
printf("bhgLoadBitstream() at bottom and returning prior writing state was FALSE, but we did write, so... we want a new tree!\n");
startedWriting = false;
}
return bytesAtThisLevel;
}

View file

@ -17,7 +17,6 @@
const int MAX_VOXEL_PACKET_SIZE = 1492;
const int MAX_TREE_SLICE_BYTES = 26;
const int TREE_SCALE = 10;
// Callback function, for recuseTreeWithOperation
typedef bool (*RecurseVoxelTreeOperation)(VoxelNode* node, bool down, void* extraData);
@ -48,25 +47,29 @@ public:
void deleteVoxelCodeFromTree(unsigned char *codeBuffer);
void printTreeForDebugging(VoxelNode *startNode);
void reaverageVoxelColors(VoxelNode *startNode);
unsigned char * loadBitstreamBuffer(unsigned char *& bitstreamBuffer,
VoxelNode *currentVoxelNode,
MarkerNode *currentMarkerNode,
unsigned char * loadBitstreamBuffer(unsigned char*& bitstreamBuffer,
VoxelNode* currentVoxelNode,
MarkerNode* currentMarkerNode,
const glm::vec3& agentPosition,
float thisNodePosition[3],
const ViewFrustum& viewFrustum,
bool viewFrustumCulling,
unsigned char * octalCode = NULL);
unsigned char* octalCode = NULL);
void loadVoxelsFile(const char* fileName, bool wantColorRandomizer);
void createSphere(float r,float xc, float yc, float zc, float s, bool solid, bool wantColorRandomizer);
void recurseTreeWithOperation(RecurseVoxelTreeOperation operation, void* extraData=NULL);
int bhgLoadBitstream(VoxelNode* node, const ViewFrustum& viewFrustum,
unsigned char*& lastOctalCode, bool& startedWriting,
unsigned char*& outputBuffer, int availableBytes) const;
private:
void recurseNodeWithOperation(VoxelNode* node,RecurseVoxelTreeOperation operation, void* extraData);
VoxelNode * nodeForOctalCode(VoxelNode *ancestorNode, unsigned char * needleCode, VoxelNode** parentOfFoundNode);
VoxelNode * createMissingNode(VoxelNode *lastParentNode, unsigned char *deepestCodeToCreate);
int readNodeData(VoxelNode *destinationNode, unsigned char * nodeData, int bufferSizeBytes);
void recurseNodeWithOperation(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData);
VoxelNode* nodeForOctalCode(VoxelNode* ancestorNode, unsigned char* needleCode, VoxelNode** parentOfFoundNode);
VoxelNode* createMissingNode(VoxelNode* lastParentNode, unsigned char* deepestCodeToCreate);
int readNodeData(VoxelNode *destinationNode, unsigned char* nodeData, int bufferSizeBytes);
};
int boundaryDistanceForRenderLevel(unsigned int renderLevel);