mirror of
https://github.com/lubosz/overte.git
synced 2025-04-26 21:35:45 +02:00
Merge remote-tracking branch 'upstream/master' into pull_mode_audio_output
This commit is contained in:
commit
332e75453e
13 changed files with 506 additions and 94 deletions
domain-server
examples
interface/src
libraries
|
@ -1,3 +1,4 @@
|
|||
</div>
|
||||
<script src='/js/jquery-2.0.3.min.js'></script>
|
||||
<script src='/js/bootstrap.min.js'></script>
|
||||
<script src='/js/bootstrap.min.js'></script>
|
||||
<script src='/js/domain-server.js'></script>
|
|
@ -8,4 +8,33 @@
|
|||
<link href="/css/style.css" rel="stylesheet" media="screen">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-default" role="navigation">
|
||||
<div class="container-fluid">
|
||||
<!-- Brand and toggle get grouped for better mobile display -->
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="/">domain-server</a>
|
||||
</div>
|
||||
|
||||
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="/">Nodes</a></li>
|
||||
<li><a href="/settings/">Settings</a></li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Assignments <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a href="/assignment">New Assignment</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div><!-- /.container-fluid -->
|
||||
</nav>
|
||||
<div class="container">
|
10
domain-server/resources/web/js/domain-server.js
Normal file
10
domain-server/resources/web/js/domain-server.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
$(document).ready(function(){
|
||||
var url = window.location;
|
||||
// Will only work if string in href matches with location
|
||||
$('ul.nav a[href="'+ url +'"]').parent().addClass('active');
|
||||
|
||||
// Will also work for relative and absolute hrefs
|
||||
$('ul.nav a').filter(function() {
|
||||
return this.href == url;
|
||||
}).parent().addClass('active');
|
||||
});
|
|
@ -28,33 +28,5 @@
|
|||
"default": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"voxels": {
|
||||
"label": "Voxels",
|
||||
"assignment-types": [2,3],
|
||||
"settings": {
|
||||
"voxel-wallet": {
|
||||
"label": "Destination Wallet ID",
|
||||
"help": "Wallet to be paid for voxel changes",
|
||||
"placeholder": "00000000-0000-0000-0000-000000000000",
|
||||
"default": ""
|
||||
},
|
||||
"per-voxel-credits": {
|
||||
"type": "double",
|
||||
"label": "Per Voxel Cost",
|
||||
"help": "Credit cost to change each voxel",
|
||||
"placeholder": "0.0",
|
||||
"default": "0.0",
|
||||
"input_addon": "₵"
|
||||
},
|
||||
"per-meter-cubed-credits": {
|
||||
"type": "double",
|
||||
"label": "Per Meter Cubed Cost",
|
||||
"help": "Credit cost to change each cubed meter of voxel space",
|
||||
"placeholder": "0.0",
|
||||
"default": "0.0",
|
||||
"input_addon": "₵"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1280,6 +1280,9 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
|
|||
const QByteArray HTTP_COOKIE_HEADER_KEY = "Cookie";
|
||||
const QString ADMIN_USERS_CONFIG_KEY = "admin-users";
|
||||
const QString ADMIN_ROLES_CONFIG_KEY = "admin-roles";
|
||||
const QString BASIC_AUTH_CONFIG_KEY = "basic-auth";
|
||||
|
||||
const QByteArray UNAUTHENTICATED_BODY = "You do not have permission to access this domain-server.";
|
||||
|
||||
if (!_oauthProviderURL.isEmpty()
|
||||
&& (_argumentVariantMap.contains(ADMIN_USERS_CONFIG_KEY) || _argumentVariantMap.contains(ADMIN_ROLES_CONFIG_KEY))) {
|
||||
|
@ -1293,6 +1296,11 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
|
|||
cookieUUID = cookieUUIDRegex.cap(1);
|
||||
}
|
||||
|
||||
if (_argumentVariantMap.contains(BASIC_AUTH_CONFIG_KEY)) {
|
||||
qDebug() << "Config file contains web admin settings for OAuth and basic HTTP authentication."
|
||||
<< "These cannot be combined - using OAuth for authentication.";
|
||||
}
|
||||
|
||||
if (!cookieUUID.isNull() && _cookieSessionHash.contains(cookieUUID)) {
|
||||
// pull the QJSONObject for the user with this cookie UUID
|
||||
DomainServerWebSessionData sessionData = _cookieSessionHash.value(cookieUUID);
|
||||
|
@ -1315,8 +1323,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
|
|||
}
|
||||
}
|
||||
|
||||
QString unauthenticatedRequest = "You do not have permission to access this domain-server.";
|
||||
connection->respond(HTTPConnection::StatusCode401, unauthenticatedRequest.toUtf8());
|
||||
connection->respond(HTTPConnection::StatusCode401, UNAUTHENTICATED_BODY);
|
||||
|
||||
// the user does not have allowed username or role, return 401
|
||||
return false;
|
||||
|
@ -1340,6 +1347,59 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
|
|||
// we don't know about this user yet, so they are not yet authenticated
|
||||
return false;
|
||||
}
|
||||
} else if (_argumentVariantMap.contains(BASIC_AUTH_CONFIG_KEY)) {
|
||||
// config file contains username and password combinations for basic auth
|
||||
const QByteArray BASIC_AUTH_HEADER_KEY = "Authorization";
|
||||
|
||||
// check if a username and password have been provided with the request
|
||||
QString basicAuthString = connection->requestHeaders().value(BASIC_AUTH_HEADER_KEY);
|
||||
|
||||
if (!basicAuthString.isEmpty()) {
|
||||
QStringList splitAuthString = basicAuthString.split(' ');
|
||||
QString base64String = splitAuthString.size() == 2 ? splitAuthString[1] : "";
|
||||
QString credentialString = QByteArray::fromBase64(base64String.toLocal8Bit());
|
||||
|
||||
if (!credentialString.isEmpty()) {
|
||||
QStringList credentialList = credentialString.split(':');
|
||||
if (credentialList.size() == 2) {
|
||||
QString username = credentialList[0];
|
||||
QString password = credentialList[1];
|
||||
|
||||
// we've pulled a username and password - now check if there is a match in our basic auth hash
|
||||
QJsonObject basicAuthObject = _argumentVariantMap.value(BASIC_AUTH_CONFIG_KEY).toJsonValue().toObject();
|
||||
|
||||
if (basicAuthObject.contains(username)) {
|
||||
const QString BASIC_AUTH_USER_PASSWORD_KEY = "password";
|
||||
QJsonObject userObject = basicAuthObject.value(username).toObject();
|
||||
|
||||
if (userObject.contains(BASIC_AUTH_USER_PASSWORD_KEY)
|
||||
&& userObject.value(BASIC_AUTH_USER_PASSWORD_KEY).toString() == password) {
|
||||
// this is username / password match - let this user in
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// basic HTTP auth being used but no username and password are present
|
||||
// or the username and password are not correct
|
||||
// send back a 401 and ask for basic auth
|
||||
|
||||
const QByteArray HTTP_AUTH_REQUEST_HEADER_KEY = "WWW-Authenticate";
|
||||
static QString HTTP_AUTH_REALM_STRING = QString("Basic realm='%1 %2'")
|
||||
.arg(_hostname.isEmpty() ? "localhost" : _hostname)
|
||||
.arg("domain-server");
|
||||
|
||||
Headers basicAuthHeader;
|
||||
basicAuthHeader.insert(HTTP_AUTH_REQUEST_HEADER_KEY, HTTP_AUTH_REALM_STRING.toUtf8());
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode401, UNAUTHENTICATED_BODY,
|
||||
HTTPConnection::DefaultContentType, basicAuthHeader);
|
||||
|
||||
// not authenticated, bubble up false
|
||||
return false;
|
||||
|
||||
} else {
|
||||
// we don't have an OAuth URL + admin roles/usernames, so all users are authenticated
|
||||
return true;
|
||||
|
|
|
@ -1051,6 +1051,11 @@ function checkController(deltaTime) {
|
|||
var numberOfTriggers = Controller.getNumberOfTriggers();
|
||||
var numberOfSpatialControls = Controller.getNumberOfSpatialControls();
|
||||
var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers;
|
||||
|
||||
if (!isActive) {
|
||||
// So that we hide the lasers bellow and keep updating the overlays position
|
||||
numberOfButtons = 0;
|
||||
}
|
||||
|
||||
// this is expected for hydras
|
||||
if (numberOfButtons==12 && numberOfTriggers == 2 && controllersPerTrigger == 2) {
|
||||
|
@ -1072,11 +1077,21 @@ function checkController(deltaTime) {
|
|||
|
||||
moveOverlays();
|
||||
}
|
||||
|
||||
var isActive = false;
|
||||
var active;
|
||||
var newModel;
|
||||
var browser;
|
||||
function initToolBar() {
|
||||
toolBar = new ToolBar(0, 0, ToolBar.VERTICAL);
|
||||
// New Model
|
||||
active = toolBar.addTool({
|
||||
imageURL: toolIconUrl + "models-tool.svg",
|
||||
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||
width: toolWidth, height: toolHeight,
|
||||
visible: true,
|
||||
alpha: 0.9
|
||||
}, true, false);
|
||||
newModel = toolBar.addTool({
|
||||
imageURL: toolIconUrl + "add-model-tool.svg",
|
||||
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||
|
@ -1194,6 +1209,11 @@ function mousePressEvent(event) {
|
|||
modelSelected = false;
|
||||
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
|
||||
|
||||
if (active == toolBar.clicked(clickedOverlay)) {
|
||||
isActive = !isActive;
|
||||
return;
|
||||
}
|
||||
|
||||
if (newModel == toolBar.clicked(clickedOverlay)) {
|
||||
var url = Window.prompt("Model URL", modelURLs[Math.floor(Math.random() * modelURLs.length)]);
|
||||
if (url == null || url == "") {
|
||||
|
@ -1229,6 +1249,11 @@ function mousePressEvent(event) {
|
|||
}
|
||||
|
||||
} else {
|
||||
// If we aren't active and didn't click on an overlay: quit
|
||||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
Vec3.print("[Mouse] Looking at: ", pickRay.origin);
|
||||
var foundIntersection = Models.findRayIntersection(pickRay);
|
||||
|
@ -1313,7 +1338,7 @@ var oldModifier = 0;
|
|||
var modifier = 0;
|
||||
var wasShifted = false;
|
||||
function mouseMoveEvent(event) {
|
||||
if (event.isAlt) {
|
||||
if (event.isAlt || !isActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1456,7 +1481,7 @@ function mouseMoveEvent(event) {
|
|||
|
||||
|
||||
function mouseReleaseEvent(event) {
|
||||
if (event.isAlt) {
|
||||
if (event.isAlt || !isActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -108,6 +108,12 @@ int RenderVisitor::visit(MetavoxelInfo& info) {
|
|||
}
|
||||
|
||||
void MetavoxelSystem::render() {
|
||||
// update the frustum
|
||||
ViewFrustum* viewFrustum = Application::getInstance()->getViewFrustum();
|
||||
_frustum.set(viewFrustum->getFarTopLeft(), viewFrustum->getFarTopRight(), viewFrustum->getFarBottomLeft(),
|
||||
viewFrustum->getFarBottomRight(), viewFrustum->getNearTopLeft(), viewFrustum->getNearTopRight(),
|
||||
viewFrustum->getNearBottomLeft(), viewFrustum->getNearBottomRight());
|
||||
|
||||
RenderVisitor renderVisitor(getLOD());
|
||||
guideToAugmented(renderVisitor);
|
||||
}
|
||||
|
@ -628,13 +634,31 @@ public:
|
|||
|
||||
SpannerRenderVisitor(const MetavoxelLOD& lod);
|
||||
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
virtual bool visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize);
|
||||
|
||||
private:
|
||||
|
||||
int _containmentDepth;
|
||||
};
|
||||
|
||||
SpannerRenderVisitor::SpannerRenderVisitor(const MetavoxelLOD& lod) :
|
||||
SpannerVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getSpannersAttribute(),
|
||||
QVector<AttributePointer>(), QVector<AttributePointer>(), QVector<AttributePointer>(),
|
||||
lod, encodeOrder(Application::getInstance()->getViewFrustum()->getDirection())) {
|
||||
lod, encodeOrder(Application::getInstance()->getViewFrustum()->getDirection())),
|
||||
_containmentDepth(INT_MAX) {
|
||||
}
|
||||
|
||||
int SpannerRenderVisitor::visit(MetavoxelInfo& info) {
|
||||
if (_containmentDepth >= _depth) {
|
||||
Frustum::IntersectionType intersection = Application::getInstance()->getMetavoxels()->getFrustum().getIntersectionType(
|
||||
info.getBounds());
|
||||
if (intersection == Frustum::NO_INTERSECTION) {
|
||||
return STOP_RECURSION;
|
||||
}
|
||||
_containmentDepth = (intersection == Frustum::CONTAINS_INTERSECTION) ? _depth : INT_MAX;
|
||||
}
|
||||
return SpannerVisitor::visit(info);
|
||||
}
|
||||
|
||||
bool SpannerRenderVisitor::visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize) {
|
||||
|
@ -652,14 +676,24 @@ public:
|
|||
private:
|
||||
|
||||
int _order;
|
||||
int _containmentDepth;
|
||||
};
|
||||
|
||||
BufferRenderVisitor::BufferRenderVisitor(const AttributePointer& attribute, const MetavoxelLOD& lod) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>() << attribute, QVector<AttributePointer>(), lod),
|
||||
_order(encodeOrder(Application::getInstance()->getViewFrustum()->getDirection())) {
|
||||
_order(encodeOrder(Application::getInstance()->getViewFrustum()->getDirection())),
|
||||
_containmentDepth(INT_MAX) {
|
||||
}
|
||||
|
||||
int BufferRenderVisitor::visit(MetavoxelInfo& info) {
|
||||
if (_containmentDepth >= _depth) {
|
||||
Frustum::IntersectionType intersection = Application::getInstance()->getMetavoxels()->getFrustum().getIntersectionType(
|
||||
info.getBounds());
|
||||
if (intersection == Frustum::NO_INTERSECTION) {
|
||||
return STOP_RECURSION;
|
||||
}
|
||||
_containmentDepth = (intersection == Frustum::CONTAINS_INTERSECTION) ? _depth : INT_MAX;
|
||||
}
|
||||
BufferDataPointer buffer = info.inputValues.at(0).getInlineValue<BufferDataPointer>();
|
||||
if (buffer) {
|
||||
buffer->render();
|
||||
|
|
|
@ -34,6 +34,8 @@ public:
|
|||
virtual void init();
|
||||
|
||||
virtual MetavoxelLOD getLOD();
|
||||
|
||||
const Frustum& getFrustum() const { return _frustum; }
|
||||
|
||||
const AttributePointer& getPointBufferAttribute() { return _pointBufferAttribute; }
|
||||
const AttributePointer& getHeightfieldBufferAttribute() { return _heightfieldBufferAttribute; }
|
||||
|
@ -56,6 +58,7 @@ private:
|
|||
|
||||
MetavoxelLOD _lod;
|
||||
QReadWriteLock _lodLock;
|
||||
Frustum _frustum;
|
||||
};
|
||||
|
||||
/// Describes contents of a point in a point buffer.
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include <QPushButton>
|
||||
#include <QRunnable>
|
||||
#include <QScrollArea>
|
||||
#include <QSpinBox>
|
||||
#include <QThreadPool>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
|
@ -116,6 +117,7 @@ MetavoxelEditor::MetavoxelEditor() :
|
|||
addTool(new ClearSpannersTool(this));
|
||||
addTool(new SetSpannerTool(this));
|
||||
addTool(new ImportHeightfieldTool(this));
|
||||
addTool(new EraseHeightfieldTool(this));
|
||||
|
||||
updateAttributes();
|
||||
|
||||
|
@ -895,40 +897,68 @@ void SetSpannerTool::applyEdit(const AttributePointer& attribute, const SharedOb
|
|||
spannerData->getVoxelizationGranularity(), directionImages));
|
||||
}
|
||||
|
||||
ImportHeightfieldTool::ImportHeightfieldTool(MetavoxelEditor* editor) :
|
||||
MetavoxelTool(editor, "Import Heightfield", false) {
|
||||
HeightfieldTool::HeightfieldTool(MetavoxelEditor* editor, const QString& name) :
|
||||
MetavoxelTool(editor, name, false) {
|
||||
|
||||
QWidget* widget = new QWidget();
|
||||
QFormLayout* form = new QFormLayout();
|
||||
widget->setLayout(form);
|
||||
widget->setLayout(_form = new QFormLayout());
|
||||
layout()->addWidget(widget);
|
||||
|
||||
form->addRow("Translation:", _translation = new Vec3Editor(widget));
|
||||
form->addRow("Scale:", _scale = new QDoubleSpinBox());
|
||||
_form->addRow("Translation:", _translation = new Vec3Editor(widget));
|
||||
_form->addRow("Scale:", _scale = new QDoubleSpinBox());
|
||||
_scale->setMinimum(-FLT_MAX);
|
||||
_scale->setMaximum(FLT_MAX);
|
||||
_scale->setPrefix("2^");
|
||||
_scale->setValue(3.0);
|
||||
form->addRow("Height:", _height = new QPushButton());
|
||||
connect(_height, &QAbstractButton::clicked, this, &ImportHeightfieldTool::selectHeightFile);
|
||||
form->addRow("Color:", _color = new QPushButton());
|
||||
connect(_color, &QAbstractButton::clicked, this, &ImportHeightfieldTool::selectColorFile);
|
||||
|
||||
QPushButton* applyButton = new QPushButton("Apply");
|
||||
layout()->addWidget(applyButton);
|
||||
connect(applyButton, &QAbstractButton::clicked, this, &ImportHeightfieldTool::apply);
|
||||
connect(applyButton, &QAbstractButton::clicked, this, &HeightfieldTool::apply);
|
||||
}
|
||||
|
||||
bool ImportHeightfieldTool::appliesTo(const AttributePointer& attribute) const {
|
||||
bool HeightfieldTool::appliesTo(const AttributePointer& attribute) const {
|
||||
return attribute->inherits("HeightfieldAttribute");
|
||||
}
|
||||
|
||||
void ImportHeightfieldTool::render() {
|
||||
void HeightfieldTool::render() {
|
||||
float scale = pow(2.0, _scale->value());
|
||||
_translation->setSingleStep(scale);
|
||||
glm::vec3 quantizedTranslation = scale * glm::floor(_translation->getValue() / scale);
|
||||
_translation->setValue(quantizedTranslation);
|
||||
_preview.render(quantizedTranslation, scale);
|
||||
}
|
||||
|
||||
ImportHeightfieldTool::ImportHeightfieldTool(MetavoxelEditor* editor) :
|
||||
HeightfieldTool(editor, "Import Heightfield") {
|
||||
|
||||
_form->addRow("Height:", _height = new QPushButton());
|
||||
connect(_height, &QAbstractButton::clicked, this, &ImportHeightfieldTool::selectHeightFile);
|
||||
_form->addRow("Color:", _color = new QPushButton());
|
||||
connect(_color, &QAbstractButton::clicked, this, &ImportHeightfieldTool::selectColorFile);
|
||||
}
|
||||
|
||||
void ImportHeightfieldTool::render() {
|
||||
HeightfieldTool::render();
|
||||
_preview.render(_translation->getValue(), _translation->getSingleStep());
|
||||
}
|
||||
|
||||
void ImportHeightfieldTool::apply() {
|
||||
float scale = _translation->getSingleStep();
|
||||
foreach (const BufferDataPointer& bufferData, _preview.getBuffers()) {
|
||||
HeightfieldBuffer* buffer = static_cast<HeightfieldBuffer*>(bufferData.data());
|
||||
MetavoxelData data;
|
||||
data.setSize(scale);
|
||||
HeightfieldDataPointer heightPointer(new HeightfieldData(buffer->getHeight()));
|
||||
data.setRoot(AttributeRegistry::getInstance()->getHeightfieldAttribute(), new MetavoxelNode(AttributeValue(
|
||||
AttributeRegistry::getInstance()->getHeightfieldAttribute(), encodeInline(heightPointer))));
|
||||
if (!buffer->getColor().isEmpty()) {
|
||||
HeightfieldDataPointer colorPointer(new HeightfieldData(buffer->getColor()));
|
||||
data.setRoot(AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), new MetavoxelNode(AttributeValue(
|
||||
AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), encodeInline(colorPointer))));
|
||||
}
|
||||
MetavoxelEditMessage message = { QVariant::fromValue(SetDataEdit(
|
||||
_translation->getValue() + buffer->getTranslation() * scale, data)) };
|
||||
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
|
||||
}
|
||||
}
|
||||
|
||||
void ImportHeightfieldTool::selectHeightFile() {
|
||||
|
@ -959,26 +989,6 @@ void ImportHeightfieldTool::selectColorFile() {
|
|||
updatePreview();
|
||||
}
|
||||
|
||||
void ImportHeightfieldTool::apply() {
|
||||
float scale = pow(2.0, _scale->value());
|
||||
foreach (const BufferDataPointer& bufferData, _preview.getBuffers()) {
|
||||
HeightfieldBuffer* buffer = static_cast<HeightfieldBuffer*>(bufferData.data());
|
||||
MetavoxelData data;
|
||||
data.setSize(scale);
|
||||
HeightfieldDataPointer heightPointer(new HeightfieldData(buffer->getHeight()));
|
||||
data.setRoot(AttributeRegistry::getInstance()->getHeightfieldAttribute(), new MetavoxelNode(AttributeValue(
|
||||
AttributeRegistry::getInstance()->getHeightfieldAttribute(), encodeInline(heightPointer))));
|
||||
if (!buffer->getColor().isEmpty()) {
|
||||
HeightfieldDataPointer colorPointer(new HeightfieldData(buffer->getColor()));
|
||||
data.setRoot(AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), new MetavoxelNode(AttributeValue(
|
||||
AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), encodeInline(colorPointer))));
|
||||
}
|
||||
MetavoxelEditMessage message = { QVariant::fromValue(SetDataEdit(
|
||||
_translation->getValue() + buffer->getTranslation() * scale, data)) };
|
||||
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
|
||||
}
|
||||
}
|
||||
|
||||
const int BLOCK_SIZE = 32;
|
||||
const int BLOCK_ADVANCEMENT = BLOCK_SIZE - 1;
|
||||
|
||||
|
@ -1018,3 +1028,49 @@ void ImportHeightfieldTool::updatePreview() {
|
|||
}
|
||||
_preview.setBuffers(buffers);
|
||||
}
|
||||
|
||||
EraseHeightfieldTool::EraseHeightfieldTool(MetavoxelEditor* editor) :
|
||||
HeightfieldTool(editor, "Erase Heightfield") {
|
||||
|
||||
_form->addRow("Width:", _width = new QSpinBox());
|
||||
_width->setMinimum(1);
|
||||
_width->setMaximum(INT_MAX);
|
||||
_form->addRow("Length:", _length = new QSpinBox());
|
||||
_length->setMinimum(1);
|
||||
_length->setMaximum(INT_MAX);
|
||||
}
|
||||
|
||||
void EraseHeightfieldTool::render() {
|
||||
HeightfieldTool::render();
|
||||
|
||||
glColor3f(1.0f, 0.0f, 0.0f);
|
||||
glLineWidth(4.0f);
|
||||
|
||||
glPushMatrix();
|
||||
glm::vec3 translation = _translation->getValue();
|
||||
glTranslatef(translation.x, translation.y, translation.z);
|
||||
float scale = _translation->getSingleStep();
|
||||
glScalef(scale * _width->value(), scale, scale * _length->value());
|
||||
glTranslatef(0.5f, 0.5f, 0.5f);
|
||||
|
||||
glutWireCube(1.0);
|
||||
|
||||
glPopMatrix();
|
||||
|
||||
glLineWidth(1.0f);
|
||||
}
|
||||
|
||||
void EraseHeightfieldTool::apply() {
|
||||
// clear the heightfield
|
||||
float scale = _translation->getSingleStep();
|
||||
BoxSetEdit edit(Box(_translation->getValue(), _translation->getValue() +
|
||||
glm::vec3(_width->value() * scale, scale, _length->value() * scale)), scale,
|
||||
OwnedAttributeValue(AttributeRegistry::getInstance()->getHeightfieldAttribute()));
|
||||
MetavoxelEditMessage message = { QVariant::fromValue(edit) };
|
||||
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
|
||||
|
||||
// and the color
|
||||
edit.value = OwnedAttributeValue(AttributeRegistry::getInstance()->getHeightfieldColorAttribute());
|
||||
message.edit = QVariant::fromValue(edit);
|
||||
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ class QGroupBox;
|
|||
class QListWidget;
|
||||
class QPushButton;
|
||||
class QScrollArea;
|
||||
class QSpinBox;
|
||||
|
||||
class MetavoxelTool;
|
||||
class Vec3Editor;
|
||||
|
@ -225,30 +226,52 @@ protected:
|
|||
virtual void applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner);
|
||||
};
|
||||
|
||||
/// Base class for heightfield tools.
|
||||
class HeightfieldTool : public MetavoxelTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
HeightfieldTool(MetavoxelEditor* editor, const QString& name);
|
||||
|
||||
virtual bool appliesTo(const AttributePointer& attribute) const;
|
||||
|
||||
virtual void render();
|
||||
|
||||
protected slots:
|
||||
|
||||
virtual void apply() = 0;
|
||||
|
||||
protected:
|
||||
|
||||
QFormLayout* _form;
|
||||
Vec3Editor* _translation;
|
||||
QDoubleSpinBox* _scale;
|
||||
};
|
||||
|
||||
/// Allows importing a heightfield.
|
||||
class ImportHeightfieldTool : public MetavoxelTool {
|
||||
class ImportHeightfieldTool : public HeightfieldTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
ImportHeightfieldTool(MetavoxelEditor* editor);
|
||||
|
||||
virtual bool appliesTo(const AttributePointer& attribute) const;
|
||||
|
||||
virtual void render();
|
||||
|
||||
protected:
|
||||
|
||||
virtual void apply();
|
||||
|
||||
private slots:
|
||||
|
||||
void selectHeightFile();
|
||||
void selectColorFile();
|
||||
void apply();
|
||||
|
||||
private:
|
||||
|
||||
void updatePreview();
|
||||
|
||||
Vec3Editor* _translation;
|
||||
QDoubleSpinBox* _scale;
|
||||
QPushButton* _height;
|
||||
QPushButton* _color;
|
||||
|
||||
|
@ -258,4 +281,24 @@ private:
|
|||
HeightfieldPreview _preview;
|
||||
};
|
||||
|
||||
// Allows clearing heighfield blocks.
|
||||
class EraseHeightfieldTool : public HeightfieldTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
EraseHeightfieldTool(MetavoxelEditor* editor);
|
||||
|
||||
virtual void render();
|
||||
|
||||
protected:
|
||||
|
||||
virtual void apply();
|
||||
|
||||
private:
|
||||
|
||||
QSpinBox* _width;
|
||||
QSpinBox* _length;
|
||||
};
|
||||
|
||||
#endif // hifi_MetavoxelEditor_h
|
||||
|
|
|
@ -315,6 +315,136 @@ QDebug& operator<<(QDebug& dbg, const Box& box) {
|
|||
return dbg.nospace() << "{type='Box', minimum=" << box.minimum << ", maximum=" << box.maximum << "}";
|
||||
}
|
||||
|
||||
AxisExtents::AxisExtents(const glm::vec3& first0, const glm::vec3& first1, const glm::vec3& first2, const glm::vec3& second) :
|
||||
axis(glm::cross(first2 - first1, first0 - first1)),
|
||||
minimum(glm::dot(first1, axis)),
|
||||
maximum(glm::dot(second, axis)) {
|
||||
}
|
||||
|
||||
AxisExtents::AxisExtents(const glm::vec3& axis, float minimum, float maximum) :
|
||||
axis(axis),
|
||||
minimum(minimum),
|
||||
maximum(maximum) {
|
||||
}
|
||||
|
||||
void Frustum::set(const glm::vec3& farTopLeft, const glm::vec3& farTopRight, const glm::vec3& farBottomLeft,
|
||||
const glm::vec3& farBottomRight, const glm::vec3& nearTopLeft, const glm::vec3& nearTopRight,
|
||||
const glm::vec3& nearBottomLeft, const glm::vec3& nearBottomRight) {
|
||||
|
||||
_vertices[0] = farBottomLeft;
|
||||
_vertices[1] = farBottomRight;
|
||||
_vertices[2] = farTopLeft;
|
||||
_vertices[3] = farTopRight;
|
||||
_vertices[4] = nearBottomLeft;
|
||||
_vertices[5] = nearBottomRight;
|
||||
_vertices[6] = nearTopLeft;
|
||||
_vertices[7] = nearTopRight;
|
||||
|
||||
// compute the bounds
|
||||
_bounds.minimum = glm::vec3(FLT_MAX, FLT_MAX, FLT_MAX);
|
||||
_bounds.maximum = glm::vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
||||
for (int i = 0; i < VERTEX_COUNT; i++) {
|
||||
_bounds.minimum = glm::min(_bounds.minimum, _vertices[i]);
|
||||
_bounds.maximum = glm::max(_bounds.maximum, _vertices[i]);
|
||||
}
|
||||
|
||||
// compute the extents for each side
|
||||
_sideExtents[0] = AxisExtents(nearBottomLeft, nearTopLeft, nearTopRight, farBottomLeft);
|
||||
_sideExtents[1] = AxisExtents(nearBottomLeft, farBottomLeft, farTopLeft, farBottomRight);
|
||||
_sideExtents[2] = AxisExtents(nearBottomRight, nearTopRight, farTopRight, farBottomLeft);
|
||||
_sideExtents[3] = AxisExtents(nearBottomLeft, nearBottomRight, farBottomRight, farTopLeft);
|
||||
_sideExtents[4] = AxisExtents(nearTopLeft, farTopLeft, farTopRight, farBottomRight);
|
||||
|
||||
// the other set of extents are derived from the cross products of the frustum and box edges
|
||||
glm::vec3 edges[] = { nearBottomRight - nearBottomLeft, nearTopLeft - nearBottomLeft, farBottomLeft - nearBottomLeft,
|
||||
farBottomRight - nearBottomRight, farTopLeft - nearTopLeft, farTopRight - nearTopRight };
|
||||
const int AXIS_COUNT = 3;
|
||||
for (uint i = 0, extentIndex = 0; i < sizeof(edges) / sizeof(edges[0]); i++) {
|
||||
for (int j = 0; j < AXIS_COUNT; j++) {
|
||||
glm::vec3 axis;
|
||||
axis[j] = 1.0f;
|
||||
glm::vec3 crossProduct = glm::cross(edges[i], axis);
|
||||
float minimum = FLT_MAX, maximum = -FLT_MAX;
|
||||
for (int k = 0; k < VERTEX_COUNT; k++) {
|
||||
float projection = glm::dot(crossProduct, _vertices[k]);
|
||||
minimum = glm::min(minimum, projection);
|
||||
maximum = glm::max(maximum, projection);
|
||||
}
|
||||
_crossProductExtents[extentIndex++] = AxisExtents(crossProduct, minimum, maximum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Frustum::IntersectionType Frustum::getIntersectionType(const Box& box) const {
|
||||
// first check the bounds (equivalent to checking frustum vertices against box extents)
|
||||
if (!_bounds.intersects(box)) {
|
||||
return NO_INTERSECTION;
|
||||
}
|
||||
|
||||
// check box vertices against side extents
|
||||
bool allInside = true;
|
||||
for (int i = 0; i < SIDE_EXTENT_COUNT; i++) {
|
||||
const AxisExtents& extents = _sideExtents[i];
|
||||
float firstProjection = glm::dot(box.getVertex(0), extents.axis);
|
||||
if (firstProjection < extents.minimum) {
|
||||
allInside = false;
|
||||
for (int j = 1; j < Box::VERTEX_COUNT; j++) {
|
||||
if (glm::dot(box.getVertex(j), extents.axis) >= extents.minimum) {
|
||||
goto sideContinue;
|
||||
}
|
||||
}
|
||||
return NO_INTERSECTION;
|
||||
|
||||
} else if (firstProjection > extents.maximum) {
|
||||
allInside = false;
|
||||
for (int j = 1; j < Box::VERTEX_COUNT; j++) {
|
||||
if (glm::dot(box.getVertex(j), extents.axis) <= extents.maximum) {
|
||||
goto sideContinue;
|
||||
}
|
||||
}
|
||||
return NO_INTERSECTION;
|
||||
|
||||
} else if (allInside) {
|
||||
for (int j = 1; j < Box::VERTEX_COUNT; j++) {
|
||||
float projection = glm::dot(box.getVertex(j), extents.axis);
|
||||
if (projection < extents.minimum || projection > extents.maximum) {
|
||||
allInside = false;
|
||||
goto sideContinue;
|
||||
}
|
||||
}
|
||||
}
|
||||
sideContinue: ;
|
||||
}
|
||||
if (allInside) {
|
||||
return CONTAINS_INTERSECTION;
|
||||
}
|
||||
|
||||
// check box vertices against cross product extents
|
||||
for (int i = 0; i < CROSS_PRODUCT_EXTENT_COUNT; i++) {
|
||||
const AxisExtents& extents = _crossProductExtents[i];
|
||||
float firstProjection = glm::dot(box.getVertex(0), extents.axis);
|
||||
if (firstProjection < extents.minimum) {
|
||||
for (int j = 1; j < Box::VERTEX_COUNT; j++) {
|
||||
if (glm::dot(box.getVertex(j), extents.axis) >= extents.minimum) {
|
||||
goto crossProductContinue;
|
||||
}
|
||||
}
|
||||
return NO_INTERSECTION;
|
||||
|
||||
} else if (firstProjection > extents.maximum) {
|
||||
for (int j = 1; j < Box::VERTEX_COUNT; j++) {
|
||||
if (glm::dot(box.getVertex(j), extents.axis) <= extents.maximum) {
|
||||
goto crossProductContinue;
|
||||
}
|
||||
}
|
||||
return NO_INTERSECTION;
|
||||
}
|
||||
crossProductContinue: ;
|
||||
}
|
||||
|
||||
return PARTIAL_INTERSECTION;
|
||||
}
|
||||
|
||||
QMetaObjectEditor::QMetaObjectEditor(QWidget* parent) : QWidget(parent) {
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
layout->setContentsMargins(QMargins());
|
||||
|
@ -407,6 +537,10 @@ void BaseVec3Editor::setSingleStep(double singleStep) {
|
|||
_z->setSingleStep(singleStep);
|
||||
}
|
||||
|
||||
double BaseVec3Editor::getSingleStep() const {
|
||||
return _x->singleStep();
|
||||
}
|
||||
|
||||
QDoubleSpinBox* BaseVec3Editor::createComponentBox() {
|
||||
QDoubleSpinBox* box = new QDoubleSpinBox();
|
||||
box->setMinimum(-FLT_MAX);
|
||||
|
|
|
@ -65,6 +65,43 @@ Box operator*(const glm::mat4& matrix, const Box& box);
|
|||
|
||||
QDebug& operator<<(QDebug& out, const Box& box);
|
||||
|
||||
/// Represents the extents along an axis.
|
||||
class AxisExtents {
|
||||
public:
|
||||
glm::vec3 axis;
|
||||
float minimum;
|
||||
float maximum;
|
||||
|
||||
/// Creates a set of extents given three points on the first plane and one on the second.
|
||||
AxisExtents(const glm::vec3& first0, const glm::vec3& first1, const glm::vec3& first2, const glm::vec3& second);
|
||||
|
||||
AxisExtents(const glm::vec3& axis = glm::vec3(), float minimum = 0.0f, float maximum = 0.0f);
|
||||
};
|
||||
|
||||
/// A simple pyramidal frustum for intersection testing.
|
||||
class Frustum {
|
||||
public:
|
||||
|
||||
void set(const glm::vec3& farTopLeft, const glm::vec3& farTopRight, const glm::vec3& farBottomLeft,
|
||||
const glm::vec3& farBottomRight, const glm::vec3& nearTopLeft, const glm::vec3& nearTopRight,
|
||||
const glm::vec3& nearBottomLeft, const glm::vec3& nearBottomRight);
|
||||
|
||||
enum IntersectionType { NO_INTERSECTION, PARTIAL_INTERSECTION, CONTAINS_INTERSECTION };
|
||||
|
||||
IntersectionType getIntersectionType(const Box& box) const;
|
||||
|
||||
private:
|
||||
|
||||
static const int VERTEX_COUNT = 8;
|
||||
static const int SIDE_EXTENT_COUNT = 5;
|
||||
static const int CROSS_PRODUCT_EXTENT_COUNT = 18;
|
||||
|
||||
glm::vec3 _vertices[VERTEX_COUNT];
|
||||
Box _bounds;
|
||||
AxisExtents _sideExtents[SIDE_EXTENT_COUNT];
|
||||
AxisExtents _crossProductExtents[CROSS_PRODUCT_EXTENT_COUNT];
|
||||
};
|
||||
|
||||
/// Editor for meta-object values.
|
||||
class QMetaObjectEditor : public QWidget {
|
||||
Q_OBJECT
|
||||
|
@ -154,6 +191,7 @@ public:
|
|||
BaseVec3Editor(QWidget* parent);
|
||||
|
||||
void setSingleStep(double singleStep);
|
||||
double getSingleStep() const;
|
||||
|
||||
protected slots:
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QJsonDocument>
|
||||
|
@ -68,29 +69,35 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL
|
|||
|
||||
int configIndex = argumentList.indexOf(CONFIG_FILE_OPTION);
|
||||
|
||||
QString configFilePath;
|
||||
|
||||
if (configIndex != -1) {
|
||||
// we have a config file - try and read it
|
||||
QString configFilePath = argumentList[configIndex + 1];
|
||||
QFile configFile(configFilePath);
|
||||
|
||||
if (configFile.exists()) {
|
||||
qDebug() << "Reading JSON config file at" << configFilePath;
|
||||
configFile.open(QIODevice::ReadOnly);
|
||||
|
||||
QJsonDocument configDocument = QJsonDocument::fromJson(configFile.readAll());
|
||||
QJsonObject rootObject = configDocument.object();
|
||||
|
||||
// enumerate the keys of the configDocument object
|
||||
foreach(const QString& key, rootObject.keys()) {
|
||||
|
||||
if (!mergedMap.contains(key)) {
|
||||
// no match in existing list, add it
|
||||
mergedMap.insert(key, QVariant(rootObject[key]));
|
||||
}
|
||||
configFilePath = argumentList[configIndex + 1];
|
||||
} else {
|
||||
// no config file - try to read a file at resources/config.json
|
||||
configFilePath = QCoreApplication::applicationDirPath() + "/resources/config.json";
|
||||
}
|
||||
|
||||
QFile configFile(configFilePath);
|
||||
|
||||
if (configFile.exists()) {
|
||||
qDebug() << "Reading JSON config file at" << configFilePath;
|
||||
configFile.open(QIODevice::ReadOnly);
|
||||
|
||||
QJsonDocument configDocument = QJsonDocument::fromJson(configFile.readAll());
|
||||
QJsonObject rootObject = configDocument.object();
|
||||
|
||||
// enumerate the keys of the configDocument object
|
||||
foreach(const QString& key, rootObject.keys()) {
|
||||
|
||||
if (!mergedMap.contains(key)) {
|
||||
// no match in existing list, add it
|
||||
mergedMap.insert(key, QVariant(rootObject[key]));
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Could not find JSON config file at" << configFilePath;
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Could not find JSON config file at" << configFilePath;
|
||||
}
|
||||
|
||||
return mergedMap;
|
||||
|
|
Loading…
Reference in a new issue