From 33faffd9d40d5eb76123e11b2f248b0f1bc52251 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 2 Sep 2014 17:45:37 -0700 Subject: [PATCH] Sphere tools for voxel editing. --- interface/src/ui/MetavoxelEditor.cpp | 109 ++++++++ interface/src/ui/MetavoxelEditor.h | 71 +++++ .../metavoxels/src/AttributeRegistry.cpp | 4 + libraries/metavoxels/src/AttributeRegistry.h | 3 + .../metavoxels/src/MetavoxelMessages.cpp | 264 ++++++++++++++++++ libraries/metavoxels/src/MetavoxelMessages.h | 39 +++ 6 files changed, 490 insertions(+) diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index 5702db39a2..b9e262f3fc 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -126,6 +126,8 @@ MetavoxelEditor::MetavoxelEditor() : addTool(new EraseHeightfieldTool(this)); addTool(new VoxelColorBoxTool(this)); addTool(new VoxelMaterialBoxTool(this)); + addTool(new VoxelColorSphereTool(this)); + addTool(new VoxelMaterialSphereTool(this)); updateAttributes(); @@ -1282,3 +1284,110 @@ void VoxelMaterialBoxTool::updateTexture() { MaterialObject* material = static_cast(_materialEditor->getObject().data()); _texture = Application::getInstance()->getTextureCache()->getTexture(material->getDiffuse(), SPLAT_TEXTURE); } + +SphereTool::SphereTool(MetavoxelEditor* editor, const QString& name) : + MetavoxelTool(editor, name, false, true) { + + QWidget* widget = new QWidget(); + widget->setLayout(_form = new QFormLayout()); + layout()->addWidget(widget); + + _form->addRow("Radius:", _radius = new QDoubleSpinBox()); + _radius->setSingleStep(0.01); + _radius->setMaximum(FLT_MAX); + _radius->setValue(1.0); +} + +void SphereTool::render() { + if (Application::getInstance()->isMouseHidden()) { + return; + } + glm::quat rotation = _editor->getGridRotation(); + glm::quat inverseRotation = glm::inverse(rotation); + glm::vec3 rayOrigin = inverseRotation * Application::getInstance()->getMouseRayOrigin(); + glm::vec3 rayDirection = inverseRotation * Application::getInstance()->getMouseRayDirection(); + float position = _editor->getGridPosition(); + if (glm::abs(rayDirection.z) < EPSILON) { + return; + } + float distance = (position - rayOrigin.z) / rayDirection.z; + _position = Application::getInstance()->getMouseRayOrigin() + + Application::getInstance()->getMouseRayDirection() * distance; + + glPushMatrix(); + glTranslatef(_position.x, _position.y, _position.z); + + const float CURSOR_ALPHA = 0.5f; + QColor color = getColor(); + glColor4f(color.redF(), color.greenF(), color.blueF(), CURSOR_ALPHA); + + glEnable(GL_CULL_FACE); + + glutSolidSphere(_radius->value(), 10, 10); + + glDisable(GL_CULL_FACE); + + glPopMatrix(); +} + +bool SphereTool::eventFilter(QObject* watched, QEvent* event) { + if (event->type() == QEvent::Wheel) { + float angle = static_cast(event)->angleDelta().y(); + const float ANGLE_SCALE = 1.0f / 1000.0f; + _radius->setValue(_radius->value() * glm::pow(2.0f, angle * ANGLE_SCALE)); + return true; + + } else if (event->type() == QEvent::MouseButtonPress) { + applyValue(_position, _radius->value()); + return true; + } + return false; +} + +VoxelColorSphereTool::VoxelColorSphereTool(MetavoxelEditor* editor) : + SphereTool(editor, "Set Voxel Color (Sphere)") { + + _form->addRow("Color:", _color = new QColorEditor(this)); +} + +bool VoxelColorSphereTool::appliesTo(const AttributePointer& attribute) const { + return attribute->inherits("VoxelColorAttribute"); +} + +QColor VoxelColorSphereTool::getColor() { + return _color->getColor(); +} + +void VoxelColorSphereTool::applyValue(const glm::vec3& position, float radius) { + MetavoxelEditMessage message = { QVariant::fromValue(VoxelColorSphereEdit(position, radius, + _editor->getGridSpacing(), _color->getColor())) }; + Application::getInstance()->getMetavoxels()->applyEdit(message, true); +} + +VoxelMaterialSphereTool::VoxelMaterialSphereTool(MetavoxelEditor* editor) : + SphereTool(editor, "Set Voxel Material (Sphere)") { + + _form->addRow(_materialEditor = new SharedObjectEditor(&MaterialObject::staticMetaObject, false)); + connect(_materialEditor, &SharedObjectEditor::objectChanged, this, &VoxelMaterialSphereTool::updateTexture); +} + +bool VoxelMaterialSphereTool::appliesTo(const AttributePointer& attribute) const { + return attribute->inherits("VoxelColorAttribute"); +} + +QColor VoxelMaterialSphereTool::getColor() { + return _texture ? _texture->getAverageColor() : QColor(); +} + +void VoxelMaterialSphereTool::applyValue(const glm::vec3& position, float radius) { + SharedObjectPointer material = _materialEditor->getObject(); + _materialEditor->detachObject(); + MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialSphereEdit(position, radius, + _editor->getGridSpacing(), material, _texture ? _texture->getAverageColor() : QColor())) }; + Application::getInstance()->getMetavoxels()->applyEdit(message, true); +} + +void VoxelMaterialSphereTool::updateTexture() { + MaterialObject* material = static_cast(_materialEditor->getObject().data()); + _texture = Application::getInstance()->getTextureCache()->getTexture(material->getDiffuse(), SPLAT_TEXTURE); +} diff --git a/interface/src/ui/MetavoxelEditor.h b/interface/src/ui/MetavoxelEditor.h index ba455dd96c..96f4a2abe9 100644 --- a/interface/src/ui/MetavoxelEditor.h +++ b/interface/src/ui/MetavoxelEditor.h @@ -455,4 +455,75 @@ private: QSharedPointer _texture; }; +/// Base class for tools based on a sphere brush. +class SphereTool : public MetavoxelTool { + Q_OBJECT + +public: + + SphereTool(MetavoxelEditor* editor, const QString& name); + + virtual void render(); + + virtual bool eventFilter(QObject* watched, QEvent* event); + +protected: + + virtual QColor getColor() = 0; + + virtual void applyValue(const glm::vec3& position, float radius) = 0; + + QFormLayout* _form; + QDoubleSpinBox* _radius; + + glm::vec3 _position; +}; + +/// Allows setting voxel colors by moving a sphere around. +class VoxelColorSphereTool : public SphereTool { + Q_OBJECT + +public: + + VoxelColorSphereTool(MetavoxelEditor* editor); + + virtual bool appliesTo(const AttributePointer& attribute) const; + +protected: + + virtual QColor getColor(); + + virtual void applyValue(const glm::vec3& position, float radius); + +private: + + QColorEditor* _color; +}; + +/// Allows setting voxel materials by moving a sphere around. +class VoxelMaterialSphereTool : public SphereTool { + Q_OBJECT + +public: + + VoxelMaterialSphereTool(MetavoxelEditor* editor); + + virtual bool appliesTo(const AttributePointer& attribute) const; + +protected: + + virtual QColor getColor(); + + virtual void applyValue(const glm::vec3& position, float radius); + +private slots: + + void updateTexture(); + +private: + + SharedObjectEditor* _materialEditor; + QSharedPointer _texture; +}; + #endif // hifi_MetavoxelEditor_h diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index db684bf1c5..70b9a1e343 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -406,6 +406,10 @@ QRgb packNormal(const glm::vec3& normal) { return qRgb((char)(normal.x * CHAR_SCALE), (char)(normal.y * CHAR_SCALE), (char)(normal.z * CHAR_SCALE)); } +QRgb packNormal(const glm::vec3& normal, int alpha) { + return qRgba((char)(normal.x * CHAR_SCALE), (char)(normal.y * CHAR_SCALE), (char)(normal.z * CHAR_SCALE), alpha); +} + glm::vec3 unpackNormal(QRgb value) { return glm::vec3((char)qRed(value) * INVERSE_CHAR_SCALE, (char)qGreen(value) * INVERSE_CHAR_SCALE, (char)qBlue(value) * INVERSE_CHAR_SCALE); diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index 371bb24852..cee01cdbef 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -416,6 +416,9 @@ public: /// Packs a normal into an RGB value. QRgb packNormal(const glm::vec3& normal); +/// Packs a normal (plus extra alpha value) into an RGBA value. +QRgb packNormal(const glm::vec3& normal, int alpha); + /// Unpacks a normal from an RGB value. glm::vec3 unpackNormal(QRgb value); diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index da886cbf5d..178f6f7523 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -814,3 +814,267 @@ void VoxelMaterialBoxEdit::apply(MetavoxelData& data, const WeakSharedObjectHash VoxelMaterialBoxEditVisitor visitor(region, granularity, material, averageColor); data.guide(visitor); } + +VoxelColorSphereEdit::VoxelColorSphereEdit(const glm::vec3& center, float radius, float granularity, const QColor& color) : + center(center), + radius(radius), + granularity(granularity), + color(color) { +} + +class VoxelMaterialSphereEditVisitor : public MetavoxelVisitor { +public: + + VoxelMaterialSphereEditVisitor(const glm::vec3& center, float radius, const Box& bounds, float granularity, + const SharedObjectPointer& material, const QColor& color); + + virtual int visit(MetavoxelInfo& info); + +private: + + glm::vec3 _center; + float _radius; + Box _bounds; + float _granularity; + SharedObjectPointer _material; + QColor _color; + float _blockSize; +}; + +VoxelMaterialSphereEditVisitor::VoxelMaterialSphereEditVisitor(const glm::vec3& center, float radius, const Box& bounds, + float granularity, const SharedObjectPointer& material, const QColor& color) : + MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getVoxelColorAttribute() << + AttributeRegistry::getInstance()->getVoxelHermiteAttribute() << + AttributeRegistry::getInstance()->getVoxelMaterialAttribute(), QVector() << + AttributeRegistry::getInstance()->getVoxelColorAttribute() << + AttributeRegistry::getInstance()->getVoxelHermiteAttribute() << + AttributeRegistry::getInstance()->getVoxelMaterialAttribute()), + _center(center), + _radius(radius), + _bounds(bounds), + _granularity(granularity), + _material(material), + _color(color), + _blockSize(granularity * VOXEL_BLOCK_SIZE) { +} + +int VoxelMaterialSphereEditVisitor::visit(MetavoxelInfo& info) { + Box bounds = info.getBounds(); + if (!bounds.intersects(_bounds)) { + return STOP_RECURSION; + } + if (info.size > _blockSize) { + return DEFAULT_ORDER; + } + VoxelColorDataPointer colorPointer = info.inputValues.at(0).getInlineValue(); + QVector colorContents = (colorPointer && colorPointer->getSize() == VOXEL_BLOCK_SAMPLES) ? + colorPointer->getContents() : QVector(VOXEL_BLOCK_VOLUME); + + Box overlap = info.getBounds().getIntersection(_bounds); + float scale = VOXEL_BLOCK_SIZE / info.size; + overlap.minimum = (overlap.minimum - info.minimum) * scale; + overlap.maximum = (overlap.maximum - info.minimum) * scale; + int minX = glm::ceil(overlap.minimum.x); + int minY = glm::ceil(overlap.minimum.y); + int minZ = glm::ceil(overlap.minimum.z); + int sizeX = (int)overlap.maximum.x - minX + 1; + int sizeY = (int)overlap.maximum.y - minY + 1; + int sizeZ = (int)overlap.maximum.z - minZ + 1; + + glm::vec3 relativeCenter = (_center - info.minimum) * scale; + float relativeRadius = _radius * scale; + float relativeRadiusSquared = relativeRadius * relativeRadius; + + QRgb rgb = _color.rgba(); + glm::vec3 position(0.0f, 0.0f, minZ); + for (QRgb* destZ = colorContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX, + *endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z++) { + position.y = minY; + for (QRgb* destY = destZ, *endY = destY + sizeY * VOXEL_BLOCK_SAMPLES; destY != endY; + destY += VOXEL_BLOCK_SAMPLES, position.y++) { + position.x = minX; + for (QRgb* destX = destY, *endX = destX + sizeX; destX != endX; destX++, position.x++) { + if (glm::distance(relativeCenter, position) <= relativeRadius) { + *destX = rgb; + } + } + } + } + + VoxelColorDataPointer newColorPointer(new VoxelColorData(colorContents, VOXEL_BLOCK_SAMPLES)); + info.outputValues[0] = AttributeValue(info.inputValues.at(0).getAttribute(), + encodeInline(newColorPointer)); + + VoxelHermiteDataPointer hermitePointer = info.inputValues.at(1).getInlineValue(); + QVector hermiteContents = (hermitePointer && hermitePointer->getSize() == VOXEL_BLOCK_SAMPLES) ? + hermitePointer->getContents() : QVector(VOXEL_BLOCK_VOLUME * VoxelHermiteData::EDGE_COUNT); + int hermiteArea = VOXEL_BLOCK_AREA * VoxelHermiteData::EDGE_COUNT; + int hermiteSamples = VOXEL_BLOCK_SAMPLES * VoxelHermiteData::EDGE_COUNT; + + int hermiteMinX = minX, hermiteMinY = minY, hermiteMinZ = minZ; + int hermiteSizeX = sizeX, hermiteSizeY = sizeY, hermiteSizeZ = sizeZ; + if (minX > 0) { + hermiteMinX--; + hermiteSizeX++; + } + if (minY > 0) { + hermiteMinY--; + hermiteSizeY++; + } + if (minZ > 0) { + hermiteMinZ--; + hermiteSizeZ++; + } + QRgb* hermiteDestZ = hermiteContents.data() + hermiteMinZ * hermiteArea + hermiteMinY * hermiteSamples + + hermiteMinX * VoxelHermiteData::EDGE_COUNT; + for (int z = hermiteMinZ, hermiteMaxZ = z + hermiteSizeZ - 1; z <= hermiteMaxZ; z++, hermiteDestZ += hermiteArea) { + QRgb* hermiteDestY = hermiteDestZ; + for (int y = hermiteMinY, hermiteMaxY = y + hermiteSizeY - 1; y <= hermiteMaxY; y++, hermiteDestY += hermiteSamples) { + QRgb* hermiteDestX = hermiteDestY; + for (int x = hermiteMinX, hermiteMaxX = x + hermiteSizeX - 1; x <= hermiteMaxX; x++, + hermiteDestX += VoxelHermiteData::EDGE_COUNT) { + hermiteDestX[0] = 0x0; + glm::vec3 offset(x - relativeCenter.x, y - relativeCenter.y, z - relativeCenter.z); + if (x != VOXEL_BLOCK_SIZE) { + const QRgb* color = colorContents.constData() + z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x; + int alpha0 = qAlpha(color[0]); + if (alpha0 != qAlpha(color[1])) { + float radicand = relativeRadiusSquared - offset.y * offset.y - offset.z * offset.z; + float parameter = 0.5f; + if (radicand >= 0.0f) { + float root = glm::sqrt(radicand); + parameter = -offset.x - root; + if (parameter < 0.0f || parameter > 1.0f) { + parameter = glm::clamp(-offset.x + root, 0.0f, 1.0f); + } + } + glm::vec3 normal = offset + glm::vec3(parameter, 0.0f, 0.0f); + float length = glm::length(normal); + if (length > EPSILON) { + normal /= length; + } else { + normal = glm::vec3(0.0f, 1.0f, 0.0f); + } + hermiteDestX[0] = packNormal(normal, parameter * EIGHT_BIT_MAXIMUM); + } + } + hermiteDestX[1] = 0x0; + if (y != VOXEL_BLOCK_SIZE) { + const QRgb* color = colorContents.constData() + z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x; + int alpha0 = qAlpha(color[0]); + if (alpha0 != qAlpha(color[VOXEL_BLOCK_SAMPLES])) { + float radicand = relativeRadiusSquared - offset.x * offset.x - offset.z * offset.z; + float parameter = 0.5f; + if (radicand >= 0.0f) { + float root = glm::sqrt(radicand); + parameter = -offset.y - root; + if (parameter < 0.0f || parameter > 1.0f) { + parameter = glm::clamp(-offset.y + root, 0.0f, 1.0f); + } + } + glm::vec3 normal = offset + glm::vec3(parameter, 0.0f, 0.0f); + float length = glm::length(normal); + if (length > EPSILON) { + normal /= length; + } else { + normal = glm::vec3(1.0f, 0.0f, 0.0f); + } + hermiteDestX[1] = packNormal(normal, parameter * EIGHT_BIT_MAXIMUM); + } + } + hermiteDestX[2] = 0x0; + if (z != VOXEL_BLOCK_SIZE) { + const QRgb* color = colorContents.constData() + z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x; + int alpha0 = qAlpha(color[0]); + if (alpha0 != qAlpha(color[VOXEL_BLOCK_AREA])) { + float radicand = relativeRadiusSquared - offset.x * offset.x - offset.y * offset.y; + float parameter = 0.5f; + if (radicand >= 0.0f) { + float root = glm::sqrt(radicand); + parameter = -offset.z - root; + if (parameter < 0.0f || parameter > 1.0f) { + parameter = glm::clamp(-offset.z + root, 0.0f, 1.0f); + } + } + glm::vec3 normal = offset + glm::vec3(parameter, 0.0f, 0.0f); + float length = glm::length(normal); + if (length > EPSILON) { + normal /= length; + } else { + normal = glm::vec3(1.0f, 0.0f, 0.0f); + } + hermiteDestX[2] = packNormal(normal, parameter * EIGHT_BIT_MAXIMUM); + } + } + } + } + } + + VoxelHermiteDataPointer newHermitePointer(new VoxelHermiteData(hermiteContents, VOXEL_BLOCK_SAMPLES)); + info.outputValues[1] = AttributeValue(info.inputValues.at(1).getAttribute(), + encodeInline(newHermitePointer)); + + VoxelMaterialDataPointer materialPointer = info.inputValues.at(2).getInlineValue(); + QByteArray materialContents; + QVector materials; + if (materialPointer && materialPointer->getSize() == VOXEL_BLOCK_SAMPLES) { + materialContents = materialPointer->getContents(); + materials = materialPointer->getMaterials(); + + } else { + materialContents = QByteArray(VOXEL_BLOCK_VOLUME, 0); + } + + uchar materialIndex = getMaterialIndex(_material, materials, materialContents); + position.z = minZ; + for (uchar* destZ = (uchar*)materialContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX, + *endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z++) { + position.y = minY; + for (uchar* destY = destZ, *endY = destY + sizeY * VOXEL_BLOCK_SAMPLES; destY != endY; + destY += VOXEL_BLOCK_SAMPLES, position.y++) { + position.x = minX; + for (uchar* destX = destY, *endX = destX + sizeX; destX != endX; destX++, position.x++) { + if (glm::distance(relativeCenter, position) <= relativeRadius) { + *destX = materialIndex; + } + } + } + } + + clearUnusedMaterials(materials, materialContents); + VoxelMaterialDataPointer newMaterialPointer(new VoxelMaterialData(materialContents, VOXEL_BLOCK_SAMPLES, materials)); + info.outputValues[2] = AttributeValue(_inputs.at(2), encodeInline(newMaterialPointer)); + + return STOP_RECURSION; +} + +void VoxelColorSphereEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { + // expand to fit the entire edit + glm::vec3 extents(radius, radius, radius); + Box bounds(center - extents, center + extents); + while (!data.getBounds().contains(bounds)) { + data.expand(); + } + VoxelMaterialSphereEditVisitor visitor(center, radius, bounds, granularity, SharedObjectPointer(), color); + data.guide(visitor); +} + +VoxelMaterialSphereEdit::VoxelMaterialSphereEdit(const glm::vec3& center, float radius, float granularity, + const SharedObjectPointer& material, const QColor& averageColor) : + center(center), + radius(radius), + granularity(granularity), + material(material), + averageColor(averageColor) { +} + +void VoxelMaterialSphereEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { + // expand to fit the entire edit + glm::vec3 extents(radius, radius, radius); + Box bounds(center - extents, center + extents); + while (!data.getBounds().contains(bounds)) { + data.expand(); + } + VoxelMaterialSphereEditVisitor visitor(center, radius, bounds, granularity, material, averageColor); + data.guide(visitor); +} diff --git a/libraries/metavoxels/src/MetavoxelMessages.h b/libraries/metavoxels/src/MetavoxelMessages.h index 15ab0d92cb..2eb5684d77 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.h +++ b/libraries/metavoxels/src/MetavoxelMessages.h @@ -296,4 +296,43 @@ public: DECLARE_STREAMABLE_METATYPE(VoxelMaterialBoxEdit) +/// An edit that sets the color of voxels within a sphere to a value. +class VoxelColorSphereEdit : public MetavoxelEdit { + STREAMABLE + +public: + + STREAM glm::vec3 center; + STREAM float radius; + STREAM float granularity; + STREAM QColor color; + + VoxelColorSphereEdit(const glm::vec3& center = glm::vec3(), float radius = 0.0f, + float granularity = 0.0f, const QColor& color = QColor()); + + virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const; +}; + +DECLARE_STREAMABLE_METATYPE(VoxelColorSphereEdit) + +/// An edit that sets the materials of voxels within a sphere to a value. +class VoxelMaterialSphereEdit : public MetavoxelEdit { + STREAMABLE + +public: + + STREAM glm::vec3 center; + STREAM float radius; + STREAM float granularity; + STREAM SharedObjectPointer material; + STREAM QColor averageColor; + + VoxelMaterialSphereEdit(const glm::vec3& center = glm::vec3(), float radius = 0.0f, float granularity = 0.0f, + const SharedObjectPointer& material = SharedObjectPointer(), const QColor& averageColor = QColor()); + + virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const; +}; + +DECLARE_STREAMABLE_METATYPE(VoxelMaterialSphereEdit) + #endif // hifi_MetavoxelMessages_h