mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-13 08:04:07 +02:00
Fixed voxel trails from animations
- removed stageForDeletion behavior of VoxelNode - replaced with VoxelNodeDeletionHook strategy - VoxelSystem now cleans up previously used VBO index slots via hook
This commit is contained in:
parent
79648ce656
commit
0c95dc4adf
7 changed files with 59 additions and 72 deletions
|
@ -58,6 +58,32 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels) :
|
|||
_tree = new VoxelTree();
|
||||
pthread_mutex_init(&_bufferWriteLock, NULL);
|
||||
pthread_mutex_init(&_treeLock, NULL);
|
||||
|
||||
_hookID = VoxelNode::addDeleteHook(voxelNodeDeleteHook, (void*)this);
|
||||
}
|
||||
|
||||
void VoxelSystem::voxelNodeDeleteHook(VoxelNode* node, void* extraData) {
|
||||
VoxelSystem* theSystem = (VoxelSystem*)extraData;
|
||||
|
||||
if (node->isKnownBufferIndex()) {
|
||||
theSystem->freeBufferIndex(node->getBufferIndex());
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelSystem::freeBufferIndex(glBufferIndex index) {
|
||||
_freeIdexes.push_back(index);
|
||||
}
|
||||
|
||||
void VoxelSystem::clearFreeBufferIndexes() {
|
||||
for (int i = 0; i < _freeIdexes.size(); i++) {
|
||||
glBufferIndex nodeIndex = _freeIdexes[i];
|
||||
glm::vec3 startVertex(FLT_MAX, FLT_MAX, FLT_MAX);
|
||||
float voxelScale = 0;
|
||||
_writeVoxelDirtyArray[nodeIndex] = true;
|
||||
nodeColor color = { 0, 0, 0, 0};
|
||||
updateNodeInArrays(nodeIndex, startVertex, voxelScale, color);
|
||||
}
|
||||
_freeIdexes.clear();
|
||||
}
|
||||
|
||||
VoxelSystem::~VoxelSystem() {
|
||||
|
@ -70,6 +96,8 @@ VoxelSystem::~VoxelSystem() {
|
|||
delete _tree;
|
||||
pthread_mutex_destroy(&_bufferWriteLock);
|
||||
pthread_mutex_destroy(&_treeLock);
|
||||
|
||||
VoxelNode::removeDeleteHook(_hookID);
|
||||
}
|
||||
|
||||
void VoxelSystem::loadVoxelsFile(const char* fileName, bool wantColorRandomizer) {
|
||||
|
@ -173,6 +201,9 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
|
|||
PerformanceWarning warn(_renderWarningsOn, "setupNewVoxelsForDrawing()"); // would like to include _voxelsInArrays, _voxelsUpdated
|
||||
uint64_t start = usecTimestampNow();
|
||||
uint64_t sinceLastTime = (start - _setupNewVoxelsForDrawingLastFinished) / 1000;
|
||||
|
||||
// clear up the VBOs for any nodes that have been recently deleted.
|
||||
clearFreeBufferIndexes();
|
||||
|
||||
bool iAmDebugging = false; // if you're debugging set this to true, so you won't get skipped for slow debugging
|
||||
if (!iAmDebugging && sinceLastTime <= std::max((float) _setupNewVoxelsForDrawingLastElapsed, SIXTY_FPS_IN_MILLISECONDS)) {
|
||||
|
@ -182,7 +213,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
|
|||
uint64_t sinceLastViewCulling = (start - _lastViewCulling) / 1000;
|
||||
// If the view frustum is no longer changing, but has changed, since last time, then remove nodes that are out of view
|
||||
if ((sinceLastViewCulling >= std::max((float) _lastViewCullingElapsed, VIEW_CULLING_RATE_IN_MILLISECONDS))
|
||||
&& !isViewChanging() && hasViewChanged()) {
|
||||
&& !isViewChanging()) {
|
||||
_lastViewCulling = start;
|
||||
|
||||
// When we call removeOutOfView() voxels, we don't actually remove the voxels from the VBOs, but we do remove
|
||||
|
@ -239,6 +270,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
|
|||
}
|
||||
|
||||
void VoxelSystem::cleanupRemovedVoxels() {
|
||||
//printf("cleanupRemovedVoxels...\n");
|
||||
PerformanceWarning warn(_renderWarningsOn, "cleanupRemovedVoxels()");
|
||||
if (!_removedVoxels.isEmpty()) {
|
||||
while (!_removedVoxels.isEmpty()) {
|
||||
|
@ -323,7 +355,7 @@ int VoxelSystem::newTreeToArrays(VoxelNode* node) {
|
|||
bool shouldRender = false; // assume we don't need to render it
|
||||
// if it's colored, we might need to render it!
|
||||
shouldRender = node->calculateShouldRender(Application::getInstance()->getViewFrustum());
|
||||
node->setShouldRender(shouldRender && !node->isStagedForDeletion());
|
||||
node->setShouldRender(shouldRender);
|
||||
// let children figure out their renderness
|
||||
if (!node->isLeaf()) {
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
|
@ -339,13 +371,6 @@ int VoxelSystem::newTreeToArrays(VoxelNode* node) {
|
|||
}
|
||||
node->clearDirtyBit(); // clear the dirty bit, do this before we potentially delete things.
|
||||
|
||||
// If the node has been asked to be deleted, but we've gotten to here, after updateNodeInArraysXXX()
|
||||
// then it means our VBOs are "clean" and our vertices have been removed or not added. So we can now
|
||||
// safely remove the node from the tree and actually delete it.
|
||||
if (node->isStagedForDeletion()) {
|
||||
_tree->deleteVoxelCodeFromTree(node->getOctalCode());
|
||||
}
|
||||
|
||||
return voxelsUpdated;
|
||||
}
|
||||
|
||||
|
@ -1111,7 +1136,7 @@ void VoxelSystem::collectStatsForTreesAndVBOs() {
|
|||
void VoxelSystem::deleteVoxelAt(float x, float y, float z, float s) {
|
||||
pthread_mutex_lock(&_treeLock);
|
||||
|
||||
_tree->deleteVoxelAt(x, y, z, s, true);
|
||||
_tree->deleteVoxelAt(x, y, z, s);
|
||||
|
||||
// redraw!
|
||||
setupNewVoxelsForDrawing(); // do we even need to do this? Or will the next network receive kick in?
|
||||
|
@ -1167,7 +1192,6 @@ struct FalseColorizeOccludedArgs {
|
|||
long nonLeaves;
|
||||
long nonLeavesOutOfView;
|
||||
long nonLeavesOccluded;
|
||||
long stagedForDeletion;
|
||||
};
|
||||
|
||||
struct FalseColorizeSubTreeOperationArgs {
|
||||
|
@ -1189,12 +1213,6 @@ bool VoxelSystem::falseColorizeOccludedOperation(VoxelNode* node, void* extraDat
|
|||
FalseColorizeOccludedArgs* args = (FalseColorizeOccludedArgs*) extraData;
|
||||
args->totalVoxels++;
|
||||
|
||||
// if this node is staged for deletion, then just return
|
||||
if (node->isStagedForDeletion()) {
|
||||
args->stagedForDeletion++;
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we are a parent, let's see if we're completely occluded.
|
||||
if (!node->isLeaf()) {
|
||||
args->nonLeaves++;
|
||||
|
@ -1275,7 +1293,6 @@ void VoxelSystem::falseColorizeOccluded() {
|
|||
args.outOfView = 0;
|
||||
args.subtreeVoxelsSkipped = 0;
|
||||
args.nonLeaves = 0;
|
||||
args.stagedForDeletion = 0;
|
||||
args.nonLeavesOutOfView = 0;
|
||||
args.nonLeavesOccluded = 0;
|
||||
args.tree = _tree;
|
||||
|
@ -1288,11 +1305,10 @@ void VoxelSystem::falseColorizeOccluded() {
|
|||
|
||||
_tree->recurseTreeWithOperationDistanceSorted(falseColorizeOccludedOperation, position, (void*)&args);
|
||||
|
||||
qDebug("falseColorizeOccluded()\n position=(%f,%f)\n total=%ld\n colored=%ld\n occluded=%ld\n notOccluded=%ld\n outOfView=%ld\n subtreeVoxelsSkipped=%ld\n stagedForDeletion=%ld\n nonLeaves=%ld\n nonLeavesOutOfView=%ld\n nonLeavesOccluded=%ld\n pointInside_calls=%ld\n occludes_calls=%ld\n intersects_calls=%ld\n",
|
||||
qDebug("falseColorizeOccluded()\n position=(%f,%f)\n total=%ld\n colored=%ld\n occluded=%ld\n notOccluded=%ld\n outOfView=%ld\n subtreeVoxelsSkipped=%ld\n nonLeaves=%ld\n nonLeavesOutOfView=%ld\n nonLeavesOccluded=%ld\n pointInside_calls=%ld\n occludes_calls=%ld\n intersects_calls=%ld\n",
|
||||
position.x, position.y,
|
||||
args.totalVoxels, args.coloredVoxels, args.occludedVoxels,
|
||||
args.notOccludedVoxels, args.outOfView, args.subtreeVoxelsSkipped,
|
||||
args.stagedForDeletion,
|
||||
args.nonLeaves, args.nonLeavesOutOfView, args.nonLeavesOccluded,
|
||||
VoxelProjectedPolygon::pointInside_calls,
|
||||
VoxelProjectedPolygon::occludes_calls,
|
||||
|
@ -1310,12 +1326,6 @@ bool VoxelSystem::falseColorizeOccludedV2Operation(VoxelNode* node, void* extraD
|
|||
FalseColorizeOccludedArgs* args = (FalseColorizeOccludedArgs*) extraData;
|
||||
args->totalVoxels++;
|
||||
|
||||
// if this node is staged for deletion, then just return
|
||||
if (node->isStagedForDeletion()) {
|
||||
args->stagedForDeletion++;
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we are a parent, let's see if we're completely occluded.
|
||||
if (!node->isLeaf()) {
|
||||
args->nonLeaves++;
|
||||
|
@ -1404,7 +1414,6 @@ void VoxelSystem::falseColorizeOccludedV2() {
|
|||
args.outOfView = 0;
|
||||
args.subtreeVoxelsSkipped = 0;
|
||||
args.nonLeaves = 0;
|
||||
args.stagedForDeletion = 0;
|
||||
args.nonLeavesOutOfView = 0;
|
||||
args.nonLeavesOccluded = 0;
|
||||
args.tree = _tree;
|
||||
|
@ -1413,11 +1422,10 @@ void VoxelSystem::falseColorizeOccludedV2() {
|
|||
|
||||
_tree->recurseTreeWithOperationDistanceSorted(falseColorizeOccludedV2Operation, position, (void*)&args);
|
||||
|
||||
qDebug("falseColorizeOccludedV2()\n position=(%f,%f)\n total=%ld\n colored=%ld\n occluded=%ld\n notOccluded=%ld\n outOfView=%ld\n subtreeVoxelsSkipped=%ld\n stagedForDeletion=%ld\n nonLeaves=%ld\n nonLeavesOutOfView=%ld\n nonLeavesOccluded=%ld\n pointInside_calls=%ld\n occludes_calls=%ld\n intersects_calls=%ld\n",
|
||||
qDebug("falseColorizeOccludedV2()\n position=(%f,%f)\n total=%ld\n colored=%ld\n occluded=%ld\n notOccluded=%ld\n outOfView=%ld\n subtreeVoxelsSkipped=%ld\n nonLeaves=%ld\n nonLeavesOutOfView=%ld\n nonLeavesOccluded=%ld\n pointInside_calls=%ld\n occludes_calls=%ld\n intersects_calls=%ld\n",
|
||||
position.x, position.y,
|
||||
args.totalVoxels, args.coloredVoxels, args.occludedVoxels,
|
||||
args.notOccludedVoxels, args.outOfView, args.subtreeVoxelsSkipped,
|
||||
args.stagedForDeletion,
|
||||
args.nonLeaves, args.nonLeavesOutOfView, args.nonLeavesOccluded,
|
||||
VoxelProjectedPolygon::pointInside_calls,
|
||||
VoxelProjectedPolygon::occludes_calls,
|
||||
|
|
|
@ -187,6 +187,13 @@ private:
|
|||
|
||||
static ProgramObject* _perlinModulateProgram;
|
||||
static GLuint _permutationNormalTextureID;
|
||||
|
||||
int _hookID;
|
||||
std::vector<glBufferIndex> _freeIdexes;
|
||||
|
||||
static void voxelNodeDeleteHook(VoxelNode* node, void* extraData);
|
||||
void freeBufferIndex(glBufferIndex index);
|
||||
void clearFreeBufferIndexes();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -50,7 +50,6 @@ void VoxelNode::init(unsigned char * octalCode) {
|
|||
_glBufferIndex = GLBUFFER_INDEX_UNKNOWN;
|
||||
_isDirty = true;
|
||||
_shouldRender = false;
|
||||
_isStagedForDeletion = false;
|
||||
markWithChangedTime();
|
||||
calculateAABox();
|
||||
}
|
||||
|
@ -159,28 +158,18 @@ VoxelNode* VoxelNode::addChildAtIndex(int childIndex) {
|
|||
}
|
||||
|
||||
// handles staging or deletion of all deep children
|
||||
void VoxelNode::safeDeepDeleteChildAtIndex(int childIndex, bool& stagedForDeletion) {
|
||||
void VoxelNode::safeDeepDeleteChildAtIndex(int childIndex) {
|
||||
VoxelNode* childToDelete = getChildAtIndex(childIndex);
|
||||
if (childToDelete) {
|
||||
// If the child is not a leaf, then call ourselves recursively on all the children
|
||||
if (!childToDelete->isLeaf()) {
|
||||
// delete all it's children
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
childToDelete->safeDeepDeleteChildAtIndex(i, stagedForDeletion);
|
||||
childToDelete->safeDeepDeleteChildAtIndex(i);
|
||||
}
|
||||
}
|
||||
// if this node has a BufferIndex then we need to stage it for deletion
|
||||
// instead of actually deleting it from the tree
|
||||
if (childToDelete->isKnownBufferIndex()) {
|
||||
stagedForDeletion = true;
|
||||
}
|
||||
if (stagedForDeletion) {
|
||||
childToDelete->stageForDeletion();
|
||||
_isDirty = true;
|
||||
} else {
|
||||
deleteChildAtIndex(childIndex);
|
||||
_isDirty = true;
|
||||
}
|
||||
deleteChildAtIndex(childIndex);
|
||||
_isDirty = true;
|
||||
markWithChangedTime();
|
||||
}
|
||||
}
|
||||
|
@ -190,7 +179,7 @@ void VoxelNode::setColorFromAverageOfChildren() {
|
|||
int colorArray[4] = {0,0,0,0};
|
||||
float density = 0.0f;
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
if (_children[i] && !_children[i]->isStagedForDeletion() && _children[i]->isColored()) {
|
||||
if (_children[i] && _children[i]->isColored()) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
colorArray[j] += _children[i]->getTrueColor()[j]; // color averaging should always be based on true colors
|
||||
}
|
||||
|
@ -279,7 +268,7 @@ bool VoxelNode::collapseIdenticalLeaves() {
|
|||
int red,green,blue;
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
// if no child, child isn't a leaf, or child doesn't have a color
|
||||
if (!_children[i] || _children[i]->isStagedForDeletion() || !_children[i]->isLeaf() || !_children[i]->isColored()) {
|
||||
if (!_children[i] || !_children[i]->isLeaf() || !_children[i]->isColored()) {
|
||||
allChildrenMatch=false;
|
||||
//qDebug("SADNESS child missing or not colored! i=%d\n",i);
|
||||
break;
|
||||
|
|
|
@ -38,7 +38,7 @@ public:
|
|||
void deleteChildAtIndex(int childIndex);
|
||||
VoxelNode* removeChildAtIndex(int childIndex);
|
||||
VoxelNode* addChildAtIndex(int childIndex);
|
||||
void safeDeepDeleteChildAtIndex(int childIndex, bool& stagedForDeletion); // handles staging or deletion of all descendents
|
||||
void safeDeepDeleteChildAtIndex(int childIndex); // handles deletion of all descendents
|
||||
|
||||
void setColorFromAverageOfChildren();
|
||||
void setRandomColor(int minimumBrightness);
|
||||
|
@ -82,10 +82,6 @@ public:
|
|||
void setShouldRender(bool shouldRender);
|
||||
bool getShouldRender() const { return _shouldRender; }
|
||||
|
||||
// Used by VoxelSystem to mark a node as to be deleted on next render pass
|
||||
void stageForDeletion() { _isStagedForDeletion = true; _isDirty = true; };
|
||||
bool isStagedForDeletion() const { return _isStagedForDeletion; }
|
||||
|
||||
#ifndef NO_FALSE_COLOR // !NO_FALSE_COLOR means, does have false color
|
||||
void setFalseColor(colorPart red, colorPart green, colorPart blue);
|
||||
void setFalseColored(bool isFalseColored);
|
||||
|
@ -127,7 +123,6 @@ private:
|
|||
bool _isDirty;
|
||||
uint64_t _lastChanged;
|
||||
bool _shouldRender;
|
||||
bool _isStagedForDeletion;
|
||||
AABox _box;
|
||||
unsigned char* _octalCode;
|
||||
VoxelNode* _children[8];
|
||||
|
|
|
@ -125,7 +125,6 @@ void VoxelNodeBag::remove(VoxelNode* node) {
|
|||
_elementsInUse--;
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelNodeBag::voxelNodeDeleteHook(VoxelNode* node, void* extraData) {
|
||||
VoxelNodeBag* theBag = (VoxelNodeBag*)extraData;
|
||||
theBag->remove(node); // note: remove can safely handle nodes that aren't in it, so we don't need to check contains()
|
||||
|
|
|
@ -311,8 +311,7 @@ int VoxelTree::readNodeData(VoxelNode* destinationNode, unsigned char* nodeData,
|
|||
// now also check the childrenInTreeMask, if the mask is missing the bit, then it means we need to delete this child
|
||||
// subtree/node, because it shouldn't actually exist in the tree.
|
||||
if (!oneAtBit(childrenInTreeMask, i) && destinationNode->getChildAtIndex(i)) {
|
||||
bool stagedForDeletion = false; // assume staging is not needed
|
||||
destinationNode->safeDeepDeleteChildAtIndex(i, stagedForDeletion);
|
||||
destinationNode->safeDeepDeleteChildAtIndex(i);
|
||||
_isDirty = true; // by definition!
|
||||
}
|
||||
}
|
||||
|
@ -366,15 +365,14 @@ void VoxelTree::readBitstreamToTree(unsigned char * bitstream, unsigned long int
|
|||
this->voxelsBytesReadStats.updateAverage(bufferSizeBytes);
|
||||
}
|
||||
|
||||
void VoxelTree::deleteVoxelAt(float x, float y, float z, float s, bool stage) {
|
||||
void VoxelTree::deleteVoxelAt(float x, float y, float z, float s) {
|
||||
unsigned char* octalCode = pointToVoxel(x,y,z,s,0,0,0);
|
||||
deleteVoxelCodeFromTree(octalCode, stage);
|
||||
deleteVoxelCodeFromTree(octalCode);
|
||||
delete[] octalCode; // cleanup memory
|
||||
}
|
||||
|
||||
class DeleteVoxelCodeFromTreeArgs {
|
||||
public:
|
||||
bool stage;
|
||||
bool collapseEmptyTrees;
|
||||
unsigned char* codeBuffer;
|
||||
int lengthOfCode;
|
||||
|
@ -384,11 +382,10 @@ public:
|
|||
|
||||
// Note: uses the codeColorBuffer format, but the color's are ignored, because
|
||||
// this only finds and deletes the node from the tree.
|
||||
void VoxelTree::deleteVoxelCodeFromTree(unsigned char* codeBuffer, bool stage, bool collapseEmptyTrees) {
|
||||
void VoxelTree::deleteVoxelCodeFromTree(unsigned char* codeBuffer, bool collapseEmptyTrees) {
|
||||
// recurse the tree while decoding the codeBuffer, once you find the node in question, recurse
|
||||
// back and implement color reaveraging, and marking of lastChanged
|
||||
DeleteVoxelCodeFromTreeArgs args;
|
||||
args.stage = stage;
|
||||
args.collapseEmptyTrees = collapseEmptyTrees;
|
||||
args.codeBuffer = codeBuffer;
|
||||
args.lengthOfCode = numberOfThreeBitSectionsInCode(codeBuffer);
|
||||
|
@ -408,7 +405,6 @@ void VoxelTree::deleteVoxelCodeFromTreeRecursion(VoxelNode* node, void* extraDat
|
|||
// matches, then we've reached our target node.
|
||||
if (lengthOfNodeCode == args->lengthOfCode) {
|
||||
// we've reached our target, depending on how we're called we may be able to operate on it
|
||||
// if we're in "stage" mode, then we can could have the node staged, otherwise we can't really delete
|
||||
// it here, we need to recurse up, and delete it there. So we handle these cases the same to keep
|
||||
// the logic consistent.
|
||||
args->deleteLastChild = true;
|
||||
|
@ -468,11 +464,7 @@ void VoxelTree::deleteVoxelCodeFromTreeRecursion(VoxelNode* node, void* extraDat
|
|||
|
||||
// If the lower level determined it needs to be deleted, then we should delete now.
|
||||
if (args->deleteLastChild) {
|
||||
if (args->stage) {
|
||||
childNode->stageForDeletion();
|
||||
} else {
|
||||
node->deleteChildAtIndex(childIndex); // note: this will track dirtiness and lastChanged for this node
|
||||
}
|
||||
node->deleteChildAtIndex(childIndex); // note: this will track dirtiness and lastChanged for this node
|
||||
|
||||
// track our tree dirtiness
|
||||
_isDirty = true;
|
||||
|
@ -602,7 +594,7 @@ void VoxelTree::processRemoveVoxelBitstream(unsigned char * bitstream, int buffe
|
|||
int codeLength = numberOfThreeBitSectionsInCode(voxelCode);
|
||||
int voxelDataSize = bytesRequiredForCodeLength(codeLength) + SIZE_OF_COLOR_DATA;
|
||||
|
||||
deleteVoxelCodeFromTree(voxelCode, ACTUALLY_DELETE, COLLAPSE_EMPTY_TREE);
|
||||
deleteVoxelCodeFromTree(voxelCode, COLLAPSE_EMPTY_TREE);
|
||||
|
||||
voxelCode+=voxelDataSize;
|
||||
atByte+=voxelDataSize;
|
||||
|
|
|
@ -27,8 +27,6 @@ typedef enum {GRADIENT, RANDOM, NATURAL} creationMode;
|
|||
#define NO_COLOR false
|
||||
#define WANT_COLOR true
|
||||
#define IGNORE_VIEW_FRUSTUM NULL
|
||||
#define JUST_STAGE_DELETION true
|
||||
#define ACTUALLY_DELETE false
|
||||
#define COLLAPSE_EMPTY_TREE true
|
||||
#define DONT_COLLAPSE false
|
||||
#define NO_OCCLUSION_CULLING false
|
||||
|
@ -113,12 +111,11 @@ public:
|
|||
bool includeColor = WANT_COLOR, bool includeExistsBits = WANT_EXISTS_BITS,
|
||||
VoxelNode* destinationNode = NULL);
|
||||
void readCodeColorBufferToTree(unsigned char* codeColorBuffer, bool destructive = false);
|
||||
void deleteVoxelCodeFromTree(unsigned char* codeBuffer, bool stage = ACTUALLY_DELETE,
|
||||
bool collapseEmptyTrees = DONT_COLLAPSE);
|
||||
void deleteVoxelCodeFromTree(unsigned char* codeBuffer, bool collapseEmptyTrees = DONT_COLLAPSE);
|
||||
void printTreeForDebugging(VoxelNode* startNode);
|
||||
void reaverageVoxelColors(VoxelNode* startNode);
|
||||
|
||||
void deleteVoxelAt(float x, float y, float z, float s, bool stage = false);
|
||||
void deleteVoxelAt(float x, float y, float z, float s);
|
||||
VoxelNode* getVoxelAt(float x, float y, float z, float s) const;
|
||||
void createVoxel(float x, float y, float z, float s,
|
||||
unsigned char red, unsigned char green, unsigned char blue, bool destructive = false);
|
||||
|
|
Loading…
Reference in a new issue