overte-HifiExperiments/interface/src/ui/MetavoxelEditor.cpp
2014-01-22 15:58:41 -08:00

339 lines
11 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 <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;
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:
return glm::angleAxis(90.0f, 0.0f, 1.0f, 0.0f);
}
}
void MetavoxelEditor::resetState() {
_state = HOVERING_STATE;
_startPosition = INVALID_VECTOR;
_height = 0.0f;
}
void MetavoxelEditor::applyValue(const glm::vec3& minimum, const glm::vec3& maximum) {
}
ProgramObject MetavoxelEditor::_gridProgram;