Merge pull request #2721 from Atlante45/improved_undo

Handle subtrees with undo
This commit is contained in:
Brad Hefta-Gaub 2014-04-25 14:32:49 -07:00
commit b0a561198b
8 changed files with 146 additions and 34 deletions

View file

@ -1088,7 +1088,6 @@ function keyPressEvent(event) {
red: colors[whichColor].red,
green: colors[whichColor].green,
blue: colors[whichColor].blue };
Voxels.eraseVoxel(newVoxel.x, newVoxel.y, newVoxel.z, newVoxel.s);
Voxels.setVoxel(newVoxel.x, newVoxel.y, newVoxel.z, newVoxel.s, newVoxel.red, newVoxel.green, newVoxel.blue);
setAudioPosition();
initialVoxelSound.playRandom();
@ -1394,7 +1393,6 @@ function checkControllers() {
if (Vec3.length(Vec3.subtract(fingerTipPosition,lastFingerAddVoxel)) > (FINGERTIP_VOXEL_SIZE / 2)) {
newColor = { red: colors[whichColor].red, green: colors[whichColor].green, blue: colors[whichColor].blue };
Voxels.eraseVoxel(fingerTipPosition.x, fingerTipPosition.y, fingerTipPosition.z, FINGERTIP_VOXEL_SIZE);
Voxels.setVoxel(fingerTipPosition.x, fingerTipPosition.y, fingerTipPosition.z, FINGERTIP_VOXEL_SIZE,
newColor.red, newColor.green, newColor.blue);

View file

@ -542,6 +542,19 @@ OctreeElement* Octree::getOctreeElementAt(float x, float y, float z, float s) co
return node;
}
OctreeElement* Octree::getOctreeEnclosingElementAt(float x, float y, float z, float s) const {
unsigned char* octalCode = pointToOctalCode(x,y,z,s);
OctreeElement* node = nodeForOctalCode(_rootNode, octalCode, NULL);
delete[] octalCode; // cleanup memory
#ifdef HAS_AUDIT_CHILDREN
if (node) {
node->auditChildren("Octree::getOctreeElementAt()");
}
#endif // def HAS_AUDIT_CHILDREN
return node;
}
OctreeElement* Octree::getOrCreateChildElementAt(float x, float y, float z, float s) {
return getRoot()->getOrCreateChildElementAt(x, y, z, s);

View file

@ -210,7 +210,15 @@ public:
void reaverageOctreeElements(OctreeElement* startNode = NULL);
void deleteOctreeElementAt(float x, float y, float z, float s);
/// Find the voxel at position x,y,z,s
/// \return pointer to the OctreeElement or NULL if none at x,y,z,s.
OctreeElement* getOctreeElementAt(float x, float y, float z, float s) const;
/// Find the voxel at position x,y,z,s
/// \return pointer to the OctreeElement or to the smallest enclosing parent if none at x,y,z,s.
OctreeElement* getOctreeEnclosingElementAt(float x, float y, float z, float s) const;
OctreeElement* getOrCreateChildElementAt(float x, float y, float z, float s);
void recurseTreeWithOperation(RecurseOctreeOperation operation, void* extraData = NULL);

View file

@ -42,7 +42,11 @@ void VoxelTree::deleteVoxelAt(float x, float y, float z, float s) {
}
VoxelTreeElement* VoxelTree::getVoxelAt(float x, float y, float z, float s) const {
return (VoxelTreeElement*)getOctreeElementAt(x, y, z, s);
return static_cast<VoxelTreeElement*>(getOctreeElementAt(x, y, z, s));
}
VoxelTreeElement* VoxelTree::getEnclosingVoxelAt(float x, float y, float z, float s) const {
return static_cast<VoxelTreeElement*>(getOctreeEnclosingElementAt(x, y, z, s));
}
void VoxelTree::createVoxel(float x, float y, float z, float s,

View file

@ -29,7 +29,15 @@ public:
VoxelTreeElement* getRoot() { return (VoxelTreeElement*)_rootNode; }
void deleteVoxelAt(float x, float y, float z, float s);
/// Find the voxel at position x,y,z,s
/// \return pointer to the VoxelTreeElement or NULL if none at x,y,z,s.
VoxelTreeElement* getVoxelAt(float x, float y, float z, float s) const;
/// Find the voxel at position x,y,z,s
/// \return pointer to the VoxelTreeElement or to the smallest enclosing parent if none at x,y,z,s.
VoxelTreeElement* getEnclosingVoxelAt(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);

View file

@ -13,6 +13,50 @@
#include "VoxelTreeCommands.h"
struct SendVoxelsOperationArgs {
const unsigned char* newBaseOctCode;
VoxelEditPacketSender* packetSender;
};
bool sendVoxelsOperation(OctreeElement* element, void* extraData) {
VoxelTreeElement* voxel = static_cast<VoxelTreeElement*>(element);
SendVoxelsOperationArgs* args = static_cast<SendVoxelsOperationArgs*>(extraData);
if (voxel->isColored()) {
const unsigned char* nodeOctalCode = voxel->getOctalCode();
unsigned char* codeColorBuffer = NULL;
int codeLength = 0;
int bytesInCode = 0;
int codeAndColorLength;
// If the newBase is NULL, then don't rebase
if (args->newBaseOctCode) {
codeColorBuffer = rebaseOctalCode(nodeOctalCode, args->newBaseOctCode, true);
codeLength = numberOfThreeBitSectionsInCode(codeColorBuffer);
bytesInCode = bytesRequiredForCodeLength(codeLength);
codeAndColorLength = bytesInCode + SIZE_OF_COLOR_DATA;
} else {
codeLength = numberOfThreeBitSectionsInCode(nodeOctalCode);
bytesInCode = bytesRequiredForCodeLength(codeLength);
codeAndColorLength = bytesInCode + SIZE_OF_COLOR_DATA;
codeColorBuffer = new unsigned char[codeAndColorLength];
memcpy(codeColorBuffer, nodeOctalCode, bytesInCode);
}
// copy the colors over
codeColorBuffer[bytesInCode + RED_INDEX] = voxel->getColor()[RED_INDEX];
codeColorBuffer[bytesInCode + GREEN_INDEX] = voxel->getColor()[GREEN_INDEX];
codeColorBuffer[bytesInCode + BLUE_INDEX] = voxel->getColor()[BLUE_INDEX];
args->packetSender->queueVoxelEditMessage(PacketTypeVoxelSetDestructive,
codeColorBuffer, codeAndColorLength);
delete[] codeColorBuffer;
}
return true; // keep going
}
AddVoxelCommand::AddVoxelCommand(VoxelTree* tree, VoxelDetail& voxel, VoxelEditPacketSender* packetSender, QUndoCommand* parent) :
QUndoCommand("Add Voxel", parent),
_tree(tree),
@ -43,11 +87,40 @@ DeleteVoxelCommand::DeleteVoxelCommand(VoxelTree* tree, VoxelDetail& voxel, Voxe
QUndoCommand("Delete Voxel", parent),
_tree(tree),
_packetSender(packetSender),
_voxel(voxel)
_voxel(voxel),
_oldTree(NULL)
{
_tree->lockForRead();
VoxelTreeElement* element = _tree->getEnclosingVoxelAt(_voxel.x, _voxel.y, _voxel.z, _voxel.s);
if (element->getScale() == _voxel.s) {
if (!element->hasContent() && !element->isLeaf()) {
_oldTree = new VoxelTree();
_tree->copySubTreeIntoNewTree(element, _oldTree, false);
} else {
_voxel.red = element->getColor()[0];
_voxel.green = element->getColor()[1];
_voxel.blue = element->getColor()[2];
}
} else if (element->hasContent() && element->isLeaf()) {
_voxel.red = element->getColor()[0];
_voxel.green = element->getColor()[1];
_voxel.blue = element->getColor()[2];
} else {
_voxel.s = 0.0f;
qDebug() << "No element for delete.";
}
_tree->unlock();
}
DeleteVoxelCommand::~DeleteVoxelCommand() {
delete _oldTree;
}
void DeleteVoxelCommand::redo() {
if (_voxel.s == 0.0f) {
return;
}
if (_tree) {
_tree->deleteVoxelAt(_voxel.x, _voxel.y, _voxel.z, _voxel.s);
}
@ -57,10 +130,32 @@ void DeleteVoxelCommand::redo() {
}
void DeleteVoxelCommand::undo() {
if (_tree) {
_tree->createVoxel(_voxel.x, _voxel.y, _voxel.z, _voxel.s, _voxel.red, _voxel.green, _voxel.blue);
if (_voxel.s == 0.0f) {
return;
}
if (_packetSender) {
_packetSender->queueVoxelEditMessages(PacketTypeVoxelSet, 1, &_voxel);
if (_oldTree) {
VoxelTreeElement* element = _oldTree->getVoxelAt(_voxel.x, _voxel.y, _voxel.z, _voxel.s);
if (element) {
if (_tree) {
_tree->lockForWrite();
_oldTree->copySubTreeIntoNewTree(element, _tree, false);
_tree->unlock();
}
if (_packetSender) {
SendVoxelsOperationArgs args;
args.newBaseOctCode = NULL;
args.packetSender = _packetSender;
_oldTree->recurseTreeWithOperation(sendVoxelsOperation, &args);
_packetSender->releaseQueuedMessages();
}
}
} else {
if (_tree) {
_tree->createVoxel(_voxel.x, _voxel.y, _voxel.z, _voxel.s, _voxel.red, _voxel.green, _voxel.blue);
}
if (_packetSender) {
_packetSender->queueVoxelEditMessages(PacketTypeVoxelSet, 1, &_voxel);
}
}
}

View file

@ -36,6 +36,7 @@ private:
class DeleteVoxelCommand : public QUndoCommand {
public:
DeleteVoxelCommand(VoxelTree* tree, VoxelDetail& voxel, VoxelEditPacketSender* packetSender = NULL, QUndoCommand* parent = NULL);
~DeleteVoxelCommand();
virtual void redo();
virtual void undo();
@ -44,6 +45,7 @@ private:
VoxelTree* _tree;
VoxelEditPacketSender* _packetSender;
VoxelDetail _voxel;
VoxelTree* _oldTree;
};
#endif // hifi_VoxelTreeCommands_h

View file

@ -76,32 +76,16 @@ void VoxelsScriptingInterface::setVoxel(float x, float y, float z, float scale,
if (_tree) {
if (_undoStack) {
AddVoxelCommand* addCommand = new AddVoxelCommand(_tree,
addVoxelDetail,
getVoxelPacketSender());
VoxelTreeElement* deleteVoxelElement = _tree->getVoxelAt(addVoxelDetail.x, addVoxelDetail.y, addVoxelDetail.z, addVoxelDetail.s);
if (deleteVoxelElement) {
nodeColor color;
memcpy(&color, &deleteVoxelElement->getColor(), sizeof(nodeColor));
VoxelDetail deleteVoxelDetail = {addVoxelDetail.x,
addVoxelDetail.y,
addVoxelDetail.z,
addVoxelDetail.s,
color[0],
color[1],
color[2]};
DeleteVoxelCommand* delCommand = new DeleteVoxelCommand(_tree,
deleteVoxelDetail,
getVoxelPacketSender());
_undoStack->beginMacro(addCommand->text());
// As QUndoStack automatically executes redo() on push, we don't need to execute the command ourselves.
_undoStack->push(delCommand);
_undoStack->push(addCommand);
_undoStack->endMacro();
} else {
// As QUndoStack automatically executes redo() on push, we don't need to execute the command ourselves.
_undoStack->push(addCommand);
}
addVoxelDetail,
getVoxelPacketSender());
DeleteVoxelCommand* deleteCommand = new DeleteVoxelCommand(_tree,
addVoxelDetail,
getVoxelPacketSender());
_undoStack->beginMacro(addCommand->text());
// As QUndoStack automatically executes redo() on push, we don't need to execute the command ourselves.
_undoStack->push(deleteCommand);
_undoStack->push(addCommand);
_undoStack->endMacro();
} else {
// queue the destructive add
queueVoxelAdd(PacketTypeVoxelSetDestructive, addVoxelDetail);