// // MetavoxelUtil.cpp // metavoxels // // Created by Andrzej Kapolka on 12/30/13. // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "MetavoxelUtil.h" #include "ScriptCache.h" static int scriptHashType = qRegisterMetaType(); static int parameterizedURLType = qRegisterMetaType(); REGISTER_SIMPLE_TYPE_STREAMER(ScriptHash) REGISTER_SIMPLE_TYPE_STREAMER(ParameterizedURL) class DelegatingItemEditorFactory : public QItemEditorFactory { public: DelegatingItemEditorFactory(); virtual QWidget* createEditor(int userType, QWidget* parent) const; virtual QByteArray valuePropertyName(int userType) const; private: const QItemEditorFactory* _parentFactory; }; class DoubleEditor : public QDoubleSpinBox { public: DoubleEditor(QWidget* parent = NULL); }; DoubleEditor::DoubleEditor(QWidget* parent) : QDoubleSpinBox(parent) { setMinimum(-FLT_MAX); setMaximum(FLT_MAX); } DelegatingItemEditorFactory::DelegatingItemEditorFactory() : _parentFactory(QItemEditorFactory::defaultFactory()) { QItemEditorFactory::setDefaultFactory(this); } QWidget* DelegatingItemEditorFactory::createEditor(int userType, QWidget* parent) const { QWidget* editor = QItemEditorFactory::createEditor(userType, parent); return (!editor) ? _parentFactory->createEditor(userType, parent) : editor; } QByteArray DelegatingItemEditorFactory::valuePropertyName(int userType) const { QByteArray propertyName = QItemEditorFactory::valuePropertyName(userType); return propertyName.isNull() ? _parentFactory->valuePropertyName(userType) : propertyName; } static QItemEditorFactory* getItemEditorFactory() { static QItemEditorFactory* factory = new DelegatingItemEditorFactory(); return factory; } /// Because Windows doesn't necessarily have the staticMetaObject available when we want to create, /// this class simply delays the value property name lookup until actually requested. template class LazyItemEditorCreator : public QItemEditorCreatorBase { public: virtual QWidget* createWidget(QWidget* parent) const { return new T(parent); } virtual QByteArray valuePropertyName() const; protected: QByteArray _valuePropertyName; }; template QByteArray LazyItemEditorCreator::valuePropertyName() const { if (_valuePropertyName.isNull()) { const_cast*>(this)->_valuePropertyName = T::staticMetaObject.userProperty().name(); } return _valuePropertyName; } static QItemEditorCreatorBase* createDoubleEditorCreator() { QItemEditorCreatorBase* creator = new LazyItemEditorCreator(); getItemEditorFactory()->registerEditor(qMetaTypeId(), creator); getItemEditorFactory()->registerEditor(qMetaTypeId(), creator); return creator; } static QItemEditorCreatorBase* createQMetaObjectEditorCreator() { QItemEditorCreatorBase* creator = new LazyItemEditorCreator(); getItemEditorFactory()->registerEditor(qMetaTypeId(), creator); return creator; } static QItemEditorCreatorBase* createQColorEditorCreator() { QItemEditorCreatorBase* creator = new LazyItemEditorCreator(); getItemEditorFactory()->registerEditor(qMetaTypeId(), creator); return creator; } static QItemEditorCreatorBase* createQUrlEditorCreator() { QItemEditorCreatorBase* creator = new LazyItemEditorCreator(); getItemEditorFactory()->registerEditor(qMetaTypeId(), creator); return creator; } static QItemEditorCreatorBase* createVec3EditorCreator() { QItemEditorCreatorBase* creator = new LazyItemEditorCreator(); getItemEditorFactory()->registerEditor(qMetaTypeId(), creator); return creator; } static QItemEditorCreatorBase* createParameterizedURLEditorCreator() { QItemEditorCreatorBase* creator = new LazyItemEditorCreator(); getItemEditorFactory()->registerEditor(qMetaTypeId(), creator); return creator; } static QItemEditorCreatorBase* doubleEditorCreator = createDoubleEditorCreator(); static QItemEditorCreatorBase* qMetaObjectEditorCreator = createQMetaObjectEditorCreator(); static QItemEditorCreatorBase* qColorEditorCreator = createQColorEditorCreator(); static QItemEditorCreatorBase* qUrlEditorCreator = createQUrlEditorCreator(); static QItemEditorCreatorBase* vec3EditorCreator = createVec3EditorCreator(); static QItemEditorCreatorBase* parameterizedURLEditorCreator = createParameterizedURLEditorCreator(); QByteArray signal(const char* signature) { static QByteArray prototype = SIGNAL(dummyMethod()); QByteArray signal = prototype; return signal.replace("dummyMethod()", signature); } Box::Box(const glm::vec3& minimum, const glm::vec3& maximum) : minimum(minimum), maximum(maximum) { } bool Box::contains(const glm::vec3& point) const { return point.x >= minimum.x && point.x <= maximum.x && point.y >= minimum.y && point.y <= maximum.y && point.z >= minimum.z && point.z <= maximum.z; } bool Box::contains(const Box& other) const { return other.minimum.x >= minimum.x && other.maximum.x <= maximum.x && other.minimum.y >= minimum.y && other.maximum.y <= maximum.y && other.minimum.z >= minimum.z && other.maximum.z <= maximum.z; } bool Box::intersects(const Box& other) const { return other.maximum.x >= minimum.x && other.minimum.x <= maximum.x && other.maximum.y >= minimum.y && other.minimum.y <= maximum.y && other.maximum.z >= minimum.z && other.minimum.z <= maximum.z; } const int X_MAXIMUM_FLAG = 1; const int Y_MAXIMUM_FLAG = 2; const int Z_MAXIMUM_FLAG = 4; glm::vec3 Box::getVertex(int index) const { return glm::vec3( (index & X_MAXIMUM_FLAG) ? maximum.x : minimum.x, (index & Y_MAXIMUM_FLAG) ? maximum.y : minimum.y, (index & Z_MAXIMUM_FLAG) ? maximum.z : minimum.z); } // finds the intersection between a ray and the facing plane on one axis static bool findIntersection(float origin, float direction, float minimum, float maximum, float& distance) { if (direction > EPSILON) { distance = (minimum - origin) / direction; return true; } else if (direction < -EPSILON) { distance = (maximum - origin) / direction; return true; } return false; } // determines whether a value is within the extents static bool isWithin(float value, float minimum, float maximum) { return value >= minimum && value <= maximum; } bool Box::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { // handle the trivial case where the box contains the origin if (contains(origin)) { distance = 0.0f; return true; } // check each axis float axisDistance; if ((findIntersection(origin.x, direction.x, minimum.x, maximum.x, axisDistance) && axisDistance >= 0 && isWithin(origin.y + axisDistance*direction.y, minimum.y, maximum.y) && isWithin(origin.z + axisDistance*direction.z, minimum.z, maximum.z))) { distance = axisDistance; return true; } if ((findIntersection(origin.y, direction.y, minimum.y, maximum.y, axisDistance) && axisDistance >= 0 && isWithin(origin.x + axisDistance*direction.x, minimum.x, maximum.x) && isWithin(origin.z + axisDistance*direction.z, minimum.z, maximum.z))) { distance = axisDistance; return true; } if ((findIntersection(origin.z, direction.z, minimum.z, maximum.z, axisDistance) && axisDistance >= 0 && isWithin(origin.y + axisDistance*direction.y, minimum.y, maximum.y) && isWithin(origin.x + axisDistance*direction.x, minimum.x, maximum.x))) { distance = axisDistance; return true; } return false; } Box operator*(const glm::mat4& matrix, const Box& box) { // start with the constant component Box newBox(glm::vec3(matrix[3][0], matrix[3][1], matrix[3][2]), glm::vec3(matrix[3][0], matrix[3][1], matrix[3][2])); // for each element, we choose the minimum or maximum based on the matrix sign if (matrix[0][0] >= 0.0f) { newBox.minimum.x += matrix[0][0] * box.minimum.x; newBox.maximum.x += matrix[0][0] * box.maximum.x; } else { newBox.minimum.x += matrix[0][0] * box.maximum.x; newBox.maximum.x += matrix[0][0] * box.minimum.x; } if (matrix[1][0] >= 0.0f) { newBox.minimum.x += matrix[1][0] * box.minimum.y; newBox.maximum.x += matrix[1][0] * box.maximum.y; } else { newBox.minimum.x += matrix[1][0] * box.maximum.y; newBox.maximum.x += matrix[1][0] * box.minimum.y; } if (matrix[2][0] >= 0.0f) { newBox.minimum.x += matrix[2][0] * box.minimum.z; newBox.maximum.x += matrix[2][0] * box.maximum.z; } else { newBox.minimum.x += matrix[2][0] * box.maximum.z; newBox.maximum.x += matrix[2][0] * box.minimum.z; } if (matrix[0][1] >= 0.0f) { newBox.minimum.y += matrix[0][1] * box.minimum.x; newBox.maximum.y += matrix[0][1] * box.maximum.x; } else { newBox.minimum.y += matrix[0][1] * box.maximum.x; newBox.maximum.y += matrix[0][1] * box.minimum.x; } if (matrix[1][1] >= 0.0f) { newBox.minimum.y += matrix[1][1] * box.minimum.y; newBox.maximum.y += matrix[1][1] * box.maximum.y; } else { newBox.minimum.y += matrix[1][1] * box.maximum.y; newBox.maximum.y += matrix[1][1] * box.minimum.y; } if (matrix[2][1] >= 0.0f) { newBox.minimum.y += matrix[2][1] * box.minimum.z; newBox.maximum.y += matrix[2][1] * box.maximum.z; } else { newBox.minimum.y += matrix[2][1] * box.maximum.z; newBox.maximum.y += matrix[2][1] * box.minimum.z; } if (matrix[0][2] >= 0.0f) { newBox.minimum.z += matrix[0][2] * box.minimum.x; newBox.maximum.z += matrix[0][2] * box.maximum.x; } else { newBox.minimum.z += matrix[0][2] * box.maximum.x; newBox.maximum.z += matrix[0][2] * box.minimum.x; } if (matrix[1][2] >= 0.0f) { newBox.minimum.z += matrix[1][2] * box.minimum.y; newBox.maximum.z += matrix[1][2] * box.maximum.y; } else { newBox.minimum.z += matrix[1][2] * box.maximum.y; newBox.maximum.z += matrix[1][2] * box.minimum.y; } if (matrix[2][2] >= 0.0f) { newBox.minimum.z += matrix[2][2] * box.minimum.z; newBox.maximum.z += matrix[2][2] * box.maximum.z; } else { newBox.minimum.z += matrix[2][2] * box.maximum.z; newBox.maximum.z += matrix[2][2] * box.minimum.z; } return newBox; } QMetaObjectEditor::QMetaObjectEditor(QWidget* parent) : QWidget(parent) { QVBoxLayout* layout = new QVBoxLayout(); layout->setContentsMargins(QMargins()); layout->setAlignment(Qt::AlignTop); setLayout(layout); layout->addWidget(_box = new QComboBox()); connect(_box, SIGNAL(currentIndexChanged(int)), SLOT(updateMetaObject())); foreach (const QMetaObject* metaObject, Bitstream::getMetaObjectSubClasses(&SharedObject::staticMetaObject)) { _box->addItem(metaObject->className(), QVariant::fromValue(metaObject)); } } void QMetaObjectEditor::setMetaObject(const QMetaObject* metaObject) { _metaObject = metaObject; _box->setCurrentIndex(_metaObject ? _box->findText(_metaObject->className()) : -1); } void QMetaObjectEditor::updateMetaObject() { int index = _box->currentIndex(); emit metaObjectChanged(_metaObject = (index == -1) ? NULL : _box->itemData(index).value()); } QColorEditor::QColorEditor(QWidget* parent) : QWidget(parent) { QVBoxLayout* layout = new QVBoxLayout(); layout->setContentsMargins(QMargins()); layout->setAlignment(Qt::AlignTop); setLayout(layout); layout->addWidget(_button = new QPushButton()); connect(_button, SIGNAL(clicked()), SLOT(selectColor())); } void QColorEditor::setColor(const QColor& color) { QString name = (_color = color).name(); _button->setStyleSheet(QString("background: %1; color: %2").arg(name, QColor::fromRgb(~color.rgb()).name())); _button->setText(name); } void QColorEditor::selectColor() { QColor color = QColorDialog::getColor(_color, this, QString(), QColorDialog::ShowAlphaChannel); if (color.isValid()) { setColor(color); emit colorChanged(color); } } QUrlEditor::QUrlEditor(QWidget* parent) : QComboBox(parent) { setEditable(true); setInsertPolicy(InsertAtTop); // populate initial URL list from settings addItems(QSettings().value("editorURLs").toStringList()); connect(this, SIGNAL(activated(const QString&)), SLOT(updateURL(const QString&))); connect(model(), SIGNAL(rowsInserted(const QModelIndex&,int,int)), SLOT(updateSettings())); } void QUrlEditor::setURL(const QUrl& url) { setCurrentText((_url = url).toString()); } void QUrlEditor::updateURL(const QString& text) { emit urlChanged(_url = text); } void QUrlEditor::updateSettings() { QStringList urls; const int MAX_STORED_URLS = 10; for (int i = 0, size = qMin(MAX_STORED_URLS, count()); i < size; i++) { urls.append(itemText(i)); } QSettings().setValue("editorURLs", urls); } Vec3Editor::Vec3Editor(QWidget* parent) : QWidget(parent) { QHBoxLayout* layout = new QHBoxLayout(); layout->setContentsMargins(QMargins()); setLayout(layout); layout->addWidget(_x = createComponentBox()); layout->addWidget(_y = createComponentBox()); layout->addWidget(_z = createComponentBox()); } void Vec3Editor::setVector(const glm::vec3& vector) { _vector = vector; _x->setValue(vector.x); _y->setValue(vector.y); _z->setValue(vector.z); } void Vec3Editor::updateVector() { emit vectorChanged(_vector = glm::vec3(_x->value(), _y->value(), _z->value())); } QDoubleSpinBox* Vec3Editor::createComponentBox() { QDoubleSpinBox* box = new QDoubleSpinBox(); box->setMinimum(-FLT_MAX); box->setMaximum(FLT_MAX); box->setMinimumWidth(50); connect(box, SIGNAL(valueChanged(double)), SLOT(updateVector())); return box; } ParameterizedURL::ParameterizedURL(const QUrl& url, const ScriptHash& parameters) : _url(url), _parameters(parameters) { } bool ParameterizedURL::operator==(const ParameterizedURL& other) const { return _url == other._url && _parameters == other._parameters; } bool ParameterizedURL::operator!=(const ParameterizedURL& other) const { return _url != other._url || _parameters != other._parameters; } uint qHash(const ParameterizedURL& url, uint seed) { // just hash on the URL, for now return qHash(url.getURL(), seed); } Bitstream& operator<<(Bitstream& out, const ParameterizedURL& url) { out << url.getURL(); out << url.getParameters(); return out; } Bitstream& operator>>(Bitstream& in, ParameterizedURL& url) { QUrl qurl; in >> qurl; ScriptHash parameters; in >> parameters; url = ParameterizedURL(qurl, parameters); return in; } ParameterizedURLEditor::ParameterizedURLEditor(QWidget* parent) : QWidget(parent) { QVBoxLayout* layout = new QVBoxLayout(); layout->setContentsMargins(QMargins()); setLayout(layout); QWidget* lineContainer = new QWidget(); layout->addWidget(lineContainer); QHBoxLayout* lineLayout = new QHBoxLayout(); lineContainer->setLayout(lineLayout); lineLayout->setContentsMargins(QMargins()); lineLayout->addWidget(&_urlEditor, 1); connect(&_urlEditor, SIGNAL(urlChanged(const QUrl&)), SLOT(updateURL())); connect(&_urlEditor, SIGNAL(urlChanged(const QUrl&)), SLOT(updateParameters())); QPushButton* refresh = new QPushButton("..."); connect(refresh, SIGNAL(clicked(bool)), SLOT(updateParameters())); lineLayout->addWidget(refresh); } void ParameterizedURLEditor::setURL(const ParameterizedURL& url) { _urlEditor.setURL((_url = url).getURL()); updateParameters(); } void ParameterizedURLEditor::updateURL() { ScriptHash parameters; if (layout()->count() > 1) { QFormLayout* form = static_cast(layout()->itemAt(1)); for (int i = 0; i < form->rowCount(); i++) { QWidget* widget = form->itemAt(i, QFormLayout::FieldRole)->widget(); QByteArray valuePropertyName = widget->property("valuePropertyName").toByteArray(); const QMetaObject* widgetMetaObject = widget->metaObject(); QMetaProperty widgetProperty = widgetMetaObject->property(widgetMetaObject->indexOfProperty(valuePropertyName)); parameters.insert(ScriptCache::getInstance()->getEngine()->toStringHandle( widget->property("parameterName").toString()), widgetProperty.read(widget)); } } emit urlChanged(_url = ParameterizedURL(_urlEditor.getURL(), parameters)); if (_program) { _program->disconnect(this); } } void ParameterizedURLEditor::updateParameters() { if (_program) { _program->disconnect(this); } _program = ScriptCache::getInstance()->getProgram(_url.getURL()); if (_program->isLoaded()) { continueUpdatingParameters(); } else { connect(_program.data(), SIGNAL(loaded()), SLOT(continueUpdatingParameters())); } } void ParameterizedURLEditor::continueUpdatingParameters() { QVBoxLayout* layout = static_cast(this->layout()); if (layout->count() > 1) { QFormLayout* form = static_cast(layout->takeAt(1)); for (int i = form->count() - 1; i >= 0; i--) { QLayoutItem* item = form->takeAt(i); if (item->widget()) { delete item->widget(); } delete item; } delete form; } QSharedPointer value = ScriptCache::getInstance()->getValue(_url.getURL()); const QList& parameters = static_cast(value.data())->getParameterInfo(); if (parameters.isEmpty()) { return; } QFormLayout* form = new QFormLayout(); layout->addLayout(form); foreach (const ParameterInfo& parameter, parameters) { QWidget* widget = QItemEditorFactory::defaultFactory()->createEditor(parameter.type, NULL); if (widget) { form->addRow(parameter.name.toString() + ":", widget); QByteArray valuePropertyName = QItemEditorFactory::defaultFactory()->valuePropertyName(parameter.type); widget->setProperty("parameterName", parameter.name.toString()); widget->setProperty("valuePropertyName", valuePropertyName); const QMetaObject* widgetMetaObject = widget->metaObject(); QMetaProperty widgetProperty = widgetMetaObject->property(widgetMetaObject->indexOfProperty(valuePropertyName)); widgetProperty.write(widget, _url.getParameters().value(parameter.name)); if (widgetProperty.hasNotifySignal()) { connect(widget, signal(widgetProperty.notifySignal().methodSignature()), SLOT(updateURL())); } } } }