diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index d9abba9950..ec0cf06dc6 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -202,7 +202,7 @@ QRgbAttribute::QRgbAttribute(const QString& name, QRgb defaultValue) : InlineAttribute(name, defaultValue) { } -bool QRgbAttribute::merge(void*& parent, void* children[]) const { +bool QRgbAttribute::merge(void*& parent, void* children[], bool postRead) const { QRgb firstValue = decodeInline(children[0]); int totalAlpha = qAlpha(firstValue); int totalRed = qRed(firstValue) * totalAlpha; @@ -261,7 +261,7 @@ PackedNormalAttribute::PackedNormalAttribute(const QString& name, QRgb defaultVa QRgbAttribute(name, defaultValue) { } -bool PackedNormalAttribute::merge(void*& parent, void* children[]) const { +bool PackedNormalAttribute::merge(void*& parent, void* children[], bool postRead) const { QRgb firstValue = decodeInline(children[0]); glm::vec3 total = unpackNormal(firstValue) * (float)qAlpha(firstValue); bool allChildrenEqual = true; @@ -270,7 +270,8 @@ bool PackedNormalAttribute::merge(void*& parent, void* children[]) const { total += unpackNormal(value) * (float)qAlpha(value); allChildrenEqual &= (firstValue == value); } - parent = encodeInline(packNormal(glm::normalize(total))); + float length = glm::length(total); + parent = encodeInline(length < EPSILON ? QRgb() : packNormal(total / length)); return allChildrenEqual; } @@ -310,13 +311,37 @@ MetavoxelNode* SpannerQRgbAttribute::createMetavoxelNode( return new MetavoxelNode(value, original); } -bool SpannerQRgbAttribute::merge(void*& parent, void* children[]) const { - for (int i = 0; i < MERGE_COUNT; i++) { - if (qAlpha(decodeInline(children[i])) != 0) { - return false; +bool SpannerQRgbAttribute::merge(void*& parent, void* children[], bool postRead) const { + if (postRead) { + for (int i = 0; i < MERGE_COUNT; i++) { + if (qAlpha(decodeInline(children[i])) != 0) { + return false; + } } + return true; } - return true; + QRgb parentValue = decodeInline(parent); + int totalAlpha = qAlpha(parentValue) * Attribute::MERGE_COUNT; + int totalRed = qRed(parentValue) * totalAlpha; + int totalGreen = qGreen(parentValue) * totalAlpha; + int totalBlue = qBlue(parentValue) * totalAlpha; + bool allChildrenTransparent = true; + for (int i = 0; i < Attribute::MERGE_COUNT; i++) { + QRgb value = decodeInline(children[i]); + int alpha = qAlpha(value); + totalRed += qRed(value) * alpha; + totalGreen += qGreen(value) * alpha; + totalBlue += qBlue(value) * alpha; + totalAlpha += alpha; + allChildrenTransparent &= (alpha == 0); + } + if (totalAlpha == 0) { + parent = encodeInline(QRgb()); + } else { + parent = encodeInline(qRgba(totalRed / totalAlpha, totalGreen / totalAlpha, + totalBlue / totalAlpha, totalAlpha / MERGE_COUNT)); + } + return allChildrenTransparent; } AttributeValue SpannerQRgbAttribute::inherit(const AttributeValue& parentValue) const { @@ -341,13 +366,27 @@ MetavoxelNode* SpannerPackedNormalAttribute::createMetavoxelNode( return new MetavoxelNode(value, original); } -bool SpannerPackedNormalAttribute::merge(void*& parent, void* children[]) const { - for (int i = 0; i < MERGE_COUNT; i++) { - if (qAlpha(decodeInline(children[i])) != 0) { - return false; +bool SpannerPackedNormalAttribute::merge(void*& parent, void* children[], bool postRead) const { + if (postRead) { + for (int i = 0; i < MERGE_COUNT; i++) { + if (qAlpha(decodeInline(children[i])) != 0) { + return false; + } } + return true; } - return true; + QRgb parentValue = decodeInline(parent); + glm::vec3 total = unpackNormal(parentValue) * (float)(qAlpha(parentValue) * Attribute::MERGE_COUNT); + bool allChildrenTransparent = true; + for (int i = 0; i < Attribute::MERGE_COUNT; i++) { + QRgb value = decodeInline(children[i]); + int alpha = qAlpha(value); + total += unpackNormal(value) * (float)alpha; + allChildrenTransparent &= (alpha == 0); + } + float length = glm::length(total); + parent = encodeInline(length < EPSILON ? QRgb() : packNormal(total / length)); + return allChildrenTransparent; } AttributeValue SpannerPackedNormalAttribute::inherit(const AttributeValue& parentValue) const { @@ -373,7 +412,7 @@ void SharedObjectAttribute::write(Bitstream& out, void* value, bool isLeaf) cons } } -bool SharedObjectAttribute::merge(void*& parent, void* children[]) const { +bool SharedObjectAttribute::merge(void*& parent, void* children[], bool postRead) const { SharedObjectPointer firstChild = decodeInline(children[0]); for (int i = 1; i < MERGE_COUNT; i++) { if (firstChild != decodeInline(children[i])) { @@ -413,7 +452,7 @@ MetavoxelNode* SharedObjectSetAttribute::createMetavoxelNode( return new MetavoxelNode(value, original); } -bool SharedObjectSetAttribute::merge(void*& parent, void* children[]) const { +bool SharedObjectSetAttribute::merge(void*& parent, void* children[], bool postRead) const { for (int i = 0; i < MERGE_COUNT; i++) { if (!decodeInline(children[i]).isEmpty()) { return false; diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index 67388b4cdb..28a110467c 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -115,7 +115,6 @@ public: template void setInlineValue(T value) { _value = encodeInline(value); } template T getInlineValue() const { return decodeInline(_value); } - template T getSafeInlineValue() const { return _attribute ? decodeInline(_value) : T(); } void* copy() const; @@ -205,8 +204,9 @@ public: virtual bool equal(void* first, void* second) const = 0; /// Merges the value of a parent and its children. + /// \param postRead whether or not the merge is happening after a read /// \return whether or not the children and parent values are all equal - virtual bool merge(void*& parent, void* children[]) const = 0; + virtual bool merge(void*& parent, void* children[], bool postRead = false) const = 0; /// Given the parent value, returns the value that children should inherit (either the parent value or the default). virtual AttributeValue inherit(const AttributeValue& parentValue) const { return parentValue; } @@ -274,7 +274,7 @@ public: Q_INVOKABLE QRgbAttribute(const QString& name = QString(), QRgb defaultValue = QRgb()); - virtual bool merge(void*& parent, void* children[]) const; + virtual bool merge(void*& parent, void* children[], bool postRead = false) const; virtual void* mix(void* first, void* second, float alpha) const; @@ -293,7 +293,7 @@ public: Q_INVOKABLE PackedNormalAttribute(const QString& name = QString(), QRgb defaultValue = QRgb()); - virtual bool merge(void*& parent, void* children[]) const; + virtual bool merge(void*& parent, void* children[], bool postRead = false) const; virtual void* mix(void* first, void* second, float alpha) const; }; @@ -317,7 +317,7 @@ public: virtual MetavoxelNode* createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const; - virtual bool merge(void*& parent, void* children[]) const; + virtual bool merge(void*& parent, void* children[], bool postRead = false) const; virtual AttributeValue inherit(const AttributeValue& parentValue) const; }; @@ -335,7 +335,7 @@ public: virtual MetavoxelNode* createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const; - virtual bool merge(void*& parent, void* children[]) const; + virtual bool merge(void*& parent, void* children[], bool postRead = false) const; virtual AttributeValue inherit(const AttributeValue& parentValue) const; }; @@ -354,7 +354,7 @@ public: virtual void read(Bitstream& in, void*& value, bool isLeaf) const; virtual void write(Bitstream& out, void* value, bool isLeaf) const; - virtual bool merge(void*& parent, void* children[]) const; + virtual bool merge(void*& parent, void* children[], bool postRead = false) const; virtual void* createFromVariant(const QVariant& value) const; @@ -382,7 +382,7 @@ public: virtual MetavoxelNode* createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const; - virtual bool merge(void*& parent, void* children[]) const; + virtual bool merge(void*& parent, void* children[], bool postRead = false) const; virtual AttributeValue inherit(const AttributeValue& parentValue) const; diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index ed1de6b4ad..aae4d7761f 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -85,7 +85,7 @@ void MetavoxelData::guide(MetavoxelVisitor& visitor) { const QVector& inputs = visitor.getInputs(); const QVector& outputs = visitor.getOutputs(); MetavoxelVisitation firstVisitation = { NULL, visitor, QVector(inputs.size() + 1), - QVector(outputs.size()), { getMinimum(), _size, + QVector(outputs.size()), { NULL, getMinimum(), _size, QVector(inputs.size() + 1), QVector(outputs.size()) } }; for (int i = 0; i < inputs.size(); i++) { MetavoxelNode* node = _roots.value(inputs.at(i)); @@ -169,7 +169,7 @@ template int SpannerUpdateVisitor::visit(MetavoxelIn if (info.size > _longestSide) { return DEFAULT_ORDER; } - SharedObjectSet set = info.inputValues.at(0).getSafeInlineValue(); + SharedObjectSet set = info.inputValues.at(0).getInlineValue(); F(set, _object); info.outputValues[0] = AttributeValue(_attribute, encodeInline(set)); return STOP_RECURSION; @@ -523,14 +523,17 @@ AttributeValue MetavoxelNode::getAttributeValue(const AttributePointer& attribut return AttributeValue(attribute, _attributeValue); } -void MetavoxelNode::mergeChildren(const AttributePointer& attribute) { +void MetavoxelNode::mergeChildren(const AttributePointer& attribute, bool postRead) { + if (isLeaf()) { + return; + } void* childValues[CHILD_COUNT]; bool allLeaves = true; for (int i = 0; i < CHILD_COUNT; i++) { childValues[i] = _children[i]->_attributeValue; allLeaves &= _children[i]->isLeaf(); } - if (attribute->merge(_attributeValue, childValues) && allLeaves) { + if (attribute->merge(_attributeValue, childValues, postRead) && allLeaves) { clearChildren(attribute); } } @@ -562,7 +565,7 @@ void MetavoxelNode::read(MetavoxelStreamState& state) { _children[i] = new MetavoxelNode(state.attribute); _children[i]->read(nextState); } - mergeChildren(state.attribute); + mergeChildren(state.attribute, true); } } @@ -620,7 +623,7 @@ void MetavoxelNode::readDelta(const MetavoxelNode& reference, MetavoxelStreamSta } } } - mergeChildren(state.attribute); + mergeChildren(state.attribute, true); } } @@ -887,7 +890,7 @@ void SpannerVisitor::prepare() { int SpannerVisitor::visit(MetavoxelInfo& info) { for (int i = _inputs.size() - _spannerInputCount; i < _inputs.size(); i++) { - foreach (const SharedObjectPointer& object, info.inputValues.at(i).getSafeInlineValue()) { + foreach (const SharedObjectPointer& object, info.inputValues.at(i).getInlineValue()) { Spanner* spanner = static_cast(object.data()); if (spanner->testAndSetVisited()) { if (!visit(spanner)) { @@ -939,7 +942,7 @@ bool operator<(const SpannerDistance& first, const SpannerDistance& second) { int RaySpannerIntersectionVisitor::visit(MetavoxelInfo& info, float distance) { QVarLengthArray spannerDistances; for (int i = _inputs.size() - _spannerInputCount; i < _inputs.size(); i++) { - foreach (const SharedObjectPointer& object, info.inputValues.at(i).getSafeInlineValue()) { + foreach (const SharedObjectPointer& object, info.inputValues.at(i).getInlineValue()) { Spanner* spanner = static_cast(object.data()); if (spanner->testAndSetVisited()) { SpannerDistance spannerDistance = { spanner }; @@ -989,7 +992,7 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) { } MetavoxelVisitation nextVisitation = { &visitation, visitation.visitor, QVector(visitation.inputNodes.size()), QVector(visitation.outputNodes.size()), - { glm::vec3(), visitation.info.size * 0.5f, QVector(visitation.inputNodes.size()), + { &visitation.info, glm::vec3(), visitation.info.size * 0.5f, QVector(visitation.inputNodes.size()), QVector(visitation.outputNodes.size()) } }; for (int i = 0; i < MetavoxelNode::CHILD_COUNT; i++) { // the encoded order tells us the child indices for each iteration @@ -1107,7 +1110,7 @@ QScriptValue ScriptedMetavoxelGuide::visit(QScriptContext* context, QScriptEngin QScriptValue infoValue = context->argument(0); QScriptValue minimum = infoValue.property(guide->_minimumHandle); MetavoxelInfo info = { - glm::vec3(minimum.property(0).toNumber(), minimum.property(1).toNumber(), minimum.property(2).toNumber()), + NULL, glm::vec3(minimum.property(0).toNumber(), minimum.property(1).toNumber(), minimum.property(2).toNumber()), (float)infoValue.property(guide->_sizeHandle).toNumber(), guide->_visitation->info.inputValues, guide->_visitation->info.outputValues, infoValue.property(guide->_isLeafHandle).toBool() }; @@ -1242,7 +1245,7 @@ const QVector& Spanner::getVoxelizedAttributes() const { return emptyVector; } -bool Spanner::getAttributeValues(MetavoxelInfo& info) const { +bool Spanner::getAttributeValues(MetavoxelInfo& info, bool force) const { return false; } @@ -1350,10 +1353,10 @@ const QVector& Sphere::getVoxelizedAttributes() const { return attributes; } -bool Sphere::getAttributeValues(MetavoxelInfo& info) const { +bool Sphere::getAttributeValues(MetavoxelInfo& info, bool force) const { // bounds check Box bounds = info.getBounds(); - if (!getBounds().intersects(bounds)) { + if (!(force || getBounds().intersects(bounds))) { return false; } // count the points inside the sphere @@ -1366,15 +1369,16 @@ bool Sphere::getAttributeValues(MetavoxelInfo& info) const { if (pointsWithin == Box::VERTEX_COUNT) { // entirely contained info.outputValues[0] = AttributeValue(getAttributes().at(0), encodeInline(_color.rgba())); - getNormal(info); + info.outputValues[1] = getNormal(info, _color.alpha()); return false; } - if (info.size <= getVoxelizationGranularity()) { + if (force || info.size <= getVoxelizationGranularity()) { // best guess if (pointsWithin > 0) { + int alpha = _color.alpha() * pointsWithin / Box::VERTEX_COUNT; info.outputValues[0] = AttributeValue(getAttributes().at(0), encodeInline(qRgba( - _color.red(), _color.green(), _color.blue(), _color.alpha() * pointsWithin / Box::VERTEX_COUNT))); - getNormal(info); + _color.red(), _color.green(), _color.blue(), alpha))); + info.outputValues[1] = getNormal(info, alpha); } return false; } @@ -1397,19 +1401,23 @@ bool Sphere::blendAttributeValues(MetavoxelInfo& info, bool force) const { if (pointsWithin == Box::VERTEX_COUNT) { // entirely contained info.outputValues[0] = AttributeValue(getAttributes().at(0), encodeInline(_color.rgba())); - info.outputValues[1] = getNormal(info); + info.outputValues[1] = getNormal(info, _color.alpha()); return false; } if (force || info.size <= getVoxelizationGranularity()) { // best guess if (pointsWithin > 0) { - int oldAlpha = qAlpha(info.inputValues.at(0).getInlineValue()); + const AttributeValue& oldColor = info.outputValues.at(0).getAttribute() ? + info.outputValues.at(0) : info.inputValues.at(0); + const AttributeValue& oldNormal = info.outputValues.at(1).getAttribute() ? + info.outputValues.at(1) : info.inputValues.at(1); + int oldAlpha = qAlpha(oldColor.getInlineValue()); int newAlpha = _color.alpha() * pointsWithin / Box::VERTEX_COUNT; float combinedAlpha = (float)newAlpha / (oldAlpha + newAlpha); - info.outputValues[0].mix(info.inputValues.at(0), AttributeValue(getAttributes().at(0), - encodeInline(qRgba(_color.red(), _color.green(), _color.blue(), - _color.alpha() * pointsWithin / Box::VERTEX_COUNT))), combinedAlpha); - info.outputValues[1].mix(info.inputValues.at(1), getNormal(info), combinedAlpha); + int baseAlpha = _color.alpha() * pointsWithin / Box::VERTEX_COUNT; + info.outputValues[0].mix(oldColor, AttributeValue(getAttributes().at(0), + encodeInline(qRgba(_color.red(), _color.green(), _color.blue(), baseAlpha))), combinedAlpha); + info.outputValues[1].mix(oldNormal, getNormal(info, baseAlpha), combinedAlpha); } return false; } @@ -1429,20 +1437,19 @@ void Sphere::updateBounds() { setBounds(Box(getTranslation() - extent, getTranslation() + extent)); } -AttributeValue Sphere::getNormal(MetavoxelInfo& info) const { +AttributeValue Sphere::getNormal(MetavoxelInfo& info, int alpha) const { glm::vec3 normal = info.getCenter() - getTranslation(); float length = glm::length(normal); QRgb color; - if (length > EPSILON) { + if (alpha != 0 && length > EPSILON) { const float NORMAL_SCALE = 127.0f; float scale = NORMAL_SCALE / length; const int BYTE_MASK = 0xFF; - color = qRgb((int)(normal.x * scale) & BYTE_MASK, (int)(normal.y * scale) & BYTE_MASK, - (int)(normal.z * scale) & BYTE_MASK); + color = qRgba((int)(normal.x * scale) & BYTE_MASK, (int)(normal.y * scale) & BYTE_MASK, + (int)(normal.z * scale) & BYTE_MASK, alpha); } else { - const QRgb DEFAULT_NORMAL = 0x007F00; - color = DEFAULT_NORMAL; + color = QRgb(); } return AttributeValue(getAttributes().at(1), encodeInline(color)); } diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index 85ff2d0850..fd7ce437cc 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -140,7 +140,7 @@ public: AttributeValue getAttributeValue(const AttributePointer& attribute) const; void* getAttributeValue() const { return _attributeValue; } - void mergeChildren(const AttributePointer& attribute); + void mergeChildren(const AttributePointer& attribute, bool postRead = false); MetavoxelNode* getChild(int index) const { return _children[index]; } void setChild(int index, MetavoxelNode* child) { _children[index] = child; } @@ -185,6 +185,7 @@ private: class MetavoxelInfo { public: + MetavoxelInfo* parentInfo; glm::vec3 minimum; ///< the minimum extent of the area covered by the voxel float size; ///< the size of the voxel in all dimensions QVector inputValues; @@ -436,7 +437,7 @@ public: /// Sets the attribute values associated with this spanner in the supplied info. /// \return true to recurse, false to stop - virtual bool getAttributeValues(MetavoxelInfo& info) const; + virtual bool getAttributeValues(MetavoxelInfo& info, bool force = false) const; /// Blends the attribute values associated with this spanner into the supplied info. /// \param force if true, blend even if we would normally subdivide @@ -536,7 +537,7 @@ public: virtual const QVector& getAttributes() const; virtual const QVector& getVoxelizedAttributes() const; - virtual bool getAttributeValues(MetavoxelInfo& info) const; + virtual bool getAttributeValues(MetavoxelInfo& info, bool force = false) const; virtual bool blendAttributeValues(MetavoxelInfo& info, bool force = false) const; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; @@ -554,7 +555,7 @@ private slots: private: - AttributeValue getNormal(MetavoxelInfo& info) const; + AttributeValue getNormal(MetavoxelInfo& info, int alpha) const; QColor _color; }; diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index 46924f8af8..59b643a249 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -104,42 +104,64 @@ InsertSpannerEdit::InsertSpannerEdit(const AttributePointer& attribute, const Sh spanner(spanner) { } -class InsertSpannerEditVisitor : public MetavoxelVisitor { +class UpdateSpannerVisitor : public MetavoxelVisitor { public: - InsertSpannerEditVisitor(const QVector& attributes, Spanner* spanner); + UpdateSpannerVisitor(const QVector& attributes, Spanner* spanner); virtual int visit(MetavoxelInfo& info); private: Spanner* _spanner; - float _longestSide; + float _voxelizationSize; + int _steps; }; -InsertSpannerEditVisitor::InsertSpannerEditVisitor(const QVector& attributes, Spanner* spanner) : - MetavoxelVisitor(attributes, attributes), +UpdateSpannerVisitor::UpdateSpannerVisitor(const QVector& attributes, Spanner* spanner) : + MetavoxelVisitor(QVector() << attributes << AttributeRegistry::getInstance()->getSpannersAttribute(), + attributes), _spanner(spanner), - _longestSide(qMax(spanner->getBounds().getLongestSide(), spanner->getPlacementGranularity()) * 2.0f / - AttributeRegistry::getInstance()->getSpannersAttribute()->getLODThresholdMultiplier()) { + _voxelizationSize(qMax(spanner->getBounds().getLongestSide(), spanner->getPlacementGranularity()) * 2.0f / + AttributeRegistry::getInstance()->getSpannersAttribute()->getLODThresholdMultiplier()), + _steps(roundf(logf(AttributeRegistry::getInstance()->getSpannersAttribute()->getLODThresholdMultiplier()) / + logf(2.0f) - 2.0f)) { } -int InsertSpannerEditVisitor::visit(MetavoxelInfo& info) { +int UpdateSpannerVisitor::visit(MetavoxelInfo& info) { if (!info.getBounds().intersects(_spanner->getBounds())) { return STOP_RECURSION; } - if (info.size > _longestSide) { - return DEFAULT_ORDER; + MetavoxelInfo* parentInfo = info.parentInfo; + for (int i = 0; i < _steps && parentInfo; i++) { + parentInfo = parentInfo->parentInfo; } - _spanner->blendAttributeValues(info, true); - return STOP_RECURSION; + if (!parentInfo) { + for (int i = 0; i < _outputs.size(); i++) { + info.outputValues[i] = AttributeValue(_outputs.at(i)); + } + return (info.size > _voxelizationSize) ? DEFAULT_ORDER : STOP_RECURSION; + } + SharedObjectSet objects = parentInfo->inputValues.at(_outputs.size()).getInlineValue(); + if (objects.isEmpty()) { + for (int i = 0; i < _outputs.size(); i++) { + info.outputValues[i] = AttributeValue(_outputs.at(i)); + } + return (info.size > _voxelizationSize) ? DEFAULT_ORDER : STOP_RECURSION; + } + SharedObjectSet::const_iterator it = objects.constBegin(); + static_cast(it->data())->getAttributeValues(info, true); + for (it++; it != objects.constEnd(); it++) { + static_cast(it->data())->blendAttributeValues(info, true); + } + return (info.size > _voxelizationSize) ? DEFAULT_ORDER : STOP_RECURSION; } void InsertSpannerEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { data.insert(attribute, this->spanner); Spanner* spanner = static_cast(this->spanner.data()); - InsertSpannerEditVisitor visitor(spanner->getVoxelizedAttributes(), spanner); + UpdateSpannerVisitor visitor(spanner->getVoxelizedAttributes(), spanner); data.guide(visitor); } @@ -154,7 +176,13 @@ void RemoveSpannerEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& o qDebug() << "Missing object to remove" << id; return; } + // keep a strong reference to the object + SharedObjectPointer sharedPointer = object; data.remove(attribute, object); + + Spanner* spanner = static_cast(object); + UpdateSpannerVisitor visitor(spanner->getVoxelizedAttributes(), spanner); + data.guide(visitor); } ClearSpannersEdit::ClearSpannersEdit(const AttributePointer& attribute) :