mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-07-12 07:47:22 +02:00
406 lines
14 KiB
C++
406 lines
14 KiB
C++
//
|
|
// MetavoxelEditor.cpp
|
|
// interface
|
|
//
|
|
// Created by Andrzej Kapolka on 1/21/14.
|
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
|
|
|
#include <QComboBox>
|
|
#include <QDialogButtonBox>
|
|
#include <QDoubleSpinBox>
|
|
#include <QFormLayout>
|
|
#include <QGroupBox>
|
|
#include <QLineEdit>
|
|
#include <QListWidget>
|
|
#include <QMetaProperty>
|
|
#include <QPushButton>
|
|
#include <QVBoxLayout>
|
|
|
|
#include <AttributeRegistry.h>
|
|
|
|
#include "Application.h"
|
|
#include "MetavoxelEditor.h"
|
|
|
|
enum GridPlane {
|
|
GRID_PLANE_XY, GRID_PLANE_XZ, GRID_PLANE_YZ
|
|
};
|
|
|
|
const glm::vec2 INVALID_VECTOR(FLT_MAX, FLT_MAX);
|
|
|
|
MetavoxelEditor::MetavoxelEditor() :
|
|
QDialog(Application::getInstance()->getGLWidget()) {
|
|
|
|
setWindowTitle("Metavoxel Editor");
|
|
setAttribute(Qt::WA_DeleteOnClose);
|
|
|
|
QVBoxLayout* topLayout = new QVBoxLayout();
|
|
setLayout(topLayout);
|
|
|
|
QGroupBox* attributeGroup = new QGroupBox();
|
|
attributeGroup->setTitle("Attributes");
|
|
topLayout->addWidget(attributeGroup);
|
|
|
|
QVBoxLayout* attributeLayout = new QVBoxLayout();
|
|
attributeGroup->setLayout(attributeLayout);
|
|
|
|
attributeLayout->addWidget(_attributes = new QListWidget());
|
|
connect(_attributes, SIGNAL(itemSelectionChanged()), SLOT(updateValueEditor()));
|
|
|
|
QPushButton* newAttribute = new QPushButton("New...");
|
|
attributeLayout->addWidget(newAttribute);
|
|
connect(newAttribute, SIGNAL(clicked()), SLOT(createNewAttribute()));
|
|
|
|
QFormLayout* formLayout = new QFormLayout();
|
|
topLayout->addLayout(formLayout);
|
|
|
|
formLayout->addRow("Grid Plane:", _gridPlane = new QComboBox());
|
|
_gridPlane->addItem("X/Y");
|
|
_gridPlane->addItem("X/Z");
|
|
_gridPlane->addItem("Y/Z");
|
|
_gridPlane->setCurrentIndex(GRID_PLANE_XZ);
|
|
|
|
formLayout->addRow("Grid Spacing:", _gridSpacing = new QDoubleSpinBox());
|
|
_gridSpacing->setValue(0.1);
|
|
_gridSpacing->setMaximum(FLT_MAX);
|
|
_gridSpacing->setSingleStep(0.01);
|
|
connect(_gridSpacing, SIGNAL(valueChanged(double)), SLOT(updateGridPosition()));
|
|
|
|
formLayout->addRow("Grid Position:", _gridPosition = new QDoubleSpinBox());
|
|
_gridPosition->setSingleStep(0.1);
|
|
_gridPosition->setMinimum(-FLT_MAX);
|
|
_gridPosition->setMaximum(FLT_MAX);
|
|
|
|
_value = new QGroupBox();
|
|
_value->setTitle("Value");
|
|
topLayout->addWidget(_value);
|
|
|
|
QVBoxLayout* valueLayout = new QVBoxLayout();
|
|
_value->setLayout(valueLayout);
|
|
|
|
updateAttributes();
|
|
|
|
connect(Application::getInstance(), SIGNAL(renderingInWorldInterface()), SLOT(render()));
|
|
|
|
Application::getInstance()->getGLWidget()->installEventFilter(this);
|
|
|
|
resetState();
|
|
|
|
show();
|
|
|
|
if (_gridProgram.isLinked()) {
|
|
return;
|
|
}
|
|
switchToResourcesParentIfRequired();
|
|
_gridProgram.addShaderFromSourceFile(QGLShader::Fragment, "resources/shaders/grid.frag");
|
|
_gridProgram.link();
|
|
}
|
|
|
|
bool MetavoxelEditor::eventFilter(QObject* watched, QEvent* event) {
|
|
switch (_state) {
|
|
case HOVERING_STATE:
|
|
if (event->type() == QEvent::MouseButtonPress && _startPosition != INVALID_VECTOR) {
|
|
_state = DRAGGING_STATE;
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case DRAGGING_STATE:
|
|
if (event->type() == QEvent::MouseButtonRelease) {
|
|
_state = RAISING_STATE;
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case RAISING_STATE:
|
|
if (event->type() == QEvent::MouseButtonPress) {
|
|
if (_height != 0) {
|
|
// find the start and end corners in X/Y
|
|
float base = _gridPosition->value();
|
|
float top = base + _height;
|
|
glm::quat rotation = getGridRotation();
|
|
glm::vec3 start = rotation * glm::vec3(glm::min(_startPosition, _endPosition), glm::min(base, top));
|
|
float spacing = _gridSpacing->value();
|
|
glm::vec3 end = rotation * glm::vec3(glm::max(_startPosition, _endPosition) +
|
|
glm::vec2(spacing, spacing), glm::max(base, top));
|
|
|
|
// find the minimum and maximum extents after rotation
|
|
applyValue(glm::min(start, end), glm::max(start, end));
|
|
}
|
|
resetState();
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MetavoxelEditor::updateValueEditor() {
|
|
QString selected = getSelectedAttribute();
|
|
if (selected.isNull()) {
|
|
_value->setVisible(false);
|
|
return;
|
|
}
|
|
_value->setVisible(true);
|
|
|
|
if (!_value->layout()->isEmpty()) {
|
|
delete _value->layout()->takeAt(0);
|
|
}
|
|
|
|
AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(selected);
|
|
QWidget* editor = attribute->createEditor();
|
|
if (editor) {
|
|
_value->layout()->addWidget(editor);
|
|
}
|
|
}
|
|
|
|
void MetavoxelEditor::createNewAttribute() {
|
|
QDialog dialog(this);
|
|
dialog.setWindowTitle("New Attribute");
|
|
|
|
QVBoxLayout layout;
|
|
dialog.setLayout(&layout);
|
|
|
|
QFormLayout form;
|
|
layout.addLayout(&form);
|
|
|
|
QLineEdit name;
|
|
form.addRow("Name:", &name);
|
|
|
|
QDialogButtonBox buttons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
|
dialog.connect(&buttons, SIGNAL(accepted()), SLOT(accept()));
|
|
dialog.connect(&buttons, SIGNAL(rejected()), SLOT(reject()));
|
|
|
|
layout.addWidget(&buttons);
|
|
|
|
if (!dialog.exec()) {
|
|
return;
|
|
}
|
|
QString nameText = name.text().trimmed();
|
|
AttributeRegistry::getInstance()->registerAttribute(new QRgbAttribute(nameText));
|
|
|
|
updateAttributes(nameText);
|
|
}
|
|
|
|
void MetavoxelEditor::updateGridPosition() {
|
|
// make sure our grid position matches our grid spacing
|
|
double step = _gridSpacing->value();
|
|
if (step > 0.0) {
|
|
_gridPosition->setSingleStep(step);
|
|
_gridPosition->setValue(step * floor(_gridPosition->value() / step));
|
|
}
|
|
}
|
|
|
|
void MetavoxelEditor::render() {
|
|
QString selected = getSelectedAttribute();
|
|
if (selected.isNull()) {
|
|
resetState();
|
|
return;
|
|
}
|
|
|
|
glDisable(GL_LIGHTING);
|
|
glDepthMask(GL_FALSE);
|
|
|
|
glPushMatrix();
|
|
|
|
glm::quat rotation = getGridRotation();
|
|
glm::vec3 axis = glm::axis(rotation);
|
|
glRotatef(glm::angle(rotation), axis.x, axis.y, axis.z);
|
|
|
|
glm::quat inverseRotation = glm::inverse(rotation);
|
|
glm::vec3 rayOrigin = inverseRotation * Application::getInstance()->getMouseRayOrigin();
|
|
glm::vec3 rayDirection = inverseRotation * Application::getInstance()->getMouseRayDirection();
|
|
float spacing = _gridSpacing->value();
|
|
float position = _gridPosition->value();
|
|
if (_state == RAISING_STATE) {
|
|
// find the plane at the mouse position, orthogonal to the plane, facing the eye position
|
|
glLineWidth(4.0f);
|
|
glm::vec3 eyePosition = inverseRotation * Application::getInstance()->getViewFrustum()->getOffsetPosition();
|
|
glm::vec3 mousePoint = glm::vec3(_mousePosition, position);
|
|
glm::vec3 right = glm::cross(glm::vec3(0.0f, 0.0f, 1.0f), eyePosition - mousePoint);
|
|
glm::vec3 normal = glm::cross(right, glm::vec3(0.0f, 0.0f, 1.0f));
|
|
float divisor = glm::dot(normal, rayDirection);
|
|
if (fabs(divisor) > EPSILON) {
|
|
float distance = (glm::dot(normal, mousePoint) - glm::dot(normal, rayOrigin)) / divisor;
|
|
float projection = rayOrigin.z + distance * rayDirection.z;
|
|
_height = spacing * roundf(projection / spacing) - position;
|
|
}
|
|
} else if (fabs(rayDirection.z) > EPSILON) {
|
|
// find the intersection of the rotated mouse ray with the plane
|
|
float distance = (position - rayOrigin.z) / rayDirection.z;
|
|
_mousePosition = glm::vec2(rayOrigin + rayDirection * distance);
|
|
glm::vec2 snappedPosition = spacing * glm::floor(_mousePosition / spacing);
|
|
|
|
if (_state == HOVERING_STATE) {
|
|
_startPosition = _endPosition = snappedPosition;
|
|
glLineWidth(2.0f);
|
|
|
|
} else if (_state == DRAGGING_STATE) {
|
|
_endPosition = snappedPosition;
|
|
glLineWidth(4.0f);
|
|
}
|
|
} else {
|
|
// cancel any operation in progress
|
|
resetState();
|
|
}
|
|
|
|
const float GRID_BRIGHTNESS = 0.5f;
|
|
if (_startPosition != INVALID_VECTOR) {
|
|
glm::vec2 minimum = glm::min(_startPosition, _endPosition);
|
|
glm::vec2 maximum = glm::max(_startPosition, _endPosition);
|
|
|
|
glPushMatrix();
|
|
glTranslatef(minimum.x, minimum.y, position);
|
|
glScalef(maximum.x + spacing - minimum.x, maximum.y + spacing - minimum.y, _height);
|
|
|
|
glTranslatef(0.5f, 0.5f, 0.5f);
|
|
if (_state != HOVERING_STATE) {
|
|
const float BOX_ALPHA = 0.25f;
|
|
QColor color = getValue().value<QColor>();
|
|
if (color.isValid()) {
|
|
glColor4f(color.redF(), color.greenF(), color.blueF(), BOX_ALPHA);
|
|
} else {
|
|
glColor4f(GRID_BRIGHTNESS, GRID_BRIGHTNESS, GRID_BRIGHTNESS, BOX_ALPHA);
|
|
}
|
|
glEnable(GL_CULL_FACE);
|
|
glutSolidCube(1.0);
|
|
glDisable(GL_CULL_FACE);
|
|
}
|
|
glutWireCube(1.0);
|
|
|
|
glPopMatrix();
|
|
}
|
|
|
|
glLineWidth(1.0f);
|
|
|
|
// center the grid around the camera position on the plane
|
|
glm::vec3 rotated = inverseRotation * Application::getInstance()->getCamera()->getPosition();
|
|
const int GRID_DIVISIONS = 300;
|
|
glTranslatef(spacing * (floorf(rotated.x / spacing) - GRID_DIVISIONS / 2),
|
|
spacing * (floorf(rotated.y / spacing) - GRID_DIVISIONS / 2), position);
|
|
|
|
float scale = GRID_DIVISIONS * spacing;
|
|
glScalef(scale, scale, scale);
|
|
|
|
_gridProgram.bind();
|
|
|
|
glColor3f(GRID_BRIGHTNESS, GRID_BRIGHTNESS, GRID_BRIGHTNESS);
|
|
Application::getInstance()->getGeometryCache()->renderGrid(GRID_DIVISIONS, GRID_DIVISIONS);
|
|
|
|
_gridProgram.release();
|
|
|
|
glPopMatrix();
|
|
|
|
glEnable(GL_LIGHTING);
|
|
glDepthMask(GL_TRUE);
|
|
}
|
|
|
|
void MetavoxelEditor::updateAttributes(const QString& select) {
|
|
// remember the selection in order to preserve it
|
|
QString selected = select.isNull() ? getSelectedAttribute() : select;
|
|
_attributes->clear();
|
|
|
|
// sort the names for consistent ordering
|
|
QList<QString> names = AttributeRegistry::getInstance()->getAttributes().keys();
|
|
qSort(names);
|
|
|
|
foreach (const QString& name, names) {
|
|
QListWidgetItem* item = new QListWidgetItem(name);
|
|
_attributes->addItem(item);
|
|
if (name == selected || selected.isNull()) {
|
|
item->setSelected(true);
|
|
selected = name;
|
|
}
|
|
}
|
|
}
|
|
|
|
QString MetavoxelEditor::getSelectedAttribute() const {
|
|
QList<QListWidgetItem*> selectedItems = _attributes->selectedItems();
|
|
return selectedItems.isEmpty() ? QString() : selectedItems.first()->text();
|
|
}
|
|
|
|
glm::quat MetavoxelEditor::getGridRotation() const {
|
|
// for simplicity, we handle the other two planes by rotating them onto X/Y and performing computation there
|
|
switch (_gridPlane->currentIndex()) {
|
|
case GRID_PLANE_XY:
|
|
return glm::quat();
|
|
|
|
case GRID_PLANE_XZ:
|
|
return glm::angleAxis(-90.0f, 1.0f, 0.0f, 0.0f);
|
|
|
|
case GRID_PLANE_YZ:
|
|
default:
|
|
return glm::angleAxis(90.0f, 0.0f, 1.0f, 0.0f);
|
|
}
|
|
}
|
|
|
|
void MetavoxelEditor::resetState() {
|
|
_state = HOVERING_STATE;
|
|
_startPosition = INVALID_VECTOR;
|
|
_height = 0.0f;
|
|
}
|
|
|
|
class Applier : public MetavoxelVisitor {
|
|
public:
|
|
|
|
Applier(const glm::vec3& minimum, const glm::vec3& maximum, float granularity, const AttributeValue& value);
|
|
|
|
virtual bool visit(MetavoxelInfo& info);
|
|
|
|
protected:
|
|
|
|
glm::vec3 _minimum;
|
|
glm::vec3 _maximum;
|
|
float _granularity;
|
|
AttributeValue _value;
|
|
};
|
|
|
|
Applier::Applier(const glm::vec3& minimum, const glm::vec3& maximum, float granularity, const AttributeValue& value) :
|
|
MetavoxelVisitor(QVector<AttributePointer>(), QVector<AttributePointer>() << value.getAttribute()),
|
|
_minimum(minimum),
|
|
_maximum(maximum),
|
|
_granularity(granularity),
|
|
_value(value) {
|
|
}
|
|
|
|
bool Applier::visit(MetavoxelInfo& info) {
|
|
// find the intersection between volume and voxel
|
|
glm::vec3 minimum = glm::max(info.minimum, _minimum);
|
|
glm::vec3 maximum = glm::min(info.minimum + glm::vec3(info.size, info.size, info.size), _maximum);
|
|
glm::vec3 size = maximum - minimum;
|
|
if (size.x <= 0.0f || size.y <= 0.0f || size.z <= 0.0f) {
|
|
return false; // disjoint
|
|
}
|
|
float volume = (size.x * size.y * size.z) / (info.size * info.size * info.size);
|
|
if (volume >= 1.0f) {
|
|
info.outputValues[0] = _value;
|
|
return false; // entirely contained
|
|
}
|
|
if (info.size <= _granularity) {
|
|
if (volume >= 0.5f) {
|
|
info.outputValues[0] = _value;
|
|
}
|
|
return false; // reached granularity limit; take best guess
|
|
}
|
|
return true; // subdivide
|
|
}
|
|
|
|
void MetavoxelEditor::applyValue(const glm::vec3& minimum, const glm::vec3& maximum) {
|
|
AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(getSelectedAttribute());
|
|
if (!attribute) {
|
|
return;
|
|
}
|
|
OwnedAttributeValue value(attribute, attribute->createFromVariant(getValue()));
|
|
|
|
Applier applier(minimum, maximum, _gridSpacing->value(), value);
|
|
Application::getInstance()->getMetavoxels()->getData().guide(applier);
|
|
}
|
|
|
|
QVariant MetavoxelEditor::getValue() const {
|
|
if (_value->layout()->isEmpty()) {
|
|
return QVariant();
|
|
}
|
|
QWidget* editor = _value->layout()->itemAt(0)->widget();
|
|
return editor->metaObject()->userProperty().read(editor);
|
|
}
|
|
|
|
ProgramObject MetavoxelEditor::_gridProgram;
|