From 5a843b13427e3d884f6779abb3af55efd67fc3c7 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 14 Dec 2017 11:03:59 -0800 Subject: [PATCH 001/569] adding support for filters to get old properties --- libraries/entities/src/EntityEditFilters.cpp | 11 ++++-- libraries/entities/src/EntityEditFilters.h | 2 +- libraries/entities/src/EntityTree.cpp | 2 +- .../entity_edit_filters/postition-example.js | 36 +++++++++++++++++++ 4 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 scripts/tutorials/entity_edit_filters/postition-example.js diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index 550c8f17c4..09c165e9cb 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -42,7 +42,7 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { } bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, - EntityTree::FilterType filterType, EntityItemID& itemID) { + EntityTree::FilterType filterType, EntityItemID& itemID, EntityItemPointer& existingEntity) { // get the ids of all the zones (plus the global entity edit filter) that the position // lies within @@ -68,9 +68,15 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper propertiesIn.setDesiredProperties(oldProperties); auto in = QJsonValue::fromVariant(inputValues.toVariant()); // grab json copy now, because the inputValues might be side effected by the filter. + + // get the current properties for then entity and include them for the filter call + auto currentProperties = existingEntity ? existingEntity->getProperties() : EntityItemProperties(); + QScriptValue currentValues = currentProperties.copyToScriptValue(filterData.engine, false, true, true); + QScriptValueList args; args << inputValues; args << filterType; + args << currentValues; QScriptValue result = filterData.filterFn.call(_nullObjectForFilter, args); if (filterData.uncaughtExceptions()) { @@ -182,8 +188,8 @@ static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { qDebug() << "script request completed for entity " << entityID; auto scriptRequest = qobject_cast(sender()); - const QString urlString = scriptRequest->getUrl().toString(); if (scriptRequest && scriptRequest->getResult() == ResourceRequest::Success) { + const QString urlString = scriptRequest->getUrl().toString(); auto scriptContents = scriptRequest->getData(); qInfo() << "Downloaded script:" << scriptContents; QScriptProgram program(scriptContents, urlString); @@ -227,6 +233,7 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { } } } else if (scriptRequest) { + const QString urlString = scriptRequest->getUrl().toString(); qCritical() << "Failed to download script at" << urlString; // See HTTPResourceRequest::onRequestFinished for interpretation of codes. For example, a 404 is code 6 and 403 is 3. A timeout is 2. Go figure. qCritical() << "ResourceRequest error was" << scriptRequest->getResult(); diff --git a/libraries/entities/src/EntityEditFilters.h b/libraries/entities/src/EntityEditFilters.h index 6aeb347603..f25c3c092e 100644 --- a/libraries/entities/src/EntityEditFilters.h +++ b/libraries/entities/src/EntityEditFilters.h @@ -43,7 +43,7 @@ public: void removeFilter(EntityItemID entityID); bool filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, - EntityTree::FilterType filterType, EntityItemID& entityID); + EntityTree::FilterType filterType, EntityItemID& entityID, EntityItemPointer& existingEntity); signals: void filterAdded(EntityItemID id, bool success); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index e62399ce95..aa7215f53f 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1099,7 +1099,7 @@ bool EntityTree::filterProperties(EntityItemPointer& existingEntity, EntityItemP if (entityEditFilters) { auto position = existingEntity ? existingEntity->getWorldPosition() : propertiesIn.getPosition(); auto entityID = existingEntity ? existingEntity->getEntityItemID() : EntityItemID(); - accepted = entityEditFilters->filter(position, propertiesIn, propertiesOut, wasChanged, filterType, entityID); + accepted = entityEditFilters->filter(position, propertiesIn, propertiesOut, wasChanged, filterType, entityID, existingEntity); } return accepted; diff --git a/scripts/tutorials/entity_edit_filters/postition-example.js b/scripts/tutorials/entity_edit_filters/postition-example.js new file mode 100644 index 0000000000..00f7391809 --- /dev/null +++ b/scripts/tutorials/entity_edit_filters/postition-example.js @@ -0,0 +1,36 @@ +function filter(properties, type, originalProperties) { + + /* Clamp position changes.*/ + var maxChange = 5; + function sign(val) { + if (val > 0) { + return 1; + } else if (val < 0) { + return -1; + } else { + return 0; + } + } + + function clamp(val, min, max) { + if (val > max) { + val = max; + } else if (val < min) { + val = min; + } + return val; + } + + + if (properties.position) { + /* Random near-zero value used as "zero" to prevent two sequential updates from being + exactly the same (which would cause them to be ignored) */ + var nearZero = 0.0001 * Math.random() + 0.001; + var maxFudgeChange = (maxChange + nearZero); + properties.position.x = clamp(properties.position.x, originalProperties.position.x - maxFudgeChange, originalProperties.position.x + maxFudgeChange); + properties.position.y = clamp(properties.position.y, originalProperties.position.y - maxFudgeChange, originalProperties.position.y + maxFudgeChange); + properties.position.z = clamp(properties.position.z, originalProperties.position.z - maxFudgeChange, originalProperties.position.z + maxFudgeChange); + } + + return properties; +} \ No newline at end of file From 17288386aeddbad5ce953087903a744e23d08105 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 14 Dec 2017 12:20:03 -0800 Subject: [PATCH 002/569] implement support for zone properties in filters --- libraries/entities/src/EntityEditFilters.cpp | 25 +++++++++++++++++++ .../keep-in-zone-example.js | 24 ++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 scripts/tutorials/entity_edit_filters/keep-in-zone-example.js diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index 09c165e9cb..13be7ebc7d 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -73,12 +73,37 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper auto currentProperties = existingEntity ? existingEntity->getProperties() : EntityItemProperties(); QScriptValue currentValues = currentProperties.copyToScriptValue(filterData.engine, false, true, true); + // get the zone properties + auto zoneEntity = _tree->findEntityByEntityItemID(id); + auto zoneProperties = zoneEntity ? zoneEntity->getProperties() : EntityItemProperties(); + QScriptValue zoneValues = zoneProperties.copyToScriptValue(filterData.engine, false, true, true); + + if (zoneEntity) { + bool success = true; + AABox aaBox = zoneEntity->getAABox(success); + + if (success) { + QScriptValue boundingBox = filterData.engine->newObject(); + QScriptValue bottomRightNear = vec3toScriptValue(filterData.engine, aaBox.getCorner()); + QScriptValue topFarLeft = vec3toScriptValue(filterData.engine, aaBox.calcTopFarLeft()); + QScriptValue center = vec3toScriptValue(filterData.engine, aaBox.calcCenter()); + QScriptValue boundingBoxDimensions = vec3toScriptValue(filterData.engine, aaBox.getDimensions()); + boundingBox.setProperty("brn", bottomRightNear); + boundingBox.setProperty("tfl", topFarLeft); + boundingBox.setProperty("center", center); + boundingBox.setProperty("dimensions", boundingBoxDimensions); + zoneValues.setProperty("boundingBox", boundingBox); + } + } + QScriptValueList args; args << inputValues; args << filterType; args << currentValues; + args << zoneValues; QScriptValue result = filterData.filterFn.call(_nullObjectForFilter, args); + if (filterData.uncaughtExceptions()) { return false; } diff --git a/scripts/tutorials/entity_edit_filters/keep-in-zone-example.js b/scripts/tutorials/entity_edit_filters/keep-in-zone-example.js new file mode 100644 index 0000000000..13355129f5 --- /dev/null +++ b/scripts/tutorials/entity_edit_filters/keep-in-zone-example.js @@ -0,0 +1,24 @@ +function filter(properties, type, originalProperties, zoneProperties) { + + var nearZero = 0.0001 * Math.random() + 0.001; + + /* Clamp position changes to bounding box of zone.*/ + function clamp(val, min, max) { + /* Random near-zero value used as "zero" to prevent two sequential updates from being + exactly the same (which would cause them to be ignored) */ + if (val > max) { + val = max - nearZero; + } else if (val < min) { + val = min + nearZero; + } + return val; + } + + if (properties.position) { + properties.position.x = clamp(properties.position.x, zoneProperties.boundingBox.brn.x, zoneProperties.boundingBox.tfl.x); + properties.position.y = clamp(properties.position.y, zoneProperties.boundingBox.brn.y, zoneProperties.boundingBox.tfl.y); + properties.position.z = clamp(properties.position.z, zoneProperties.boundingBox.brn.z, zoneProperties.boundingBox.tfl.z); + } + + return properties; +} \ No newline at end of file From 32d05bc163c16303ef5625e65df73f756228080d Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 19 Dec 2017 07:34:56 -0800 Subject: [PATCH 003/569] CR feedback --- scripts/tutorials/entity_edit_filters/keep-in-zone-example.js | 2 +- .../{postition-example.js => position-example.js} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename scripts/tutorials/entity_edit_filters/{postition-example.js => position-example.js} (99%) diff --git a/scripts/tutorials/entity_edit_filters/keep-in-zone-example.js b/scripts/tutorials/entity_edit_filters/keep-in-zone-example.js index 13355129f5..9013edda96 100644 --- a/scripts/tutorials/entity_edit_filters/keep-in-zone-example.js +++ b/scripts/tutorials/entity_edit_filters/keep-in-zone-example.js @@ -21,4 +21,4 @@ function filter(properties, type, originalProperties, zoneProperties) { } return properties; -} \ No newline at end of file +} diff --git a/scripts/tutorials/entity_edit_filters/postition-example.js b/scripts/tutorials/entity_edit_filters/position-example.js similarity index 99% rename from scripts/tutorials/entity_edit_filters/postition-example.js rename to scripts/tutorials/entity_edit_filters/position-example.js index 00f7391809..5f17f04542 100644 --- a/scripts/tutorials/entity_edit_filters/postition-example.js +++ b/scripts/tutorials/entity_edit_filters/position-example.js @@ -33,4 +33,4 @@ function filter(properties, type, originalProperties) { } return properties; -} \ No newline at end of file +} From c3801fee7e5093405a460be2b5df19f9625baa4b Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 27 Dec 2017 11:58:00 -0800 Subject: [PATCH 004/569] ka --- libraries/fbx/src/OBJReader.cpp | 20 +++++++++++++------- libraries/fbx/src/OBJReader.h | 4 +++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 315c6a86d2..bd4158bcad 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -256,7 +256,13 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { default: materials[matName] = currentMaterial; #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader Last material shininess:" << currentMaterial.shininess << " opacity:" << currentMaterial.opacity << " diffuse color:" << currentMaterial.diffuseColor << " specular color:" << currentMaterial.specularColor << " diffuse texture:" << currentMaterial.diffuseTextureFilename << " specular texture:" << currentMaterial.specularTextureFilename; + qCDebug(modelformat) << "OBJ Reader Last material shininess:" << currentMaterial.shininess << " opacity:" << + currentMaterial.opacity << " diffuse color:" << currentMaterial.diffuseColor << + " specular color:" << currentMaterial.specularColor << " emissive color:" << + currentMaterial.emissiveColor << " diffuse texture:" << + currentMaterial.diffuseTextureFilename << " specular texture:" << + currentMaterial.specularTextureFilename << " emissive texture:" << + currentMaterial.emissiveTextureFilename; #endif return; } @@ -277,14 +283,12 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { } else if ((token == "d") || (token == "Tr")) { currentMaterial.opacity = tokenizer.getFloat(); } else if (token == "Ka") { - #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader Ignoring material Ka " << tokenizer.getVec3(); - #endif + currentMaterial.emissiveColor = tokenizer.getVec3(); } else if (token == "Kd") { currentMaterial.diffuseColor = tokenizer.getVec3(); } else if (token == "Ks") { currentMaterial.specularColor = tokenizer.getVec3(); - } else if ((token == "map_Kd") || (token == "map_Ks")) { + } else if ((token == "map_Ka") || (token == "map_Kd") || (token == "map_Ks")) { QByteArray filename = QUrl(tokenizer.getLineAsDatum()).fileName().toUtf8(); if (filename.endsWith(".tga")) { #ifdef WANT_DEBUG @@ -292,7 +296,9 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { #endif break; } - if (token == "map_Kd") { + if (token == "map_Ka") { + currentMaterial.emissiveTextureFilename = filename; + } else if (token == "map_Kd") { currentMaterial.diffuseTextureFilename = filename; } else if( token == "map_Ks" ) { currentMaterial.specularTextureFilename = filename; @@ -725,7 +731,7 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, } geometry.materials[materialID] = FBXMaterial(objMaterial.diffuseColor, objMaterial.specularColor, - glm::vec3(0.0f), + objMaterial.emissiveColor, objMaterial.shininess, objMaterial.opacity); FBXMaterial& fbxMaterial = geometry.materials[materialID]; diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index 45e3f79480..e25cd7c5e9 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -56,11 +56,13 @@ public: float opacity; glm::vec3 diffuseColor; glm::vec3 specularColor; + glm::vec3 emissiveColor; QByteArray diffuseTextureFilename; QByteArray specularTextureFilename; + QByteArray emissiveTextureFilename; bool used { false }; bool userSpecifiesUV { false }; - OBJMaterial() : shininess(0.0f), opacity(1.0f), diffuseColor(0.9f), specularColor(0.9f) {} + OBJMaterial() : shininess(0.0f), opacity(1.0f), diffuseColor(0.9f), specularColor(0.9f), emissiveColor(0.9f) {} }; class OBJReader: public QObject { // QObject so we can make network requests. From 5e45ee456c610733a5b598780aab45573dd7cf73 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 27 Dec 2017 16:55:45 -0800 Subject: [PATCH 005/569] more ka --- interface/resources/qml/js/Utils.jsc | Bin 6596 -> 6516 bytes libraries/fbx/src/OBJReader.cpp | 5 +++++ 2 files changed, 5 insertions(+) diff --git a/interface/resources/qml/js/Utils.jsc b/interface/resources/qml/js/Utils.jsc index ab20e996b9469915ac6a89901da175143e6b5024..8da68e4e192018ee2b4f3d835ec91f36f0161eca 100644 GIT binary patch delta 522 zcmX?N{KZJ8u*@VmC9xz?kb!}Lk&~5STcM<00wV*14l4u0rhn_zD^;ej?k>4g!mi%G zd85u0My4%nlMgYvF+C98%*ABHq-@E+z>vm}$&ka4%3#Hy4~8yaRtiHBLn1>mL-yoY zme9>xSavcq8f?zvxX;LFu-T8xhB-~cvzy7I+abZT(?w;5Z?~9lcMw!410*y7MW_HI z)PW*Y0TOCJ7HViFnI&N2cysApZo@sr}K+4E}OiT-VC&A*2iV;C72bXXY}mb_f{tI)LMb*EnX9J{G= zWjE?fVPxWFpL~eXjmbb{GZ&K)lcEI!149}^CPNNGDuWe+J{UTKSV!qu$P@xQv&;%5r0+3J# zickeer~z51p@D&c!LzePrQ-kp|4@m^A9y8vAm%nqfJ$c|q`_`zm;seYK$lnmm56{y zI39Phm>j})kdv7eWRZ)?jLG}?J(xDJOx`D`JXuG8Z}JL$VaC6c_wai&DZm7I_&)Iq z-167~3SS0>5B8H41jOnE9)IGGd+4!a)=UuiXrJc6pLZgSU+*D*n>GUj15EV<2e8B^ z{2Kn8s9?FPGD3MA9~ zMq$_Fat`Uu69ftv87(F=3MGm|!vW+*kU5~R2l;h#rBDLnsetEmissive(fbxMaterial.emissiveColor); modelMaterial->setAlbedo(fbxMaterial.diffuseColor); From 38d2266d28bef0d418525acf251d6d76e201708c Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 27 Dec 2017 17:14:45 -0800 Subject: [PATCH 006/569] Tr --- libraries/fbx/src/OBJReader.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 0395173c68..de60efbc1d 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -282,8 +282,10 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { //currentMaterial.specularTextureFilename = ""; } else if (token == "Ns") { currentMaterial.shininess = tokenizer.getFloat(); - } else if ((token == "d") || (token == "Tr")) { + } else if (token == "d") { currentMaterial.opacity = tokenizer.getFloat(); + } else if (token == "Tr") { + currentMaterial.opacity = 1.0f - tokenizer.getFloat(); } else if (token == "Ka") { currentMaterial.emissiveColor = tokenizer.getVec3(); } else if (token == "Kd") { From 188e476f2f203bc5e832fb18afd9192a759ef260 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 28 Dec 2017 13:57:12 -0800 Subject: [PATCH 007/569] temp obj import fixes --- libraries/fbx/src/OBJReader.cpp | 24 ++++++++++++++++++++++-- libraries/render-utils/src/Model.cpp | 2 +- libraries/shared/src/Transform.cpp | 2 +- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index de60efbc1d..51e7c3cafb 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -467,14 +467,34 @@ bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mappi // vertex-index/texture-index // vertex-index/texture-index/surface-normal-index QByteArray token = tokenizer.getDatum(); - if (!isdigit(token[0])) { // Tokenizer treats line endings as whitespace. Non-digit indicates done; + auto firstChar = token[0]; + // Tokenizer treats line endings as whitespace. Non-digit and non-negative sign indicates done; + if (!isdigit(firstChar) && firstChar != '-') { tokenizer.pushBackToken(OBJTokenizer::DATUM_TOKEN); break; } QList parts = token.split('/'); assert(parts.count() >= 1); assert(parts.count() <= 3); - const QByteArray noData {}; + // If indices are negative relative indices then adjust them to absolute indices based on current vector sizes + // Also add 1 to each index as 1 will be subtracted later on from each index in OBJFace::add + int part0 = parts[0].toInt(); + if (part0 < 0) { + parts[0].setNum(vertices.size() - abs(part0) + 1); + } + if (parts.count() > 1) { + int part1 = parts[1].toInt(); + if (part1 < 0) { + parts[1].setNum(textureUVs.size() - abs(part1) + 1); + } + if (parts.count() > 2) { + int part2 = parts[2].toInt(); + if (part2 < 0) { + parts[2].setNum(normals.size() - abs(part2) + 1); + } + } + } + const QByteArray noData{}; face.add(parts[0], (parts.count() > 1) ? parts[1] : noData, (parts.count() > 2) ? parts[2] : noData, vertices, vertexColors); face.groupName = currentGroup; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 7717ceda6f..fd9f753a75 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -163,7 +163,7 @@ void Model::setScale(const glm::vec3& scale) { _scaledToFit = false; } -const float SCALE_CHANGE_EPSILON = 0.001f; +const float SCALE_CHANGE_EPSILON = 0.0000001f; void Model::setScaleInternal(const glm::vec3& scale) { if (glm::distance(_scale, scale) > SCALE_CHANGE_EPSILON) { diff --git a/libraries/shared/src/Transform.cpp b/libraries/shared/src/Transform.cpp index 3e29c38add..eeb726ba72 100644 --- a/libraries/shared/src/Transform.cpp +++ b/libraries/shared/src/Transform.cpp @@ -19,7 +19,7 @@ #include "shared/JSONHelpers.h" void Transform::evalRotationScale(Quat& rotation, Vec3& scale, const Mat3& rotationScaleMatrix) { - const float ACCURACY_THREASHOLD = 0.00001f; + const float ACCURACY_THREASHOLD = 0.000001f; // Following technique taken from: // http://callumhay.blogspot.com/2010/10/decomposing-affine-transforms.html From dee06e72f6c627da15e97dc1c67ac8873b5cab94 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 28 Dec 2017 16:08:53 -0800 Subject: [PATCH 008/569] ke is the new ka --- libraries/fbx/src/OBJReader.cpp | 18 +++++++++++------- libraries/fbx/src/OBJReader.h | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 51e7c3cafb..07d445fb3e 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -277,9 +277,9 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJ Reader Starting new material definition " << matName; #endif - //currentMaterial.emissiveTextureFilename = ""; currentMaterial.diffuseTextureFilename = ""; - //currentMaterial.specularTextureFilename = ""; + currentMaterial.emissiveTextureFilename = ""; + currentMaterial.specularTextureFilename = ""; } else if (token == "Ns") { currentMaterial.shininess = tokenizer.getFloat(); } else if (token == "d") { @@ -287,12 +287,16 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { } else if (token == "Tr") { currentMaterial.opacity = 1.0f - tokenizer.getFloat(); } else if (token == "Ka") { - currentMaterial.emissiveColor = tokenizer.getVec3(); + #ifdef WANT_DEBUG + qCDebug(modelformat) << "OBJ Reader Ignoring material Ka " << tokenizer.getVec3(); + #endif } else if (token == "Kd") { currentMaterial.diffuseColor = tokenizer.getVec3(); + } else if (token == "Ke") { + currentMaterial.emissiveColor = tokenizer.getVec3(); } else if (token == "Ks") { currentMaterial.specularColor = tokenizer.getVec3(); - } else if ((token == "map_Ka") || (token == "map_Kd") || (token == "map_Ks")) { + } else if ((token == "map_Kd") || (token == "map_Ke") || (token == "map_Ks")) { QByteArray filename = QUrl(tokenizer.getLineAsDatum()).fileName().toUtf8(); if (filename.endsWith(".tga")) { #ifdef WANT_DEBUG @@ -300,10 +304,10 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { #endif break; } - if (token == "map_Ka") { - currentMaterial.emissiveTextureFilename = filename; - } else if (token == "map_Kd") { + if (token == "map_Kd") { currentMaterial.diffuseTextureFilename = filename; + } else if (token == "map_Ke") { + currentMaterial.emissiveTextureFilename = filename; } else if( token == "map_Ks" ) { currentMaterial.specularTextureFilename = filename; } diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index e25cd7c5e9..f0852c9c22 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -62,7 +62,7 @@ public: QByteArray emissiveTextureFilename; bool used { false }; bool userSpecifiesUV { false }; - OBJMaterial() : shininess(0.0f), opacity(1.0f), diffuseColor(0.9f), specularColor(0.9f), emissiveColor(0.9f) {} + OBJMaterial() : shininess(0.0f), opacity(1.0f), diffuseColor(0.9f), specularColor(0.9f), emissiveColor(0.0f) {} }; class OBJReader: public QObject { // QObject so we can make network requests. From a855916eb89c1ef3b8d744e2ef9b7f477715f29d Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 28 Dec 2017 16:39:02 -0800 Subject: [PATCH 009/569] fix relative paths --- libraries/fbx/src/OBJReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 07d445fb3e..3b4eed6746 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -297,7 +297,7 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { } else if (token == "Ks") { currentMaterial.specularColor = tokenizer.getVec3(); } else if ((token == "map_Kd") || (token == "map_Ke") || (token == "map_Ks")) { - QByteArray filename = QUrl(tokenizer.getLineAsDatum()).fileName().toUtf8(); + QByteArray filename = QString(tokenizer.getLineAsDatum()).toUtf8(); if (filename.endsWith(".tga")) { #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJ Reader WARNING: currently ignoring tga texture " << filename << " in " << _url; From cee6bd270079125729dd9748665e4714efb0f32f Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 28 Dec 2017 17:20:16 -0800 Subject: [PATCH 010/569] ignore Ni --- libraries/fbx/src/OBJReader.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 3b4eed6746..d6955a6945 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -282,6 +282,10 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { currentMaterial.specularTextureFilename = ""; } else if (token == "Ns") { currentMaterial.shininess = tokenizer.getFloat(); + } else if (token == "Ni") { + #ifdef WANT_DEBUG + qCDebug(modelformat) << "OBJ Reader Ignoring material Ni " << tokenizer.getFloat(); + #endif } else if (token == "d") { currentMaterial.opacity = tokenizer.getFloat(); } else if (token == "Tr") { From 7531a2ef3b9e134e2000ed79c507bed7b1acafca Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 29 Dec 2017 18:00:07 -0800 Subject: [PATCH 011/569] map_bump wip --- interface/resources/qml/js/Utils.jsc | Bin 6516 -> 6548 bytes libraries/fbx/src/OBJReader.cpp | 15 ++++++++++++--- libraries/fbx/src/OBJReader.h | 1 + 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/js/Utils.jsc b/interface/resources/qml/js/Utils.jsc index 8da68e4e192018ee2b4f3d835ec91f36f0161eca..0e4b04d46e53fb0fdea77cfc8b85627fa9276aad 100644 GIT binary patch delta 180 zcmW;8JqrPG9KiAK@A_XIoALs@0CnO@Nl`9F7A%U(LtQK$x|L+t^_Lf5mJ%uN!D2Uf z0Y)2x(r5X+)0<3k95~&cH}hhEL`*t~bR|LRQXGhvF2i${P@ W51R@;Gu>8oK_AsT%v-0F>BfHt$1Yz0 delta 155 zcmbPY{KZJ8u*@VmC9xz?kb!}Lk&~5STcM<00wV*14l4u0rhn_zD^;ej?k>4g!mi%G zd7@4Wqrt?D;Y<(MCv!1|F&>zl$QaJ3FnK4VKhp!@$y`j^1h=p-FdSiFV7S7setEmissive(fbxMaterial.emissiveColor); modelMaterial->setAlbedo(fbxMaterial.diffuseColor); diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index f0852c9c22..d0bbd539e9 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -60,6 +60,7 @@ public: QByteArray diffuseTextureFilename; QByteArray specularTextureFilename; QByteArray emissiveTextureFilename; + QByteArray bumpTextureFilename; bool used { false }; bool userSpecifiesUV { false }; OBJMaterial() : shininess(0.0f), opacity(1.0f), diffuseColor(0.9f), specularColor(0.9f), emissiveColor(0.0f) {} From a44e00142dfb867e960596397689e7c3f617210c Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 2 Jan 2018 15:41:14 -0800 Subject: [PATCH 012/569] texture line parse wip --- libraries/fbx/src/OBJReader.cpp | 31 ++++++++++++++++++++++++++++--- libraries/fbx/src/OBJReader.h | 7 +++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 73c0f07fa6..d5eeec51d0 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -303,8 +303,10 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { } else if (token == "Ks") { currentMaterial.specularColor = tokenizer.getVec3(); } else if ((token == "map_Kd") || (token == "map_Ke") || (token == "map_Ks") || (token == "map_bump")) { - QByteArray textureLine = QString(tokenizer.getLineAsDatum()).toUtf8(); - QByteArray filename = textureLine; // TODO: parse texture options and filename from line + const QByteArray textureLine = tokenizer.getLineAsDatum(); + QByteArray filename; + OBJMaterialTextureOptions textureOptions; + parseTextureLine(textureLine, filename, textureOptions); if (filename.endsWith(".tga")) { #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJ Reader WARNING: currently ignoring tga texture " << filename << " in " << _url; @@ -315,13 +317,36 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { currentMaterial.diffuseTextureFilename = filename; } else if (token == "map_Ke") { currentMaterial.emissiveTextureFilename = filename; - } else if( token == "map_Ks" ) { + } else if (token == "map_Ks" ) { currentMaterial.specularTextureFilename = filename; } else if (token == "map_bump") { currentMaterial.bumpTextureFilename = filename; } } } +} + +void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& filename, OBJMaterialTextureOptions& textureOptions) { + QString parser = textureLine; + while (parser.length() > 0) { + if (parser.startsWith("-bm")) { + parser.remove(0, 4); + int multiplierEnd = parser.indexOf(' '); + if (multiplierEnd < 0) { + multiplierEnd = parser.length(); + } + QString multiplier = parser.left(multiplierEnd); + textureOptions.bumpMultiplier = std::stof(multiplier.toStdString()); + parser.remove(0, multiplier.length() + 1); + } else { + int fileEnd = parser.indexOf(' '); + if (fileEnd < 0) { + fileEnd = parser.length(); + } + filename = parser.left(fileEnd).toUtf8(); + parser.remove(0, filename.length() + 1); + } + } } std::tuple requestData(QUrl& url) { diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index d0bbd539e9..2c1db7ccf7 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -66,6 +66,12 @@ public: OBJMaterial() : shininess(0.0f), opacity(1.0f), diffuseColor(0.9f), specularColor(0.9f), emissiveColor(0.0f) {} }; +class OBJMaterialTextureOptions { +public: + float bumpMultiplier; + OBJMaterialTextureOptions() : bumpMultiplier(1.0f) {} +}; + class OBJReader: public QObject { // QObject so we can make network requests. Q_OBJECT public: @@ -87,6 +93,7 @@ private: bool parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, FBXGeometry& geometry, float& scaleGuess, bool combineParts); void parseMaterialLibrary(QIODevice* device); + void parseTextureLine(const QByteArray& textureLine, QByteArray& filename, OBJMaterialTextureOptions& textureOptions); bool isValidTexture(const QByteArray &filename); // true if the file exists. TODO?: check content-type header and that it is a supported format. int _partCounter { 0 }; From 88b034aa78ddb3be7c95190bf6bf38742609b616 Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 2 Jan 2018 16:35:43 -0800 Subject: [PATCH 013/569] bump multiplier --- libraries/fbx/src/FBX.h | 2 ++ libraries/fbx/src/OBJReader.cpp | 8 ++++++-- libraries/fbx/src/OBJReader.h | 3 ++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/libraries/fbx/src/FBX.h b/libraries/fbx/src/FBX.h index 7d3328a2dd..70344512ca 100644 --- a/libraries/fbx/src/FBX.h +++ b/libraries/fbx/src/FBX.h @@ -178,6 +178,8 @@ public: float emissiveIntensity{ 1.0f }; float ambientFactor{ 1.0f }; + float bumpMultiplier{ 1.0f }; + QString materialID; QString name; QString shadingModel; diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index d5eeec51d0..6127b454b8 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -302,7 +302,7 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { currentMaterial.emissiveColor = tokenizer.getVec3(); } else if (token == "Ks") { currentMaterial.specularColor = tokenizer.getVec3(); - } else if ((token == "map_Kd") || (token == "map_Ke") || (token == "map_Ks") || (token == "map_bump")) { + } else if ((token == "map_Kd") || (token == "map_Ke") || (token == "map_Ks") || (token == "map_bump") || (token == "bump")) { const QByteArray textureLine = tokenizer.getLineAsDatum(); QByteArray filename; OBJMaterialTextureOptions textureOptions; @@ -319,8 +319,9 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { currentMaterial.emissiveTextureFilename = filename; } else if (token == "map_Ks" ) { currentMaterial.specularTextureFilename = filename; - } else if (token == "map_bump") { + } else if ((token == "map_bump") || (token == "bump")) { currentMaterial.bumpTextureFilename = filename; + currentMaterial.bumpMultiplier = textureOptions.bumpMultiplier; } } } @@ -338,6 +339,8 @@ void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& file QString multiplier = parser.left(multiplierEnd); textureOptions.bumpMultiplier = std::stof(multiplier.toStdString()); parser.remove(0, multiplier.length() + 1); + } else if (parser[0] == '-') { + // TO DO } else { int fileEnd = parser.indexOf(' '); if (fileEnd < 0) { @@ -813,6 +816,7 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, if (!objMaterial.bumpTextureFilename.isEmpty()) { fbxMaterial.normalTexture.filename = objMaterial.bumpTextureFilename; fbxMaterial.normalTexture.isBumpmap = true; + fbxMaterial.bumpMultiplier = objMaterial.bumpMultiplier; } modelMaterial->setEmissive(fbxMaterial.emissiveColor); diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index 2c1db7ccf7..ab2fd2fd78 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -61,9 +61,10 @@ public: QByteArray specularTextureFilename; QByteArray emissiveTextureFilename; QByteArray bumpTextureFilename; + float bumpMultiplier; bool used { false }; bool userSpecifiesUV { false }; - OBJMaterial() : shininess(0.0f), opacity(1.0f), diffuseColor(0.9f), specularColor(0.9f), emissiveColor(0.0f) {} + OBJMaterial() : shininess(0.0f), opacity(1.0f), diffuseColor(0.9f), specularColor(0.9f), emissiveColor(0.0f), bumpMultiplier(1.0f) {} }; class OBJMaterialTextureOptions { From dfb9d9ce6b3d82bc404f4683c6638dd11023477b Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 3 Jan 2018 13:58:27 -0800 Subject: [PATCH 014/569] illum model wip --- libraries/fbx/src/OBJReader.cpp | 53 ++++++++++++++++++++++++++++----- libraries/fbx/src/OBJReader.h | 1 + 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 6127b454b8..1e31371178 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -256,12 +256,12 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { default: materials[matName] = currentMaterial; #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader Last material shininess:" << currentMaterial.shininess << " opacity:" << - currentMaterial.opacity << " diffuse color:" << currentMaterial.diffuseColor << - " specular color:" << currentMaterial.specularColor << " emissive color:" << - currentMaterial.emissiveColor << " diffuse texture:" << - currentMaterial.diffuseTextureFilename << " specular texture:" << - currentMaterial.specularTextureFilename << " emissive texture:" << + qCDebug(modelformat) << "OBJ Reader Last material illumination model:" << currentMaterial.illuminationModel << + " shininess:" << currentMaterial.shininess << " opacity:" << currentMaterial.opacity << + " diffuse color:" << currentMaterial.diffuseColor << " specular color:" << + currentMaterial.specularColor << " emissive color:" << currentMaterial.emissiveColor << + " diffuse texture:" << currentMaterial.diffuseTextureFilename << " specular texture:" << + currentMaterial.specularTextureFilename << " emissive texture:" << currentMaterial.emissiveTextureFilename << " bump texture:" << currentMaterial.bumpTextureFilename; #endif @@ -302,6 +302,8 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { currentMaterial.emissiveColor = tokenizer.getVec3(); } else if (token == "Ks") { currentMaterial.specularColor = tokenizer.getVec3(); + } else if (token == "illum") { + currentMaterial.illuminationModel = tokenizer.getFloat(); } else if ((token == "map_Kd") || (token == "map_Ke") || (token == "map_Ks") || (token == "map_bump") || (token == "bump")) { const QByteArray textureLine = tokenizer.getLineAsDatum(); QByteArray filename; @@ -824,8 +826,45 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, modelMaterial->setMetallic(glm::length(fbxMaterial.specularColor)); modelMaterial->setRoughness(model::Material::shininessToRoughness(fbxMaterial.shininess)); + // Illumination model reference http://paulbourke.net/dataformats/mtl/ + switch (objMaterial.illuminationModel) { + case 0: // Color on and Ambient off + fbxMaterial.ambientFactor = 0.0f; + break; + case 1: // Color on and Ambient on + fbxMaterial.ambientFactor = 1.0f; + break; + case 2: // Highlight on + fbxMaterial.specularFactor = 1.0f; + break; + case 3: // Reflection on and Ray trace on + break; + case 4: // Transparency: Glass on and Reflection: Ray trace on + fbxMaterial.opacity = 0.0f; + break; + case 5: // Reflection: Fresnel on and Ray trace on + modelMaterial->setFresnel(glm::vec3(1.0f)); + break; + case 6: // Transparency: Refraction on and Reflection: Fresnel off and Ray trace on + fbxMaterial.opacity = 0.0f; + modelMaterial->setFresnel(glm::vec3(0.0f)); + break; + case 7: // Transparency: Refraction on and Reflection: Fresnel on and Ray trace on + fbxMaterial.opacity = 0.0f; + modelMaterial->setFresnel(glm::vec3(1.0f)); + break; + case 8: // Reflection on and Ray trace off + break; + case 9: // Transparency: Glass on and Reflection: Ray trace off + fbxMaterial.opacity = 0.0f; + break; + case 10: // Casts shadows onto invisible surfaces + // Do nothing? + break; + } + if (fbxMaterial.opacity <= 0.0f) { - modelMaterial->setOpacity(1.0f); + modelMaterial->setOpacity(0.0f); // previous was 1.0f? } else { modelMaterial->setOpacity(fbxMaterial.opacity); } diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index ab2fd2fd78..eb7376c64f 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -62,6 +62,7 @@ public: QByteArray emissiveTextureFilename; QByteArray bumpTextureFilename; float bumpMultiplier; + int illuminationModel; bool used { false }; bool userSpecifiesUV { false }; OBJMaterial() : shininess(0.0f), opacity(1.0f), diffuseColor(0.9f), specularColor(0.9f), emissiveColor(0.0f), bumpMultiplier(1.0f) {} From cbaba8688171d3530859f659038a4131afd604d1 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 3 Jan 2018 15:50:24 -0800 Subject: [PATCH 015/569] more texture options --- interface/resources/qml/js/Utils.jsc | Bin 6548 -> 6516 bytes libraries/fbx/src/OBJReader.cpp | 131 +++++++++++++++++++++++---- libraries/fbx/src/OBJReader.h | 18 ++-- 3 files changed, 121 insertions(+), 28 deletions(-) diff --git a/interface/resources/qml/js/Utils.jsc b/interface/resources/qml/js/Utils.jsc index 0e4b04d46e53fb0fdea77cfc8b85627fa9276aad..8da68e4e192018ee2b4f3d835ec91f36f0161eca 100644 GIT binary patch delta 155 zcmbPY{KZJ8u*@VmC9xz?kb!}Lk&~5STcM<00wV*14l4u0rhn_zD^;ej?k>4g!mi%G zd7@4Wqrt?D;Y<(MCv!1|F&>zl$QaJ3FnK4VKhp!@$y`j^1h=p-FdSiFV7S7R|LRQXGhvF2i${P@ W51R@;Gu>8oK_AsT%v-0F>BfHt$1Yz0 diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 1e31371178..2e4fff700c 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -323,33 +323,124 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { currentMaterial.specularTextureFilename = filename; } else if ((token == "map_bump") || (token == "bump")) { currentMaterial.bumpTextureFilename = filename; - currentMaterial.bumpMultiplier = textureOptions.bumpMultiplier; + currentMaterial.bumpTextureOptions = textureOptions; } } } } +bool OBJReader::parseTextureLineFloat(QString& parser, float& result) { + if (parser.length() == 0) { + return false; + } + auto firstChar = parser[0]; + if ((firstChar < '0' || firstChar > '9') && firstChar != '-') { + return false; + } + int floatEnd = parser.indexOf(' '); + if (floatEnd < 0) { + floatEnd = parser.length(); + } + QString floatStr = parser.left(floatEnd); + try + { + result = std::stof(floatStr.toStdString()); + parser.remove(0, floatStr.length() + 1); + return true; + } + catch (const std::exception& /*e*/) + { + return false; + } +} + +bool OBJReader::parseTextureLineString(QString& parser, QByteArray& result) { + if (parser.length() == 0) { + return false; + } + int stringEnd = parser.indexOf(' '); + if (stringEnd < 0) { + stringEnd = parser.length(); + } + result = parser.left(stringEnd).toUtf8(); + parser.remove(0, result.length() + 1); + return true; +} + void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& filename, OBJMaterialTextureOptions& textureOptions) { + // Texture options reference http://paulbourke.net/dataformats/mtl/ + // and https://wikivisually.com/wiki/Material_Template_Library QString parser = textureLine; while (parser.length() > 0) { - if (parser.startsWith("-bm")) { - parser.remove(0, 4); - int multiplierEnd = parser.indexOf(' '); - if (multiplierEnd < 0) { - multiplierEnd = parser.length(); - } - QString multiplier = parser.left(multiplierEnd); - textureOptions.bumpMultiplier = std::stof(multiplier.toStdString()); - parser.remove(0, multiplier.length() + 1); - } else if (parser[0] == '-') { - // TO DO - } else { - int fileEnd = parser.indexOf(' '); - if (fileEnd < 0) { - fileEnd = parser.length(); - } - filename = parser.left(fileEnd).toUtf8(); - parser.remove(0, filename.length() + 1); + if (parser.startsWith("-blendu") || parser.startsWith("-blendv")) { + parser.remove(0, 11); // remove through "-blendu on " or "-blendu off" + if (parser[0] == ' ') // extra character for space after off + parser.remove(0, 1); + #ifdef WANT_DEBUG + qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option -blendu/-blendv"; + #endif + } else if (parser.startsWith("-bm")) { + parser.remove(0, 4); // remove through "-bm " + parseTextureLineFloat(parser, textureOptions.bumpMultiplier); + } else if (parser.startsWith("-boost")) { + parser.remove(0, 7); // remove through "-boost " + float ignore; + parseTextureLineFloat(parser, ignore); + #ifdef WANT_DEBUG + qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option -boost"; + #endif + } else if (parser.startsWith("-cc")) { + parser.remove(0, 7); // remove through "-cc on " or "-cc off" + if (parser[0] == ' ') // extra character for space after off + parser.remove(0, 1); + #ifdef WANT_DEBUG + qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option -cc"; + #endif + } else if (parser.startsWith("-clamp")) { + parser.remove(0, 10); // remove through "-clamp on " or "-clamp off" + if (parser[0] == ' ') // extra character for space after off + parser.remove(0, 1); + #ifdef WANT_DEBUG + qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option -clamp"; + #endif + } else if (parser.startsWith("-imfchan")) { + parser.remove(0, 11); // remove through "-imfchan X " + #ifdef WANT_DEBUG + qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option -imfchan"; + #endif + } else if (parser.startsWith("-mm")) { + parser.remove(0, 4); // remove through "-mm " + float ignore; + parseTextureLineFloat(parser, ignore); + parseTextureLineFloat(parser, ignore); + #ifdef WANT_DEBUG + qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option -mm"; + #endif + } else if (parser.startsWith("-o") || parser.startsWith("-s") || parser.startsWith("-t")) { + parser.remove(0, 3); // remove through "-o " + float ignore; + parseTextureLineFloat(parser, ignore); + parseTextureLineFloat(parser, ignore); + parseTextureLineFloat(parser, ignore); + #ifdef WANT_DEBUG + qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option -o/-s/-t"; + #endif + } else if (parser.startsWith("-texres")) { + parser.remove(0, 8); // remove through "-texres " + float ignore; + parseTextureLineFloat(parser, ignore); + #ifdef WANT_DEBUG + qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option -texres"; + #endif + } else if (parser.startsWith("-type")) { + parser.remove(0, 6); // remove through "-type " + QByteArray ignore; + parseTextureLineString(parser, ignore); + #ifdef WANT_DEBUG + qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option -type"; + #endif + } else { // assume filename + parseTextureLineString(parser, filename); } } } @@ -818,7 +909,7 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, if (!objMaterial.bumpTextureFilename.isEmpty()) { fbxMaterial.normalTexture.filename = objMaterial.bumpTextureFilename; fbxMaterial.normalTexture.isBumpmap = true; - fbxMaterial.bumpMultiplier = objMaterial.bumpMultiplier; + fbxMaterial.bumpMultiplier = objMaterial.bumpTextureOptions.bumpMultiplier; } modelMaterial->setEmissive(fbxMaterial.emissiveColor); diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index eb7376c64f..a4582d6ef4 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -48,6 +48,12 @@ private: void addFrom(const OBJFace* face, int index); }; +class OBJMaterialTextureOptions { +public: + float bumpMultiplier; + OBJMaterialTextureOptions() : bumpMultiplier(1.0f) {} +} +; // Materials and references to material names can come in any order, and different mesh parts can refer to the same material. // Therefore it would get pretty hacky to try to use FBXMeshPart to store these as we traverse the files. class OBJMaterial { @@ -61,17 +67,11 @@ public: QByteArray specularTextureFilename; QByteArray emissiveTextureFilename; QByteArray bumpTextureFilename; - float bumpMultiplier; + OBJMaterialTextureOptions bumpTextureOptions; int illuminationModel; bool used { false }; bool userSpecifiesUV { false }; - OBJMaterial() : shininess(0.0f), opacity(1.0f), diffuseColor(0.9f), specularColor(0.9f), emissiveColor(0.0f), bumpMultiplier(1.0f) {} -}; - -class OBJMaterialTextureOptions { -public: - float bumpMultiplier; - OBJMaterialTextureOptions() : bumpMultiplier(1.0f) {} + OBJMaterial() : shininess(0.0f), opacity(1.0f), diffuseColor(0.9f), specularColor(0.9f), emissiveColor(0.0f), illuminationModel(0) {} }; class OBJReader: public QObject { // QObject so we can make network requests. @@ -95,6 +95,8 @@ private: bool parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, FBXGeometry& geometry, float& scaleGuess, bool combineParts); void parseMaterialLibrary(QIODevice* device); + bool parseTextureLineFloat(QString& parser, float& result); + bool parseTextureLineString(QString& parser, QByteArray& result); void parseTextureLine(const QByteArray& textureLine, QByteArray& filename, OBJMaterialTextureOptions& textureOptions); bool isValidTexture(const QByteArray &filename); // true if the file exists. TODO?: check content-type header and that it is a supported format. From e9f30549f487604fb8f5384770f7da7269de19b8 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 3 Jan 2018 18:20:07 -0800 Subject: [PATCH 016/569] ignore Tf, disable illum test --- libraries/fbx/src/OBJReader.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 2e4fff700c..ca69ccb64d 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -292,6 +292,12 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { currentMaterial.opacity = tokenizer.getFloat(); } else if (token == "Tr") { currentMaterial.opacity = 1.0f - tokenizer.getFloat(); + } else if (token == "illum") { + currentMaterial.illuminationModel = tokenizer.getFloat(); + } else if (token == "Tf") { + #ifdef WANT_DEBUG + qCDebug(modelformat) << "OBJ Reader Ignoring material Tf " << tokenizer.getVec3(); + #endif } else if (token == "Ka") { #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJ Reader Ignoring material Ka " << tokenizer.getVec3(); @@ -302,8 +308,6 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { currentMaterial.emissiveColor = tokenizer.getVec3(); } else if (token == "Ks") { currentMaterial.specularColor = tokenizer.getVec3(); - } else if (token == "illum") { - currentMaterial.illuminationModel = tokenizer.getFloat(); } else if ((token == "map_Kd") || (token == "map_Ke") || (token == "map_Ks") || (token == "map_bump") || (token == "bump")) { const QByteArray textureLine = tokenizer.getLineAsDatum(); QByteArray filename; @@ -917,6 +921,7 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, modelMaterial->setMetallic(glm::length(fbxMaterial.specularColor)); modelMaterial->setRoughness(model::Material::shininessToRoughness(fbxMaterial.shininess)); + /* // Illumination model reference http://paulbourke.net/dataformats/mtl/ switch (objMaterial.illuminationModel) { case 0: // Color on and Ambient off @@ -953,12 +958,9 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, // Do nothing? break; } + */ - if (fbxMaterial.opacity <= 0.0f) { - modelMaterial->setOpacity(0.0f); // previous was 1.0f? - } else { - modelMaterial->setOpacity(fbxMaterial.opacity); - } + modelMaterial->setOpacity(fbxMaterial.opacity); } return geometryPtr; From 2c5a433ea1f695cf6a3cb46d1f46e7cc55565b31 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 4 Jan 2018 12:56:57 -0800 Subject: [PATCH 017/569] fix datum reset with comment token --- libraries/fbx/src/OBJReader.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index ca69ccb64d..d4cb5de497 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -70,6 +70,7 @@ int OBJTokenizer::nextToken(bool allowSpaceChar /*= false*/) { } switch (ch) { case '#': { + _datum = ""; _comment = _device->readLine(); // stash comment for a future call to getComment return COMMENT_TOKEN; } From fcac489efb92c9da28742e6ed80a465a530cb1a1 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 4 Jan 2018 16:39:44 -0800 Subject: [PATCH 018/569] illum mode adjustments, small tweaks --- libraries/fbx/src/FBX.h | 2 +- libraries/fbx/src/OBJReader.cpp | 72 +++++++++++++++++++++++++-------- 2 files changed, 56 insertions(+), 18 deletions(-) diff --git a/libraries/fbx/src/FBX.h b/libraries/fbx/src/FBX.h index 70344512ca..c1842dbee4 100644 --- a/libraries/fbx/src/FBX.h +++ b/libraries/fbx/src/FBX.h @@ -178,7 +178,7 @@ public: float emissiveIntensity{ 1.0f }; float ambientFactor{ 1.0f }; - float bumpMultiplier{ 1.0f }; + float bumpMultiplier{ 1.0f }; // TODO: to be implemented QString materialID; QString name; diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index d4cb5de497..8fc683b0d5 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -286,8 +286,9 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { } else if (token == "Ns") { currentMaterial.shininess = tokenizer.getFloat(); } else if (token == "Ni") { + float ignore = tokenizer.getFloat(); #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader Ignoring material Ni " << tokenizer.getFloat(); + qCDebug(modelformat) << "OBJ Reader Ignoring material Ni " << ignore; #endif } else if (token == "d") { currentMaterial.opacity = tokenizer.getFloat(); @@ -296,12 +297,14 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { } else if (token == "illum") { currentMaterial.illuminationModel = tokenizer.getFloat(); } else if (token == "Tf") { + glm::vec3 ignore = tokenizer.getVec3(); #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader Ignoring material Tf " << tokenizer.getVec3(); + qCDebug(modelformat) << "OBJ Reader Ignoring material Tf " << ignore; #endif } else if (token == "Ka") { + glm::vec3 ignore = tokenizer.getVec3(); #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader Ignoring material Ka " << tokenizer.getVec3(); + qCDebug(modelformat) << "OBJ Reader Ignoring material Ka " << ignore; #endif } else if (token == "Kd") { currentMaterial.diffuseColor = tokenizer.getVec3(); @@ -379,8 +382,9 @@ void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& file while (parser.length() > 0) { if (parser.startsWith("-blendu") || parser.startsWith("-blendv")) { parser.remove(0, 11); // remove through "-blendu on " or "-blendu off" - if (parser[0] == ' ') // extra character for space after off + if (parser[0] == ' ') { // extra character for space after off parser.remove(0, 1); + } #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option -blendu/-blendv"; #endif @@ -396,15 +400,17 @@ void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& file #endif } else if (parser.startsWith("-cc")) { parser.remove(0, 7); // remove through "-cc on " or "-cc off" - if (parser[0] == ' ') // extra character for space after off + if (parser[0] == ' ') { // extra character for space after off parser.remove(0, 1); + } #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option -cc"; #endif } else if (parser.startsWith("-clamp")) { parser.remove(0, 10); // remove through "-clamp on " or "-clamp off" - if (parser[0] == ' ') // extra character for space after off + if (parser[0] == ' ') { // extra character for space after off parser.remove(0, 1); + } #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option -clamp"; #endif @@ -922,44 +928,76 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, modelMaterial->setMetallic(glm::length(fbxMaterial.specularColor)); modelMaterial->setRoughness(model::Material::shininessToRoughness(fbxMaterial.shininess)); - /* + bool applyTransparency = false; + bool applyShininess = false; + bool applyRoughness = false; + bool applyNonMetallic = false; + bool fresnelOn = false; + bool fresnelOff = false; + // Illumination model reference http://paulbourke.net/dataformats/mtl/ switch (objMaterial.illuminationModel) { case 0: // Color on and Ambient off - fbxMaterial.ambientFactor = 0.0f; + // We don't support ambient - do nothing? break; case 1: // Color on and Ambient on - fbxMaterial.ambientFactor = 1.0f; + // We don't support ambient - do nothing? break; case 2: // Highlight on fbxMaterial.specularFactor = 1.0f; break; case 3: // Reflection on and Ray trace on + applyShininess = true; break; case 4: // Transparency: Glass on and Reflection: Ray trace on - fbxMaterial.opacity = 0.0f; + applyTransparency = true; + applyShininess = true; break; case 5: // Reflection: Fresnel on and Ray trace on - modelMaterial->setFresnel(glm::vec3(1.0f)); + applyShininess = true; + fresnelOn = true; break; case 6: // Transparency: Refraction on and Reflection: Fresnel off and Ray trace on - fbxMaterial.opacity = 0.0f; - modelMaterial->setFresnel(glm::vec3(0.0f)); + applyTransparency = true; + applyNonMetallic = true; + applyShininess = true; + fresnelOff = true; break; case 7: // Transparency: Refraction on and Reflection: Fresnel on and Ray trace on - fbxMaterial.opacity = 0.0f; - modelMaterial->setFresnel(glm::vec3(1.0f)); + applyTransparency = true; + applyNonMetallic = true; + applyShininess = true; + fresnelOn = true; break; case 8: // Reflection on and Ray trace off + applyShininess = true; break; case 9: // Transparency: Glass on and Reflection: Ray trace off - fbxMaterial.opacity = 0.0f; + applyTransparency = true; + applyNonMetallic = true; + applyRoughness = true; break; case 10: // Casts shadows onto invisible surfaces // Do nothing? break; + } + + if (applyTransparency && fbxMaterial.opacity <= 0.1f) { + fbxMaterial.opacity = 0.1f; + } + if (applyShininess) { + modelMaterial->setRoughness(0.25f); + } else if (applyRoughness) { + modelMaterial->setRoughness(1.0f); + } + if (applyNonMetallic) { + modelMaterial->setMetallic(0.0f); + } + if (fresnelOn) { + modelMaterial->setFresnel(glm::vec3(1.0f)); + } else if (fresnelOff) { + modelMaterial->setFresnel(glm::vec3(0.0f)); } - */ modelMaterial->setOpacity(fbxMaterial.opacity); } From 7c99c85ecae4eb7066c7bb7a679cc542afc4c9d9 Mon Sep 17 00:00:00 2001 From: David Back Date: Fri, 5 Jan 2018 13:54:16 -0800 Subject: [PATCH 019/569] Revert "temp obj import fixes" This reverts commit 188e476f2f203bc5e832fb18afd9192a759ef260. --- libraries/fbx/src/OBJReader.cpp | 24 ++---------------------- libraries/render-utils/src/Model.cpp | 2 +- libraries/shared/src/Transform.cpp | 2 +- 3 files changed, 4 insertions(+), 24 deletions(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 8fc683b0d5..2c09b2021d 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -612,34 +612,14 @@ bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mappi // vertex-index/texture-index // vertex-index/texture-index/surface-normal-index QByteArray token = tokenizer.getDatum(); - auto firstChar = token[0]; - // Tokenizer treats line endings as whitespace. Non-digit and non-negative sign indicates done; - if (!isdigit(firstChar) && firstChar != '-') { + if (!isdigit(token[0])) { // Tokenizer treats line endings as whitespace. Non-digit indicates done; tokenizer.pushBackToken(OBJTokenizer::DATUM_TOKEN); break; } QList parts = token.split('/'); assert(parts.count() >= 1); assert(parts.count() <= 3); - // If indices are negative relative indices then adjust them to absolute indices based on current vector sizes - // Also add 1 to each index as 1 will be subtracted later on from each index in OBJFace::add - int part0 = parts[0].toInt(); - if (part0 < 0) { - parts[0].setNum(vertices.size() - abs(part0) + 1); - } - if (parts.count() > 1) { - int part1 = parts[1].toInt(); - if (part1 < 0) { - parts[1].setNum(textureUVs.size() - abs(part1) + 1); - } - if (parts.count() > 2) { - int part2 = parts[2].toInt(); - if (part2 < 0) { - parts[2].setNum(normals.size() - abs(part2) + 1); - } - } - } - const QByteArray noData{}; + const QByteArray noData {}; face.add(parts[0], (parts.count() > 1) ? parts[1] : noData, (parts.count() > 2) ? parts[2] : noData, vertices, vertexColors); face.groupName = currentGroup; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 536e76e44e..b91f4dd405 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -163,7 +163,7 @@ void Model::setScale(const glm::vec3& scale) { _scaledToFit = false; } -const float SCALE_CHANGE_EPSILON = 0.0000001f; +const float SCALE_CHANGE_EPSILON = 0.001f; void Model::setScaleInternal(const glm::vec3& scale) { if (glm::distance(_scale, scale) > SCALE_CHANGE_EPSILON) { diff --git a/libraries/shared/src/Transform.cpp b/libraries/shared/src/Transform.cpp index eeb726ba72..3e29c38add 100644 --- a/libraries/shared/src/Transform.cpp +++ b/libraries/shared/src/Transform.cpp @@ -19,7 +19,7 @@ #include "shared/JSONHelpers.h" void Transform::evalRotationScale(Quat& rotation, Vec3& scale, const Mat3& rotationScaleMatrix) { - const float ACCURACY_THREASHOLD = 0.000001f; + const float ACCURACY_THREASHOLD = 0.00001f; // Following technique taken from: // http://callumhay.blogspot.com/2010/10/decomposing-affine-transforms.html From eaee9b387273e1fef7185551d57a8c315e4ac795 Mon Sep 17 00:00:00 2001 From: David Back Date: Mon, 8 Jan 2018 16:41:51 -0800 Subject: [PATCH 020/569] constants, pre-PR tweaks --- libraries/fbx/src/OBJReader.cpp | 33 +++++++++++++++++++-------------- libraries/fbx/src/OBJReader.h | 2 +- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index a248f6fb68..e5d4fb7bd4 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -35,6 +35,11 @@ QHash COMMENT_SCALE_HINTS = {{"This file uses centimeters as uni const QString SMART_DEFAULT_MATERIAL_NAME = "High Fidelity smart default material name"; +const float ILLUMINATION_MODEL_MIN_OPACITY = 0.1f; +const float ILLUMINATION_MODEL_APPLY_SHININESS = 0.25f; +const float ILLUMINATION_MODEL_APPLY_ROUGHNESS = 1.0f; +const float ILLUMINATION_MODEL_APPLY_NON_METALLIC = 0.0f; + namespace { template T& checked_at(QVector& vector, int i) { @@ -380,7 +385,7 @@ void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& file // and https://wikivisually.com/wiki/Material_Template_Library QString parser = textureLine; while (parser.length() > 0) { - if (parser.startsWith("-blendu") || parser.startsWith("-blendv")) { + if (parser.startsWith("-blend")) { // -blendu/-blendv parser.remove(0, 11); // remove through "-blendu on " or "-blendu off" if (parser[0] == ' ') { // extra character for space after off parser.remove(0, 1); @@ -421,18 +426,18 @@ void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& file #endif } else if (parser.startsWith("-mm")) { parser.remove(0, 4); // remove through "-mm " - float ignore; - parseTextureLineFloat(parser, ignore); + float ignore, ignore2; parseTextureLineFloat(parser, ignore); + parseTextureLineFloat(parser, ignore2); #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option -mm"; #endif - } else if (parser.startsWith("-o") || parser.startsWith("-s") || parser.startsWith("-t")) { - parser.remove(0, 3); // remove through "-o " - float ignore; - parseTextureLineFloat(parser, ignore); - parseTextureLineFloat(parser, ignore); + } else if (parser.startsWith("-o ") || parser.startsWith("-s ") || parser.startsWith("-t ")) { + parser.remove(0, 3); // remove through "-o/-s/-t " + float ignore, ignore2, ignore3; parseTextureLineFloat(parser, ignore); + bool hasValue2 = parseTextureLineFloat(parser, ignore2); + bool hasValue3 = parseTextureLineFloat(parser, ignore3); #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option -o/-s/-t"; #endif @@ -944,7 +949,7 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, // We don't support ambient - do nothing? break; case 2: // Highlight on - fbxMaterial.specularFactor = 1.0f; + // Change specular intensity? break; case 3: // Reflection on and Ray trace on applyShininess = true; @@ -982,16 +987,16 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, break; } - if (applyTransparency && fbxMaterial.opacity <= 0.1f) { - fbxMaterial.opacity = 0.1f; + if (applyTransparency && fbxMaterial.opacity <= ILLUMINATION_MODEL_MIN_OPACITY) { + fbxMaterial.opacity = ILLUMINATION_MODEL_MIN_OPACITY; } if (applyShininess) { - modelMaterial->setRoughness(0.25f); + modelMaterial->setRoughness(ILLUMINATION_MODEL_APPLY_SHININESS); } else if (applyRoughness) { - modelMaterial->setRoughness(1.0f); + modelMaterial->setRoughness(ILLUMINATION_MODEL_APPLY_ROUGHNESS); } if (applyNonMetallic) { - modelMaterial->setMetallic(0.0f); + modelMaterial->setMetallic(ILLUMINATION_MODEL_APPLY_NON_METALLIC); } if (fresnelOn) { modelMaterial->setFresnel(glm::vec3(1.0f)); diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index a4582d6ef4..44382e3603 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -71,7 +71,7 @@ public: int illuminationModel; bool used { false }; bool userSpecifiesUV { false }; - OBJMaterial() : shininess(0.0f), opacity(1.0f), diffuseColor(0.9f), specularColor(0.9f), emissiveColor(0.0f), illuminationModel(0) {} + OBJMaterial() : shininess(0.0f), opacity(1.0f), diffuseColor(0.9f), specularColor(0.9f), emissiveColor(0.0f), illuminationModel(-1) {} }; class OBJReader: public QObject { // QObject so we can make network requests. From 8aa0525f4536b965a6b3bd1586291ca38df164a7 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 10 Jan 2018 10:32:28 -0800 Subject: [PATCH 021/569] CR style feedback --- .../keep-in-zone-example.js | 15 +++++++- .../entity_edit_filters/position-example.js | 37 +++++++++++-------- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/scripts/tutorials/entity_edit_filters/keep-in-zone-example.js b/scripts/tutorials/entity_edit_filters/keep-in-zone-example.js index 9013edda96..eb9cfbeb4d 100644 --- a/scripts/tutorials/entity_edit_filters/keep-in-zone-example.js +++ b/scripts/tutorials/entity_edit_filters/keep-in-zone-example.js @@ -1,8 +1,21 @@ +// +// keep-in-zone-example.js +// +// +// Created by Brad Hefta-Gaub to use Entities on Dec. 15, 2017 +// Copyright 2017 High Fidelity, Inc. +// +// This sample entity edit filter script will keep any entity inside the bounding box of the zone this filter is applied to. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + function filter(properties, type, originalProperties, zoneProperties) { var nearZero = 0.0001 * Math.random() + 0.001; - /* Clamp position changes to bounding box of zone.*/ + /* Clamp position changes to bounding box of zone.*/ function clamp(val, min, max) { /* Random near-zero value used as "zero" to prevent two sequential updates from being exactly the same (which would cause them to be ignored) */ diff --git a/scripts/tutorials/entity_edit_filters/position-example.js b/scripts/tutorials/entity_edit_filters/position-example.js index 5f17f04542..a6cafe6bcb 100644 --- a/scripts/tutorials/entity_edit_filters/position-example.js +++ b/scripts/tutorials/entity_edit_filters/position-example.js @@ -1,16 +1,20 @@ +// +// position-example.js +// +// +// Created by Brad Hefta-Gaub to use Entities on Dec. 15, 2017 +// Copyright 2017 High Fidelity, Inc. +// +// This sample entity edit filter script will only allow position to be changed by no more than 5 meters on any axis. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + function filter(properties, type, originalProperties) { - - /* Clamp position changes.*/ - var maxChange = 5; - function sign(val) { - if (val > 0) { - return 1; - } else if (val < 0) { - return -1; - } else { - return 0; - } - } + + /* Clamp position changes.*/ + var maxChange = 5; function clamp(val, min, max) { if (val > max) { @@ -27,9 +31,12 @@ function filter(properties, type, originalProperties) { exactly the same (which would cause them to be ignored) */ var nearZero = 0.0001 * Math.random() + 0.001; var maxFudgeChange = (maxChange + nearZero); - properties.position.x = clamp(properties.position.x, originalProperties.position.x - maxFudgeChange, originalProperties.position.x + maxFudgeChange); - properties.position.y = clamp(properties.position.y, originalProperties.position.y - maxFudgeChange, originalProperties.position.y + maxFudgeChange); - properties.position.z = clamp(properties.position.z, originalProperties.position.z - maxFudgeChange, originalProperties.position.z + maxFudgeChange); + properties.position.x = clamp(properties.position.x, originalProperties.position.x + - maxFudgeChange, originalProperties.position.x + maxFudgeChange); + properties.position.y = clamp(properties.position.y, originalProperties.position.y + - maxFudgeChange, originalProperties.position.y + maxFudgeChange); + properties.position.z = clamp(properties.position.z, originalProperties.position.z + - maxFudgeChange, originalProperties.position.z + maxFudgeChange); } return properties; From 612f9621b449f07a64425312d8cc16533b675cd0 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 11 Jan 2018 10:00:49 -0800 Subject: [PATCH 022/569] adding filter properties to allow optimization --- libraries/entities/src/EntityEditFilters.cpp | 136 ++++++++++++++---- libraries/entities/src/EntityEditFilters.h | 6 + .../keep-in-zone-example.js | 4 + .../entity_edit_filters/position-example.js | 2 + 4 files changed, 117 insertions(+), 31 deletions(-) diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index 13be7ebc7d..2b0dcc9f73 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -61,6 +61,7 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper if (filterData.rejectAll) { return false; } + auto oldProperties = propertiesIn.getDesiredProperties(); auto specifiedProperties = propertiesIn.getChangedProperties(); propertiesIn.setDesiredProperties(specifiedProperties); @@ -69,38 +70,44 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper auto in = QJsonValue::fromVariant(inputValues.toVariant()); // grab json copy now, because the inputValues might be side effected by the filter. - // get the current properties for then entity and include them for the filter call - auto currentProperties = existingEntity ? existingEntity->getProperties() : EntityItemProperties(); - QScriptValue currentValues = currentProperties.copyToScriptValue(filterData.engine, false, true, true); - - // get the zone properties - auto zoneEntity = _tree->findEntityByEntityItemID(id); - auto zoneProperties = zoneEntity ? zoneEntity->getProperties() : EntityItemProperties(); - QScriptValue zoneValues = zoneProperties.copyToScriptValue(filterData.engine, false, true, true); - - if (zoneEntity) { - bool success = true; - AABox aaBox = zoneEntity->getAABox(success); - - if (success) { - QScriptValue boundingBox = filterData.engine->newObject(); - QScriptValue bottomRightNear = vec3toScriptValue(filterData.engine, aaBox.getCorner()); - QScriptValue topFarLeft = vec3toScriptValue(filterData.engine, aaBox.calcTopFarLeft()); - QScriptValue center = vec3toScriptValue(filterData.engine, aaBox.calcCenter()); - QScriptValue boundingBoxDimensions = vec3toScriptValue(filterData.engine, aaBox.getDimensions()); - boundingBox.setProperty("brn", bottomRightNear); - boundingBox.setProperty("tfl", topFarLeft); - boundingBox.setProperty("center", center); - boundingBox.setProperty("dimensions", boundingBoxDimensions); - zoneValues.setProperty("boundingBox", boundingBox); - } - } - QScriptValueList args; args << inputValues; args << filterType; - args << currentValues; - args << zoneValues; + + // get the current properties for then entity and include them for the filter call + if (existingEntity && filterData.wantsOriginalProperties) { + auto currentProperties = existingEntity->getProperties(filterData.includedOriginalProperties); + QScriptValue currentValues = currentProperties.copyToScriptValue(filterData.engine, false, true, true); + args << currentValues; + } + + + // get the zone properties + if (filterData.wantsZoneProperties) { + auto zoneEntity = _tree->findEntityByEntityItemID(id); + if (zoneEntity) { + auto zoneProperties = zoneEntity->getProperties(filterData.includedZoneProperties); + QScriptValue zoneValues = zoneProperties.copyToScriptValue(filterData.engine, false, true, true); + + if (filterData.wantsZoneBoundingBox) { + bool success = true; + AABox aaBox = zoneEntity->getAABox(success); + if (success) { + QScriptValue boundingBox = filterData.engine->newObject(); + QScriptValue bottomRightNear = vec3toScriptValue(filterData.engine, aaBox.getCorner()); + QScriptValue topFarLeft = vec3toScriptValue(filterData.engine, aaBox.calcTopFarLeft()); + QScriptValue center = vec3toScriptValue(filterData.engine, aaBox.calcCenter()); + QScriptValue boundingBoxDimensions = vec3toScriptValue(filterData.engine, aaBox.getDimensions()); + boundingBox.setProperty("brn", bottomRightNear); + boundingBox.setProperty("tfl", topFarLeft); + boundingBox.setProperty("center", center); + boundingBox.setProperty("dimensions", boundingBoxDimensions); + zoneValues.setProperty("boundingBox", boundingBox); + } + } + args << zoneValues; + } + } QScriptValue result = filterData.filterFn.call(_nullObjectForFilter, args); @@ -245,8 +252,75 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { delete engine; filterData.rejectAll=true; } - - + + // check to see if the filterFn has properties asking for Original props + QScriptValue wantsOriginalPropertiesValue = filterData.filterFn.property("wantsOriginalProperties"); + // if the wantsOriginalProperties is a boolean, or a string, or list of strings, then evaluate as follows: + // - boolean - true - include all original properties + // false - no properties at all + // - string - empty - no properties at all + // any valid property - include just that property in the Original properties + // - list of strings - include only those properties in the Original properties + if (wantsOriginalPropertiesValue.isBool()) { + filterData.wantsOriginalProperties = wantsOriginalPropertiesValue.toBool(); + } else if (wantsOriginalPropertiesValue.isString()) { + auto stringValue = wantsOriginalPropertiesValue.toString(); + filterData.wantsOriginalProperties = !stringValue.isEmpty(); + if (filterData.wantsOriginalProperties) { + EntityPropertyFlagsFromScriptValue(wantsOriginalPropertiesValue, filterData.includedOriginalProperties); + } + } else if (wantsOriginalPropertiesValue.isArray()) { + auto length = wantsOriginalPropertiesValue.property("length").toInteger(); + for (int i; i < length; i++) { + auto stringValue = wantsOriginalPropertiesValue.property(i).toString(); + if (!stringValue.isEmpty()) { + filterData.wantsOriginalProperties = true; + break; + } + } + if (filterData.wantsOriginalProperties) { + EntityPropertyFlagsFromScriptValue(wantsOriginalPropertiesValue, filterData.includedOriginalProperties); + } + } + + // check to see if the filterFn has properties asking for Zone props + QScriptValue wantsZonePropertiesValue = filterData.filterFn.property("wantsZoneProperties"); + // if the wantsZoneProperties is a boolean, or a string, or list of strings, then evaluate as follows: + // - boolean - true - include all Zone properties + // false - no properties at all + // - string - empty - no properties at all + // any valid property - include just that property in the Zone properties + // - list of strings - include only those properties in the Zone properties + if (wantsZonePropertiesValue.isBool()) { + filterData.wantsZoneProperties = wantsZonePropertiesValue.toBool(); + filterData.wantsZoneBoundingBox = filterData.wantsZoneProperties; // include this too + } else if (wantsZonePropertiesValue.isString()) { + auto stringValue = wantsZonePropertiesValue.toString(); + filterData.wantsZoneProperties = !stringValue.isEmpty(); + if (filterData.wantsZoneProperties) { + EntityPropertyFlagsFromScriptValue(wantsZonePropertiesValue, filterData.includedZoneProperties); + } + } else if (wantsZonePropertiesValue.isArray()) { + auto length = wantsZonePropertiesValue.property("length").toInteger(); + for (int i; i < length; i++) { + auto stringValue = wantsZonePropertiesValue.property(i).toString(); + if (!stringValue.isEmpty()) { + filterData.wantsZoneProperties = true; + + // boundingBox is a special case since it's not a true EntityPropertyFlag, so we + // need to detect it here. + if (stringValue == "boundingBox") { + filterData.wantsZoneBoundingBox = true; + break; // we can break here, since there are no other special cases + } + + } + } + if (filterData.wantsZoneProperties) { + EntityPropertyFlagsFromScriptValue(wantsZonePropertiesValue, filterData.includedZoneProperties); + } + } + _lock.lockForWrite(); _filterDataMap.insert(entityID, filterData); _lock.unlock(); diff --git a/libraries/entities/src/EntityEditFilters.h b/libraries/entities/src/EntityEditFilters.h index f25c3c092e..be3df50d3f 100644 --- a/libraries/entities/src/EntityEditFilters.h +++ b/libraries/entities/src/EntityEditFilters.h @@ -28,6 +28,12 @@ class EntityEditFilters : public QObject, public Dependency { public: struct FilterData { QScriptValue filterFn; + bool wantsOriginalProperties { false }; + bool wantsZoneProperties { false }; + EntityPropertyFlags includedOriginalProperties; + EntityPropertyFlags includedZoneProperties; + bool wantsZoneBoundingBox { false }; + std::function uncaughtExceptions; QScriptEngine* engine; bool rejectAll; diff --git a/scripts/tutorials/entity_edit_filters/keep-in-zone-example.js b/scripts/tutorials/entity_edit_filters/keep-in-zone-example.js index eb9cfbeb4d..6d83ce1410 100644 --- a/scripts/tutorials/entity_edit_filters/keep-in-zone-example.js +++ b/scripts/tutorials/entity_edit_filters/keep-in-zone-example.js @@ -35,3 +35,7 @@ function filter(properties, type, originalProperties, zoneProperties) { return properties; } + +filter.wantsOriginalProperties = true; +filter.wantsZoneProperties = true; +filter; \ No newline at end of file diff --git a/scripts/tutorials/entity_edit_filters/position-example.js b/scripts/tutorials/entity_edit_filters/position-example.js index a6cafe6bcb..01eabee7db 100644 --- a/scripts/tutorials/entity_edit_filters/position-example.js +++ b/scripts/tutorials/entity_edit_filters/position-example.js @@ -41,3 +41,5 @@ function filter(properties, type, originalProperties) { return properties; } +filter.wantsOriginalProperties = true; +filter; \ No newline at end of file From 0145b770da39868ab0209a7cbed7aabbbf0d5ec3 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 11 Jan 2018 17:16:22 -0800 Subject: [PATCH 023/569] fix warnings --- libraries/fbx/src/OBJReader.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index e5d4fb7bd4..008b51b0be 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -291,9 +291,10 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { } else if (token == "Ns") { currentMaterial.shininess = tokenizer.getFloat(); } else if (token == "Ni") { - float ignore = tokenizer.getFloat(); #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader Ignoring material Ni " << ignore; + qCDebug(modelformat) << "OBJ Reader Ignoring material Ni " << tokenizer.getFloat(); + #else + tokenizer.getFloat(); #endif } else if (token == "d") { currentMaterial.opacity = tokenizer.getFloat(); @@ -302,14 +303,16 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { } else if (token == "illum") { currentMaterial.illuminationModel = tokenizer.getFloat(); } else if (token == "Tf") { - glm::vec3 ignore = tokenizer.getVec3(); #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader Ignoring material Tf " << ignore; + qCDebug(modelformat) << "OBJ Reader Ignoring material Tf " << tokenizer.getVec3(); + #else + tokenizer.getVec3(); #endif } else if (token == "Ka") { - glm::vec3 ignore = tokenizer.getVec3(); #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader Ignoring material Ka " << ignore; + qCDebug(modelformat) << "OBJ Reader Ignoring material Ka " << tokenizer.getVec3();; + #else + tokenizer.getVec3(); #endif } else if (token == "Kd") { currentMaterial.diffuseColor = tokenizer.getVec3(); @@ -436,8 +439,8 @@ void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& file parser.remove(0, 3); // remove through "-o/-s/-t " float ignore, ignore2, ignore3; parseTextureLineFloat(parser, ignore); - bool hasValue2 = parseTextureLineFloat(parser, ignore2); - bool hasValue3 = parseTextureLineFloat(parser, ignore3); + parseTextureLineFloat(parser, ignore2); + parseTextureLineFloat(parser, ignore3); #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option -o/-s/-t"; #endif From 814de4ab81f335d550d09ee42b6d84b0b1dff196 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 12 Jan 2018 11:59:19 +0100 Subject: [PATCH 024/569] Scribe now outputs shaders as cpp files. --- cmake/macros/AutoScribeShader.cmake | 8 +- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 4 +- .../RenderableParticleEffectEntityItem.cpp | 4 +- .../src/RenderablePolyLineEntityItem.cpp | 8 +- .../src/RenderablePolyVoxEntityItem.cpp | 18 +-- .../src/RenderableShapeEntityItem.cpp | 4 +- libraries/gpu/src/gpu/Shader.h | 2 + libraries/gpu/src/gpu/StandardShaderLib.cpp | 24 ++-- libraries/model/src/model/Skybox.cpp | 4 +- .../procedural/src/procedural/Procedural.cpp | 2 +- .../src/procedural/ProceduralSkybox.cpp | 4 +- .../src/AmbientOcclusionEffect.cpp | 10 +- libraries/render-utils/src/AnimDebugDraw.cpp | 5 +- .../render-utils/src/AntialiasingEffect.cpp | 6 +- libraries/render-utils/src/BloomEffect.cpp | 4 +- .../render-utils/src/DebugDeferredBuffer.cpp | 4 +- .../src/DeferredLightingEffect.cpp | 18 +-- libraries/render-utils/src/DrawHaze.cpp | 2 +- libraries/render-utils/src/GeometryCache.cpp | 26 ++-- .../render-utils/src/HighlightEffect.cpp | 20 +-- libraries/render-utils/src/LightClusters.cpp | 12 +- .../render-utils/src/RenderForwardTask.cpp | 2 +- .../render-utils/src/RenderPipelines.cpp | 132 +++++++++--------- .../render-utils/src/StencilMaskPass.cpp | 2 +- .../render-utils/src/SubsurfaceScattering.cpp | 8 +- .../render-utils/src/SurfaceGeometryPass.cpp | 6 +- libraries/render-utils/src/TextRenderer3D.cpp | 4 +- .../render-utils/src/ToneMappingEffect.cpp | 2 +- libraries/render-utils/src/ZoneRenderer.cpp | 6 +- libraries/render-utils/src/text/Font.cpp | 6 +- libraries/render/src/render/BlurTask.cpp | 8 +- .../render/src/render/DrawSceneOctree.cpp | 10 +- libraries/render/src/render/DrawStatus.cpp | 8 +- libraries/render/src/render/DrawTask.cpp | 4 +- .../render/src/render/drawItemBounds.slf | 1 - tests/shaders/src/main.cpp | 94 ++++++------- tools/scribe/src/main.cpp | 8 +- 37 files changed, 245 insertions(+), 245 deletions(-) diff --git a/cmake/macros/AutoScribeShader.cmake b/cmake/macros/AutoScribeShader.cmake index 1919ecf00a..d25e0e553f 100755 --- a/cmake/macros/AutoScribeShader.cmake +++ b/cmake/macros/AutoScribeShader.cmake @@ -39,11 +39,11 @@ function(AUTOSCRIBE_SHADER SHADER_FILE) get_filename_component(SHADER_TARGET ${SHADER_FILE} NAME_WE) get_filename_component(SHADER_EXT ${SHADER_FILE} EXT) if(SHADER_EXT STREQUAL .slv) - set(SHADER_TARGET ${SHADER_TARGET}_vert.h) + set(SHADER_TARGET ${SHADER_TARGET}_vert.cpp) elseif(${SHADER_EXT} STREQUAL .slf) - set(SHADER_TARGET ${SHADER_TARGET}_frag.h) + set(SHADER_TARGET ${SHADER_TARGET}_frag.cpp) elseif(${SHADER_EXT} STREQUAL .slg) - set(SHADER_TARGET ${SHADER_TARGET}_geom.h) + set(SHADER_TARGET ${SHADER_TARGET}_geom.cpp) endif() set(SHADER_TARGET "${SHADERS_DIR}/${SHADER_TARGET}") @@ -134,6 +134,6 @@ macro(AUTOSCRIBE_SHADER_LIB) list(APPEND AUTOSCRIBE_SHADER_LIB_SRC ${AUTOSCRIBE_SHADER_SRC}) # Link library shaders, if they exist - include_directories("${SHADERS_DIR}") + #include_directories("${SHADERS_DIR}") endmacro() diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 1d7fee38eb..4f50b5ecd9 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -33,8 +33,8 @@ #include "../Logging.h" #include "../CompositorHelper.h" -#include "render-utils/hmd_ui_vert.h" -#include "render-utils/hmd_ui_frag.h" +INCLUDE_SHADER(hmd_ui_vert) +INCLUDE_SHADER(hmd_ui_frag) static const QString MONO_PREVIEW = "Mono Preview"; static const QString DISABLE_PREVIEW = "Disable Preview"; diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index a3e6cd4341..b8f979b7fd 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -14,8 +14,8 @@ #include -#include "textured_particle_vert.h" -#include "textured_particle_frag.h" +INCLUDE_SHADER(textured_particle_vert) +INCLUDE_SHADER(textured_particle_frag) using namespace render; using namespace render::entities; diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index b362721cde..b85771f5b6 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -23,11 +23,11 @@ # include #endif -#include "paintStroke_vert.h" -#include "paintStroke_frag.h" +INCLUDE_SHADER(paintStroke_vert) +INCLUDE_SHADER(paintStroke_frag) -#include "paintStroke_fade_vert.h" -#include "paintStroke_fade_frag.h" +INCLUDE_SHADER(paintStroke_fade_vert) +INCLUDE_SHADER(paintStroke_fade_frag) using namespace render; using namespace render::entities; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index ad0202457e..b245ed7cda 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -27,10 +27,11 @@ #include #include "EntityTreeRenderer.h" -#include "polyvox_vert.h" -#include "polyvox_frag.h" -#include "polyvox_fade_vert.h" -#include "polyvox_fade_frag.h" + +INCLUDE_SHADER(polyvox_vert) +INCLUDE_SHADER(polyvox_frag) +INCLUDE_SHADER(polyvox_fade_vert) +INCLUDE_SHADER(polyvox_fade_frag) #ifdef POLYVOX_ENTITY_USE_FADE_EFFECT # include @@ -70,10 +71,11 @@ #include "StencilMaskPass.h" #include "EntityTreeRenderer.h" -#include "polyvox_vert.h" -#include "polyvox_frag.h" -#include "polyvox_fade_vert.h" -#include "polyvox_fade_frag.h" + +INCLUDE_SHADER(polyvox_vert) +INCLUDE_SHADER(polyvox_frag) +INCLUDE_SHADER(polyvox_fade_vert) +INCLUDE_SHADER(polyvox_fade_frag) #include "RenderablePolyVoxEntityItem.h" #include "EntityEditPacketSender.h" diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index cdee2c5ec9..cd02186440 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -16,8 +16,8 @@ #include #include -#include -#include +INCLUDE_SHADER(simple_vert) +INCLUDE_SHADER(simple_frag) //#define SHAPE_ENTITY_USE_FADE_EFFECT #ifdef SHAPE_ENTITY_USE_FADE_EFFECT diff --git a/libraries/gpu/src/gpu/Shader.h b/libraries/gpu/src/gpu/Shader.h index 181c9b5e78..5cfdbc8bf4 100755 --- a/libraries/gpu/src/gpu/Shader.h +++ b/libraries/gpu/src/gpu/Shader.h @@ -17,6 +17,8 @@ #include #include + +#define INCLUDE_SHADER(source) extern const char source[]; namespace gpu { diff --git a/libraries/gpu/src/gpu/StandardShaderLib.cpp b/libraries/gpu/src/gpu/StandardShaderLib.cpp index 0d8d131e0b..93c2228de6 100755 --- a/libraries/gpu/src/gpu/StandardShaderLib.cpp +++ b/libraries/gpu/src/gpu/StandardShaderLib.cpp @@ -12,21 +12,21 @@ // #include "StandardShaderLib.h" -#include "DrawUnitQuadTexcoord_vert.h" -#include "DrawTransformUnitQuad_vert.h" -#include "DrawTexcoordRectTransformUnitQuad_vert.h" -#include "DrawViewportQuadTransformTexcoord_vert.h" -#include "DrawVertexPosition_vert.h" -#include "DrawTransformVertexPosition_vert.h" +INCLUDE_SHADER(DrawUnitQuadTexcoord_vert) +INCLUDE_SHADER(DrawTransformUnitQuad_vert) +INCLUDE_SHADER(DrawTexcoordRectTransformUnitQuad_vert) +INCLUDE_SHADER(DrawViewportQuadTransformTexcoord_vert) +INCLUDE_SHADER(DrawVertexPosition_vert) +INCLUDE_SHADER(DrawTransformVertexPosition_vert) const char DrawNada_frag[] = "void main(void) {}"; // DrawNada is really simple... -#include "DrawWhite_frag.h" -#include "DrawColor_frag.h" -#include "DrawTexture_frag.h" -#include "DrawTextureMirroredX_frag.h" -#include "DrawTextureOpaque_frag.h" -#include "DrawColoredTexture_frag.h" +INCLUDE_SHADER(DrawWhite_frag) +INCLUDE_SHADER(DrawColor_frag) +INCLUDE_SHADER(DrawTexture_frag) +INCLUDE_SHADER(DrawTextureMirroredX_frag) +INCLUDE_SHADER(DrawTextureOpaque_frag) +INCLUDE_SHADER(DrawColoredTexture_frag) using namespace gpu; diff --git a/libraries/model/src/model/Skybox.cpp b/libraries/model/src/model/Skybox.cpp index fd3061afa5..7e03890c85 100755 --- a/libraries/model/src/model/Skybox.cpp +++ b/libraries/model/src/model/Skybox.cpp @@ -15,8 +15,8 @@ #include #include -#include "skybox_vert.h" -#include "skybox_frag.h" +INCLUDE_SHADER(skybox_vert) +INCLUDE_SHADER(skybox_frag) using namespace model; diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index 7b718515a8..c0ebdbb186 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -20,7 +20,7 @@ #include #include -#include "ProceduralCommon_frag.h" +INCLUDE_SHADER(ProceduralCommon_frag) #include "Logging.h" diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.cpp b/libraries/procedural/src/procedural/ProceduralSkybox.cpp index 9544759037..452d3f639a 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.cpp +++ b/libraries/procedural/src/procedural/ProceduralSkybox.cpp @@ -15,8 +15,8 @@ #include #include -#include -#include +INCLUDE_SHADER(skybox_vert) +INCLUDE_SHADER(skybox_frag) ProceduralSkybox::ProceduralSkybox() : model::Skybox() { _procedural._vertexSource = skybox_vert; diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.cpp b/libraries/render-utils/src/AmbientOcclusionEffect.cpp index 83753131c8..f5b4f0f6bb 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.cpp +++ b/libraries/render-utils/src/AmbientOcclusionEffect.cpp @@ -28,11 +28,11 @@ #include "DependencyManager.h" #include "ViewFrustum.h" -#include "ssao_makePyramid_frag.h" -#include "ssao_makeOcclusion_frag.h" -#include "ssao_debugOcclusion_frag.h" -#include "ssao_makeHorizontalBlur_frag.h" -#include "ssao_makeVerticalBlur_frag.h" +INCLUDE_SHADER(ssao_makePyramid_frag) +INCLUDE_SHADER(ssao_makeOcclusion_frag) +INCLUDE_SHADER(ssao_debugOcclusion_frag) +INCLUDE_SHADER(ssao_makeHorizontalBlur_frag) +INCLUDE_SHADER(ssao_makeVerticalBlur_frag) AmbientOcclusionFramebuffer::AmbientOcclusionFramebuffer() { diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index c22e99cbbc..53ed3ea9a0 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -9,8 +9,6 @@ #include -#include "animdebugdraw_vert.h" -#include "animdebugdraw_frag.h" #include #include "AbstractViewStateInterface.h" #include "RenderUtilsLogging.h" @@ -19,6 +17,9 @@ #include "AnimDebugDraw.h" +INCLUDE_SHADER(animdebugdraw_vert) +INCLUDE_SHADER(animdebugdraw_frag) + class AnimDebugDrawData { public: diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index 70c2e3b5ce..078e906fad 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -23,9 +23,9 @@ #include "ViewFrustum.h" #include "GeometryCache.h" -#include "fxaa_vert.h" -#include "fxaa_frag.h" -#include "fxaa_blend_frag.h" +INCLUDE_SHADER(fxaa_vert) +INCLUDE_SHADER(fxaa_frag) +INCLUDE_SHADER(fxaa_blend_frag) Antialiasing::Antialiasing() { diff --git a/libraries/render-utils/src/BloomEffect.cpp b/libraries/render-utils/src/BloomEffect.cpp index 9d9367a6d5..ace347f99e 100644 --- a/libraries/render-utils/src/BloomEffect.cpp +++ b/libraries/render-utils/src/BloomEffect.cpp @@ -16,8 +16,8 @@ #include #include -#include "BloomThreshold_frag.h" -#include "BloomApply_frag.h" +INCLUDE_SHADER(BloomThreshold_frag) +INCLUDE_SHADER(BloomApply_frag) #define BLOOM_BLUR_LEVEL_COUNT 3 diff --git a/libraries/render-utils/src/DebugDeferredBuffer.cpp b/libraries/render-utils/src/DebugDeferredBuffer.cpp index fe03ead4e1..05cd6a6f0e 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.cpp +++ b/libraries/render-utils/src/DebugDeferredBuffer.cpp @@ -23,8 +23,8 @@ #include "TextureCache.h" #include "DeferredLightingEffect.h" -#include "debug_deferred_buffer_vert.h" -#include "debug_deferred_buffer_frag.h" +INCLUDE_SHADER(debug_deferred_buffer_vert) +INCLUDE_SHADER(debug_deferred_buffer_frag) using namespace render; diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 81a33f17e3..8ec2c74353 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -24,18 +24,18 @@ #include "TextureCache.h" #include "FramebufferCache.h" -#include "deferred_light_vert.h" -#include "deferred_light_point_vert.h" -#include "deferred_light_spot_vert.h" +INCLUDE_SHADER(deferred_light_vert) +INCLUDE_SHADER(deferred_light_point_vert) +INCLUDE_SHADER(deferred_light_spot_vert) -#include "directional_ambient_light_frag.h" -#include "directional_skybox_light_frag.h" +INCLUDE_SHADER(directional_ambient_light_frag) +INCLUDE_SHADER(directional_skybox_light_frag) -#include "directional_ambient_light_shadow_frag.h" -#include "directional_skybox_light_shadow_frag.h" +INCLUDE_SHADER(directional_ambient_light_shadow_frag) +INCLUDE_SHADER(directional_skybox_light_shadow_frag) -#include "local_lights_shading_frag.h" -#include "local_lights_drawOutline_frag.h" +INCLUDE_SHADER(local_lights_shading_frag) +INCLUDE_SHADER(local_lights_drawOutline_frag) using namespace render; diff --git a/libraries/render-utils/src/DrawHaze.cpp b/libraries/render-utils/src/DrawHaze.cpp index da07f5bd9b..dc1893b347 100644 --- a/libraries/render-utils/src/DrawHaze.cpp +++ b/libraries/render-utils/src/DrawHaze.cpp @@ -19,7 +19,7 @@ #include "HazeStage.h" #include "LightStage.h" -#include "Haze_frag.h" +INCLUDE_SHADER(Haze_frag) void HazeConfig::setHazeColor(const glm::vec3 value) { hazeColor = value; diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 2616d08600..ac8b300e49 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -34,21 +34,21 @@ #include "model/TextureMap.h" #include "render/Args.h" -#include "standardTransformPNTC_vert.h" -#include "standardDrawTexture_frag.h" +INCLUDE_SHADER(standardTransformPNTC_vert) +INCLUDE_SHADER(standardDrawTexture_frag) -#include "simple_vert.h" -#include "simple_textured_frag.h" -#include "simple_textured_unlit_frag.h" -#include "simple_fade_vert.h" -#include "simple_textured_fade_frag.h" -#include "simple_textured_unlit_fade_frag.h" -#include "simple_opaque_web_browser_frag.h" -#include "simple_transparent_web_browser_frag.h" -#include "glowLine_vert.h" -#include "glowLine_frag.h" +INCLUDE_SHADER(simple_vert) +INCLUDE_SHADER(simple_textured_frag) +INCLUDE_SHADER(simple_textured_unlit_frag) +INCLUDE_SHADER(simple_fade_vert) +INCLUDE_SHADER(simple_textured_fade_frag) +INCLUDE_SHADER(simple_textured_unlit_fade_frag) +INCLUDE_SHADER(simple_opaque_web_browser_frag) +INCLUDE_SHADER(simple_transparent_web_browser_frag) +INCLUDE_SHADER(glowLine_vert) +INCLUDE_SHADER(glowLine_frag) -#include "grid_frag.h" +INCLUDE_SHADER(grid_frag) //#define WANT_DEBUG diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp index fee1f4a568..69feffb055 100644 --- a/libraries/render-utils/src/HighlightEffect.cpp +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -22,13 +22,13 @@ #include -#include "surfaceGeometry_copyDepth_frag.h" -#include "debug_deferred_buffer_vert.h" -#include "debug_deferred_buffer_frag.h" -#include "Highlight_frag.h" -#include "Highlight_filled_frag.h" -#include "Highlight_aabox_vert.h" -#include "nop_frag.h" +INCLUDE_SHADER(surfaceGeometry_copyDepth_frag) +INCLUDE_SHADER(debug_deferred_buffer_vert) +INCLUDE_SHADER(debug_deferred_buffer_frag) +INCLUDE_SHADER(Highlight_frag) +INCLUDE_SHADER(Highlight_filled_frag) +INCLUDE_SHADER(Highlight_aabox_vert) +INCLUDE_SHADER(nop_frag) using namespace render; @@ -547,10 +547,10 @@ const render::Varying DrawHighlightTask::addSelectItemJobs(JobModel& task, const return task.addJob("TransparentSelection", selectItemInput); } -#include "model_shadow_vert.h" -#include "skin_model_shadow_vert.h" +INCLUDE_SHADER(model_shadow_vert) +INCLUDE_SHADER(skin_model_shadow_vert) -#include "model_shadow_frag.h" +INCLUDE_SHADER(model_shadow_frag) void DrawHighlightTask::initMaskPipelines(render::ShapePlumber& shapePlumber, gpu::StatePointer state) { auto modelVertex = gpu::Shader::createVertex(std::string(model_shadow_vert)); diff --git a/libraries/render-utils/src/LightClusters.cpp b/libraries/render-utils/src/LightClusters.cpp index d6ac7fd2e2..79d755683f 100644 --- a/libraries/render-utils/src/LightClusters.cpp +++ b/libraries/render-utils/src/LightClusters.cpp @@ -18,15 +18,15 @@ #include "StencilMaskPass.h" -#include "lightClusters_drawGrid_vert.h" -#include "lightClusters_drawGrid_frag.h" +INCLUDE_SHADER(lightClusters_drawGrid_vert) +INCLUDE_SHADER(lightClusters_drawGrid_frag) -//#include "lightClusters_drawClusterFromDepth_vert.h" -#include "lightClusters_drawClusterFromDepth_frag.h" +//INCLUDE_SHADER(lightClusters_drawClusterFromDepth_vert) +INCLUDE_SHADER(lightClusters_drawClusterFromDepth_frag) -#include "lightClusters_drawClusterContent_vert.h" -#include "lightClusters_drawClusterContent_frag.h" +INCLUDE_SHADER(lightClusters_drawClusterContent_vert) +INCLUDE_SHADER(lightClusters_drawClusterContent_frag) enum LightClusterGridShader_MapSlot { DEFERRED_BUFFER_LINEAR_DEPTH_UNIT = 0, diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index c83251c605..8d02281d05 100755 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -24,7 +24,7 @@ #include -#include "nop_frag.h" +INCLUDE_SHADER(nop_frag) using namespace render; extern void initForwardPipelines(ShapePlumber& plumber); diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index 7f644add72..62252d3a0b 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -20,86 +20,86 @@ #include "TextureCache.h" #include "render/DrawTask.h" -#include "model_vert.h" -#include "model_normal_map_vert.h" -#include "model_lightmap_vert.h" -#include "model_lightmap_normal_map_vert.h" -#include "skin_model_vert.h" -#include "skin_model_normal_map_vert.h" +INCLUDE_SHADER(model_vert) +INCLUDE_SHADER(model_normal_map_vert) +INCLUDE_SHADER(model_lightmap_vert) +INCLUDE_SHADER(model_lightmap_normal_map_vert) +INCLUDE_SHADER(skin_model_vert) +INCLUDE_SHADER(skin_model_normal_map_vert) -#include "model_lightmap_fade_vert.h" -#include "model_lightmap_normal_map_fade_vert.h" -#include "skin_model_fade_vert.h" -#include "skin_model_normal_map_fade_vert.h" +INCLUDE_SHADER(model_lightmap_fade_vert) +INCLUDE_SHADER(model_lightmap_normal_map_fade_vert) +INCLUDE_SHADER(skin_model_fade_vert) +INCLUDE_SHADER(skin_model_normal_map_fade_vert) -#include "simple_vert.h" -#include "simple_textured_frag.h" -#include "simple_textured_unlit_frag.h" -#include "simple_transparent_textured_frag.h" -#include "simple_transparent_textured_unlit_frag.h" +INCLUDE_SHADER(simple_vert) +INCLUDE_SHADER(simple_textured_frag) +INCLUDE_SHADER(simple_textured_unlit_frag) +INCLUDE_SHADER(simple_transparent_textured_frag) +INCLUDE_SHADER(simple_transparent_textured_unlit_frag) -#include "simple_fade_vert.h" -#include "simple_textured_fade_frag.h" -#include "simple_textured_unlit_fade_frag.h" -#include "simple_transparent_textured_fade_frag.h" -#include "simple_transparent_textured_unlit_fade_frag.h" +INCLUDE_SHADER(simple_fade_vert) +INCLUDE_SHADER(simple_textured_fade_frag) +INCLUDE_SHADER(simple_textured_unlit_fade_frag) +INCLUDE_SHADER(simple_transparent_textured_fade_frag) +INCLUDE_SHADER(simple_transparent_textured_unlit_fade_frag) -#include "model_frag.h" -#include "model_unlit_frag.h" -#include "model_normal_map_frag.h" -#include "model_normal_specular_map_frag.h" -#include "model_specular_map_frag.h" +INCLUDE_SHADER(model_frag) +INCLUDE_SHADER(model_unlit_frag) +INCLUDE_SHADER(model_normal_map_frag) +INCLUDE_SHADER(model_normal_specular_map_frag) +INCLUDE_SHADER(model_specular_map_frag) -#include "model_fade_vert.h" -#include "model_normal_map_fade_vert.h" +INCLUDE_SHADER(model_fade_vert) +INCLUDE_SHADER(model_normal_map_fade_vert) -#include "model_fade_frag.h" -#include "model_unlit_fade_frag.h" -#include "model_normal_map_fade_frag.h" -#include "model_normal_specular_map_fade_frag.h" -#include "model_specular_map_fade_frag.h" +INCLUDE_SHADER(model_fade_frag) +INCLUDE_SHADER(model_unlit_fade_frag) +INCLUDE_SHADER(model_normal_map_fade_frag) +INCLUDE_SHADER(model_normal_specular_map_fade_frag) +INCLUDE_SHADER(model_specular_map_fade_frag) -#include "forward_model_frag.h" -#include "forward_model_unlit_frag.h" -#include "forward_model_normal_map_frag.h" -#include "forward_model_normal_specular_map_frag.h" -#include "forward_model_specular_map_frag.h" +INCLUDE_SHADER(forward_model_frag) +INCLUDE_SHADER(forward_model_unlit_frag) +INCLUDE_SHADER(forward_model_normal_map_frag) +INCLUDE_SHADER(forward_model_normal_specular_map_frag) +INCLUDE_SHADER(forward_model_specular_map_frag) -#include "model_lightmap_frag.h" -#include "model_lightmap_normal_map_frag.h" -#include "model_lightmap_normal_specular_map_frag.h" -#include "model_lightmap_specular_map_frag.h" -#include "model_translucent_frag.h" -#include "model_translucent_unlit_frag.h" +INCLUDE_SHADER(model_lightmap_frag) +INCLUDE_SHADER(model_lightmap_normal_map_frag) +INCLUDE_SHADER(model_lightmap_normal_specular_map_frag) +INCLUDE_SHADER(model_lightmap_specular_map_frag) +INCLUDE_SHADER(model_translucent_frag) +INCLUDE_SHADER(model_translucent_unlit_frag) -#include "model_lightmap_fade_frag.h" -#include "model_lightmap_normal_map_fade_frag.h" -#include "model_lightmap_normal_specular_map_fade_frag.h" -#include "model_lightmap_specular_map_fade_frag.h" -#include "model_translucent_fade_frag.h" -#include "model_translucent_unlit_fade_frag.h" +INCLUDE_SHADER(model_lightmap_fade_frag) +INCLUDE_SHADER(model_lightmap_normal_map_fade_frag) +INCLUDE_SHADER(model_lightmap_normal_specular_map_fade_frag) +INCLUDE_SHADER(model_lightmap_specular_map_fade_frag) +INCLUDE_SHADER(model_translucent_fade_frag) +INCLUDE_SHADER(model_translucent_unlit_fade_frag) -#include "overlay3D_vert.h" -#include "overlay3D_frag.h" -#include "overlay3D_model_frag.h" -#include "overlay3D_model_translucent_frag.h" -#include "overlay3D_translucent_frag.h" -#include "overlay3D_unlit_frag.h" -#include "overlay3D_translucent_unlit_frag.h" -#include "overlay3D_model_unlit_frag.h" -#include "overlay3D_model_translucent_unlit_frag.h" +INCLUDE_SHADER(overlay3D_vert) +INCLUDE_SHADER(overlay3D_frag) +INCLUDE_SHADER(overlay3D_model_frag) +INCLUDE_SHADER(overlay3D_model_translucent_frag) +INCLUDE_SHADER(overlay3D_translucent_frag) +INCLUDE_SHADER(overlay3D_unlit_frag) +INCLUDE_SHADER(overlay3D_translucent_unlit_frag) +INCLUDE_SHADER(overlay3D_model_unlit_frag) +INCLUDE_SHADER(overlay3D_model_translucent_unlit_frag) -#include "model_shadow_vert.h" -#include "skin_model_shadow_vert.h" +INCLUDE_SHADER(model_shadow_vert) +INCLUDE_SHADER(skin_model_shadow_vert) -#include "model_shadow_frag.h" -#include "skin_model_shadow_frag.h" +INCLUDE_SHADER(model_shadow_frag) +INCLUDE_SHADER(skin_model_shadow_frag) -#include "model_shadow_fade_vert.h" -#include "skin_model_shadow_fade_vert.h" +INCLUDE_SHADER(model_shadow_fade_vert) +INCLUDE_SHADER(skin_model_shadow_fade_vert) -#include "model_shadow_fade_frag.h" -#include "skin_model_shadow_fade_frag.h" +INCLUDE_SHADER(model_shadow_fade_frag) +INCLUDE_SHADER(skin_model_shadow_fade_frag) using namespace render; using namespace std::placeholders; diff --git a/libraries/render-utils/src/StencilMaskPass.cpp b/libraries/render-utils/src/StencilMaskPass.cpp index f71111b64e..e2a8b31f08 100644 --- a/libraries/render-utils/src/StencilMaskPass.cpp +++ b/libraries/render-utils/src/StencilMaskPass.cpp @@ -17,7 +17,7 @@ #include -#include "stencil_drawMask_frag.h" +INCLUDE_SHADER(stencil_drawMask_frag) using namespace render; diff --git a/libraries/render-utils/src/SubsurfaceScattering.cpp b/libraries/render-utils/src/SubsurfaceScattering.cpp index 1786898e57..e8d7e23ec2 100644 --- a/libraries/render-utils/src/SubsurfaceScattering.cpp +++ b/libraries/render-utils/src/SubsurfaceScattering.cpp @@ -17,11 +17,11 @@ #include "DeferredLightingEffect.h" -#include "subsurfaceScattering_makeProfile_frag.h" -#include "subsurfaceScattering_makeLUT_frag.h" -#include "subsurfaceScattering_makeSpecularBeckmann_frag.h" +INCLUDE_SHADER(subsurfaceScattering_makeProfile_frag) +INCLUDE_SHADER(subsurfaceScattering_makeLUT_frag) +INCLUDE_SHADER(subsurfaceScattering_makeSpecularBeckmann_frag) -#include "subsurfaceScattering_drawScattering_frag.h" +INCLUDE_SHADER(subsurfaceScattering_drawScattering_frag) enum ScatteringShaderBufferSlots { ScatteringTask_FrameTransformSlot = 0, diff --git a/libraries/render-utils/src/SurfaceGeometryPass.cpp b/libraries/render-utils/src/SurfaceGeometryPass.cpp index c4eea7ce7f..6ad8dc6137 100644 --- a/libraries/render-utils/src/SurfaceGeometryPass.cpp +++ b/libraries/render-utils/src/SurfaceGeometryPass.cpp @@ -25,10 +25,10 @@ const int SurfaceGeometryPass_ParamsSlot = 1; const int SurfaceGeometryPass_DepthMapSlot = 0; const int SurfaceGeometryPass_NormalMapSlot = 1; -#include "surfaceGeometry_makeLinearDepth_frag.h" -#include "surfaceGeometry_downsampleDepthNormal_frag.h" +INCLUDE_SHADER(surfaceGeometry_makeLinearDepth_frag) +INCLUDE_SHADER(surfaceGeometry_downsampleDepthNormal_frag) -#include "surfaceGeometry_makeCurvature_frag.h" +INCLUDE_SHADER(surfaceGeometry_makeCurvature_frag) diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index 9c85952107..06e041685a 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -25,8 +25,8 @@ #include "MatrixStack.h" #include "RenderUtilsLogging.h" -#include "sdf_text3D_vert.h" -#include "sdf_text3D_frag.h" +INCLUDE_SHADER(sdf_text3D_vert) +INCLUDE_SHADER(sdf_text3D_frag) #include "GeometryCache.h" diff --git a/libraries/render-utils/src/ToneMappingEffect.cpp b/libraries/render-utils/src/ToneMappingEffect.cpp index 72d692c5b2..3d3a11f7b3 100644 --- a/libraries/render-utils/src/ToneMappingEffect.cpp +++ b/libraries/render-utils/src/ToneMappingEffect.cpp @@ -17,7 +17,7 @@ #include "StencilMaskPass.h" #include "FramebufferCache.h" -#include "toneMapping_frag.h" +INCLUDE_SHADER(toneMapping_frag) const int ToneMappingEffect_ParamsSlot = 0; const int ToneMappingEffect_LightingMapSlot = 0; diff --git a/libraries/render-utils/src/ZoneRenderer.cpp b/libraries/render-utils/src/ZoneRenderer.cpp index c0d01c2eaf..77b5c492d3 100644 --- a/libraries/render-utils/src/ZoneRenderer.cpp +++ b/libraries/render-utils/src/ZoneRenderer.cpp @@ -20,9 +20,9 @@ #include "StencilMaskPass.h" #include "DeferredLightingEffect.h" -#include "zone_drawKeyLight_frag.h" -#include "zone_drawAmbient_frag.h" -#include "zone_drawSkybox_frag.h" +INCLUDE_SHADER(zone_drawKeyLight_frag) +INCLUDE_SHADER(zone_drawAmbient_frag) +INCLUDE_SHADER(zone_drawSkybox_frag) using namespace render; diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index 8449c58c7c..9e0fbd1522 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -7,9 +7,9 @@ #include -#include "sdf_text3D_vert.h" -#include "sdf_text3D_frag.h" -#include "sdf_text3D_transparent_frag.h" +INCLUDE_SHADER(sdf_text3D_vert) +INCLUDE_SHADER(sdf_text3D_frag) +INCLUDE_SHADER(sdf_text3D_transparent_frag) #include "../RenderUtilsLogging.h" #include "FontFamilies.h" diff --git a/libraries/render/src/render/BlurTask.cpp b/libraries/render/src/render/BlurTask.cpp index 2be6f8fad2..4b5d6da8e3 100644 --- a/libraries/render/src/render/BlurTask.cpp +++ b/libraries/render/src/render/BlurTask.cpp @@ -13,11 +13,11 @@ #include #include -#include "blurGaussianV_frag.h" -#include "blurGaussianH_frag.h" +INCLUDE_SHADER(blurGaussianV_frag) +INCLUDE_SHADER(blurGaussianH_frag) -#include "blurGaussianDepthAwareV_frag.h" -#include "blurGaussianDepthAwareH_frag.h" +INCLUDE_SHADER(blurGaussianDepthAwareV_frag) +INCLUDE_SHADER(blurGaussianDepthAwareH_frag) using namespace render; diff --git a/libraries/render/src/render/DrawSceneOctree.cpp b/libraries/render/src/render/DrawSceneOctree.cpp index 36663a454a..7823d85dae 100644 --- a/libraries/render/src/render/DrawSceneOctree.cpp +++ b/libraries/render/src/render/DrawSceneOctree.cpp @@ -22,12 +22,12 @@ #include "Args.h" -#include "drawCellBounds_vert.h" -#include "drawCellBounds_frag.h" -#include "drawLODReticle_frag.h" +INCLUDE_SHADER(drawCellBounds_vert) +INCLUDE_SHADER(drawCellBounds_frag) +INCLUDE_SHADER(drawLODReticle_frag) -#include "drawItemBounds_vert.h" -#include "drawItemBounds_frag.h" +INCLUDE_SHADER(drawItemBounds_vert) +INCLUDE_SHADER(drawItemBounds_frag) using namespace render; diff --git a/libraries/render/src/render/DrawStatus.cpp b/libraries/render/src/render/DrawStatus.cpp index 148e104453..1c6d7749f8 100644 --- a/libraries/render/src/render/DrawStatus.cpp +++ b/libraries/render/src/render/DrawStatus.cpp @@ -21,10 +21,10 @@ #include "Args.h" -#include "drawItemBounds_vert.h" -#include "drawItemBounds_frag.h" -#include "drawItemStatus_vert.h" -#include "drawItemStatus_frag.h" +INCLUDE_SHADER(drawItemBounds_vert) +INCLUDE_SHADER(drawItemBounds_frag) +INCLUDE_SHADER(drawItemStatus_vert) +INCLUDE_SHADER(drawItemStatus_frag) using namespace render; diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index 629cc55ccb..a6d5072cca 100755 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -22,8 +22,8 @@ #include #include -#include -#include +INCLUDE_SHADER(drawItemBounds_vert) +INCLUDE_SHADER(drawItemBounds_frag) using namespace render; diff --git a/libraries/render/src/render/drawItemBounds.slf b/libraries/render/src/render/drawItemBounds.slf index e01d1607bd..84c47d0933 100644 --- a/libraries/render/src/render/drawItemBounds.slf +++ b/libraries/render/src/render/drawItemBounds.slf @@ -15,7 +15,6 @@ in vec4 varColor; in vec2 varTexcoord; out vec4 outFragColor; - void main(void) { float var = step(fract(varTexcoord.x * varTexcoord.y * 1.0), 0.5); diff --git a/tests/shaders/src/main.cpp b/tests/shaders/src/main.cpp index 7c6886ad93..e0babc8b47 100644 --- a/tests/shaders/src/main.cpp +++ b/tests/shaders/src/main.cpp @@ -23,65 +23,65 @@ #include -#include -#include -#include -#include +INCLUDE_SHADER(simple_vert) +INCLUDE_SHADER(simple_frag) +INCLUDE_SHADER(simple_textured_frag) +INCLUDE_SHADER(simple_textured_unlit_frag) -#include -#include -#include +INCLUDE_SHADER(deferred_light_vert) +INCLUDE_SHADER(deferred_light_point_vert) +INCLUDE_SHADER(deferred_light_spot_vert) -#include -#include +INCLUDE_SHADER(directional_ambient_light_frag) +INCLUDE_SHADER(directional_skybox_light_frag) -#include -#include +INCLUDE_SHADER(standardTransformPNTC_vert) +INCLUDE_SHADER(standardDrawTexture_frag) -#include -#include -#include -#include -#include -#include -#include -#include +INCLUDE_SHADER(model_vert) +INCLUDE_SHADER(model_shadow_vert) +INCLUDE_SHADER(model_normal_map_vert) +INCLUDE_SHADER(model_lightmap_vert) +INCLUDE_SHADER(model_lightmap_normal_map_vert) +INCLUDE_SHADER(skin_model_vert) +INCLUDE_SHADER(skin_model_shadow_vert) +INCLUDE_SHADER(skin_model_normal_map_vert) -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +INCLUDE_SHADER(model_frag) +INCLUDE_SHADER(model_shadow_frag) +INCLUDE_SHADER(model_normal_map_frag) +INCLUDE_SHADER(model_normal_specular_map_frag) +INCLUDE_SHADER(model_specular_map_frag) +INCLUDE_SHADER(model_lightmap_frag) +INCLUDE_SHADER(model_lightmap_normal_map_frag) +INCLUDE_SHADER(model_lightmap_normal_specular_map_frag) +INCLUDE_SHADER(model_lightmap_specular_map_frag) +INCLUDE_SHADER(model_translucent_frag) -#include -#include +INCLUDE_SHADER(textured_particle_frag) +INCLUDE_SHADER(textured_particle_vert) -#include -#include +INCLUDE_SHADER(overlay3D_vert) +INCLUDE_SHADER(overlay3D_frag) -#include -#include +INCLUDE_SHADER(skybox_vert) +INCLUDE_SHADER(skybox_frag) -#include -#include -#include -#include -#include -#include +INCLUDE_SHADER(DrawTransformUnitQuad_vert) +INCLUDE_SHADER(DrawTexcoordRectTransformUnitQuad_vert) +INCLUDE_SHADER(DrawViewportQuadTransformTexcoord_vert) +INCLUDE_SHADER(DrawTexture_frag) +INCLUDE_SHADER(DrawTextureOpaque_frag) +INCLUDE_SHADER(DrawColoredTexture_frag) -#include -#include +INCLUDE_SHADER(sdf_text3D_vert) +INCLUDE_SHADER(sdf_text3D_frag) -#include -#include +INCLUDE_SHADER(paintStroke_vert) +INCLUDE_SHADER(paintStroke_frag) -#include -#include +INCLUDE_SHADER(polyvox_vert) +INCLUDE_SHADER(polyvox_frag) // Create a simple OpenGL window that renders text in various ways class QTestWindow : public QWindow { diff --git a/tools/scribe/src/main.cpp b/tools/scribe/src/main.cpp index 810f6c0f45..fef2ea0308 100755 --- a/tools/scribe/src/main.cpp +++ b/tools/scribe/src/main.cpp @@ -123,7 +123,7 @@ int main (int argc, char** argv) { cerr << " varname and varvalue must be made of alpha numerical characters with no spaces." << endl; cerr << " -listVars : Will list the vars name and value in the standard output." << endl; cerr << " -showParseTree : Draw the tree obtained while parsing the source" << endl; - cerr << " -c++ : Generate a c++ header file containing the output file stream stored as a char[] variable" << endl; + cerr << " -c++ : Generate a c++ source file containing the output file stream stored as a char[] variable" << endl; return 0; } @@ -209,9 +209,7 @@ int main (int argc, char** argv) { } targetStringStream << "// File generated by Scribe " << vars["_SCRIBE_DATE"] << std::endl; - targetStringStream << "#ifndef scribe_" << targetName << "_h" << std::endl; - targetStringStream << "#define scribe_" << targetName << "_h" << std::endl << std::endl; - + targetStringStream << "extern const char " << targetName << "[];\n"; targetStringStream << "const char " << targetName << "[] = \n"; // Write the pages content @@ -219,8 +217,6 @@ int main (int argc, char** argv) { targetStringStream << "R\"SCRIBE(\n" << page->str() << "\n)SCRIBE\"\n"; } targetStringStream << ";\n" << std::endl << std::endl; - - targetStringStream << "#endif" << std::endl; } else { targetStringStream << destStringStream.str(); } From 420b58fd5b031bac28f11e281e006cd6d891f113 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 12 Jan 2018 15:13:55 +0100 Subject: [PATCH 025/569] Trying to fix Android build error --- tools/scribe/src/main.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/scribe/src/main.cpp b/tools/scribe/src/main.cpp index fef2ea0308..b95d1df6ec 100755 --- a/tools/scribe/src/main.cpp +++ b/tools/scribe/src/main.cpp @@ -209,8 +209,7 @@ int main (int argc, char** argv) { } targetStringStream << "// File generated by Scribe " << vars["_SCRIBE_DATE"] << std::endl; - targetStringStream << "extern const char " << targetName << "[];\n"; - targetStringStream << "const char " << targetName << "[] = \n"; + targetStringStream << "extern const char " << targetName << "[] = \n"; // Write the pages content for (auto page : pages) { From 98a47e56fc447f9ad3ad2fd1057ed1ee218ac146 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 12 Jan 2018 17:32:12 +0100 Subject: [PATCH 026/569] Added a hack to force referencing of shader source on Android --- tools/scribe/src/main.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/scribe/src/main.cpp b/tools/scribe/src/main.cpp index b95d1df6ec..8e6ca31c71 100755 --- a/tools/scribe/src/main.cpp +++ b/tools/scribe/src/main.cpp @@ -127,7 +127,7 @@ int main (int argc, char** argv) { return 0; } - // Define targetName: if none, get destFilenmae, if none get srcFilename + // Define targetName: if none, get destFilename, if none get srcFilename if (targetName.empty()) { if (destFilename.empty()) { targetName = srcFilename; @@ -216,6 +216,8 @@ int main (int argc, char** argv) { targetStringStream << "R\"SCRIBE(\n" << page->str() << "\n)SCRIBE\"\n"; } targetStringStream << ";\n" << std::endl << std::endl; + targetStringStream << "// Hack to fix Android link error by forcing a reference to global variable\n"; + targetStringStream << "class " << targetName << "_used {\npublic:\nstatic const char* get() { return " << targetName << "; }\n};\n" << std::endl; } else { targetStringStream << destStringStream.str(); } From 241849139ffe1c0a28181c7cd32a0d65857379dd Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 15 Jan 2018 10:21:16 -0800 Subject: [PATCH 027/569] wip --- libraries/entities/CMakeLists.txt | 6 +- .../entities/src/EntityItemProperties.cpp | 43 +++++++ libraries/entities/src/EntityItemProperties.h | 37 ++++-- .../entities/src/EntityItemPropertiesMacros.h | 1 + libraries/entities/src/EntityPropertyFlags.h | 7 + libraries/entities/src/EntityTypes.cpp | 2 + libraries/entities/src/EntityTypes.h | 3 +- libraries/entities/src/MaterialEntityItem.cpp | 120 ++++++++++++++++++ libraries/entities/src/MaterialEntityItem.h | 79 ++++++++++++ libraries/shared/src/MaterialMode.h | 18 +++ 10 files changed, 301 insertions(+), 15 deletions(-) create mode 100644 libraries/entities/src/MaterialEntityItem.cpp create mode 100644 libraries/entities/src/MaterialEntityItem.h create mode 100644 libraries/shared/src/MaterialMode.h diff --git a/libraries/entities/CMakeLists.txt b/libraries/entities/CMakeLists.txt index c23740654e..191e43ebbe 100644 --- a/libraries/entities/CMakeLists.txt +++ b/libraries/entities/CMakeLists.txt @@ -1,4 +1,8 @@ set(TARGET_NAME entities) setup_hifi_library(Network Script) include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}") -link_hifi_libraries(shared networking octree avatars model) +include_hifi_library_headers(fbx) +include_hifi_library_headers(gpu) +include_hifi_library_headers(image) +include_hifi_library_headers(ktx) +link_hifi_libraries(shared networking octree avatars model model-networking) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 8bfce7e2d2..0edec88edf 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -330,6 +330,13 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_RADIUS_START, radiusStart); CHECK_PROPERTY_CHANGE(PROP_RADIUS_FINISH, radiusFinish); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_URL, materialURL); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_TYPE, materialMode); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_BLEND_FACTOR, blendFactor); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_PRIORITY, priority); + CHECK_PROPERTY_CHANGE(PROP_PARENT_SHAPE_ID, shapeID); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_BOUNDS, materialBounds); + // Certifiable Properties CHECK_PROPERTY_CHANGE(PROP_ITEM_NAME, itemName); CHECK_PROPERTY_CHANGE(PROP_ITEM_DESCRIPTION, itemDescription); @@ -623,6 +630,16 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IS_UV_MODE_STRETCH, isUVModeStretch); } + // Materials + if (_type == EntityTypes::Material) { + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_URL, materialURL); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_TYPE, materialMode); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_BLEND_FACTOR, blendFactor); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_PRIORITY, priority); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_SHAPE_ID, shapeID); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_BOUNDS, materialBounds); + } + if (!skipDefaults && !strictSemantics) { AABox aaBox = getAABox(); QScriptValue boundingBox = engine->newObject(); @@ -755,6 +772,12 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(radiusSpread, float, setRadiusSpread); COPY_PROPERTY_FROM_QSCRIPTVALUE(radiusStart, float, setRadiusStart); COPY_PROPERTY_FROM_QSCRIPTVALUE(radiusFinish, float, setRadiusFinish); + COPY_PROPERTY_FROM_QSCRIPTVALUE(materialURL, QString, setMaterialURL); + COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMode, MaterialMode, setMaterialMode); + COPY_PROPERTY_FROM_QSCRIPTVALUE(blendFactor, float, setBlendFactor); + COPY_PROPERTY_FROM_QSCRIPTVALUE(priority, int, setPriority); + COPY_PROPERTY_FROM_QSCRIPTVALUE(shapeID, int, setShapeID); + COPY_PROPERTY_FROM_QSCRIPTVALUE(materialBounds, glmVec4, setMaterialBounds); // Certifiable Properties COPY_PROPERTY_FROM_QSCRIPTVALUE(itemName, QString, setItemName); @@ -1490,6 +1513,16 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape()); } + // Materials + if (properties.getType() == EntityTypes::Material) { + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_URL, properties.getMaterialURL()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_TYPE, (uint32_t)properties.getMaterialMode()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_BLEND_FACTOR, properties.getBlendFactor()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_PRIORITY, properties.getPriority()); + APPEND_ENTITY_PROPERTY(PROP_PARENT_SHAPE_ID, properties.getShapeID()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_BOUNDS, properties.getMaterialBounds()); + } + APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL()); APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, properties.getActionData()); @@ -1845,6 +1878,16 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE, QString, setShape); } + // Materials + if (properties.getType() == EntityTypes::Material) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_URL, QString, setMaterialURL); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_TYPE, MaterialMode, setMaterialMode); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_BLEND_FACTOR, float, setBlendFactor); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_PRIORITY, uint32_t, setPriority); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARENT_SHAPE_ID, uint32_t, setShapeID); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_BOUNDS, glmVec4, setMaterialBounds); + } + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ACTION_DATA, QByteArray, setActionData); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index c3d04dc7ad..3190d66b63 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -44,6 +44,8 @@ #include "TextEntityItem.h" #include "ZoneEntityItem.h" +#include "MaterialMode.h" + const quint64 UNKNOWN_CREATED_TIME = 0; using ComponentPair = std::pair; @@ -58,19 +60,21 @@ const std::array COMPONENT_MODES = { { /// set of entity item properties via JavaScript hashes/QScriptValues /// all units for SI units (meter, second, radian, etc) class EntityItemProperties { - friend class EntityItem; // TODO: consider removing this friend relationship and use public methods - friend class ModelEntityItem; // TODO: consider removing this friend relationship and use public methods - friend class BoxEntityItem; // TODO: consider removing this friend relationship and use public methods - friend class SphereEntityItem; // TODO: consider removing this friend relationship and use public methods - friend class LightEntityItem; // TODO: consider removing this friend relationship and use public methods - friend class TextEntityItem; // TODO: consider removing this friend relationship and use public methods - friend class ParticleEffectEntityItem; // TODO: consider removing this friend relationship and use public methods - friend class ZoneEntityItem; // TODO: consider removing this friend relationship and use public methods - friend class WebEntityItem; // TODO: consider removing this friend relationship and use public methods - friend class LineEntityItem; // TODO: consider removing this friend relationship and use public methods - friend class PolyVoxEntityItem; // TODO: consider removing this friend relationship and use public methods - friend class PolyLineEntityItem; // TODO: consider removing this friend relationship and use public methods - friend class ShapeEntityItem; // TODO: consider removing this friend relationship and use public methods + // TODO: consider removing these friend relationship and use public methods + friend class EntityItem; + friend class ModelEntityItem; + friend class BoxEntityItem; + friend class SphereEntityItem; + friend class LightEntityItem; + friend class TextEntityItem; + friend class ParticleEffectEntityItem; + friend class ZoneEntityItem; + friend class WebEntityItem; + friend class LineEntityItem; + friend class PolyVoxEntityItem; + friend class PolyLineEntityItem; + friend class ShapeEntityItem; + friend class MaterialEntityItem; public: EntityItemProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()); virtual ~EntityItemProperties() = default; @@ -218,6 +222,13 @@ public: DEFINE_PROPERTY_REF(PROP_QUERY_AA_CUBE, QueryAACube, queryAACube, AACube, AACube()); DEFINE_PROPERTY_REF(PROP_SHAPE, Shape, shape, QString, "Sphere"); + DEFINE_PROPERTY_REF(PROP_MATERIAL_URL, MaterialURL, materialURL, QString, ""); + DEFINE_PROPERTY_REF_ENUM(PROP_MATERIAL_TYPE, MaterialMode, materialMode, MaterialMode, UV); + DEFINE_PROPERTY_REF(PROP_MATERIAL_BLEND_FACTOR, BlendFactor, blendFactor, float, 1.0f); + DEFINE_PROPERTY_REF(PROP_MATERIAL_PRIORITY, Priority, priority, uint32_t, 0); + DEFINE_PROPERTY_REF(PROP_PARENT_SHAPE_ID, ShapeID, shapeID, uint32_t, 0); + DEFINE_PROPERTY_REF(PROP_MATERIAL_BOUNDS, MaterialBounds, materialBounds, glmVec4, glm::vec4(0, 0, 1, 1)); + // Certifiable Properties - related to Proof of Purchase certificates DEFINE_PROPERTY_REF(PROP_ITEM_NAME, ItemName, itemName, QString, ENTITY_ITEM_DEFAULT_ITEM_NAME); DEFINE_PROPERTY_REF(PROP_ITEM_DESCRIPTION, ItemDescription, itemDescription, QString, ENTITY_ITEM_DEFAULT_ITEM_DESCRIPTION); diff --git a/libraries/entities/src/EntityItemPropertiesMacros.h b/libraries/entities/src/EntityItemPropertiesMacros.h index 278d945ab0..c8c8a658ce 100644 --- a/libraries/entities/src/EntityItemPropertiesMacros.h +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -184,6 +184,7 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const AACube& v) { retu } typedef glm::vec3 glmVec3; +typedef glm::vec4 glmVec4; typedef glm::quat glmQuat; typedef QVector qVectorVec3; typedef QVector qVectorQuat; diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 7fd72bf0ee..fc9b5cd709 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -226,6 +226,13 @@ enum EntityPropertyList { PROP_LOCAL_DIMENSIONS, // only used to convert values to and from scripts + PROP_MATERIAL_URL, + PROP_MATERIAL_TYPE, + PROP_MATERIAL_BLEND_FACTOR, + PROP_MATERIAL_PRIORITY, + PROP_PARENT_SHAPE_ID, + PROP_MATERIAL_BOUNDS, + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line PROP_AFTER_LAST_ITEM, diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index cb17c28fd7..307371c649 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -29,6 +29,7 @@ #include "PolyVoxEntityItem.h" #include "PolyLineEntityItem.h" #include "ShapeEntityItem.h" +#include "MaterialEntityItem.h" QMap EntityTypes::_typeToNameMap; QMap EntityTypes::_nameToTypeMap; @@ -50,6 +51,7 @@ REGISTER_ENTITY_TYPE(PolyLine) REGISTER_ENTITY_TYPE(Shape) REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, ShapeEntityItem::boxFactory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, ShapeEntityItem::sphereFactory) +REGISTER_ENTITY_TYPE(Material) const QString& EntityTypes::getEntityTypeName(EntityType entityType) { QMap::iterator matchedTypeName = _typeToNameMap.find(entityType); diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 316bf2b813..62011c6e26 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -49,7 +49,8 @@ public: PolyVox, PolyLine, Shape, - LAST = Shape + Material, + LAST = Material } EntityType; static const QString& getEntityTypeName(EntityType entityType); diff --git a/libraries/entities/src/MaterialEntityItem.cpp b/libraries/entities/src/MaterialEntityItem.cpp new file mode 100644 index 0000000000..19daa11490 --- /dev/null +++ b/libraries/entities/src/MaterialEntityItem.cpp @@ -0,0 +1,120 @@ +// +// Created by Sam Gondelman on 1/12/18 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "MaterialEntityItem.h" + +#include "EntityItemProperties.h" + +EntityItemPointer MaterialEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + Pointer entity(new MaterialEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); + entity->setProperties(properties); + return entity; +} + +// our non-pure virtual subclass for now... +MaterialEntityItem::MaterialEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { + _type = EntityTypes::Material; +} + +EntityItemProperties MaterialEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { + EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class + COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialURL, getMaterialURL); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMode, getMaterialMode); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(blendFactor, getBlendFactor); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(priority, getPriority); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeID, getShapeID); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialBounds, getMaterialBounds); + return properties; +} + +bool MaterialEntityItem::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class + + SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialURL, setMaterialURL); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMode, setMaterialMode); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(blendFactor, setBlendFactor); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(priority, setPriority); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeID, setShapeID); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialBounds, setMaterialBounds); + + if (somethingChanged) { + bool wantDebug = false; + if (wantDebug) { + uint64_t now = usecTimestampNow(); + int elapsed = now - getLastEdited(); + qCDebug(entities) << "MaterialEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << + "now=" << now << " getLastEdited()=" << getLastEdited(); + } + setLastEdited(properties.getLastEdited()); + } + return somethingChanged; +} + +int MaterialEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) { + + int bytesRead = 0; + const unsigned char* dataAt = data; + + READ_ENTITY_PROPERTY(PROP_MATERIAL_URL, QString, setMaterialURL); + READ_ENTITY_PROPERTY(PROP_MATERIAL_TYPE, MaterialMode, setMaterialMode); + READ_ENTITY_PROPERTY(PROP_MATERIAL_BLEND_FACTOR, float, setBlendFactor); + READ_ENTITY_PROPERTY(PROP_MATERIAL_PRIORITY, int, setPriority); + READ_ENTITY_PROPERTY(PROP_PARENT_SHAPE_ID, int, setShapeID); + READ_ENTITY_PROPERTY(PROP_MATERIAL_BOUNDS, glm::vec4, setMaterialBounds); + + return bytesRead; +} + + +// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time +EntityPropertyFlags MaterialEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); + requestedProperties += PROP_MATERIAL_URL; + requestedProperties += PROP_MATERIAL_TYPE; + requestedProperties += PROP_MATERIAL_BLEND_FACTOR; + requestedProperties += PROP_MATERIAL_PRIORITY; + requestedProperties += PROP_PARENT_SHAPE_ID; + requestedProperties += PROP_MATERIAL_BOUNDS; + return requestedProperties; +} + +void MaterialEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_URL, getMaterialURL()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_TYPE, getMaterialMode()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_BLEND_FACTOR, getBlendFactor()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_PRIORITY, getPriority()); + APPEND_ENTITY_PROPERTY(PROP_PARENT_SHAPE_ID, getShapeID()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_BOUNDS, getMaterialBounds()); +} + +void MaterialEntityItem::debugDump() const { + quint64 now = usecTimestampNow(); + qCDebug(entities) << "MATERIAL EntityItem id:" << getEntityItemID() << "---------------------------------------------"; + qCDebug(entities) << " name:" << _name; + qCDebug(entities) << " url:" << _materialURL; + qCDebug(entities) << " material type:" << _materialMode; + qCDebug(entities) << " blend factor:" << _blendFactor; + qCDebug(entities) << " priority:" << _priority; + qCDebug(entities) << " parent shape ID:" << _shapeID; + qCDebug(entities) << " material bounds:" << _materialBounds; + qCDebug(entities) << " position:" << debugTreeVector(getWorldPosition()); + qCDebug(entities) << " dimensions:" << debugTreeVector(getScaledDimensions()); + qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); + qCDebug(entities) << "SHAPE EntityItem Ptr:" << this; +} diff --git a/libraries/entities/src/MaterialEntityItem.h b/libraries/entities/src/MaterialEntityItem.h new file mode 100644 index 0000000000..094c94f24c --- /dev/null +++ b/libraries/entities/src/MaterialEntityItem.h @@ -0,0 +1,79 @@ +// +// Created by Sam Gondelman on 1/12/18 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_MaterialEntityItem_h +#define hifi_MaterialEntityItem_h + +#include "EntityItem.h" + +#include "MaterialMode.h" +#include + +class MaterialEntityItem : public EntityItem { + using Pointer = std::shared_ptr; +public: + static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); + + MaterialEntityItem(const EntityItemID& entityItemID); + + ALLOW_INSTANTIATION // This class can be instantiated + + // methods for getting/setting all properties of an entity + virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; + virtual bool setProperties(const EntityItemProperties& properties) override; + + // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time + virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; + + virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const override; + + + virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) override; + + void debugDump() const override; + + const QString& getMaterialURL() { return _materialURL; } + void setMaterialURL(const QString& materialURL) { _materialURL = materialURL; } + + MaterialMode getMaterialType() { return _materialMode; } + void setMaterialMode(MaterialMode mode); + + float getBlendFactor() { return _blendFactor; } + void setBlendFactor(float blendFactor) { _blendFactor = blendFactor; } + + int getPriority() { return _priority; } + void setPriority(int priority) { _priority = priority; } + + int getShapeID() { return _shapeID; } + void setShapeID(int shapeID) { _shapeID = shapeID; } + + const glm::vec4& getMaterialBounds() { return _materialBounds; } + void setMaterialBounds(const glm::vec4& materialBounds) { _materialBounds = materialBounds; } + +protected: + QString _materialURL; + MaterialMode _materialMode { UV }; + float _blendFactor { 1.0f }; + int _priority { 0 }; + int _shapeID { 0 }; + glm::vec4 _materialBounds { 0, 0, 1, 1 }; + + //NetworkMaterial _material; + +}; + +#endif // hifi_MaterialEntityItem_h diff --git a/libraries/shared/src/MaterialMode.h b/libraries/shared/src/MaterialMode.h new file mode 100644 index 0000000000..05c6d295a5 --- /dev/null +++ b/libraries/shared/src/MaterialMode.h @@ -0,0 +1,18 @@ +// +// Created by Sam Gondelman on 1/12/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_MaterialMode_h +#define hifi_MaterialMode_h + +enum MaterialMode { + UV = 0, + PROJECTED +}; + +#endif // hifi_MaterialMode_h + From 3911ce59cc94828ba6c418af947c993d73903632 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Tue, 16 Jan 2018 19:02:12 +0100 Subject: [PATCH 028/569] Scribe now outputs .h and .cpp. Need to change how shader source is referenced in C++ code --- cmake/macros/AutoScribeShader.cmake | 37 ++-- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 4 +- .../RenderableParticleEffectEntityItem.cpp | 4 +- .../src/RenderablePolyLineEntityItem.cpp | 8 +- .../src/RenderablePolyVoxEntityItem.cpp | 16 +- .../src/RenderableShapeEntityItem.cpp | 4 +- libraries/gpu/src/gpu/Shader.h | 2 - libraries/gpu/src/gpu/StandardShaderLib.cpp | 48 ++--- libraries/model/src/model/Skybox.cpp | 8 +- .../procedural/src/procedural/Procedural.cpp | 4 +- .../src/procedural/ProceduralSkybox.cpp | 4 +- .../src/AmbientOcclusionEffect.cpp | 10 +- libraries/render-utils/src/AnimDebugDraw.cpp | 4 +- .../render-utils/src/AntialiasingEffect.cpp | 6 +- libraries/render-utils/src/BloomEffect.cpp | 4 +- .../render-utils/src/DebugDeferredBuffer.cpp | 4 +- .../src/DeferredLightingEffect.cpp | 18 +- libraries/render-utils/src/DrawHaze.cpp | 2 +- libraries/render-utils/src/GeometryCache.cpp | 26 +-- .../render-utils/src/HighlightEffect.cpp | 20 +- libraries/render-utils/src/LightClusters.cpp | 12 +- .../render-utils/src/RenderForwardTask.cpp | 4 +- .../render-utils/src/RenderPipelines.cpp | 132 +++++++------- .../render-utils/src/StencilMaskPass.cpp | 4 +- .../render-utils/src/SubsurfaceScattering.cpp | 8 +- .../render-utils/src/SurfaceGeometryPass.cpp | 12 +- libraries/render-utils/src/TextRenderer3D.cpp | 4 +- .../render-utils/src/ToneMappingEffect.cpp | 4 +- libraries/render-utils/src/ZoneRenderer.cpp | 6 +- libraries/render-utils/src/text/Font.cpp | 6 +- libraries/render/src/render/BlurTask.cpp | 8 +- .../render/src/render/DrawSceneOctree.cpp | 10 +- libraries/render/src/render/DrawStatus.cpp | 8 +- libraries/render/src/render/DrawTask.cpp | 4 +- tests/shaders/src/main.cpp | 172 +++++++++--------- tools/scribe/src/main.cpp | 136 ++++++++++++-- 36 files changed, 433 insertions(+), 330 deletions(-) diff --git a/cmake/macros/AutoScribeShader.cmake b/cmake/macros/AutoScribeShader.cmake index d25e0e553f..c92f7a3ffe 100755 --- a/cmake/macros/AutoScribeShader.cmake +++ b/cmake/macros/AutoScribeShader.cmake @@ -39,24 +39,28 @@ function(AUTOSCRIBE_SHADER SHADER_FILE) get_filename_component(SHADER_TARGET ${SHADER_FILE} NAME_WE) get_filename_component(SHADER_EXT ${SHADER_FILE} EXT) if(SHADER_EXT STREQUAL .slv) - set(SHADER_TARGET ${SHADER_TARGET}_vert.cpp) + set(SHADER_TYPE vert) elseif(${SHADER_EXT} STREQUAL .slf) - set(SHADER_TARGET ${SHADER_TARGET}_frag.cpp) + set(SHADER_TYPE frag) elseif(${SHADER_EXT} STREQUAL .slg) - set(SHADER_TARGET ${SHADER_TARGET}_geom.cpp) + set(SHADER_TYPE geom) endif() + set(SHADER_TARGET ${SHADER_TARGET}_${SHADER_TYPE}) set(SHADER_TARGET "${SHADERS_DIR}/${SHADER_TARGET}") + set(SHADER_TARGET_HEADER ${SHADER_TARGET}.h) + set(SHADER_TARGET_SOURCE ${SHADER_TARGET}.cpp) # Target dependant Custom rule on the SHADER_FILE if (APPLE) set(GLPROFILE MAC_GL) - set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) + set(SCRIBE_ARGS -c++ -${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) - add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) + add_custom_command(OUTPUT ${SHADER_TARGET_HEADER} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe) + add_custom_command(OUTPUT ${SHADER_TARGET_SOURCE} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) elseif (ANDROID) set(GLPROFILE LINUX_GL) - set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) + set(SCRIBE_ARGS -c++ -${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) # for an android build, we can't use the scribe that cmake would normally produce as a target, # since it's unrunnable by the cross-compiling build machine @@ -72,23 +76,25 @@ function(AUTOSCRIBE_SHADER SHADER_FILE) ") endif () - add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND ${NATIVE_SCRIBE} ${SCRIBE_ARGS} DEPENDS ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) + add_custom_command(OUTPUT ${SHADER_TARGET_HEADER} COMMAND ${NATIVE_SCRIBE} ${SCRIBE_ARGS}) + add_custom_command(OUTPUT ${SHADER_TARGET_SOURCE} COMMAND ${NATIVE_SCRIBE} ${SCRIBE_ARGS} DEPENDS ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) elseif (UNIX) set(GLPROFILE LINUX_GL) - set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) + set(SCRIBE_ARGS -c++ -${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) - add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) + add_custom_command(OUTPUT ${SHADER_TARGET_HEADER} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe) + add_custom_command(OUTPUT ${SHADER_TARGET_SOURCE} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) else () set(GLPROFILE PC_GL) - set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) + set(SCRIBE_ARGS -c++ -${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) - add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) + add_custom_command(OUTPUT ${SHADER_TARGET_HEADER} ${SHADER_TARGET_SOURCE} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) endif() #output the generated file name - set(AUTOSCRIBE_SHADER_RETURN ${SHADER_TARGET} PARENT_SCOPE) + set(AUTOSCRIBE_SHADER_RETURN ${SHADER_TARGET_HEADER} ${SHADER_TARGET_SOURCE} PARENT_SCOPE) - file(GLOB INCLUDE_FILES ${SHADER_TARGET}) + file(GLOB INCLUDE_FILES ${SHADER_TARGET_HEADER}) endfunction() @@ -134,6 +140,9 @@ macro(AUTOSCRIBE_SHADER_LIB) list(APPEND AUTOSCRIBE_SHADER_LIB_SRC ${AUTOSCRIBE_SHADER_SRC}) # Link library shaders, if they exist - #include_directories("${SHADERS_DIR}") + include_directories("${SHADERS_DIR}") + + # Add search directory to find gpu/Shader.h + include_directories("${HIFI_LIBRARY_DIR}/gpu/src") endmacro() diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 4f50b5ecd9..f39203c89d 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -33,8 +33,8 @@ #include "../Logging.h" #include "../CompositorHelper.h" -INCLUDE_SHADER(hmd_ui_vert) -INCLUDE_SHADER(hmd_ui_frag) +#include "hmd_ui_vert.h" +#include "hmd_ui_frag.h" static const QString MONO_PREVIEW = "Mono Preview"; static const QString DISABLE_PREVIEW = "Disable Preview"; diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index b8f979b7fd..a3e6cd4341 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -14,8 +14,8 @@ #include -INCLUDE_SHADER(textured_particle_vert) -INCLUDE_SHADER(textured_particle_frag) +#include "textured_particle_vert.h" +#include "textured_particle_frag.h" using namespace render; using namespace render::entities; diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index b85771f5b6..b362721cde 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -23,11 +23,11 @@ # include #endif -INCLUDE_SHADER(paintStroke_vert) -INCLUDE_SHADER(paintStroke_frag) +#include "paintStroke_vert.h" +#include "paintStroke_frag.h" -INCLUDE_SHADER(paintStroke_fade_vert) -INCLUDE_SHADER(paintStroke_fade_frag) +#include "paintStroke_fade_vert.h" +#include "paintStroke_fade_frag.h" using namespace render; using namespace render::entities; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index b245ed7cda..cf12da86e9 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -28,10 +28,10 @@ #include "EntityTreeRenderer.h" -INCLUDE_SHADER(polyvox_vert) -INCLUDE_SHADER(polyvox_frag) -INCLUDE_SHADER(polyvox_fade_vert) -INCLUDE_SHADER(polyvox_fade_frag) +#include "polyvox_vert.h" +#include "polyvox_frag.h" +#include "polyvox_fade_vert.h" +#include "polyvox_fade_frag.h" #ifdef POLYVOX_ENTITY_USE_FADE_EFFECT # include @@ -72,10 +72,10 @@ INCLUDE_SHADER(polyvox_fade_frag) #include "EntityTreeRenderer.h" -INCLUDE_SHADER(polyvox_vert) -INCLUDE_SHADER(polyvox_frag) -INCLUDE_SHADER(polyvox_fade_vert) -INCLUDE_SHADER(polyvox_fade_frag) +#include "polyvox_vert.h" +#include "polyvox_frag.h" +#include "polyvox_fade_vert.h" +#include "polyvox_fade_frag.h" #include "RenderablePolyVoxEntityItem.h" #include "EntityEditPacketSender.h" diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index cd02186440..eddde317fe 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -16,8 +16,8 @@ #include #include -INCLUDE_SHADER(simple_vert) -INCLUDE_SHADER(simple_frag) +#include "simple_vert.h" +#include "simple_frag.h" //#define SHAPE_ENTITY_USE_FADE_EFFECT #ifdef SHAPE_ENTITY_USE_FADE_EFFECT diff --git a/libraries/gpu/src/gpu/Shader.h b/libraries/gpu/src/gpu/Shader.h index 5cfdbc8bf4..181c9b5e78 100755 --- a/libraries/gpu/src/gpu/Shader.h +++ b/libraries/gpu/src/gpu/Shader.h @@ -17,8 +17,6 @@ #include #include - -#define INCLUDE_SHADER(source) extern const char source[]; namespace gpu { diff --git a/libraries/gpu/src/gpu/StandardShaderLib.cpp b/libraries/gpu/src/gpu/StandardShaderLib.cpp index 93c2228de6..0d829fb21f 100755 --- a/libraries/gpu/src/gpu/StandardShaderLib.cpp +++ b/libraries/gpu/src/gpu/StandardShaderLib.cpp @@ -12,21 +12,21 @@ // #include "StandardShaderLib.h" -INCLUDE_SHADER(DrawUnitQuadTexcoord_vert) -INCLUDE_SHADER(DrawTransformUnitQuad_vert) -INCLUDE_SHADER(DrawTexcoordRectTransformUnitQuad_vert) -INCLUDE_SHADER(DrawViewportQuadTransformTexcoord_vert) -INCLUDE_SHADER(DrawVertexPosition_vert) -INCLUDE_SHADER(DrawTransformVertexPosition_vert) +#include "DrawUnitQuadTexcoord_vert.h" +#include "DrawTransformUnitQuad_vert.h" +#include "DrawTexcoordRectTransformUnitQuad_vert.h" +#include "DrawViewportQuadTransformTexcoord_vert.h" +#include "DrawVertexPosition_vert.h" +#include "DrawTransformVertexPosition_vert.h" const char DrawNada_frag[] = "void main(void) {}"; // DrawNada is really simple... -INCLUDE_SHADER(DrawWhite_frag) -INCLUDE_SHADER(DrawColor_frag) -INCLUDE_SHADER(DrawTexture_frag) -INCLUDE_SHADER(DrawTextureMirroredX_frag) -INCLUDE_SHADER(DrawTextureOpaque_frag) -INCLUDE_SHADER(DrawColoredTexture_frag) +#include "DrawWhite_frag.h" +#include "DrawColor_frag.h" +#include "DrawTexture_frag.h" +#include "DrawTextureMirroredX_frag.h" +#include "DrawTextureOpaque_frag.h" +#include "DrawColoredTexture_frag.h" using namespace gpu; @@ -73,42 +73,42 @@ ShaderPointer StandardShaderLib::getProgram(GetShader getVS, GetShader getPS) { ShaderPointer StandardShaderLib::getDrawUnitQuadTexcoordVS() { if (!_drawUnitQuadTexcoordVS) { - _drawUnitQuadTexcoordVS = gpu::Shader::createVertex(std::string(DrawUnitQuadTexcoord_vert)); + _drawUnitQuadTexcoordVS = DrawUnitQuadTexcoord_vert::getShader(); } return _drawUnitQuadTexcoordVS; } ShaderPointer StandardShaderLib::getDrawTransformUnitQuadVS() { if (!_drawTransformUnitQuadVS) { - _drawTransformUnitQuadVS = gpu::Shader::createVertex(std::string(DrawTransformUnitQuad_vert)); + _drawTransformUnitQuadVS = DrawTransformUnitQuad_vert::getShader(); } return _drawTransformUnitQuadVS; } ShaderPointer StandardShaderLib::getDrawTexcoordRectTransformUnitQuadVS() { if (!_drawTexcoordRectTransformUnitQuadVS) { - _drawTexcoordRectTransformUnitQuadVS = gpu::Shader::createVertex(std::string(DrawTexcoordRectTransformUnitQuad_vert)); + _drawTexcoordRectTransformUnitQuadVS = DrawTexcoordRectTransformUnitQuad_vert::getShader(); } return _drawTexcoordRectTransformUnitQuadVS; } ShaderPointer StandardShaderLib::getDrawViewportQuadTransformTexcoordVS() { if (!_drawViewportQuadTransformTexcoordVS) { - _drawViewportQuadTransformTexcoordVS = gpu::Shader::createVertex(std::string(DrawViewportQuadTransformTexcoord_vert)); + _drawViewportQuadTransformTexcoordVS = DrawViewportQuadTransformTexcoord_vert::getShader(); } return _drawViewportQuadTransformTexcoordVS; } ShaderPointer StandardShaderLib::getDrawVertexPositionVS() { if (!_drawVertexPositionVS) { - _drawVertexPositionVS = gpu::Shader::createVertex(std::string(DrawVertexPosition_vert)); + _drawVertexPositionVS = DrawVertexPosition_vert::getShader(); } return _drawVertexPositionVS; } ShaderPointer StandardShaderLib::getDrawTransformVertexPositionVS() { if (!_drawTransformVertexPositionVS) { - _drawTransformVertexPositionVS = gpu::Shader::createVertex(std::string(DrawTransformVertexPosition_vert)); + _drawTransformVertexPositionVS = DrawTransformVertexPosition_vert::getShader(); } return _drawTransformVertexPositionVS; } @@ -122,42 +122,42 @@ ShaderPointer StandardShaderLib::getDrawNadaPS() { ShaderPointer StandardShaderLib::getDrawWhitePS() { if (!_drawWhitePS) { - _drawWhitePS = gpu::Shader::createPixel(std::string(DrawWhite_frag)); + _drawWhitePS = DrawWhite_frag::getShader(); } return _drawWhitePS; } ShaderPointer StandardShaderLib::getDrawColorPS() { if (!_drawColorPS) { - _drawColorPS = gpu::Shader::createPixel(std::string(DrawColor_frag)); + _drawColorPS = DrawColor_frag::getShader(); } return _drawColorPS; } ShaderPointer StandardShaderLib::getDrawTexturePS() { if (!_drawTexturePS) { - _drawTexturePS = gpu::Shader::createPixel(std::string(DrawTexture_frag)); + _drawTexturePS = DrawTexture_frag::getShader(); } return _drawTexturePS; } ShaderPointer StandardShaderLib::getDrawTextureMirroredXPS() { if (!_drawTextureMirroredXPS) { - _drawTextureMirroredXPS = gpu::Shader::createPixel(std::string(DrawTextureMirroredX_frag)); + _drawTextureMirroredXPS = DrawTextureMirroredX_frag::getShader(); } return _drawTextureMirroredXPS; } ShaderPointer StandardShaderLib::getDrawTextureOpaquePS() { if (!_drawTextureOpaquePS) { - _drawTextureOpaquePS = gpu::Shader::createPixel(std::string(DrawTextureOpaque_frag)); + _drawTextureOpaquePS = DrawTextureOpaque_frag::getShader(); } return _drawTextureOpaquePS; } ShaderPointer StandardShaderLib::getDrawColoredTexturePS() { if (!_drawColoredTexturePS) { - _drawColoredTexturePS = gpu::Shader::createPixel(std::string(DrawColoredTexture_frag)); + _drawColoredTexturePS = DrawColoredTexture_frag::getShader(); } return _drawColoredTexturePS; } diff --git a/libraries/model/src/model/Skybox.cpp b/libraries/model/src/model/Skybox.cpp index 7e03890c85..e2877d4bb4 100755 --- a/libraries/model/src/model/Skybox.cpp +++ b/libraries/model/src/model/Skybox.cpp @@ -15,8 +15,8 @@ #include #include -INCLUDE_SHADER(skybox_vert) -INCLUDE_SHADER(skybox_frag) +#include "skybox_vert.h" +#include "skybox_frag.h" using namespace model; @@ -85,8 +85,8 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky static std::once_flag once; std::call_once(once, [&] { { - auto skyVS = gpu::Shader::createVertex(std::string(skybox_vert)); - auto skyFS = gpu::Shader::createPixel(std::string(skybox_frag)); + auto skyVS = skybox_vert::getShader(); + auto skyFS = skybox_frag::getShader(); auto skyShader = gpu::Shader::createProgram(skyVS, skyFS); gpu::Shader::BindingSet bindings; diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index c0ebdbb186..ba29768f68 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -20,7 +20,7 @@ #include #include -INCLUDE_SHADER(ProceduralCommon_frag) +#include "ProceduralCommon_frag.h" #include "Logging.h" @@ -241,7 +241,7 @@ void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm std::string fragmentShaderSource = _fragmentSource; size_t replaceIndex = fragmentShaderSource.find(PROCEDURAL_COMMON_BLOCK); if (replaceIndex != std::string::npos) { - fragmentShaderSource.replace(replaceIndex, PROCEDURAL_COMMON_BLOCK.size(), ProceduralCommon_frag); + fragmentShaderSource.replace(replaceIndex, PROCEDURAL_COMMON_BLOCK.size(), ProceduralCommon_frag.h"; } replaceIndex = fragmentShaderSource.find(PROCEDURAL_VERSION); diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.cpp b/libraries/procedural/src/procedural/ProceduralSkybox.cpp index 452d3f639a..60fde7bd14 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.cpp +++ b/libraries/procedural/src/procedural/ProceduralSkybox.cpp @@ -15,8 +15,8 @@ #include #include -INCLUDE_SHADER(skybox_vert) -INCLUDE_SHADER(skybox_frag) +#include "skybox_vert.h" +#include "skybox_frag.h" ProceduralSkybox::ProceduralSkybox() : model::Skybox() { _procedural._vertexSource = skybox_vert; diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.cpp b/libraries/render-utils/src/AmbientOcclusionEffect.cpp index f5b4f0f6bb..83753131c8 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.cpp +++ b/libraries/render-utils/src/AmbientOcclusionEffect.cpp @@ -28,11 +28,11 @@ #include "DependencyManager.h" #include "ViewFrustum.h" -INCLUDE_SHADER(ssao_makePyramid_frag) -INCLUDE_SHADER(ssao_makeOcclusion_frag) -INCLUDE_SHADER(ssao_debugOcclusion_frag) -INCLUDE_SHADER(ssao_makeHorizontalBlur_frag) -INCLUDE_SHADER(ssao_makeVerticalBlur_frag) +#include "ssao_makePyramid_frag.h" +#include "ssao_makeOcclusion_frag.h" +#include "ssao_debugOcclusion_frag.h" +#include "ssao_makeHorizontalBlur_frag.h" +#include "ssao_makeVerticalBlur_frag.h" AmbientOcclusionFramebuffer::AmbientOcclusionFramebuffer() { diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index 53ed3ea9a0..382b4e2d93 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -17,8 +17,8 @@ #include "AnimDebugDraw.h" -INCLUDE_SHADER(animdebugdraw_vert) -INCLUDE_SHADER(animdebugdraw_frag) +#include "animdebugdraw_vert.h" +#include "animdebugdraw_frag.h" class AnimDebugDrawData { public: diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index 078e906fad..70c2e3b5ce 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -23,9 +23,9 @@ #include "ViewFrustum.h" #include "GeometryCache.h" -INCLUDE_SHADER(fxaa_vert) -INCLUDE_SHADER(fxaa_frag) -INCLUDE_SHADER(fxaa_blend_frag) +#include "fxaa_vert.h" +#include "fxaa_frag.h" +#include "fxaa_blend_frag.h" Antialiasing::Antialiasing() { diff --git a/libraries/render-utils/src/BloomEffect.cpp b/libraries/render-utils/src/BloomEffect.cpp index ace347f99e..9d9367a6d5 100644 --- a/libraries/render-utils/src/BloomEffect.cpp +++ b/libraries/render-utils/src/BloomEffect.cpp @@ -16,8 +16,8 @@ #include #include -INCLUDE_SHADER(BloomThreshold_frag) -INCLUDE_SHADER(BloomApply_frag) +#include "BloomThreshold_frag.h" +#include "BloomApply_frag.h" #define BLOOM_BLUR_LEVEL_COUNT 3 diff --git a/libraries/render-utils/src/DebugDeferredBuffer.cpp b/libraries/render-utils/src/DebugDeferredBuffer.cpp index 05cd6a6f0e..fe03ead4e1 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.cpp +++ b/libraries/render-utils/src/DebugDeferredBuffer.cpp @@ -23,8 +23,8 @@ #include "TextureCache.h" #include "DeferredLightingEffect.h" -INCLUDE_SHADER(debug_deferred_buffer_vert) -INCLUDE_SHADER(debug_deferred_buffer_frag) +#include "debug_deferred_buffer_vert.h" +#include "debug_deferred_buffer_frag.h" using namespace render; diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 8ec2c74353..81a33f17e3 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -24,18 +24,18 @@ #include "TextureCache.h" #include "FramebufferCache.h" -INCLUDE_SHADER(deferred_light_vert) -INCLUDE_SHADER(deferred_light_point_vert) -INCLUDE_SHADER(deferred_light_spot_vert) +#include "deferred_light_vert.h" +#include "deferred_light_point_vert.h" +#include "deferred_light_spot_vert.h" -INCLUDE_SHADER(directional_ambient_light_frag) -INCLUDE_SHADER(directional_skybox_light_frag) +#include "directional_ambient_light_frag.h" +#include "directional_skybox_light_frag.h" -INCLUDE_SHADER(directional_ambient_light_shadow_frag) -INCLUDE_SHADER(directional_skybox_light_shadow_frag) +#include "directional_ambient_light_shadow_frag.h" +#include "directional_skybox_light_shadow_frag.h" -INCLUDE_SHADER(local_lights_shading_frag) -INCLUDE_SHADER(local_lights_drawOutline_frag) +#include "local_lights_shading_frag.h" +#include "local_lights_drawOutline_frag.h" using namespace render; diff --git a/libraries/render-utils/src/DrawHaze.cpp b/libraries/render-utils/src/DrawHaze.cpp index dc1893b347..da07f5bd9b 100644 --- a/libraries/render-utils/src/DrawHaze.cpp +++ b/libraries/render-utils/src/DrawHaze.cpp @@ -19,7 +19,7 @@ #include "HazeStage.h" #include "LightStage.h" -INCLUDE_SHADER(Haze_frag) +#include "Haze_frag.h" void HazeConfig::setHazeColor(const glm::vec3 value) { hazeColor = value; diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index ac8b300e49..2616d08600 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -34,21 +34,21 @@ #include "model/TextureMap.h" #include "render/Args.h" -INCLUDE_SHADER(standardTransformPNTC_vert) -INCLUDE_SHADER(standardDrawTexture_frag) +#include "standardTransformPNTC_vert.h" +#include "standardDrawTexture_frag.h" -INCLUDE_SHADER(simple_vert) -INCLUDE_SHADER(simple_textured_frag) -INCLUDE_SHADER(simple_textured_unlit_frag) -INCLUDE_SHADER(simple_fade_vert) -INCLUDE_SHADER(simple_textured_fade_frag) -INCLUDE_SHADER(simple_textured_unlit_fade_frag) -INCLUDE_SHADER(simple_opaque_web_browser_frag) -INCLUDE_SHADER(simple_transparent_web_browser_frag) -INCLUDE_SHADER(glowLine_vert) -INCLUDE_SHADER(glowLine_frag) +#include "simple_vert.h" +#include "simple_textured_frag.h" +#include "simple_textured_unlit_frag.h" +#include "simple_fade_vert.h" +#include "simple_textured_fade_frag.h" +#include "simple_textured_unlit_fade_frag.h" +#include "simple_opaque_web_browser_frag.h" +#include "simple_transparent_web_browser_frag.h" +#include "glowLine_vert.h" +#include "glowLine_frag.h" -INCLUDE_SHADER(grid_frag) +#include "grid_frag.h" //#define WANT_DEBUG diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp index 69feffb055..fee1f4a568 100644 --- a/libraries/render-utils/src/HighlightEffect.cpp +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -22,13 +22,13 @@ #include -INCLUDE_SHADER(surfaceGeometry_copyDepth_frag) -INCLUDE_SHADER(debug_deferred_buffer_vert) -INCLUDE_SHADER(debug_deferred_buffer_frag) -INCLUDE_SHADER(Highlight_frag) -INCLUDE_SHADER(Highlight_filled_frag) -INCLUDE_SHADER(Highlight_aabox_vert) -INCLUDE_SHADER(nop_frag) +#include "surfaceGeometry_copyDepth_frag.h" +#include "debug_deferred_buffer_vert.h" +#include "debug_deferred_buffer_frag.h" +#include "Highlight_frag.h" +#include "Highlight_filled_frag.h" +#include "Highlight_aabox_vert.h" +#include "nop_frag.h" using namespace render; @@ -547,10 +547,10 @@ const render::Varying DrawHighlightTask::addSelectItemJobs(JobModel& task, const return task.addJob("TransparentSelection", selectItemInput); } -INCLUDE_SHADER(model_shadow_vert) -INCLUDE_SHADER(skin_model_shadow_vert) +#include "model_shadow_vert.h" +#include "skin_model_shadow_vert.h" -INCLUDE_SHADER(model_shadow_frag) +#include "model_shadow_frag.h" void DrawHighlightTask::initMaskPipelines(render::ShapePlumber& shapePlumber, gpu::StatePointer state) { auto modelVertex = gpu::Shader::createVertex(std::string(model_shadow_vert)); diff --git a/libraries/render-utils/src/LightClusters.cpp b/libraries/render-utils/src/LightClusters.cpp index 79d755683f..d6ac7fd2e2 100644 --- a/libraries/render-utils/src/LightClusters.cpp +++ b/libraries/render-utils/src/LightClusters.cpp @@ -18,15 +18,15 @@ #include "StencilMaskPass.h" -INCLUDE_SHADER(lightClusters_drawGrid_vert) -INCLUDE_SHADER(lightClusters_drawGrid_frag) +#include "lightClusters_drawGrid_vert.h" +#include "lightClusters_drawGrid_frag.h" -//INCLUDE_SHADER(lightClusters_drawClusterFromDepth_vert) -INCLUDE_SHADER(lightClusters_drawClusterFromDepth_frag) +//#include "lightClusters_drawClusterFromDepth_vert.h" +#include "lightClusters_drawClusterFromDepth_frag.h" -INCLUDE_SHADER(lightClusters_drawClusterContent_vert) -INCLUDE_SHADER(lightClusters_drawClusterContent_frag) +#include "lightClusters_drawClusterContent_vert.h" +#include "lightClusters_drawClusterContent_frag.h" enum LightClusterGridShader_MapSlot { DEFERRED_BUFFER_LINEAR_DEPTH_UNIT = 0, diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index 8d02281d05..16e739f432 100755 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -24,7 +24,7 @@ #include -INCLUDE_SHADER(nop_frag) +#include "nop_frag.h" using namespace render; extern void initForwardPipelines(ShapePlumber& plumber); @@ -125,7 +125,7 @@ void Draw::run(const RenderContextPointer& renderContext, const gpu::PipelinePointer Stencil::getPipeline() { if (!_stencilPipeline) { auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(nop_frag)); + auto ps = gpu::Shader::createPixel(std::string(nop_frag); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::makeProgram(*program); diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index 62252d3a0b..7f644add72 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -20,86 +20,86 @@ #include "TextureCache.h" #include "render/DrawTask.h" -INCLUDE_SHADER(model_vert) -INCLUDE_SHADER(model_normal_map_vert) -INCLUDE_SHADER(model_lightmap_vert) -INCLUDE_SHADER(model_lightmap_normal_map_vert) -INCLUDE_SHADER(skin_model_vert) -INCLUDE_SHADER(skin_model_normal_map_vert) +#include "model_vert.h" +#include "model_normal_map_vert.h" +#include "model_lightmap_vert.h" +#include "model_lightmap_normal_map_vert.h" +#include "skin_model_vert.h" +#include "skin_model_normal_map_vert.h" -INCLUDE_SHADER(model_lightmap_fade_vert) -INCLUDE_SHADER(model_lightmap_normal_map_fade_vert) -INCLUDE_SHADER(skin_model_fade_vert) -INCLUDE_SHADER(skin_model_normal_map_fade_vert) +#include "model_lightmap_fade_vert.h" +#include "model_lightmap_normal_map_fade_vert.h" +#include "skin_model_fade_vert.h" +#include "skin_model_normal_map_fade_vert.h" -INCLUDE_SHADER(simple_vert) -INCLUDE_SHADER(simple_textured_frag) -INCLUDE_SHADER(simple_textured_unlit_frag) -INCLUDE_SHADER(simple_transparent_textured_frag) -INCLUDE_SHADER(simple_transparent_textured_unlit_frag) +#include "simple_vert.h" +#include "simple_textured_frag.h" +#include "simple_textured_unlit_frag.h" +#include "simple_transparent_textured_frag.h" +#include "simple_transparent_textured_unlit_frag.h" -INCLUDE_SHADER(simple_fade_vert) -INCLUDE_SHADER(simple_textured_fade_frag) -INCLUDE_SHADER(simple_textured_unlit_fade_frag) -INCLUDE_SHADER(simple_transparent_textured_fade_frag) -INCLUDE_SHADER(simple_transparent_textured_unlit_fade_frag) +#include "simple_fade_vert.h" +#include "simple_textured_fade_frag.h" +#include "simple_textured_unlit_fade_frag.h" +#include "simple_transparent_textured_fade_frag.h" +#include "simple_transparent_textured_unlit_fade_frag.h" -INCLUDE_SHADER(model_frag) -INCLUDE_SHADER(model_unlit_frag) -INCLUDE_SHADER(model_normal_map_frag) -INCLUDE_SHADER(model_normal_specular_map_frag) -INCLUDE_SHADER(model_specular_map_frag) +#include "model_frag.h" +#include "model_unlit_frag.h" +#include "model_normal_map_frag.h" +#include "model_normal_specular_map_frag.h" +#include "model_specular_map_frag.h" -INCLUDE_SHADER(model_fade_vert) -INCLUDE_SHADER(model_normal_map_fade_vert) +#include "model_fade_vert.h" +#include "model_normal_map_fade_vert.h" -INCLUDE_SHADER(model_fade_frag) -INCLUDE_SHADER(model_unlit_fade_frag) -INCLUDE_SHADER(model_normal_map_fade_frag) -INCLUDE_SHADER(model_normal_specular_map_fade_frag) -INCLUDE_SHADER(model_specular_map_fade_frag) +#include "model_fade_frag.h" +#include "model_unlit_fade_frag.h" +#include "model_normal_map_fade_frag.h" +#include "model_normal_specular_map_fade_frag.h" +#include "model_specular_map_fade_frag.h" -INCLUDE_SHADER(forward_model_frag) -INCLUDE_SHADER(forward_model_unlit_frag) -INCLUDE_SHADER(forward_model_normal_map_frag) -INCLUDE_SHADER(forward_model_normal_specular_map_frag) -INCLUDE_SHADER(forward_model_specular_map_frag) +#include "forward_model_frag.h" +#include "forward_model_unlit_frag.h" +#include "forward_model_normal_map_frag.h" +#include "forward_model_normal_specular_map_frag.h" +#include "forward_model_specular_map_frag.h" -INCLUDE_SHADER(model_lightmap_frag) -INCLUDE_SHADER(model_lightmap_normal_map_frag) -INCLUDE_SHADER(model_lightmap_normal_specular_map_frag) -INCLUDE_SHADER(model_lightmap_specular_map_frag) -INCLUDE_SHADER(model_translucent_frag) -INCLUDE_SHADER(model_translucent_unlit_frag) +#include "model_lightmap_frag.h" +#include "model_lightmap_normal_map_frag.h" +#include "model_lightmap_normal_specular_map_frag.h" +#include "model_lightmap_specular_map_frag.h" +#include "model_translucent_frag.h" +#include "model_translucent_unlit_frag.h" -INCLUDE_SHADER(model_lightmap_fade_frag) -INCLUDE_SHADER(model_lightmap_normal_map_fade_frag) -INCLUDE_SHADER(model_lightmap_normal_specular_map_fade_frag) -INCLUDE_SHADER(model_lightmap_specular_map_fade_frag) -INCLUDE_SHADER(model_translucent_fade_frag) -INCLUDE_SHADER(model_translucent_unlit_fade_frag) +#include "model_lightmap_fade_frag.h" +#include "model_lightmap_normal_map_fade_frag.h" +#include "model_lightmap_normal_specular_map_fade_frag.h" +#include "model_lightmap_specular_map_fade_frag.h" +#include "model_translucent_fade_frag.h" +#include "model_translucent_unlit_fade_frag.h" -INCLUDE_SHADER(overlay3D_vert) -INCLUDE_SHADER(overlay3D_frag) -INCLUDE_SHADER(overlay3D_model_frag) -INCLUDE_SHADER(overlay3D_model_translucent_frag) -INCLUDE_SHADER(overlay3D_translucent_frag) -INCLUDE_SHADER(overlay3D_unlit_frag) -INCLUDE_SHADER(overlay3D_translucent_unlit_frag) -INCLUDE_SHADER(overlay3D_model_unlit_frag) -INCLUDE_SHADER(overlay3D_model_translucent_unlit_frag) +#include "overlay3D_vert.h" +#include "overlay3D_frag.h" +#include "overlay3D_model_frag.h" +#include "overlay3D_model_translucent_frag.h" +#include "overlay3D_translucent_frag.h" +#include "overlay3D_unlit_frag.h" +#include "overlay3D_translucent_unlit_frag.h" +#include "overlay3D_model_unlit_frag.h" +#include "overlay3D_model_translucent_unlit_frag.h" -INCLUDE_SHADER(model_shadow_vert) -INCLUDE_SHADER(skin_model_shadow_vert) +#include "model_shadow_vert.h" +#include "skin_model_shadow_vert.h" -INCLUDE_SHADER(model_shadow_frag) -INCLUDE_SHADER(skin_model_shadow_frag) +#include "model_shadow_frag.h" +#include "skin_model_shadow_frag.h" -INCLUDE_SHADER(model_shadow_fade_vert) -INCLUDE_SHADER(skin_model_shadow_fade_vert) +#include "model_shadow_fade_vert.h" +#include "skin_model_shadow_fade_vert.h" -INCLUDE_SHADER(model_shadow_fade_frag) -INCLUDE_SHADER(skin_model_shadow_fade_frag) +#include "model_shadow_fade_frag.h" +#include "skin_model_shadow_fade_frag.h" using namespace render; using namespace std::placeholders; diff --git a/libraries/render-utils/src/StencilMaskPass.cpp b/libraries/render-utils/src/StencilMaskPass.cpp index e2a8b31f08..80c97cf29f 100644 --- a/libraries/render-utils/src/StencilMaskPass.cpp +++ b/libraries/render-utils/src/StencilMaskPass.cpp @@ -17,7 +17,7 @@ #include -INCLUDE_SHADER(stencil_drawMask_frag) +#include "stencil_drawMask_frag.h" using namespace render; @@ -60,7 +60,7 @@ gpu::PipelinePointer PrepareStencil::getMeshStencilPipeline() { gpu::PipelinePointer PrepareStencil::getPaintStencilPipeline() { if (!_paintStencilPipeline) { auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(stencil_drawMask_frag)); + auto ps = gpu::Shader::createPixel(std::string(stencil_drawMask_frag); auto program = gpu::Shader::createProgram(vs, ps); gpu::Shader::makeProgram((*program)); diff --git a/libraries/render-utils/src/SubsurfaceScattering.cpp b/libraries/render-utils/src/SubsurfaceScattering.cpp index e8d7e23ec2..1786898e57 100644 --- a/libraries/render-utils/src/SubsurfaceScattering.cpp +++ b/libraries/render-utils/src/SubsurfaceScattering.cpp @@ -17,11 +17,11 @@ #include "DeferredLightingEffect.h" -INCLUDE_SHADER(subsurfaceScattering_makeProfile_frag) -INCLUDE_SHADER(subsurfaceScattering_makeLUT_frag) -INCLUDE_SHADER(subsurfaceScattering_makeSpecularBeckmann_frag) +#include "subsurfaceScattering_makeProfile_frag.h" +#include "subsurfaceScattering_makeLUT_frag.h" +#include "subsurfaceScattering_makeSpecularBeckmann_frag.h" -INCLUDE_SHADER(subsurfaceScattering_drawScattering_frag) +#include "subsurfaceScattering_drawScattering_frag.h" enum ScatteringShaderBufferSlots { ScatteringTask_FrameTransformSlot = 0, diff --git a/libraries/render-utils/src/SurfaceGeometryPass.cpp b/libraries/render-utils/src/SurfaceGeometryPass.cpp index 6ad8dc6137..af6ff09082 100644 --- a/libraries/render-utils/src/SurfaceGeometryPass.cpp +++ b/libraries/render-utils/src/SurfaceGeometryPass.cpp @@ -25,10 +25,10 @@ const int SurfaceGeometryPass_ParamsSlot = 1; const int SurfaceGeometryPass_DepthMapSlot = 0; const int SurfaceGeometryPass_NormalMapSlot = 1; -INCLUDE_SHADER(surfaceGeometry_makeLinearDepth_frag) -INCLUDE_SHADER(surfaceGeometry_downsampleDepthNormal_frag) +#include "surfaceGeometry_makeLinearDepth_frag.h" +#include "surfaceGeometry_downsampleDepthNormal_frag.h" -INCLUDE_SHADER(surfaceGeometry_makeCurvature_frag) +#include "surfaceGeometry_makeCurvature_frag.h" @@ -212,7 +212,7 @@ void LinearDepthPass::run(const render::RenderContextPointer& renderContext, con const gpu::PipelinePointer& LinearDepthPass::getLinearDepthPipeline() { if (!_linearDepthPipeline) { auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(surfaceGeometry_makeLinearDepth_frag)); + auto ps = gpu::Shader::createPixel(std::string(surfaceGeometry_makeLinearDepth_frag); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -239,7 +239,7 @@ const gpu::PipelinePointer& LinearDepthPass::getLinearDepthPipeline() { const gpu::PipelinePointer& LinearDepthPass::getDownsamplePipeline() { if (!_downsamplePipeline) { auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(surfaceGeometry_downsampleDepthNormal_frag)); + auto ps = gpu::Shader::createPixel(std::string(surfaceGeometry_downsampleDepthNormal_frag); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -540,7 +540,7 @@ void SurfaceGeometryPass::run(const render::RenderContextPointer& renderContext, const gpu::PipelinePointer& SurfaceGeometryPass::getCurvaturePipeline() { if (!_curvaturePipeline) { auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(surfaceGeometry_makeCurvature_frag)); + auto ps = gpu::Shader::createPixel(std::string(surfaceGeometry_makeCurvature_frag); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index 06e041685a..9c85952107 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -25,8 +25,8 @@ #include "MatrixStack.h" #include "RenderUtilsLogging.h" -INCLUDE_SHADER(sdf_text3D_vert) -INCLUDE_SHADER(sdf_text3D_frag) +#include "sdf_text3D_vert.h" +#include "sdf_text3D_frag.h" #include "GeometryCache.h" diff --git a/libraries/render-utils/src/ToneMappingEffect.cpp b/libraries/render-utils/src/ToneMappingEffect.cpp index 3d3a11f7b3..6cb9541dae 100644 --- a/libraries/render-utils/src/ToneMappingEffect.cpp +++ b/libraries/render-utils/src/ToneMappingEffect.cpp @@ -17,7 +17,7 @@ #include "StencilMaskPass.h" #include "FramebufferCache.h" -INCLUDE_SHADER(toneMapping_frag) +#include "toneMapping_frag.h" const int ToneMappingEffect_ParamsSlot = 0; const int ToneMappingEffect_LightingMapSlot = 0; @@ -28,7 +28,7 @@ ToneMappingEffect::ToneMappingEffect() { } void ToneMappingEffect::init() { - auto blitPS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(toneMapping_frag))); + auto blitPS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(toneMapping_frag)); auto blitVS = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); auto blitProgram = gpu::ShaderPointer(gpu::Shader::createProgram(blitVS, blitPS)); diff --git a/libraries/render-utils/src/ZoneRenderer.cpp b/libraries/render-utils/src/ZoneRenderer.cpp index 77b5c492d3..c0d01c2eaf 100644 --- a/libraries/render-utils/src/ZoneRenderer.cpp +++ b/libraries/render-utils/src/ZoneRenderer.cpp @@ -20,9 +20,9 @@ #include "StencilMaskPass.h" #include "DeferredLightingEffect.h" -INCLUDE_SHADER(zone_drawKeyLight_frag) -INCLUDE_SHADER(zone_drawAmbient_frag) -INCLUDE_SHADER(zone_drawSkybox_frag) +#include "zone_drawKeyLight_frag.h" +#include "zone_drawAmbient_frag.h" +#include "zone_drawSkybox_frag.h" using namespace render; diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index 9e0fbd1522..8449c58c7c 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -7,9 +7,9 @@ #include -INCLUDE_SHADER(sdf_text3D_vert) -INCLUDE_SHADER(sdf_text3D_frag) -INCLUDE_SHADER(sdf_text3D_transparent_frag) +#include "sdf_text3D_vert.h" +#include "sdf_text3D_frag.h" +#include "sdf_text3D_transparent_frag.h" #include "../RenderUtilsLogging.h" #include "FontFamilies.h" diff --git a/libraries/render/src/render/BlurTask.cpp b/libraries/render/src/render/BlurTask.cpp index 4b5d6da8e3..2be6f8fad2 100644 --- a/libraries/render/src/render/BlurTask.cpp +++ b/libraries/render/src/render/BlurTask.cpp @@ -13,11 +13,11 @@ #include #include -INCLUDE_SHADER(blurGaussianV_frag) -INCLUDE_SHADER(blurGaussianH_frag) +#include "blurGaussianV_frag.h" +#include "blurGaussianH_frag.h" -INCLUDE_SHADER(blurGaussianDepthAwareV_frag) -INCLUDE_SHADER(blurGaussianDepthAwareH_frag) +#include "blurGaussianDepthAwareV_frag.h" +#include "blurGaussianDepthAwareH_frag.h" using namespace render; diff --git a/libraries/render/src/render/DrawSceneOctree.cpp b/libraries/render/src/render/DrawSceneOctree.cpp index 7823d85dae..36663a454a 100644 --- a/libraries/render/src/render/DrawSceneOctree.cpp +++ b/libraries/render/src/render/DrawSceneOctree.cpp @@ -22,12 +22,12 @@ #include "Args.h" -INCLUDE_SHADER(drawCellBounds_vert) -INCLUDE_SHADER(drawCellBounds_frag) -INCLUDE_SHADER(drawLODReticle_frag) +#include "drawCellBounds_vert.h" +#include "drawCellBounds_frag.h" +#include "drawLODReticle_frag.h" -INCLUDE_SHADER(drawItemBounds_vert) -INCLUDE_SHADER(drawItemBounds_frag) +#include "drawItemBounds_vert.h" +#include "drawItemBounds_frag.h" using namespace render; diff --git a/libraries/render/src/render/DrawStatus.cpp b/libraries/render/src/render/DrawStatus.cpp index 1c6d7749f8..148e104453 100644 --- a/libraries/render/src/render/DrawStatus.cpp +++ b/libraries/render/src/render/DrawStatus.cpp @@ -21,10 +21,10 @@ #include "Args.h" -INCLUDE_SHADER(drawItemBounds_vert) -INCLUDE_SHADER(drawItemBounds_frag) -INCLUDE_SHADER(drawItemStatus_vert) -INCLUDE_SHADER(drawItemStatus_frag) +#include "drawItemBounds_vert.h" +#include "drawItemBounds_frag.h" +#include "drawItemStatus_vert.h" +#include "drawItemStatus_frag.h" using namespace render; diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index a6d5072cca..a60bf91062 100755 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -22,8 +22,8 @@ #include #include -INCLUDE_SHADER(drawItemBounds_vert) -INCLUDE_SHADER(drawItemBounds_frag) +#include "drawItemBounds_vert.h" +#include "drawItemBounds_frag.h" using namespace render; diff --git a/tests/shaders/src/main.cpp b/tests/shaders/src/main.cpp index e0babc8b47..de37c505a6 100644 --- a/tests/shaders/src/main.cpp +++ b/tests/shaders/src/main.cpp @@ -23,65 +23,65 @@ #include -INCLUDE_SHADER(simple_vert) -INCLUDE_SHADER(simple_frag) -INCLUDE_SHADER(simple_textured_frag) -INCLUDE_SHADER(simple_textured_unlit_frag) +#include "simple_vert.h" +#include "simple_frag.h" +#include "simple_textured_frag.h" +#include "simple_textured_unlit_frag.h" -INCLUDE_SHADER(deferred_light_vert) -INCLUDE_SHADER(deferred_light_point_vert) -INCLUDE_SHADER(deferred_light_spot_vert) +#include "deferred_light_vert.h" +#include "deferred_light_point_vert.h" +#include "deferred_light_spot_vert.h" -INCLUDE_SHADER(directional_ambient_light_frag) -INCLUDE_SHADER(directional_skybox_light_frag) +#include "directional_ambient_light_frag.h" +#include "directional_skybox_light_frag.h" -INCLUDE_SHADER(standardTransformPNTC_vert) -INCLUDE_SHADER(standardDrawTexture_frag) +#include "standardTransformPNTC_vert.h" +#include "standardDrawTexture_frag.h" -INCLUDE_SHADER(model_vert) -INCLUDE_SHADER(model_shadow_vert) -INCLUDE_SHADER(model_normal_map_vert) -INCLUDE_SHADER(model_lightmap_vert) -INCLUDE_SHADER(model_lightmap_normal_map_vert) -INCLUDE_SHADER(skin_model_vert) -INCLUDE_SHADER(skin_model_shadow_vert) -INCLUDE_SHADER(skin_model_normal_map_vert) +#include "model_vert.h" +#include "model_shadow_vert.h" +#include "model_normal_map_vert.h" +#include "model_lightmap_vert.h" +#include "model_lightmap_normal_map_vert.h" +#include "skin_model_vert.h" +#include "skin_model_shadow_vert.h" +#include "skin_model_normal_map_vert.h" -INCLUDE_SHADER(model_frag) -INCLUDE_SHADER(model_shadow_frag) -INCLUDE_SHADER(model_normal_map_frag) -INCLUDE_SHADER(model_normal_specular_map_frag) -INCLUDE_SHADER(model_specular_map_frag) -INCLUDE_SHADER(model_lightmap_frag) -INCLUDE_SHADER(model_lightmap_normal_map_frag) -INCLUDE_SHADER(model_lightmap_normal_specular_map_frag) -INCLUDE_SHADER(model_lightmap_specular_map_frag) -INCLUDE_SHADER(model_translucent_frag) +#include "model_frag.h" +#include "model_shadow_frag.h" +#include "model_normal_map_frag.h" +#include "model_normal_specular_map_frag.h" +#include "model_specular_map_frag.h" +#include "model_lightmap_frag.h" +#include "model_lightmap_normal_map_frag.h" +#include "model_lightmap_normal_specular_map_frag.h" +#include "model_lightmap_specular_map_frag.h" +#include "model_translucent_frag.h" -INCLUDE_SHADER(textured_particle_frag) -INCLUDE_SHADER(textured_particle_vert) +#include "textured_particle_frag.h" +#include "textured_particle_vert.h" -INCLUDE_SHADER(overlay3D_vert) -INCLUDE_SHADER(overlay3D_frag) +#include "overlay3D_vert.h" +#include "overlay3D_frag.h" -INCLUDE_SHADER(skybox_vert) -INCLUDE_SHADER(skybox_frag) +#include "skybox_vert.h" +#include "skybox_frag.h" -INCLUDE_SHADER(DrawTransformUnitQuad_vert) -INCLUDE_SHADER(DrawTexcoordRectTransformUnitQuad_vert) -INCLUDE_SHADER(DrawViewportQuadTransformTexcoord_vert) -INCLUDE_SHADER(DrawTexture_frag) -INCLUDE_SHADER(DrawTextureOpaque_frag) -INCLUDE_SHADER(DrawColoredTexture_frag) +#include "DrawTransformUnitQuad_vert.h" +#include "DrawTexcoordRectTransformUnitQuad_vert.h" +#include "DrawViewportQuadTransformTexcoord_vert.h" +#include "DrawTexture_frag.h" +#include "DrawTextureOpaque_frag.h" +#include "DrawColoredTexture_frag.h" -INCLUDE_SHADER(sdf_text3D_vert) -INCLUDE_SHADER(sdf_text3D_frag) +#include "sdf_text3D_vert.h" +#include "sdf_text3D_frag.h" -INCLUDE_SHADER(paintStroke_vert) -INCLUDE_SHADER(paintStroke_frag) +#include "paintStroke_vert.h" +#include "paintStroke_frag.h" -INCLUDE_SHADER(polyvox_vert) -INCLUDE_SHADER(polyvox_frag) +#include "polyvox_vert.h" +#include "polyvox_frag.h" // Create a simple OpenGL window that renders text in various ways class QTestWindow : public QWindow { @@ -159,54 +159,54 @@ void QTestWindow::draw() { static std::once_flag once; std::call_once(once, [&]{ - testShaderBuild(sdf_text3D_vert, sdf_text3D_frag); + testShaderBuild(sdf_text3D_vert, sdf_text3D_frag.h"; - testShaderBuild(DrawTransformUnitQuad_vert, DrawTexture_frag); - testShaderBuild(DrawTexcoordRectTransformUnitQuad_vert, DrawTexture_frag); - testShaderBuild(DrawViewportQuadTransformTexcoord_vert, DrawTexture_frag); - testShaderBuild(DrawTransformUnitQuad_vert, DrawTextureOpaque_frag); - testShaderBuild(DrawTransformUnitQuad_vert, DrawColoredTexture_frag); + testShaderBuild(DrawTransformUnitQuad_vert, DrawTexture_frag.h"; + testShaderBuild(DrawTexcoordRectTransformUnitQuad_vert, DrawTexture_frag.h"; + testShaderBuild(DrawViewportQuadTransformTexcoord_vert, DrawTexture_frag.h"; + testShaderBuild(DrawTransformUnitQuad_vert, DrawTextureOpaque_frag.h"; + testShaderBuild(DrawTransformUnitQuad_vert, DrawColoredTexture_frag.h"; - testShaderBuild(skybox_vert, skybox_frag); - testShaderBuild(simple_vert, simple_frag); - testShaderBuild(simple_vert, simple_textured_frag); - testShaderBuild(simple_vert, simple_textured_unlit_frag); - testShaderBuild(deferred_light_vert, directional_ambient_light_frag); - testShaderBuild(deferred_light_vert, directional_skybox_light_frag); - testShaderBuild(standardTransformPNTC_vert, standardDrawTexture_frag); - testShaderBuild(standardTransformPNTC_vert, DrawTextureOpaque_frag); + testShaderBuild(skybox_vert, skybox_frag.h"; + testShaderBuild(simple_vert, simple_frag.h"; + testShaderBuild(simple_vert, simple_textured_frag.h"; + testShaderBuild(simple_vert, simple_textured_unlit_frag.h"; + testShaderBuild(deferred_light_vert, directional_ambient_light_frag.h"; + testShaderBuild(deferred_light_vert, directional_skybox_light_frag.h"; + testShaderBuild(standardTransformPNTC_vert, standardDrawTexture_frag.h"; + testShaderBuild(standardTransformPNTC_vert, DrawTextureOpaque_frag.h"; - testShaderBuild(model_vert, model_frag); - testShaderBuild(model_normal_map_vert, model_normal_map_frag); - testShaderBuild(model_vert, model_specular_map_frag); - testShaderBuild(model_normal_map_vert, model_normal_specular_map_frag); - testShaderBuild(model_vert, model_translucent_frag); - testShaderBuild(model_normal_map_vert, model_translucent_frag); - testShaderBuild(model_lightmap_vert, model_lightmap_frag); - testShaderBuild(model_lightmap_normal_map_vert, model_lightmap_normal_map_frag); - testShaderBuild(model_lightmap_vert, model_lightmap_specular_map_frag); - testShaderBuild(model_lightmap_normal_map_vert, model_lightmap_normal_specular_map_frag); + testShaderBuild(model_vert, model_frag.h"; + testShaderBuild(model_normal_map_vert, model_normal_map_frag.h"; + testShaderBuild(model_vert, model_specular_map_frag.h"; + testShaderBuild(model_normal_map_vert, model_normal_specular_map_frag.h"; + testShaderBuild(model_vert, model_translucent_frag.h"; + testShaderBuild(model_normal_map_vert, model_translucent_frag.h"; + testShaderBuild(model_lightmap_vert, model_lightmap_frag.h"; + testShaderBuild(model_lightmap_normal_map_vert, model_lightmap_normal_map_frag.h"; + testShaderBuild(model_lightmap_vert, model_lightmap_specular_map_frag.h"; + testShaderBuild(model_lightmap_normal_map_vert, model_lightmap_normal_specular_map_frag.h"; - testShaderBuild(skin_model_vert, model_frag); - testShaderBuild(skin_model_normal_map_vert, model_normal_map_frag); - testShaderBuild(skin_model_vert, model_specular_map_frag); - testShaderBuild(skin_model_normal_map_vert, model_normal_specular_map_frag); - testShaderBuild(skin_model_vert, model_translucent_frag); - testShaderBuild(skin_model_normal_map_vert, model_translucent_frag); + testShaderBuild(skin_model_vert, model_frag.h"; + testShaderBuild(skin_model_normal_map_vert, model_normal_map_frag.h"; + testShaderBuild(skin_model_vert, model_specular_map_frag.h"; + testShaderBuild(skin_model_normal_map_vert, model_normal_specular_map_frag.h"; + testShaderBuild(skin_model_vert, model_translucent_frag.h"; + testShaderBuild(skin_model_normal_map_vert, model_translucent_frag.h"; - testShaderBuild(model_shadow_vert, model_shadow_frag); - testShaderBuild(textured_particle_vert, textured_particle_frag); + testShaderBuild(model_shadow_vert, model_shadow_frag.h"; + testShaderBuild(textured_particle_vert, textured_particle_frag.h"; /* FIXME: Bring back the ssao shader tests - testShaderBuild(gaussian_blur_vertical_vert, gaussian_blur_frag); - testShaderBuild(gaussian_blur_horizontal_vert, gaussian_blur_frag); - testShaderBuild(ambient_occlusion_vert, ambient_occlusion_frag); - testShaderBuild(ambient_occlusion_vert, occlusion_blend_frag); + testShaderBuild(gaussian_blur_vertical_vert, gaussian_blur_frag.h"; + testShaderBuild(gaussian_blur_horizontal_vert, gaussian_blur_frag.h"; + testShaderBuild(ambient_occlusion_vert, ambient_occlusion_frag.h"; + testShaderBuild(ambient_occlusion_vert, occlusion_blend_frag.h"; */ - testShaderBuild(overlay3D_vert, overlay3D_frag); + testShaderBuild(overlay3D_vert, overlay3D_frag.h"; - testShaderBuild(paintStroke_vert,paintStroke_frag); - testShaderBuild(polyvox_vert, polyvox_frag); + testShaderBuild(paintStroke_vert,paintStroke_frag.h"; + testShaderBuild(polyvox_vert, polyvox_frag.h"; }); _context.swapBuffers(this); diff --git a/tools/scribe/src/main.cpp b/tools/scribe/src/main.cpp index 8e6ca31c71..2092bc0ea2 100755 --- a/tools/scribe/src/main.cpp +++ b/tools/scribe/src/main.cpp @@ -44,6 +44,18 @@ int main (int argc, char** argv) { EXIT, } mode = READY; + enum Type { + VERTEX = 0, + FRAGMENT, + GEOMETRY, + } type = VERTEX; + static const char* shaderTypeString[] = { + "VERTEX", "PIXEL", "GEOMETRY" + }; + static const char* shaderCreateString[] = { + "Vertex", "Pixel", "Geometry" + }; + for (int ii = 1; (mode != EXIT) && (ii < argc); ii++) { inputs.push_back(argv[ii]); @@ -66,6 +78,15 @@ int main (int argc, char** argv) { } else if (inputs.back() == "-c++") { makeCPlusPlus = true; mode = READY; + } else if (inputs.back() == "-vert") { + type = VERTEX; + mode = READY; + } else if (inputs.back() == "-frag") { + type = FRAGMENT; + mode = READY; + } else if (inputs.back() == "-geom") { + type = GEOMETRY; + mode = READY; } else { // just grabbed the source filename, stop parameter parsing srcFilename = inputs.back(); @@ -186,7 +207,6 @@ int main (int argc, char** argv) { scribe->displayTree(cerr, level); } - std::ostringstream targetStringStream; if (makeCPlusPlus) { // Because there is a maximum size for literal strings declared in source we need to partition the // full source string stream into pages that seems to be around that value... @@ -208,32 +228,108 @@ int main (int argc, char** argv) { pageSize += lineSize; } - targetStringStream << "// File generated by Scribe " << vars["_SCRIBE_DATE"] << std::endl; - targetStringStream << "extern const char " << targetName << "[] = \n"; + std::stringstream headerStringStream; + std::stringstream sourceStringStream; + std::string headerFileName = destFilename + ".h"; + std::string sourceFileName = destFilename + ".cpp"; + sourceStringStream << "// File generated by Scribe " << vars["_SCRIBE_DATE"] << std::endl; + + // Write header file + headerStringStream << "#ifndef " << targetName << "_h" << std::endl; + headerStringStream << "#define " << targetName << "_h\n" << std::endl; + headerStringStream << "#include \n" << std::endl; + headerStringStream << "class " << targetName << " {" << std::endl; + headerStringStream << "public:" << std::endl; + headerStringStream << "\tstatic gpu::Shader::Type getType() { return gpu::Shader::" << shaderTypeString[type] << "; }" << std::endl; + headerStringStream << "\tstatic const char* getSource() { return _source; }" << std::endl; + headerStringStream << "\tstatic gpu::ShaderPointer getShader();" << std::endl; + headerStringStream << "private:" << std::endl; + headerStringStream << "\tstatic const char* _source;" << std::endl; + headerStringStream << "\tstatic gpu::ShaderPointer _shader;" << std::endl; + headerStringStream << "};\n" << std::endl; + headerStringStream << "#endif // " << targetName << "_h" << std::endl; + + bool mustOutputHeader = destFilename.empty(); + // Compare with existing file + { + std::fstream headerFile; + headerFile.open(headerFileName, std::fstream::in); + if (headerFile.is_open()) { + // Skip first line + std::string line; + std::stringstream previousHeaderStringStream; + std::getline(headerFile, line); + + previousHeaderStringStream << headerFile.rdbuf(); + mustOutputHeader = mustOutputHeader || previousHeaderStringStream.str() != headerStringStream.str(); + } else { + mustOutputHeader = true; + } + } + + if (mustOutputHeader) { + if (!destFilename.empty()) { + // File content has changed so write it + std::fstream headerFile; + headerFile.open(headerFileName, std::fstream::out); + if (headerFile.is_open()) { + // First line contains the date of modification + headerFile << sourceStringStream.str(); + headerFile << headerStringStream.str(); + } else { + cerr << "Scribe output file <" << headerFileName << "> failed to open." << endl; + return 0; + } + } else { + cerr << sourceStringStream.str(); + cerr << headerStringStream.str(); + } + } + + // Write source file + sourceStringStream << "#include \"" << targetName << ".h\"\n" << std::endl; + sourceStringStream << "gpu::ShaderPointer " << targetName << "::_shader;" << std::endl; + sourceStringStream << "const char* " << targetName << "::_source = "; // Write the pages content for (auto page : pages) { - targetStringStream << "R\"SCRIBE(\n" << page->str() << "\n)SCRIBE\"\n"; + sourceStringStream << "R\"SCRIBE(\n" << page->str() << "\n)SCRIBE\"\n"; } - targetStringStream << ";\n" << std::endl << std::endl; - targetStringStream << "// Hack to fix Android link error by forcing a reference to global variable\n"; - targetStringStream << "class " << targetName << "_used {\npublic:\nstatic const char* get() { return " << targetName << "; }\n};\n" << std::endl; - } else { - targetStringStream << destStringStream.str(); - } + sourceStringStream << ";\n" << std::endl << std::endl; - // Destination stream - if (!destFilename.empty()) { - std::fstream destFileStream; - destFileStream.open(destFilename, std::fstream::out); - if (!destFileStream.is_open()) { - cerr << "Scribe output file " << destFilename << "> failed to open." << endl; - return 0; + sourceStringStream << "gpu::ShaderPointer " << targetName << "::getShader() {" << std::endl; + sourceStringStream << "\tif (_shader==nullptr) {" << std::endl; + sourceStringStream << "\t\t_shader = gpu::Shader::create" << shaderCreateString[type] << "(std::string(_source));" << std::endl; + sourceStringStream << "\t}" << std::endl; + sourceStringStream << "\treturn _shader;" << std::endl; + sourceStringStream << "}\n" << std::endl; + + // Destination stream + if (!destFilename.empty()) { + std::fstream sourceFile; + sourceFile.open(sourceFileName, std::fstream::out); + if (!sourceFile.is_open()) { + cerr << "Scribe output file <" << sourceFileName << "> failed to open." << endl; + return 0; + } + sourceFile << sourceStringStream.str(); + } else { + cerr << sourceStringStream.str(); } - - destFileStream << targetStringStream.str(); } else { - cerr << targetStringStream.str(); + // Destination stream + if (!destFilename.empty()) { + std::fstream destFileStream; + destFileStream.open(destFilename, std::fstream::out); + if (!destFileStream.is_open()) { + cerr << "Scribe output file <" << destFilename << "> failed to open." << endl; + return 0; + } + + destFileStream << destStringStream.str(); + } else { + cerr << destStringStream.str(); + } } return 0; From 49549ced171962fc090ee8bfe4d3d8e27be260f0 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Wed, 17 Jan 2018 10:52:58 +0100 Subject: [PATCH 029/569] Fixed compilation with new shader system --- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 8 +- .../RenderableParticleEffectEntityItem.cpp | 4 +- .../src/RenderablePolyLineEntityItem.cpp | 4 +- .../src/RenderablePolyVoxEntityItem.cpp | 4 +- .../src/RenderableShapeEntityItem.cpp | 8 +- .../procedural/src/procedural/Procedural.cpp | 2 +- .../src/procedural/ProceduralSkybox.cpp | 8 +- .../src/AmbientOcclusionEffect.cpp | 8 +- libraries/render-utils/src/AnimDebugDraw.cpp | 4 +- .../render-utils/src/AntialiasingEffect.cpp | 8 +- libraries/render-utils/src/BloomEffect.cpp | 4 +- .../render-utils/src/DebugDeferredBuffer.cpp | 5 +- .../src/DeferredLightingEffect.cpp | 25 ++- libraries/render-utils/src/DrawHaze.cpp | 2 +- libraries/render-utils/src/GeometryCache.cpp | 35 ++-- .../render-utils/src/HighlightEffect.cpp | 19 +- libraries/render-utils/src/LightClusters.cpp | 8 +- .../render-utils/src/RenderForwardTask.cpp | 2 +- .../render-utils/src/RenderPipelines.cpp | 154 ++++++++-------- .../render-utils/src/StencilMaskPass.cpp | 2 +- .../render-utils/src/SubsurfaceScattering.cpp | 8 +- .../render-utils/src/SurfaceGeometryPass.cpp | 6 +- .../render-utils/src/ToneMappingEffect.cpp | 2 +- libraries/render-utils/src/ZoneRenderer.cpp | 6 +- libraries/render-utils/src/text/Font.cpp | 6 +- libraries/render/src/render/BlurTask.cpp | 8 +- .../render/src/render/DrawSceneOctree.cpp | 10 +- libraries/render/src/render/DrawStatus.cpp | 8 +- libraries/render/src/render/DrawTask.cpp | 4 +- tests/shaders/src/main.cpp | 172 +++++++++--------- tools/scribe/src/main.cpp | 10 +- 31 files changed, 273 insertions(+), 281 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index f39203c89d..0a5704f6da 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -33,8 +33,8 @@ #include "../Logging.h" #include "../CompositorHelper.h" -#include "hmd_ui_vert.h" -#include "hmd_ui_frag.h" +#include "render-utils/hmd_ui_vert.h" +#include "render-utils/hmd_ui_frag.h" static const QString MONO_PREVIEW = "Mono Preview"; static const QString DISABLE_PREVIEW = "Disable Preview"; @@ -403,8 +403,8 @@ void HmdDisplayPlugin::HUDRenderer::build() { void HmdDisplayPlugin::HUDRenderer::updatePipeline() { if (!pipeline) { - auto vs = gpu::Shader::createVertex(std::string(hmd_ui_vert)); - auto ps = gpu::Shader::createPixel(std::string(hmd_ui_frag)); + auto vs = hmd_ui_vert::getShader(); + auto ps = hmd_ui_frag::getShader(); auto program = gpu::Shader::createProgram(vs, ps); gpu::gl::GLBackend::makeProgram(*program, gpu::Shader::BindingSet()); uniformsLocation = program->getUniformBuffers().findLocation("hudBuffer"); diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index a3e6cd4341..9981245e6f 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -36,8 +36,8 @@ static ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, co gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); PrepareStencil::testMask(*state); - auto vertShader = gpu::Shader::createVertex(std::string(textured_particle_vert)); - auto fragShader = gpu::Shader::createPixel(std::string(textured_particle_frag)); + auto vertShader = textured_particle_vert::getShader(); + auto fragShader = textured_particle_frag::getShader(); auto program = gpu::Shader::createProgram(vertShader, fragShader); gpu::Shader::BindingSet slotBindings; diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index b362721cde..8a53c9fba5 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -48,8 +48,8 @@ struct PolyLineUniforms { static render::ShapePipelinePointer shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key) { if (!polylinePipeline) { - auto VS = gpu::Shader::createVertex(std::string(paintStroke_vert)); - auto PS = gpu::Shader::createPixel(std::string(paintStroke_frag)); + auto VS = paintStroke_vert::getShader(); + auto PS = paintStroke_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(VS, PS); #ifdef POLYLINE_ENTITY_USE_FADE_EFFECT auto fadeVS = gpu::Shader::createVertex(std::string(paintStroke_fade_vert)); diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index cf12da86e9..379103d4b3 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -1461,8 +1461,8 @@ static gpu::Stream::FormatPointer _vertexFormat; ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, const ShapeKey& key) { if (!_pipelines[0]) { - gpu::ShaderPointer vertexShaders[2] = { gpu::Shader::createVertex(std::string(polyvox_vert)), gpu::Shader::createVertex(std::string(polyvox_fade_vert)) }; - gpu::ShaderPointer pixelShaders[2] = { gpu::Shader::createPixel(std::string(polyvox_frag)), gpu::Shader::createPixel(std::string(polyvox_fade_frag)) }; + gpu::ShaderPointer vertexShaders[2] = { polyvox_vert::getShader(), polyvox_fade_vert::getShader() }; + gpu::ShaderPointer pixelShaders[2] = { polyvox_frag::getShader(), polyvox_fade_frag::getShader() }; gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), MATERIAL_GPU_SLOT)); diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index eddde317fe..0c4f8cbd39 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -16,8 +16,8 @@ #include #include -#include "simple_vert.h" -#include "simple_frag.h" +#include "render-utils/simple_vert.h" +#include "render-utils/simple_frag.h" //#define SHAPE_ENTITY_USE_FADE_EFFECT #ifdef SHAPE_ENTITY_USE_FADE_EFFECT @@ -32,8 +32,8 @@ static const float SPHERE_ENTITY_SCALE = 0.5f; ShapeEntityRenderer::ShapeEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { - _procedural._vertexSource = simple_vert; - _procedural._fragmentSource = simple_frag; + _procedural._vertexSource = simple_vert::getSource(); + _procedural._fragmentSource = simple_frag::getSource(); _procedural._opaqueState->setCullMode(gpu::State::CULL_NONE); _procedural._opaqueState->setDepthTest(true, true, gpu::LESS_EQUAL); PrepareStencil::testMaskDrawShape(*_procedural._opaqueState); diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index ba29768f68..52c56d1e7d 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -241,7 +241,7 @@ void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm std::string fragmentShaderSource = _fragmentSource; size_t replaceIndex = fragmentShaderSource.find(PROCEDURAL_COMMON_BLOCK); if (replaceIndex != std::string::npos) { - fragmentShaderSource.replace(replaceIndex, PROCEDURAL_COMMON_BLOCK.size(), ProceduralCommon_frag.h"; + fragmentShaderSource.replace(replaceIndex, PROCEDURAL_COMMON_BLOCK.size(), ProceduralCommon_frag::getSource()); } replaceIndex = fragmentShaderSource.find(PROCEDURAL_VERSION); diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.cpp b/libraries/procedural/src/procedural/ProceduralSkybox.cpp index 60fde7bd14..8fb74e0b1b 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.cpp +++ b/libraries/procedural/src/procedural/ProceduralSkybox.cpp @@ -15,12 +15,12 @@ #include #include -#include "skybox_vert.h" -#include "skybox_frag.h" +#include "model/skybox_vert.h" +#include "model/skybox_frag.h" ProceduralSkybox::ProceduralSkybox() : model::Skybox() { - _procedural._vertexSource = skybox_vert; - _procedural._fragmentSource = skybox_frag; + _procedural._vertexSource = skybox_vert::getSource(); + _procedural._fragmentSource = skybox_frag::getSource(); // Adjust the pipeline state for background using the stencil test _procedural.setDoesFade(false); // Must match PrepareStencil::STENCIL_BACKGROUND diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.cpp b/libraries/render-utils/src/AmbientOcclusionEffect.cpp index 83753131c8..015f5678c8 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.cpp +++ b/libraries/render-utils/src/AmbientOcclusionEffect.cpp @@ -263,7 +263,7 @@ void AmbientOcclusionEffect::configure(const Config& config) { const gpu::PipelinePointer& AmbientOcclusionEffect::getOcclusionPipeline() { if (!_occlusionPipeline) { auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(ssao_makeOcclusion_frag)); + auto ps = ssao_makeOcclusion_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -288,7 +288,7 @@ const gpu::PipelinePointer& AmbientOcclusionEffect::getOcclusionPipeline() { const gpu::PipelinePointer& AmbientOcclusionEffect::getHBlurPipeline() { if (!_hBlurPipeline) { auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(ssao_makeHorizontalBlur_frag)); + auto ps = ssao_makeHorizontalBlur_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -311,7 +311,7 @@ const gpu::PipelinePointer& AmbientOcclusionEffect::getHBlurPipeline() { const gpu::PipelinePointer& AmbientOcclusionEffect::getVBlurPipeline() { if (!_vBlurPipeline) { auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(ssao_makeVerticalBlur_frag)); + auto ps = ssao_makeVerticalBlur_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -458,7 +458,7 @@ void DebugAmbientOcclusion::configure(const Config& config) { const gpu::PipelinePointer& DebugAmbientOcclusion::getDebugPipeline() { if (!_debugPipeline) { auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(ssao_debugOcclusion_frag)); + auto ps = ssao_debugOcclusion_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index 382b4e2d93..dc9a4d8a1b 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -102,8 +102,8 @@ AnimDebugDraw::AnimDebugDraw() : state->setBlendFunction(false, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - auto vertShader = gpu::Shader::createVertex(std::string(animdebugdraw_vert)); - auto fragShader = gpu::Shader::createPixel(std::string(animdebugdraw_frag)); + auto vertShader = animdebugdraw_vert::getShader(); + auto fragShader = animdebugdraw_frag::getShader(); auto program = gpu::Shader::createProgram(vertShader, fragShader); _pipeline = gpu::Pipeline::create(program, state); diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index 70c2e3b5ce..bdd8f19a5c 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -57,8 +57,8 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline(RenderArgs* ar } if (!_antialiasingPipeline) { - auto vs = gpu::Shader::createVertex(std::string(fxaa_vert)); - auto ps = gpu::Shader::createPixel(std::string(fxaa_frag)); + auto vs = fxaa_vert::getShader(); + auto ps = fxaa_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -82,8 +82,8 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline(RenderArgs* ar const gpu::PipelinePointer& Antialiasing::getBlendPipeline() { if (!_blendPipeline) { - auto vs = gpu::Shader::createVertex(std::string(fxaa_vert)); - auto ps = gpu::Shader::createPixel(std::string(fxaa_blend_frag)); + auto vs = fxaa_vert::getShader(); + auto ps = fxaa_blend_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; diff --git a/libraries/render-utils/src/BloomEffect.cpp b/libraries/render-utils/src/BloomEffect.cpp index 9d9367a6d5..89a83a651a 100644 --- a/libraries/render-utils/src/BloomEffect.cpp +++ b/libraries/render-utils/src/BloomEffect.cpp @@ -61,7 +61,7 @@ void BloomThreshold::run(const render::RenderContextPointer& renderContext, cons if (!_pipeline) { auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); - auto ps = gpu::Shader::createPixel(std::string(BloomThreshold_frag)); + auto ps = BloomThreshold_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -113,7 +113,7 @@ void BloomApply::run(const render::RenderContextPointer& renderContext, const In if (!_pipeline) { auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); - auto ps = gpu::Shader::createPixel(std::string(BloomApply_frag)); + auto ps = BloomApply_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; diff --git a/libraries/render-utils/src/DebugDeferredBuffer.cpp b/libraries/render-utils/src/DebugDeferredBuffer.cpp index fe03ead4e1..24cffe2fb8 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.cpp +++ b/libraries/render-utils/src/DebugDeferredBuffer.cpp @@ -369,8 +369,7 @@ bool DebugDeferredBuffer::pipelineNeedsUpdate(Mode mode, std::string customFile) const gpu::PipelinePointer& DebugDeferredBuffer::getPipeline(Mode mode, std::string customFile) { if (pipelineNeedsUpdate(mode, customFile)) { - static const std::string VERTEX_SHADER { debug_deferred_buffer_vert }; - static const std::string FRAGMENT_SHADER { debug_deferred_buffer_frag }; + static const std::string FRAGMENT_SHADER { debug_deferred_buffer_frag::getSource() }; static const std::string SOURCE_PLACEHOLDER { "//SOURCE_PLACEHOLDER" }; static const auto SOURCE_PLACEHOLDER_INDEX = FRAGMENT_SHADER.find(SOURCE_PLACEHOLDER); Q_ASSERT_X(SOURCE_PLACEHOLDER_INDEX != std::string::npos, Q_FUNC_INFO, @@ -380,7 +379,7 @@ const gpu::PipelinePointer& DebugDeferredBuffer::getPipeline(Mode mode, std::str bakedFragmentShader.replace(SOURCE_PLACEHOLDER_INDEX, SOURCE_PLACEHOLDER.size(), getShaderSourceCode(mode, customFile)); - static const auto vs = gpu::Shader::createVertex(VERTEX_SHADER); + const auto vs = debug_deferred_buffer_vert::getShader(); const auto ps = gpu::Shader::createPixel(bakedFragmentShader); const auto program = gpu::Shader::createProgram(vs, ps); diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 81a33f17e3..8c3b76f557 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -79,7 +79,7 @@ enum DeferredShader_BufferSlot { LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, }; -static void loadLightProgram(const char* vertSource, const char* fragSource, bool lightVolume, gpu::PipelinePointer& program, LightLocationsPtr& locations); +static void loadLightProgram(const gpu::ShaderPointer& vertShader, const gpu::ShaderPointer& fragShader, bool lightVolume, gpu::PipelinePointer& program, LightLocationsPtr& locations); void DeferredLightingEffect::init() { _directionalAmbientSphereLightLocations = std::make_shared(); @@ -91,14 +91,14 @@ void DeferredLightingEffect::init() { _localLightLocations = std::make_shared(); _localLightOutlineLocations = std::make_shared(); - loadLightProgram(deferred_light_vert, directional_ambient_light_frag, false, _directionalAmbientSphereLight, _directionalAmbientSphereLightLocations); - loadLightProgram(deferred_light_vert, directional_skybox_light_frag, false, _directionalSkyboxLight, _directionalSkyboxLightLocations); + loadLightProgram(deferred_light_vert::getShader(), directional_ambient_light_frag::getShader(), false, _directionalAmbientSphereLight, _directionalAmbientSphereLightLocations); + loadLightProgram(deferred_light_vert::getShader(), directional_skybox_light_frag::getShader(), false, _directionalSkyboxLight, _directionalSkyboxLightLocations); - loadLightProgram(deferred_light_vert, directional_ambient_light_shadow_frag, false, _directionalAmbientSphereLightShadow, _directionalAmbientSphereLightShadowLocations); - loadLightProgram(deferred_light_vert, directional_skybox_light_shadow_frag, false, _directionalSkyboxLightShadow, _directionalSkyboxLightShadowLocations); + loadLightProgram(deferred_light_vert::getShader(), directional_ambient_light_shadow_frag::getShader(), false, _directionalAmbientSphereLightShadow, _directionalAmbientSphereLightShadowLocations); + loadLightProgram(deferred_light_vert::getShader(), directional_skybox_light_shadow_frag::getShader(), false, _directionalSkyboxLightShadow, _directionalSkyboxLightShadowLocations); - loadLightProgram(deferred_light_vert, local_lights_shading_frag, true, _localLight, _localLightLocations); - loadLightProgram(deferred_light_vert, local_lights_drawOutline_frag, true, _localLightOutline, _localLightOutlineLocations); + loadLightProgram(deferred_light_vert::getShader(), local_lights_shading_frag::getShader(), true, _localLight, _localLightLocations); + loadLightProgram(deferred_light_vert::getShader(), local_lights_drawOutline_frag::getShader(), true, _localLightOutline, _localLightOutlineLocations); } void DeferredLightingEffect::setupKeyLightBatch(const RenderArgs* args, gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit) { @@ -144,11 +144,8 @@ void DeferredLightingEffect::unsetKeyLightBatch(gpu::Batch& batch, int lightBuff } } -static gpu::ShaderPointer makeLightProgram(const char* vertSource, const char* fragSource, LightLocationsPtr& locations) { - auto VS = gpu::Shader::createVertex(std::string(vertSource)); - auto PS = gpu::Shader::createPixel(std::string(fragSource)); - - gpu::ShaderPointer program = gpu::Shader::createProgram(VS, PS); +static gpu::ShaderPointer makeLightProgram(const gpu::ShaderPointer& vertShader, const gpu::ShaderPointer& fragShader, LightLocationsPtr& locations) { + gpu::ShaderPointer program = gpu::Shader::createProgram(vertShader, fragShader); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("colorMap"), DEFERRED_BUFFER_COLOR_UNIT)); @@ -196,9 +193,9 @@ static gpu::ShaderPointer makeLightProgram(const char* vertSource, const char* f return program; } -static void loadLightProgram(const char* vertSource, const char* fragSource, bool lightVolume, gpu::PipelinePointer& pipeline, LightLocationsPtr& locations) { +static void loadLightProgram(const gpu::ShaderPointer& vertShader, const gpu::ShaderPointer& fragShader, bool lightVolume, gpu::PipelinePointer& pipeline, LightLocationsPtr& locations) { - gpu::ShaderPointer program = makeLightProgram(vertSource, fragSource, locations); + gpu::ShaderPointer program = makeLightProgram(vertShader, fragShader, locations); auto state = std::make_shared(); state->setColorWriteMask(true, true, true, false); diff --git a/libraries/render-utils/src/DrawHaze.cpp b/libraries/render-utils/src/DrawHaze.cpp index da07f5bd9b..986212ca6e 100644 --- a/libraries/render-utils/src/DrawHaze.cpp +++ b/libraries/render-utils/src/DrawHaze.cpp @@ -133,7 +133,7 @@ void DrawHaze::run(const render::RenderContextPointer& renderContext, const Inpu RenderArgs* args = renderContext->args; if (!_hazePipeline) { - gpu::ShaderPointer ps = gpu::Shader::createPixel(std::string(Haze_frag)); + gpu::ShaderPointer ps = Haze_frag::getShader(); gpu::ShaderPointer vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 2616d08600..e962c2901f 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -1946,8 +1946,8 @@ void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const static std::once_flag once; std::call_once(once, [&] { auto state = std::make_shared(); - auto VS = gpu::Shader::createVertex(std::string(glowLine_vert)); - auto PS = gpu::Shader::createPixel(std::string(glowLine_frag)); + auto VS = glowLine_vert::getShader(); + auto PS = glowLine_frag::getShader(); auto program = gpu::Shader::createProgram(VS, PS); state->setCullMode(gpu::State::CULL_NONE); state->setDepthTest(true, false, gpu::LESS_EQUAL); @@ -2002,8 +2002,8 @@ void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { if (!_standardDrawPipeline) { - auto vs = gpu::Shader::createVertex(std::string(standardTransformPNTC_vert)); - auto ps = gpu::Shader::createPixel(std::string(standardDrawTexture_frag)); + auto vs = standardTransformPNTC_vert::getShader(); + auto ps = standardDrawTexture_frag::getShader(); auto program = gpu::Shader::createProgram(vs, ps); gpu::Shader::makeProgram((*program)); @@ -2033,8 +2033,8 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { void GeometryCache::useGridPipeline(gpu::Batch& batch, GridBuffer gridBuffer, bool isLayered) { if (!_gridPipeline) { - auto vs = gpu::Shader::createVertex(std::string(standardTransformPNTC_vert)); - auto ps = gpu::Shader::createPixel(std::string(grid_frag)); + auto vs = standardTransformPNTC_vert::getShader(); + auto ps = grid_frag::getShader(); auto program = gpu::Shader::createProgram(vs, ps); gpu::Shader::makeProgram((*program)); _gridSlot = program->getUniformBuffers().findLocation("gridBuffer"); @@ -2117,12 +2117,9 @@ inline bool operator==(const SimpleProgramKey& a, const SimpleProgramKey& b) { return a.getRaw() == b.getRaw(); } -static void buildWebShader(const std::string& vertShaderText, const std::string& fragShaderText, bool blendEnable, +static void buildWebShader(const gpu::ShaderPointer& vertShader, const gpu::ShaderPointer& fragShader, bool blendEnable, gpu::ShaderPointer& shaderPointerOut, gpu::PipelinePointer& pipelinePointerOut) { - auto VS = gpu::Shader::createVertex(vertShaderText); - auto PS = gpu::Shader::createPixel(fragShaderText); - - shaderPointerOut = gpu::Shader::createProgram(VS, PS); + shaderPointerOut = gpu::Shader::createProgram(vertShader, fragShader); gpu::Shader::BindingSet slotBindings; gpu::Shader::makeProgram(*shaderPointerOut, slotBindings); @@ -2145,8 +2142,8 @@ void GeometryCache::bindWebBrowserProgram(gpu::Batch& batch, bool transparent) { gpu::PipelinePointer GeometryCache::getWebBrowserProgram(bool transparent) { static std::once_flag once; std::call_once(once, [&]() { - buildWebShader(simple_vert, simple_opaque_web_browser_frag, false, _simpleOpaqueWebBrowserShader, _simpleOpaqueWebBrowserPipelineNoAA); - buildWebShader(simple_vert, simple_transparent_web_browser_frag, true, _simpleTransparentWebBrowserShader, _simpleTransparentWebBrowserPipelineNoAA); + buildWebShader(simple_vert::getShader(), simple_opaque_web_browser_frag::getShader(), false, _simpleOpaqueWebBrowserShader, _simpleOpaqueWebBrowserPipelineNoAA); + buildWebShader(simple_vert::getShader(), simple_transparent_web_browser_frag::getShader(), true, _simpleTransparentWebBrowserShader, _simpleTransparentWebBrowserPipelineNoAA); }); return transparent ? _simpleTransparentWebBrowserPipelineNoAA : _simpleOpaqueWebBrowserPipelineNoAA; @@ -2175,9 +2172,9 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transp if (!fading) { static std::once_flag once; std::call_once(once, [&]() { - auto VS = gpu::Shader::createVertex(std::string(simple_vert)); - auto PS = gpu::Shader::createPixel(std::string(simple_textured_frag)); - auto PSUnlit = gpu::Shader::createPixel(std::string(simple_textured_unlit_frag)); + auto VS = simple_vert::getShader(); + auto PS = simple_textured_frag::getShader(); + auto PSUnlit = simple_textured_unlit_frag::getShader(); _simpleShader = gpu::Shader::createProgram(VS, PS); _unlitShader = gpu::Shader::createProgram(VS, PSUnlit); @@ -2190,9 +2187,9 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transp } else { static std::once_flag once; std::call_once(once, [&]() { - auto VS = gpu::Shader::createVertex(std::string(simple_fade_vert)); - auto PS = gpu::Shader::createPixel(std::string(simple_textured_fade_frag)); - auto PSUnlit = gpu::Shader::createPixel(std::string(simple_textured_unlit_fade_frag)); + auto VS = simple_fade_vert::getShader(); + auto PS = simple_textured_fade_frag::getShader(); + auto PSUnlit = simple_textured_unlit_fade_frag::getShader(); _simpleFadeShader = gpu::Shader::createProgram(VS, PS); _unlitFadeShader = gpu::Shader::createProgram(VS, PSUnlit); diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp index fee1f4a568..9501a74d52 100644 --- a/libraries/render-utils/src/HighlightEffect.cpp +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -130,8 +130,8 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c fillState->setColorWriteMask(false, false, false, false); fillState->setCullMode(gpu::State::CULL_FRONT); - auto vs = gpu::Shader::createVertex(std::string(Highlight_aabox_vert)); - auto ps = gpu::Shader::createPixel(std::string(nop_frag)); + auto vs = Highlight_aabox_vert::getShader(); + auto ps = nop_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -313,7 +313,7 @@ const gpu::PipelinePointer& DrawHighlight::getPipeline(const render::HighlightSt state->setStencilTest(true, 0, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::EQUAL)); auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(Highlight_frag)); + auto ps = Highlight_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -325,7 +325,7 @@ const gpu::PipelinePointer& DrawHighlight::getPipeline(const render::HighlightSt _pipeline = gpu::Pipeline::create(program, state); - ps = gpu::Shader::createPixel(std::string(Highlight_filled_frag)); + ps = Highlight_filled_frag::getShader(); program = gpu::Shader::createProgram(vs, ps); gpu::Shader::makeProgram(*program, slotBindings); _pipelineFilled = gpu::Pipeline::create(program, state); @@ -385,8 +385,7 @@ void DebugHighlight::run(const render::RenderContextPointer& renderContext, cons } void DebugHighlight::initializePipelines() { - static const std::string VERTEX_SHADER{ debug_deferred_buffer_vert }; - static const std::string FRAGMENT_SHADER{ debug_deferred_buffer_frag }; + static const std::string FRAGMENT_SHADER{ debug_deferred_buffer_frag::getSource() }; static const std::string SOURCE_PLACEHOLDER{ "//SOURCE_PLACEHOLDER" }; static const auto SOURCE_PLACEHOLDER_INDEX = FRAGMENT_SHADER.find(SOURCE_PLACEHOLDER); Q_ASSERT_X(SOURCE_PLACEHOLDER_INDEX != std::string::npos, Q_FUNC_INFO, @@ -396,7 +395,7 @@ void DebugHighlight::initializePipelines() { state->setDepthTest(gpu::State::DepthTest(false, false)); state->setStencilTest(true, 0, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::EQUAL)); - const auto vs = gpu::Shader::createVertex(VERTEX_SHADER); + const auto vs = debug_deferred_buffer_vert::getShader(); // Depth shader { @@ -553,14 +552,14 @@ const render::Varying DrawHighlightTask::addSelectItemJobs(JobModel& task, const #include "model_shadow_frag.h" void DrawHighlightTask::initMaskPipelines(render::ShapePlumber& shapePlumber, gpu::StatePointer state) { - auto modelVertex = gpu::Shader::createVertex(std::string(model_shadow_vert)); - auto modelPixel = gpu::Shader::createPixel(std::string(model_shadow_frag)); + auto modelVertex = model_shadow_vert::getShader(); + auto modelPixel = model_shadow_frag::getShader(); gpu::ShaderPointer modelProgram = gpu::Shader::createProgram(modelVertex, modelPixel); shapePlumber.addPipeline( ShapeKey::Filter::Builder().withoutSkinned(), modelProgram, state); - auto skinVertex = gpu::Shader::createVertex(std::string(skin_model_shadow_vert)); + auto skinVertex = skin_model_shadow_vert::getShader(); gpu::ShaderPointer skinProgram = gpu::Shader::createProgram(skinVertex, modelPixel); shapePlumber.addPipeline( ShapeKey::Filter::Builder().withSkinned(), diff --git a/libraries/render-utils/src/LightClusters.cpp b/libraries/render-utils/src/LightClusters.cpp index d6ac7fd2e2..ea02edb601 100644 --- a/libraries/render-utils/src/LightClusters.cpp +++ b/libraries/render-utils/src/LightClusters.cpp @@ -605,8 +605,8 @@ void DebugLightClusters::configure(const Config& config) { const gpu::PipelinePointer DebugLightClusters::getDrawClusterGridPipeline() { if (!_drawClusterGrid) { - auto vs = gpu::Shader::createVertex(std::string(lightClusters_drawGrid_vert)); - auto ps = gpu::Shader::createPixel(std::string(lightClusters_drawGrid_frag)); + auto vs = lightClusters_drawGrid_vert::getShader(); + auto ps = lightClusters_drawGrid_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -635,7 +635,7 @@ const gpu::PipelinePointer DebugLightClusters::getDrawClusterFromDepthPipeline() if (!_drawClusterFromDepth) { // auto vs = gpu::Shader::createVertex(std::string(lightClusters_drawGrid_vert)); auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(lightClusters_drawClusterFromDepth_frag)); + auto ps = lightClusters_drawClusterFromDepth_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -665,7 +665,7 @@ const gpu::PipelinePointer DebugLightClusters::getDrawClusterContentPipeline() { if (!_drawClusterContent) { // auto vs = gpu::Shader::createVertex(std::string(lightClusters_drawClusterContent_vert)); auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(lightClusters_drawClusterContent_frag)); + auto ps = lightClusters_drawClusterContent_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index 16e739f432..1eeac7f449 100755 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -125,7 +125,7 @@ void Draw::run(const RenderContextPointer& renderContext, const gpu::PipelinePointer Stencil::getPipeline() { if (!_stencilPipeline) { auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(nop_frag); + auto ps = nop_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::makeProgram(*program); diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index 7f644add72..76e5cbc72a 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -117,16 +117,16 @@ void batchSetter(const ShapePipeline& pipeline, gpu::Batch& batch, RenderArgs* a void lightBatchSetter(const ShapePipeline& pipeline, gpu::Batch& batch, RenderArgs* args); void initOverlay3DPipelines(ShapePlumber& plumber, bool depthTest) { - auto vertex = gpu::Shader::createVertex(std::string(overlay3D_vert)); - auto vertexModel = gpu::Shader::createVertex(std::string(model_vert)); - auto pixel = gpu::Shader::createPixel(std::string(overlay3D_frag)); - auto pixelTranslucent = gpu::Shader::createPixel(std::string(overlay3D_translucent_frag)); - auto pixelUnlit = gpu::Shader::createPixel(std::string(overlay3D_unlit_frag)); - auto pixelTranslucentUnlit = gpu::Shader::createPixel(std::string(overlay3D_translucent_unlit_frag)); - auto pixelModel = gpu::Shader::createPixel(std::string(overlay3D_model_frag)); - auto pixelModelTranslucent = gpu::Shader::createPixel(std::string(overlay3D_model_translucent_frag)); - auto pixelModelUnlit = gpu::Shader::createPixel(std::string(overlay3D_model_unlit_frag)); - auto pixelModelTranslucentUnlit = gpu::Shader::createPixel(std::string(overlay3D_model_translucent_unlit_frag)); + auto vertex = overlay3D_vert::getShader(); + auto vertexModel = model_vert::getShader(); + auto pixel = overlay3D_frag::getShader(); + auto pixelTranslucent = overlay3D_translucent_frag::getShader(); + auto pixelUnlit = overlay3D_unlit_frag::getShader(); + auto pixelTranslucentUnlit = overlay3D_translucent_unlit_frag::getShader(); + auto pixelModel = overlay3D_model_frag::getShader(); + auto pixelModelTranslucent = overlay3D_model_translucent_frag::getShader(); + auto pixelModelUnlit = overlay3D_model_unlit_frag::getShader(); + auto pixelModelTranslucentUnlit = overlay3D_model_translucent_unlit_frag::getShader(); auto opaqueProgram = gpu::Shader::createProgram(vertex, pixel); auto translucentProgram = gpu::Shader::createProgram(vertex, pixelTranslucent); @@ -183,60 +183,60 @@ void initOverlay3DPipelines(ShapePlumber& plumber, bool depthTest) { void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter) { // Vertex shaders - auto simpleVertex = gpu::Shader::createVertex(std::string(simple_vert)); - auto modelVertex = gpu::Shader::createVertex(std::string(model_vert)); - auto modelNormalMapVertex = gpu::Shader::createVertex(std::string(model_normal_map_vert)); - auto modelLightmapVertex = gpu::Shader::createVertex(std::string(model_lightmap_vert)); - auto modelLightmapNormalMapVertex = gpu::Shader::createVertex(std::string(model_lightmap_normal_map_vert)); - auto modelShadowVertex = gpu::Shader::createVertex(std::string(model_shadow_vert)); - auto skinModelVertex = gpu::Shader::createVertex(std::string(skin_model_vert)); - auto skinModelNormalMapVertex = gpu::Shader::createVertex(std::string(skin_model_normal_map_vert)); - auto skinModelShadowVertex = gpu::Shader::createVertex(std::string(skin_model_shadow_vert)); - auto modelLightmapFadeVertex = gpu::Shader::createVertex(std::string(model_lightmap_fade_vert)); - auto modelLightmapNormalMapFadeVertex = gpu::Shader::createVertex(std::string(model_lightmap_normal_map_fade_vert)); - auto skinModelFadeVertex = gpu::Shader::createVertex(std::string(skin_model_fade_vert)); - auto skinModelNormalMapFadeVertex = gpu::Shader::createVertex(std::string(skin_model_normal_map_fade_vert)); + auto simpleVertex = simple_vert::getShader(); + auto modelVertex = model_vert::getShader(); + auto modelNormalMapVertex = model_normal_map_vert::getShader(); + auto modelLightmapVertex = model_lightmap_vert::getShader(); + auto modelLightmapNormalMapVertex = model_lightmap_normal_map_vert::getShader(); + auto modelShadowVertex = model_shadow_vert::getShader(); + auto skinModelVertex = skin_model_vert::getShader(); + auto skinModelNormalMapVertex = skin_model_normal_map_vert::getShader(); + auto skinModelShadowVertex = skin_model_shadow_vert::getShader(); + auto modelLightmapFadeVertex = model_lightmap_fade_vert::getShader(); + auto modelLightmapNormalMapFadeVertex = model_lightmap_normal_map_fade_vert::getShader(); + auto skinModelFadeVertex = skin_model_fade_vert::getShader(); + auto skinModelNormalMapFadeVertex = skin_model_normal_map_fade_vert::getShader(); - auto modelFadeVertex = gpu::Shader::createVertex(std::string(model_fade_vert)); - auto modelNormalMapFadeVertex = gpu::Shader::createVertex(std::string(model_normal_map_fade_vert)); - auto simpleFadeVertex = gpu::Shader::createVertex(std::string(simple_fade_vert)); - auto modelShadowFadeVertex = gpu::Shader::createVertex(std::string(model_shadow_fade_vert)); - auto skinModelShadowFadeVertex = gpu::Shader::createVertex(std::string(skin_model_shadow_fade_vert)); + auto modelFadeVertex = model_fade_vert::getShader(); + auto modelNormalMapFadeVertex = model_normal_map_fade_vert::getShader(); + auto simpleFadeVertex = simple_fade_vert::getShader(); + auto modelShadowFadeVertex = model_shadow_fade_vert::getShader(); + auto skinModelShadowFadeVertex = skin_model_shadow_fade_vert::getShader(); // Pixel shaders - auto simplePixel = gpu::Shader::createPixel(std::string(simple_textured_frag)); - auto simpleUnlitPixel = gpu::Shader::createPixel(std::string(simple_textured_unlit_frag)); - auto simpleTranslucentPixel = gpu::Shader::createPixel(std::string(simple_transparent_textured_frag)); - auto simpleTranslucentUnlitPixel = gpu::Shader::createPixel(std::string(simple_transparent_textured_unlit_frag)); - auto modelPixel = gpu::Shader::createPixel(std::string(model_frag)); - auto modelUnlitPixel = gpu::Shader::createPixel(std::string(model_unlit_frag)); - auto modelNormalMapPixel = gpu::Shader::createPixel(std::string(model_normal_map_frag)); - auto modelSpecularMapPixel = gpu::Shader::createPixel(std::string(model_specular_map_frag)); - auto modelNormalSpecularMapPixel = gpu::Shader::createPixel(std::string(model_normal_specular_map_frag)); - auto modelTranslucentPixel = gpu::Shader::createPixel(std::string(model_translucent_frag)); - auto modelTranslucentUnlitPixel = gpu::Shader::createPixel(std::string(model_translucent_unlit_frag)); - auto modelShadowPixel = gpu::Shader::createPixel(std::string(model_shadow_frag)); - auto modelLightmapPixel = gpu::Shader::createPixel(std::string(model_lightmap_frag)); - auto modelLightmapNormalMapPixel = gpu::Shader::createPixel(std::string(model_lightmap_normal_map_frag)); - auto modelLightmapSpecularMapPixel = gpu::Shader::createPixel(std::string(model_lightmap_specular_map_frag)); - auto modelLightmapNormalSpecularMapPixel = gpu::Shader::createPixel(std::string(model_lightmap_normal_specular_map_frag)); - auto modelLightmapFadePixel = gpu::Shader::createPixel(std::string(model_lightmap_fade_frag)); - auto modelLightmapNormalMapFadePixel = gpu::Shader::createPixel(std::string(model_lightmap_normal_map_fade_frag)); - auto modelLightmapSpecularMapFadePixel = gpu::Shader::createPixel(std::string(model_lightmap_specular_map_fade_frag)); - auto modelLightmapNormalSpecularMapFadePixel = gpu::Shader::createPixel(std::string(model_lightmap_normal_specular_map_fade_frag)); + auto simplePixel = simple_textured_frag::getShader(); + auto simpleUnlitPixel = simple_textured_unlit_frag::getShader(); + auto simpleTranslucentPixel = simple_transparent_textured_frag::getShader(); + auto simpleTranslucentUnlitPixel = simple_transparent_textured_unlit_frag::getShader(); + auto modelPixel = model_frag::getShader(); + auto modelUnlitPixel = model_unlit_frag::getShader(); + auto modelNormalMapPixel = model_normal_map_frag::getShader(); + auto modelSpecularMapPixel = model_specular_map_frag::getShader(); + auto modelNormalSpecularMapPixel = model_normal_specular_map_frag::getShader(); + auto modelTranslucentPixel = model_translucent_frag::getShader(); + auto modelTranslucentUnlitPixel = model_translucent_unlit_frag::getShader(); + auto modelShadowPixel = model_shadow_frag::getShader(); + auto modelLightmapPixel = model_lightmap_frag::getShader(); + auto modelLightmapNormalMapPixel = model_lightmap_normal_map_frag::getShader(); + auto modelLightmapSpecularMapPixel = model_lightmap_specular_map_frag::getShader(); + auto modelLightmapNormalSpecularMapPixel = model_lightmap_normal_specular_map_frag::getShader(); + auto modelLightmapFadePixel = model_lightmap_fade_frag::getShader(); + auto modelLightmapNormalMapFadePixel = model_lightmap_normal_map_fade_frag::getShader(); + auto modelLightmapSpecularMapFadePixel = model_lightmap_specular_map_fade_frag::getShader(); + auto modelLightmapNormalSpecularMapFadePixel = model_lightmap_normal_specular_map_fade_frag::getShader(); - auto modelFadePixel = gpu::Shader::createPixel(std::string(model_fade_frag)); - auto modelUnlitFadePixel = gpu::Shader::createPixel(std::string(model_unlit_fade_frag)); - auto modelNormalMapFadePixel = gpu::Shader::createPixel(std::string(model_normal_map_fade_frag)); - auto modelSpecularMapFadePixel = gpu::Shader::createPixel(std::string(model_specular_map_fade_frag)); - auto modelNormalSpecularMapFadePixel = gpu::Shader::createPixel(std::string(model_normal_specular_map_fade_frag)); - auto modelShadowFadePixel = gpu::Shader::createPixel(std::string(model_shadow_fade_frag)); - auto modelTranslucentFadePixel = gpu::Shader::createPixel(std::string(model_translucent_fade_frag)); - auto modelTranslucentUnlitFadePixel = gpu::Shader::createPixel(std::string(model_translucent_unlit_fade_frag)); - auto simpleFadePixel = gpu::Shader::createPixel(std::string(simple_textured_fade_frag)); - auto simpleUnlitFadePixel = gpu::Shader::createPixel(std::string(simple_textured_unlit_fade_frag)); - auto simpleTranslucentFadePixel = gpu::Shader::createPixel(std::string(simple_transparent_textured_fade_frag)); - auto simpleTranslucentUnlitFadePixel = gpu::Shader::createPixel(std::string(simple_transparent_textured_unlit_fade_frag)); + auto modelFadePixel = model_fade_frag::getShader(); + auto modelUnlitFadePixel = model_unlit_fade_frag::getShader(); + auto modelNormalMapFadePixel = model_normal_map_fade_frag::getShader(); + auto modelSpecularMapFadePixel = model_specular_map_fade_frag::getShader(); + auto modelNormalSpecularMapFadePixel = model_normal_specular_map_fade_frag::getShader(); + auto modelShadowFadePixel = model_shadow_fade_frag::getShader(); + auto modelTranslucentFadePixel = model_translucent_fade_frag::getShader(); + auto modelTranslucentUnlitFadePixel = model_translucent_unlit_fade_frag::getShader(); + auto simpleFadePixel = simple_textured_fade_frag::getShader(); + auto simpleUnlitFadePixel = simple_textured_unlit_fade_frag::getShader(); + auto simpleTranslucentFadePixel = simple_transparent_textured_fade_frag::getShader(); + auto simpleTranslucentUnlitFadePixel = simple_transparent_textured_unlit_fade_frag::getShader(); using Key = render::ShapeKey; auto addPipeline = std::bind(&addPlumberPipeline, std::ref(plumber), _1, _2, _3, _4, _5); @@ -438,17 +438,17 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip void initForwardPipelines(render::ShapePlumber& plumber) { // Vertex shaders - auto modelVertex = gpu::Shader::createVertex(std::string(model_vert)); - auto modelNormalMapVertex = gpu::Shader::createVertex(std::string(model_normal_map_vert)); - auto skinModelVertex = gpu::Shader::createVertex(std::string(skin_model_vert)); - auto skinModelNormalMapVertex = gpu::Shader::createVertex(std::string(skin_model_normal_map_vert)); + auto modelVertex = model_vert::getShader(); + auto modelNormalMapVertex = model_normal_map_vert::getShader(); + auto skinModelVertex = skin_model_vert::getShader(); + auto skinModelNormalMapVertex = skin_model_normal_map_vert::getShader(); // Pixel shaders - auto modelPixel = gpu::Shader::createPixel(std::string(forward_model_frag)); - auto modelUnlitPixel = gpu::Shader::createPixel(std::string(forward_model_unlit_frag)); - auto modelNormalMapPixel = gpu::Shader::createPixel(std::string(forward_model_normal_map_frag)); - auto modelSpecularMapPixel = gpu::Shader::createPixel(std::string(forward_model_specular_map_frag)); - auto modelNormalSpecularMapPixel = gpu::Shader::createPixel(std::string(forward_model_normal_specular_map_frag)); + auto modelPixel = forward_model_frag::getShader(); + auto modelUnlitPixel = forward_model_unlit_frag::getShader(); + auto modelNormalMapPixel = forward_model_normal_map_frag::getShader(); + auto modelSpecularMapPixel = forward_model_specular_map_frag::getShader(); + auto modelNormalSpecularMapPixel = forward_model_normal_specular_map_frag::getShader(); using Key = render::ShapeKey; auto addPipeline = std::bind(&addPlumberPipeline, std::ref(plumber), _1, _2, _3, nullptr, nullptr); @@ -574,29 +574,29 @@ void lightBatchSetter(const ShapePipeline& pipeline, gpu::Batch& batch, RenderAr } void initZPassPipelines(ShapePlumber& shapePlumber, gpu::StatePointer state) { - auto modelVertex = gpu::Shader::createVertex(std::string(model_shadow_vert)); - auto modelPixel = gpu::Shader::createPixel(std::string(model_shadow_frag)); + auto modelVertex = model_shadow_vert::getShader(); + auto modelPixel = model_shadow_frag::getShader(); gpu::ShaderPointer modelProgram = gpu::Shader::createProgram(modelVertex, modelPixel); shapePlumber.addPipeline( ShapeKey::Filter::Builder().withoutSkinned().withoutFade(), modelProgram, state); - auto skinVertex = gpu::Shader::createVertex(std::string(skin_model_shadow_vert)); - auto skinPixel = gpu::Shader::createPixel(std::string(skin_model_shadow_frag)); + auto skinVertex = skin_model_shadow_vert::getShader(); + auto skinPixel = skin_model_shadow_frag::getShader(); gpu::ShaderPointer skinProgram = gpu::Shader::createProgram(skinVertex, skinPixel); shapePlumber.addPipeline( ShapeKey::Filter::Builder().withSkinned().withoutFade(), skinProgram, state); - auto modelFadeVertex = gpu::Shader::createVertex(std::string(model_shadow_fade_vert)); - auto modelFadePixel = gpu::Shader::createPixel(std::string(model_shadow_fade_frag)); + auto modelFadeVertex = model_shadow_fade_vert::getShader(); + auto modelFadePixel = model_shadow_fade_frag::getShader(); gpu::ShaderPointer modelFadeProgram = gpu::Shader::createProgram(modelFadeVertex, modelFadePixel); shapePlumber.addPipeline( ShapeKey::Filter::Builder().withoutSkinned().withFade(), modelFadeProgram, state); - auto skinFadeVertex = gpu::Shader::createVertex(std::string(skin_model_shadow_fade_vert)); - auto skinFadePixel = gpu::Shader::createPixel(std::string(skin_model_shadow_fade_frag)); + auto skinFadeVertex = skin_model_shadow_fade_vert::getShader(); + auto skinFadePixel = skin_model_shadow_fade_frag::getShader(); gpu::ShaderPointer skinFadeProgram = gpu::Shader::createProgram(skinFadeVertex, skinFadePixel); shapePlumber.addPipeline( ShapeKey::Filter::Builder().withSkinned().withFade(), diff --git a/libraries/render-utils/src/StencilMaskPass.cpp b/libraries/render-utils/src/StencilMaskPass.cpp index 80c97cf29f..48beda78bc 100644 --- a/libraries/render-utils/src/StencilMaskPass.cpp +++ b/libraries/render-utils/src/StencilMaskPass.cpp @@ -60,7 +60,7 @@ gpu::PipelinePointer PrepareStencil::getMeshStencilPipeline() { gpu::PipelinePointer PrepareStencil::getPaintStencilPipeline() { if (!_paintStencilPipeline) { auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(stencil_drawMask_frag); + auto ps = stencil_drawMask_frag::getShader(); auto program = gpu::Shader::createProgram(vs, ps); gpu::Shader::makeProgram((*program)); diff --git a/libraries/render-utils/src/SubsurfaceScattering.cpp b/libraries/render-utils/src/SubsurfaceScattering.cpp index 1786898e57..d6ec73da85 100644 --- a/libraries/render-utils/src/SubsurfaceScattering.cpp +++ b/libraries/render-utils/src/SubsurfaceScattering.cpp @@ -308,7 +308,7 @@ void diffuseProfileGPU(gpu::TexturePointer& profileMap, RenderArgs* args) { gpu::PipelinePointer makePipeline; { auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(subsurfaceScattering_makeProfile_frag)); + auto ps = subsurfaceScattering_makeProfile_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -344,7 +344,7 @@ void diffuseScatterGPU(const gpu::TexturePointer& profileMap, gpu::TexturePointe gpu::PipelinePointer makePipeline; { auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(subsurfaceScattering_makeLUT_frag)); + auto ps = subsurfaceScattering_makeLUT_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -382,7 +382,7 @@ void computeSpecularBeckmannGPU(gpu::TexturePointer& beckmannMap, RenderArgs* ar gpu::PipelinePointer makePipeline; { auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(subsurfaceScattering_makeSpecularBeckmann_frag)); + auto ps = subsurfaceScattering_makeSpecularBeckmann_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -457,7 +457,7 @@ void DebugSubsurfaceScattering::configure(const Config& config) { gpu::PipelinePointer DebugSubsurfaceScattering::getScatteringPipeline() { if (!_scatteringPipeline) { auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(subsurfaceScattering_drawScattering_frag)); + auto ps = subsurfaceScattering_drawScattering_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; diff --git a/libraries/render-utils/src/SurfaceGeometryPass.cpp b/libraries/render-utils/src/SurfaceGeometryPass.cpp index af6ff09082..afed9ee8fd 100644 --- a/libraries/render-utils/src/SurfaceGeometryPass.cpp +++ b/libraries/render-utils/src/SurfaceGeometryPass.cpp @@ -212,7 +212,7 @@ void LinearDepthPass::run(const render::RenderContextPointer& renderContext, con const gpu::PipelinePointer& LinearDepthPass::getLinearDepthPipeline() { if (!_linearDepthPipeline) { auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(surfaceGeometry_makeLinearDepth_frag); + auto ps = surfaceGeometry_makeLinearDepth_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -239,7 +239,7 @@ const gpu::PipelinePointer& LinearDepthPass::getLinearDepthPipeline() { const gpu::PipelinePointer& LinearDepthPass::getDownsamplePipeline() { if (!_downsamplePipeline) { auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(surfaceGeometry_downsampleDepthNormal_frag); + auto ps = surfaceGeometry_downsampleDepthNormal_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -540,7 +540,7 @@ void SurfaceGeometryPass::run(const render::RenderContextPointer& renderContext, const gpu::PipelinePointer& SurfaceGeometryPass::getCurvaturePipeline() { if (!_curvaturePipeline) { auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(surfaceGeometry_makeCurvature_frag); + auto ps = surfaceGeometry_makeCurvature_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; diff --git a/libraries/render-utils/src/ToneMappingEffect.cpp b/libraries/render-utils/src/ToneMappingEffect.cpp index 6cb9541dae..e1abefd681 100644 --- a/libraries/render-utils/src/ToneMappingEffect.cpp +++ b/libraries/render-utils/src/ToneMappingEffect.cpp @@ -28,7 +28,7 @@ ToneMappingEffect::ToneMappingEffect() { } void ToneMappingEffect::init() { - auto blitPS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(toneMapping_frag)); + auto blitPS = toneMapping_frag::getShader(); auto blitVS = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); auto blitProgram = gpu::ShaderPointer(gpu::Shader::createProgram(blitVS, blitPS)); diff --git a/libraries/render-utils/src/ZoneRenderer.cpp b/libraries/render-utils/src/ZoneRenderer.cpp index c0d01c2eaf..19a0419a5f 100644 --- a/libraries/render-utils/src/ZoneRenderer.cpp +++ b/libraries/render-utils/src/ZoneRenderer.cpp @@ -78,7 +78,7 @@ void SetupZones::run(const RenderContextPointer& context, const Inputs& inputs) const gpu::PipelinePointer& DebugZoneLighting::getKeyLightPipeline() { if (!_keyLightPipeline) { auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); - auto ps = gpu::Shader::createPixel(std::string(zone_drawKeyLight_frag)); + auto ps = zone_drawKeyLight_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -99,7 +99,7 @@ const gpu::PipelinePointer& DebugZoneLighting::getKeyLightPipeline() { const gpu::PipelinePointer& DebugZoneLighting::getAmbientPipeline() { if (!_ambientPipeline) { auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); - auto ps = gpu::Shader::createPixel(std::string(zone_drawAmbient_frag)); + auto ps = zone_drawAmbient_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -120,7 +120,7 @@ const gpu::PipelinePointer& DebugZoneLighting::getAmbientPipeline() { const gpu::PipelinePointer& DebugZoneLighting::getBackgroundPipeline() { if (!_backgroundPipeline) { auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); - auto ps = gpu::Shader::createPixel(std::string(zone_drawSkybox_frag)); + auto ps = zone_drawSkybox_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index 8449c58c7c..bcd14a4fbc 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -223,9 +223,9 @@ void Font::setupGPU() { // Setup render pipeline { - auto vertexShader = gpu::Shader::createVertex(std::string(sdf_text3D_vert)); - auto pixelShader = gpu::Shader::createPixel(std::string(sdf_text3D_frag)); - auto pixelShaderTransparent = gpu::Shader::createPixel(std::string(sdf_text3D_transparent_frag)); + auto vertexShader = sdf_text3D_vert::getShader(); + auto pixelShader = sdf_text3D_frag::getShader(); + auto pixelShaderTransparent = sdf_text3D_transparent_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vertexShader, pixelShader); gpu::ShaderPointer programTransparent = gpu::Shader::createProgram(vertexShader, pixelShaderTransparent); diff --git a/libraries/render/src/render/BlurTask.cpp b/libraries/render/src/render/BlurTask.cpp index 2be6f8fad2..0625179a6d 100644 --- a/libraries/render/src/render/BlurTask.cpp +++ b/libraries/render/src/render/BlurTask.cpp @@ -210,7 +210,7 @@ BlurGaussian::BlurGaussian(bool generateOutputFramebuffer, unsigned int downsamp gpu::PipelinePointer BlurGaussian::getBlurVPipeline() { if (!_blurVPipeline) { auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(blurGaussianV_frag)); + auto ps = blurGaussianV_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -232,7 +232,7 @@ gpu::PipelinePointer BlurGaussian::getBlurVPipeline() { gpu::PipelinePointer BlurGaussian::getBlurHPipeline() { if (!_blurHPipeline) { auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(blurGaussianH_frag)); + auto ps = blurGaussianH_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -324,7 +324,7 @@ BlurGaussianDepthAware::BlurGaussianDepthAware(bool generateOutputFramebuffer, c gpu::PipelinePointer BlurGaussianDepthAware::getBlurVPipeline() { if (!_blurVPipeline) { auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(blurGaussianDepthAwareV_frag)); + auto ps = blurGaussianDepthAwareV_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -347,7 +347,7 @@ gpu::PipelinePointer BlurGaussianDepthAware::getBlurVPipeline() { gpu::PipelinePointer BlurGaussianDepthAware::getBlurHPipeline() { if (!_blurHPipeline) { auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(blurGaussianDepthAwareH_frag)); + auto ps = blurGaussianDepthAwareH_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; diff --git a/libraries/render/src/render/DrawSceneOctree.cpp b/libraries/render/src/render/DrawSceneOctree.cpp index 36663a454a..f1e85dbb71 100644 --- a/libraries/render/src/render/DrawSceneOctree.cpp +++ b/libraries/render/src/render/DrawSceneOctree.cpp @@ -34,8 +34,8 @@ using namespace render; const gpu::PipelinePointer DrawSceneOctree::getDrawCellBoundsPipeline() { if (!_drawCellBoundsPipeline) { - auto vs = gpu::Shader::createVertex(std::string(drawCellBounds_vert)); - auto ps = gpu::Shader::createPixel(std::string(drawCellBounds_frag)); + auto vs = drawCellBounds_vert::getShader(); + auto ps = drawCellBounds_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -59,7 +59,7 @@ const gpu::PipelinePointer DrawSceneOctree::getDrawCellBoundsPipeline() { const gpu::PipelinePointer DrawSceneOctree::getDrawLODReticlePipeline() { if (!_drawLODReticlePipeline) { auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); - auto ps = gpu::Shader::createPixel(std::string(drawLODReticle_frag)); + auto ps = drawLODReticle_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -162,8 +162,8 @@ void DrawSceneOctree::run(const RenderContextPointer& renderContext, const ItemS const gpu::PipelinePointer DrawItemSelection::getDrawItemBoundPipeline() { if (!_drawItemBoundPipeline) { - auto vs = gpu::Shader::createVertex(std::string(drawItemBounds_vert)); - auto ps = gpu::Shader::createPixel(std::string(drawItemBounds_frag)); + auto vs = drawItemBounds_vert::getShader(); + auto ps = drawItemBounds_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; diff --git a/libraries/render/src/render/DrawStatus.cpp b/libraries/render/src/render/DrawStatus.cpp index 148e104453..a11e9b1a88 100644 --- a/libraries/render/src/render/DrawStatus.cpp +++ b/libraries/render/src/render/DrawStatus.cpp @@ -35,8 +35,8 @@ void DrawStatusConfig::dirtyHelper() { const gpu::PipelinePointer DrawStatus::getDrawItemBoundsPipeline() { if (!_drawItemBoundsPipeline) { - auto vs = gpu::Shader::createVertex(std::string(drawItemBounds_vert)); - auto ps = gpu::Shader::createPixel(std::string(drawItemBounds_frag)); + auto vs = drawItemBounds_vert::getShader(); + auto ps = drawItemBounds_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -63,8 +63,8 @@ const gpu::PipelinePointer DrawStatus::getDrawItemBoundsPipeline() { const gpu::PipelinePointer DrawStatus::getDrawItemStatusPipeline() { if (!_drawItemStatusPipeline) { - auto vs = gpu::Shader::createVertex(std::string(drawItemStatus_vert)); - auto ps = gpu::Shader::createPixel(std::string(drawItemStatus_frag)); + auto vs = drawItemStatus_vert::getShader(); + auto ps = drawItemStatus_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index a60bf91062..88d38d1c66 100755 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -155,8 +155,8 @@ void DrawLight::run(const RenderContextPointer& renderContext, const ItemBounds& const gpu::PipelinePointer DrawBounds::getPipeline() { if (!_boundsPipeline) { - auto vs = gpu::Shader::createVertex(std::string(drawItemBounds_vert)); - auto ps = gpu::Shader::createPixel(std::string(drawItemBounds_frag)); + auto vs = drawItemBounds_vert::getShader(); + auto ps = drawItemBounds_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; diff --git a/tests/shaders/src/main.cpp b/tests/shaders/src/main.cpp index de37c505a6..83ada05fbd 100644 --- a/tests/shaders/src/main.cpp +++ b/tests/shaders/src/main.cpp @@ -23,65 +23,65 @@ #include -#include "simple_vert.h" -#include "simple_frag.h" -#include "simple_textured_frag.h" -#include "simple_textured_unlit_frag.h" +#include "render-utils/simple_vert.h" +#include "render-utils/simple_frag.h" +#include "render-utils/simple_textured_frag.h" +#include "render-utils/simple_textured_unlit_frag.h" -#include "deferred_light_vert.h" -#include "deferred_light_point_vert.h" -#include "deferred_light_spot_vert.h" +#include "render-utils/deferred_light_vert.h" +#include "render-utils/deferred_light_point_vert.h" +#include "render-utils/deferred_light_spot_vert.h" -#include "directional_ambient_light_frag.h" -#include "directional_skybox_light_frag.h" +#include "render-utils/directional_ambient_light_frag.h" +#include "render-utils/directional_skybox_light_frag.h" -#include "standardTransformPNTC_vert.h" -#include "standardDrawTexture_frag.h" +#include "render-utils/standardTransformPNTC_vert.h" +#include "render-utils/standardDrawTexture_frag.h" -#include "model_vert.h" -#include "model_shadow_vert.h" -#include "model_normal_map_vert.h" -#include "model_lightmap_vert.h" -#include "model_lightmap_normal_map_vert.h" -#include "skin_model_vert.h" -#include "skin_model_shadow_vert.h" -#include "skin_model_normal_map_vert.h" +#include "render-utils/model_vert.h" +#include "render-utils/model_shadow_vert.h" +#include "render-utils/model_normal_map_vert.h" +#include "render-utils/model_lightmap_vert.h" +#include "render-utils/model_lightmap_normal_map_vert.h" +#include "render-utils/skin_model_vert.h" +#include "render-utils/skin_model_shadow_vert.h" +#include "render-utils/skin_model_normal_map_vert.h" -#include "model_frag.h" -#include "model_shadow_frag.h" -#include "model_normal_map_frag.h" -#include "model_normal_specular_map_frag.h" -#include "model_specular_map_frag.h" -#include "model_lightmap_frag.h" -#include "model_lightmap_normal_map_frag.h" -#include "model_lightmap_normal_specular_map_frag.h" -#include "model_lightmap_specular_map_frag.h" -#include "model_translucent_frag.h" +#include "render-utils/model_frag.h" +#include "render-utils/model_shadow_frag.h" +#include "render-utils/model_normal_map_frag.h" +#include "render-utils/model_normal_specular_map_frag.h" +#include "render-utils/model_specular_map_frag.h" +#include "render-utils/model_lightmap_frag.h" +#include "render-utils/model_lightmap_normal_map_frag.h" +#include "render-utils/model_lightmap_normal_specular_map_frag.h" +#include "render-utils/model_lightmap_specular_map_frag.h" +#include "render-utils/model_translucent_frag.h" -#include "textured_particle_frag.h" -#include "textured_particle_vert.h" +#include "entities-renderer/textured_particle_frag.h" +#include "entities-renderer/textured_particle_vert.h" -#include "overlay3D_vert.h" -#include "overlay3D_frag.h" +#include "render-utils/overlay3D_vert.h" +#include "render-utils/overlay3D_frag.h" -#include "skybox_vert.h" -#include "skybox_frag.h" +#include "model/skybox_vert.h" +#include "model/skybox_frag.h" -#include "DrawTransformUnitQuad_vert.h" -#include "DrawTexcoordRectTransformUnitQuad_vert.h" -#include "DrawViewportQuadTransformTexcoord_vert.h" -#include "DrawTexture_frag.h" -#include "DrawTextureOpaque_frag.h" -#include "DrawColoredTexture_frag.h" +#include "gpu/DrawTransformUnitQuad_vert.h" +#include "gpu/DrawTexcoordRectTransformUnitQuad_vert.h" +#include "gpu/DrawViewportQuadTransformTexcoord_vert.h" +#include "gpu/DrawTexture_frag.h" +#include "gpu/DrawTextureOpaque_frag.h" +#include "gpu/DrawColoredTexture_frag.h" -#include "sdf_text3D_vert.h" -#include "sdf_text3D_frag.h" +#include "render-utils/sdf_text3D_vert.h" +#include "render-utils/sdf_text3D_frag.h" -#include "paintStroke_vert.h" -#include "paintStroke_frag.h" +#include "entities-renderer/paintStroke_vert.h" +#include "entities-renderer/paintStroke_frag.h" -#include "polyvox_vert.h" -#include "polyvox_frag.h" +#include "entities-renderer/polyvox_vert.h" +#include "entities-renderer/polyvox_frag.h" // Create a simple OpenGL window that renders text in various ways class QTestWindow : public QWindow { @@ -159,54 +159,54 @@ void QTestWindow::draw() { static std::once_flag once; std::call_once(once, [&]{ - testShaderBuild(sdf_text3D_vert, sdf_text3D_frag.h"; + testShaderBuild(sdf_text3D_vert::getSource(), sdf_text3D_frag::getSource()); - testShaderBuild(DrawTransformUnitQuad_vert, DrawTexture_frag.h"; - testShaderBuild(DrawTexcoordRectTransformUnitQuad_vert, DrawTexture_frag.h"; - testShaderBuild(DrawViewportQuadTransformTexcoord_vert, DrawTexture_frag.h"; - testShaderBuild(DrawTransformUnitQuad_vert, DrawTextureOpaque_frag.h"; - testShaderBuild(DrawTransformUnitQuad_vert, DrawColoredTexture_frag.h"; + testShaderBuild(DrawTransformUnitQuad_vert::getSource(), DrawTexture_frag::getSource()); + testShaderBuild(DrawTexcoordRectTransformUnitQuad_vert::getSource(), DrawTexture_frag::getSource()); + testShaderBuild(DrawViewportQuadTransformTexcoord_vert::getSource(), DrawTexture_frag::getSource()); + testShaderBuild(DrawTransformUnitQuad_vert::getSource(), DrawTextureOpaque_frag::getSource()); + testShaderBuild(DrawTransformUnitQuad_vert::getSource(), DrawColoredTexture_frag::getSource()); - testShaderBuild(skybox_vert, skybox_frag.h"; - testShaderBuild(simple_vert, simple_frag.h"; - testShaderBuild(simple_vert, simple_textured_frag.h"; - testShaderBuild(simple_vert, simple_textured_unlit_frag.h"; - testShaderBuild(deferred_light_vert, directional_ambient_light_frag.h"; - testShaderBuild(deferred_light_vert, directional_skybox_light_frag.h"; - testShaderBuild(standardTransformPNTC_vert, standardDrawTexture_frag.h"; - testShaderBuild(standardTransformPNTC_vert, DrawTextureOpaque_frag.h"; + testShaderBuild(skybox_vert::getSource(), skybox_frag::getSource()); + testShaderBuild(simple_vert::getSource(), simple_frag::getSource()); + testShaderBuild(simple_vert::getSource(), simple_textured_frag::getSource()); + testShaderBuild(simple_vert::getSource(), simple_textured_unlit_frag::getSource()); + testShaderBuild(deferred_light_vert::getSource(), directional_ambient_light_frag::getSource()); + testShaderBuild(deferred_light_vert::getSource(), directional_skybox_light_frag::getSource()); + testShaderBuild(standardTransformPNTC_vert::getSource(), standardDrawTexture_frag::getSource()); + testShaderBuild(standardTransformPNTC_vert::getSource(), DrawTextureOpaque_frag::getSource()); - testShaderBuild(model_vert, model_frag.h"; - testShaderBuild(model_normal_map_vert, model_normal_map_frag.h"; - testShaderBuild(model_vert, model_specular_map_frag.h"; - testShaderBuild(model_normal_map_vert, model_normal_specular_map_frag.h"; - testShaderBuild(model_vert, model_translucent_frag.h"; - testShaderBuild(model_normal_map_vert, model_translucent_frag.h"; - testShaderBuild(model_lightmap_vert, model_lightmap_frag.h"; - testShaderBuild(model_lightmap_normal_map_vert, model_lightmap_normal_map_frag.h"; - testShaderBuild(model_lightmap_vert, model_lightmap_specular_map_frag.h"; - testShaderBuild(model_lightmap_normal_map_vert, model_lightmap_normal_specular_map_frag.h"; + testShaderBuild(model_vert::getSource(), model_frag::getSource()); + testShaderBuild(model_normal_map_vert::getSource(), model_normal_map_frag::getSource()); + testShaderBuild(model_vert::getSource(), model_specular_map_frag::getSource()); + testShaderBuild(model_normal_map_vert::getSource(), model_normal_specular_map_frag::getSource()); + testShaderBuild(model_vert::getSource(), model_translucent_frag::getSource()); + testShaderBuild(model_normal_map_vert::getSource(), model_translucent_frag::getSource()); + testShaderBuild(model_lightmap_vert::getSource(), model_lightmap_frag::getSource()); + testShaderBuild(model_lightmap_normal_map_vert::getSource(), model_lightmap_normal_map_frag::getSource()); + testShaderBuild(model_lightmap_vert::getSource(), model_lightmap_specular_map_frag::getSource()); + testShaderBuild(model_lightmap_normal_map_vert::getSource(), model_lightmap_normal_specular_map_frag::getSource()); - testShaderBuild(skin_model_vert, model_frag.h"; - testShaderBuild(skin_model_normal_map_vert, model_normal_map_frag.h"; - testShaderBuild(skin_model_vert, model_specular_map_frag.h"; - testShaderBuild(skin_model_normal_map_vert, model_normal_specular_map_frag.h"; - testShaderBuild(skin_model_vert, model_translucent_frag.h"; - testShaderBuild(skin_model_normal_map_vert, model_translucent_frag.h"; + testShaderBuild(skin_model_vert::getSource(), model_frag::getSource()); + testShaderBuild(skin_model_normal_map_vert::getSource(), model_normal_map_frag::getSource()); + testShaderBuild(skin_model_vert::getSource(), model_specular_map_frag::getSource()); + testShaderBuild(skin_model_normal_map_vert::getSource(), model_normal_specular_map_frag::getSource()); + testShaderBuild(skin_model_vert::getSource(), model_translucent_frag::getSource()); + testShaderBuild(skin_model_normal_map_vert::getSource(), model_translucent_frag::getSource()); - testShaderBuild(model_shadow_vert, model_shadow_frag.h"; - testShaderBuild(textured_particle_vert, textured_particle_frag.h"; + testShaderBuild(model_shadow_vert::getSource(), model_shadow_frag::getSource()); + testShaderBuild(textured_particle_vert::getSource(), textured_particle_frag::getSource()); /* FIXME: Bring back the ssao shader tests - testShaderBuild(gaussian_blur_vertical_vert, gaussian_blur_frag.h"; - testShaderBuild(gaussian_blur_horizontal_vert, gaussian_blur_frag.h"; - testShaderBuild(ambient_occlusion_vert, ambient_occlusion_frag.h"; - testShaderBuild(ambient_occlusion_vert, occlusion_blend_frag.h"; + testShaderBuild(gaussian_blur_vert::getSource()ical_vert::getSource(), gaussian_blur_frag::getSource()); + testShaderBuild(gaussian_blur_horizontal_vert::getSource(), gaussian_blur_frag::getSource()); + testShaderBuild(ambient_occlusion_vert::getSource(), ambient_occlusion_frag::getSource()); + testShaderBuild(ambient_occlusion_vert::getSource(), occlusion_blend_frag::getSource()); */ - testShaderBuild(overlay3D_vert, overlay3D_frag.h"; + testShaderBuild(overlay3D_vert::getSource(), overlay3D_frag::getSource()); - testShaderBuild(paintStroke_vert,paintStroke_frag.h"; - testShaderBuild(polyvox_vert, polyvox_frag.h"; + testShaderBuild(paintStroke_vert::getSource(),paintStroke_frag::getSource()); + testShaderBuild(polyvox_vert::getSource(), polyvox_frag::getSource()); }); _context.swapBuffers(this); diff --git a/tools/scribe/src/main.cpp b/tools/scribe/src/main.cpp index 2092bc0ea2..a7d12d677d 100755 --- a/tools/scribe/src/main.cpp +++ b/tools/scribe/src/main.cpp @@ -184,7 +184,7 @@ int main (int argc, char** argv) { srcStream.open(srcFilename, std::fstream::in); if (!srcStream.is_open()) { cerr << "Failed to open source file <" << srcFilename << ">" << endl; - return 0; + return 1; } auto scribe = std::make_shared(srcFilename, config); @@ -194,7 +194,7 @@ int main (int argc, char** argv) { int numErrors = scribe->scribe(destStringStream, srcStream, vars); if (numErrors) { cerr << "Scribe " << srcFilename << "> failed: " << numErrors << " errors." << endl; - return 0; + return 1; }; @@ -279,7 +279,7 @@ int main (int argc, char** argv) { headerFile << headerStringStream.str(); } else { cerr << "Scribe output file <" << headerFileName << "> failed to open." << endl; - return 0; + return 1; } } else { cerr << sourceStringStream.str(); @@ -310,7 +310,7 @@ int main (int argc, char** argv) { sourceFile.open(sourceFileName, std::fstream::out); if (!sourceFile.is_open()) { cerr << "Scribe output file <" << sourceFileName << "> failed to open." << endl; - return 0; + return 1; } sourceFile << sourceStringStream.str(); } else { @@ -323,7 +323,7 @@ int main (int argc, char** argv) { destFileStream.open(destFilename, std::fstream::out); if (!destFileStream.is_open()) { cerr << "Scribe output file <" << destFilename << "> failed to open." << endl; - return 0; + return 1; } destFileStream << destStringStream.str(); From 7b420d48e2cf1972d9398ee0032901dc9257cd39 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Wed, 17 Jan 2018 10:59:48 +0100 Subject: [PATCH 030/569] Generated shader files are now grouped in a sub-filter group in the project --- cmake/macros/AutoScribeShader.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/macros/AutoScribeShader.cmake b/cmake/macros/AutoScribeShader.cmake index c92f7a3ffe..32b0bc3a2e 100755 --- a/cmake/macros/AutoScribeShader.cmake +++ b/cmake/macros/AutoScribeShader.cmake @@ -132,7 +132,7 @@ macro(AUTOSCRIBE_SHADER_LIB) if (WIN32) source_group("Shaders" FILES ${SHADER_INCLUDE_FILES}) source_group("Shaders" FILES ${SHADER_SOURCE_FILES}) - source_group("Shaders" FILES ${AUTOSCRIBE_SHADER_SRC}) + source_group("Shaders\\generated" FILES ${AUTOSCRIBE_SHADER_SRC}) endif() list(APPEND AUTOSCRIBE_SHADER_LIB_SRC ${SHADER_INCLUDE_FILES}) From 54690219e3a22846af3a6ff17353a6eda76a3df4 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Wed, 17 Jan 2018 11:12:37 +0100 Subject: [PATCH 031/569] Changed the way shader type is sent to Scribe --- cmake/macros/AutoScribeShader.cmake | 8 ++++---- tools/scribe/src/main.cpp | 29 ++++++++++++++++++++--------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/cmake/macros/AutoScribeShader.cmake b/cmake/macros/AutoScribeShader.cmake index 32b0bc3a2e..313de1437d 100755 --- a/cmake/macros/AutoScribeShader.cmake +++ b/cmake/macros/AutoScribeShader.cmake @@ -54,13 +54,13 @@ function(AUTOSCRIBE_SHADER SHADER_FILE) # Target dependant Custom rule on the SHADER_FILE if (APPLE) set(GLPROFILE MAC_GL) - set(SCRIBE_ARGS -c++ -${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) + set(SCRIBE_ARGS -c++ -T ${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) add_custom_command(OUTPUT ${SHADER_TARGET_HEADER} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe) add_custom_command(OUTPUT ${SHADER_TARGET_SOURCE} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) elseif (ANDROID) set(GLPROFILE LINUX_GL) - set(SCRIBE_ARGS -c++ -${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) + set(SCRIBE_ARGS -c++ -T ${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) # for an android build, we can't use the scribe that cmake would normally produce as a target, # since it's unrunnable by the cross-compiling build machine @@ -80,13 +80,13 @@ function(AUTOSCRIBE_SHADER SHADER_FILE) add_custom_command(OUTPUT ${SHADER_TARGET_SOURCE} COMMAND ${NATIVE_SCRIBE} ${SCRIBE_ARGS} DEPENDS ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) elseif (UNIX) set(GLPROFILE LINUX_GL) - set(SCRIBE_ARGS -c++ -${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) + set(SCRIBE_ARGS -c++ -T ${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) add_custom_command(OUTPUT ${SHADER_TARGET_HEADER} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe) add_custom_command(OUTPUT ${SHADER_TARGET_SOURCE} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) else () set(GLPROFILE PC_GL) - set(SCRIBE_ARGS -c++ -${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) + set(SCRIBE_ARGS -c++ -T ${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) add_custom_command(OUTPUT ${SHADER_TARGET_HEADER} ${SHADER_TARGET_SOURCE} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) endif() diff --git a/tools/scribe/src/main.cpp b/tools/scribe/src/main.cpp index a7d12d677d..83c2fe287a 100755 --- a/tools/scribe/src/main.cpp +++ b/tools/scribe/src/main.cpp @@ -41,6 +41,7 @@ int main (int argc, char** argv) { GRAB_VAR_VALUE, GRAB_INCLUDE_PATH, GRAB_TARGET_NAME, + GRAB_SHADER_TYPE, EXIT, } mode = READY; @@ -78,15 +79,8 @@ int main (int argc, char** argv) { } else if (inputs.back() == "-c++") { makeCPlusPlus = true; mode = READY; - } else if (inputs.back() == "-vert") { - type = VERTEX; - mode = READY; - } else if (inputs.back() == "-frag") { - type = FRAGMENT; - mode = READY; - } else if (inputs.back() == "-geom") { - type = GEOMETRY; - mode = READY; + } else if (inputs.back() == "-T") { + mode = GRAB_SHADER_TYPE; } else { // just grabbed the source filename, stop parameter parsing srcFilename = inputs.back(); @@ -127,6 +121,21 @@ int main (int argc, char** argv) { } break; + case GRAB_SHADER_TYPE: + { + if (inputs.back() == "frag") { + type = FRAGMENT; + } else if (inputs.back() == "geom") { + type = GEOMETRY; + } else if (inputs.back() == "vert") { + type = VERTEX; + } else { + cerr << "Unrecognized shader type. Supported is vert, frag or geom" << endl; + } + mode = READY; + } + break; + case EXIT: { // THis shouldn't happen } @@ -145,6 +154,8 @@ int main (int argc, char** argv) { cerr << " -listVars : Will list the vars name and value in the standard output." << endl; cerr << " -showParseTree : Draw the tree obtained while parsing the source" << endl; cerr << " -c++ : Generate a c++ source file containing the output file stream stored as a char[] variable" << endl; + cerr << " -T vert/frag/geom : define the type of the shader. Defaults to VERTEX if not specified." << endl; + cerr << " This is necessary if the -c++ option is used." << endl; return 0; } From 157a229a4fd57c571a4c05b656bb6ab9efa09342 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Wed, 17 Jan 2018 15:09:32 +0100 Subject: [PATCH 032/569] Tried to add GLSL validation in scribe. Issues with determining the correct version... --- tools/scribe/src/main.cpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tools/scribe/src/main.cpp b/tools/scribe/src/main.cpp index 83c2fe287a..9bdcccbfa7 100755 --- a/tools/scribe/src/main.cpp +++ b/tools/scribe/src/main.cpp @@ -56,6 +56,7 @@ int main (int argc, char** argv) { static const char* shaderCreateString[] = { "Vertex", "Pixel", "Geometry" }; + std::string shaderStage{ "vert" }; for (int ii = 1; (mode != EXIT) && (ii < argc); ii++) { inputs.push_back(argv[ii]); @@ -124,10 +125,13 @@ int main (int argc, char** argv) { case GRAB_SHADER_TYPE: { if (inputs.back() == "frag") { + shaderStage = inputs.back(); type = FRAGMENT; } else if (inputs.back() == "geom") { + shaderStage = inputs.back(); type = GEOMETRY; } else if (inputs.back() == "vert") { + shaderStage = inputs.back(); type = VERTEX; } else { cerr << "Unrecognized shader type. Supported is vert, frag or geom" << endl; @@ -218,6 +222,36 @@ int main (int argc, char** argv) { scribe->displayTree(cerr, level); } + // This would be nice to implement but not sure how to handle GLSL version +#if 0 + // Check if we need to validate the code + auto validatorPath = getenv("SCRIBE_VALIDATOR"); + if (validatorPath) { + // Create temporary file with shader code + char tempFileNameStub[L_tmpnam]; + tmpnam(tempFileNameStub); + std::string tempFileName{ tempFileNameStub }; + tempFileName += "."; + tempFileName += shaderStage; + std::ofstream tempStream(tempFileName); + if (tempStream.is_open()) { + tempStream << destStringStream.str(); + tempStream.close(); + std::string validationCommand{ validatorPath }; + validationCommand += " "; + validationCommand += tempFileName; + cout << validationCommand << endl; + auto returnCode = system(validationCommand.c_str()); + if (returnCode != 0) { + cerr << "Scribe shader " << targetName << " validation error." << endl; + } + //remove(tempFileName.c_str()); + } else { + cerr << "Scribe is unable to write shader " << targetName << " to temporary file for validation." << endl; + } + } +#endif + if (makeCPlusPlus) { // Because there is a maximum size for literal strings declared in source we need to partition the // full source string stream into pages that seems to be around that value... From 604817bb59ee3fac1211bd2e743e1107def4556e Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Wed, 17 Jan 2018 19:21:58 +0100 Subject: [PATCH 033/569] Forgot to fix output custom command on all other platforms than Windows --- cmake/macros/AutoScribeShader.cmake | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/cmake/macros/AutoScribeShader.cmake b/cmake/macros/AutoScribeShader.cmake index 313de1437d..f15930a7c2 100755 --- a/cmake/macros/AutoScribeShader.cmake +++ b/cmake/macros/AutoScribeShader.cmake @@ -56,8 +56,7 @@ function(AUTOSCRIBE_SHADER SHADER_FILE) set(GLPROFILE MAC_GL) set(SCRIBE_ARGS -c++ -T ${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) - add_custom_command(OUTPUT ${SHADER_TARGET_HEADER} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe) - add_custom_command(OUTPUT ${SHADER_TARGET_SOURCE} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) + add_custom_command(OUTPUT ${SHADER_TARGET_HEADER} ${SHADER_TARGET_SOURCE} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) elseif (ANDROID) set(GLPROFILE LINUX_GL) set(SCRIBE_ARGS -c++ -T ${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) @@ -76,14 +75,12 @@ function(AUTOSCRIBE_SHADER SHADER_FILE) ") endif () - add_custom_command(OUTPUT ${SHADER_TARGET_HEADER} COMMAND ${NATIVE_SCRIBE} ${SCRIBE_ARGS}) - add_custom_command(OUTPUT ${SHADER_TARGET_SOURCE} COMMAND ${NATIVE_SCRIBE} ${SCRIBE_ARGS} DEPENDS ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) + add_custom_command(OUTPUT ${SHADER_TARGET_HEADER} ${SHADER_TARGET_SOURCE} COMMAND ${NATIVE_SCRIBE} ${SCRIBE_ARGS} DEPENDS ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) elseif (UNIX) set(GLPROFILE LINUX_GL) set(SCRIBE_ARGS -c++ -T ${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) - add_custom_command(OUTPUT ${SHADER_TARGET_HEADER} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe) - add_custom_command(OUTPUT ${SHADER_TARGET_SOURCE} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) + add_custom_command(OUTPUT ${SHADER_TARGET_HEADER} ${SHADER_TARGET_SOURCE} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) else () set(GLPROFILE PC_GL) set(SCRIBE_ARGS -c++ -T ${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) From 514eea5477e5aa73a01fb2e1a610f52787a63dff Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 17 Jan 2018 17:02:10 -0800 Subject: [PATCH 034/569] CR changes --- libraries/fbx/src/FBX.h | 2 +- libraries/fbx/src/OBJReader.cpp | 32 +++++++++++--------------------- libraries/fbx/src/OBJReader.h | 3 +-- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/libraries/fbx/src/FBX.h b/libraries/fbx/src/FBX.h index 0e85891398..56cda9d137 100644 --- a/libraries/fbx/src/FBX.h +++ b/libraries/fbx/src/FBX.h @@ -179,7 +179,7 @@ public: float emissiveIntensity{ 1.0f }; float ambientFactor{ 1.0f }; - float bumpMultiplier{ 1.0f }; // TODO: to be implemented + float bumpMultiplier { 1.0f }; // TODO: to be implemented QString materialID; QString name; diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 008b51b0be..8624efdd56 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -389,10 +389,8 @@ void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& file QString parser = textureLine; while (parser.length() > 0) { if (parser.startsWith("-blend")) { // -blendu/-blendv - parser.remove(0, 11); // remove through "-blendu on " or "-blendu off" - if (parser[0] == ' ') { // extra character for space after off - parser.remove(0, 1); - } + int removeLength = parser[10] == 'f' ? 12 : 11; + parser.remove(0, removeLength); // remove through "-blendu on " or "-blendu off" #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option -blendu/-blendv"; #endif @@ -407,18 +405,14 @@ void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& file qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option -boost"; #endif } else if (parser.startsWith("-cc")) { - parser.remove(0, 7); // remove through "-cc on " or "-cc off" - if (parser[0] == ' ') { // extra character for space after off - parser.remove(0, 1); - } + int removeLength = parser[6] == 'f' ? 8 : 7; + parser.remove(0, removeLength); // remove through "-cc on " or "-cc off" #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option -cc"; #endif } else if (parser.startsWith("-clamp")) { - parser.remove(0, 10); // remove through "-clamp on " or "-clamp off" - if (parser[0] == ' ') { // extra character for space after off - parser.remove(0, 1); - } + int removeLength = parser[9] == 'f' ? 11 : 10; + parser.remove(0, removeLength); // remove through "-clamp on " or "-clamp off" #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJ Reader WARNING: Ignoring texture option -clamp"; #endif @@ -941,18 +935,17 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, bool applyRoughness = false; bool applyNonMetallic = false; bool fresnelOn = false; - bool fresnelOff = false; // Illumination model reference http://paulbourke.net/dataformats/mtl/ switch (objMaterial.illuminationModel) { case 0: // Color on and Ambient off - // We don't support ambient - do nothing? + // We don't support ambient = do nothing? break; case 1: // Color on and Ambient on - // We don't support ambient - do nothing? + // We don't support ambient = do nothing? break; case 2: // Highlight on - // Change specular intensity? + // Change specular intensity = do nothing for now? break; case 3: // Reflection on and Ray trace on applyShininess = true; @@ -969,7 +962,6 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, applyTransparency = true; applyNonMetallic = true; applyShininess = true; - fresnelOff = true; break; case 7: // Transparency: Refraction on and Reflection: Fresnel on and Ray trace on applyTransparency = true; @@ -990,8 +982,8 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, break; } - if (applyTransparency && fbxMaterial.opacity <= ILLUMINATION_MODEL_MIN_OPACITY) { - fbxMaterial.opacity = ILLUMINATION_MODEL_MIN_OPACITY; + if (applyTransparency) { + fbxMaterial.opacity = std::max(fbxMaterial.opacity, ILLUMINATION_MODEL_MIN_OPACITY); } if (applyShininess) { modelMaterial->setRoughness(ILLUMINATION_MODEL_APPLY_SHININESS); @@ -1003,8 +995,6 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, } if (fresnelOn) { modelMaterial->setFresnel(glm::vec3(1.0f)); - } else if (fresnelOff) { - modelMaterial->setFresnel(glm::vec3(0.0f)); } modelMaterial->setOpacity(fbxMaterial.opacity); diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index 44382e3603..9083a69340 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -50,8 +50,7 @@ private: class OBJMaterialTextureOptions { public: - float bumpMultiplier; - OBJMaterialTextureOptions() : bumpMultiplier(1.0f) {} + float bumpMultiplier { 1.0f }; } ; // Materials and references to material names can come in any order, and different mesh parts can refer to the same material. From 26bf78fb5dbb58baf3f4251ca852518c29ef4660 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 17 Jan 2018 18:30:31 -0800 Subject: [PATCH 035/569] first pass new entity selection edit tools wip --- .../system/libraries/entitySelectionTool.js | 4248 ++++------------- 1 file changed, 964 insertions(+), 3284 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index b8ba146757..220a7b7c70 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1,9 +1,10 @@ // -// entitySelectionToolClass.js +// entitySelectionTool.js // examples // // Created by Brad hefta-Gaub on 10/1/14. // Modified by Daniela Fontes * @DanielaFifo and Tiago Andrade @TagoWill on 4/7/2017 +// Modified by David Back on 1/9/2018 // Copyright 2014 High Fidelity, Inc. // // This script implements a class useful for building tools for editing entities. @@ -21,12 +22,6 @@ SPACE_WORLD = "world"; Script.include("./controllers.js"); -function objectTranslationPlanePoint(position, dimensions) { - var newPosition = { x: position.x, y: position.y, z: position.z }; - newPosition.y -= dimensions.y / 2.0; - return newPosition; -} - SelectionManager = (function() { var that = {}; @@ -53,10 +48,6 @@ SelectionManager = (function() { print("ERROR: entitySelectionTool.handleEntitySelectionToolUpdates - got malformed message: " + message); } - // if (message === 'callUpdate') { - // that._update(); - // } - if (messageParsed.method === "selectEntity") { if (wantDebug) { print("setting selection to " + messageParsed.entityID); @@ -235,20 +226,49 @@ function getRelativeCenterPosition(dimensions, registrationPoint) { SelectionDisplay = (function() { var that = {}; - var MINIMUM_DIMENSION = 0.001; + var COLOR_GREEN = { red:0, green:255, blue:0 }; + var COLOR_BLUE = { red:0, green:0, blue:255 }; + var COLOR_RED = { red:255, green:0, blue:0 }; - var GRABBER_DISTANCE_TO_SIZE_RATIO = 0.0075; + var GRABBER_TRANSLATE_ARROW_CONE_OFFSET = 0.3625; + var GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET = 0.3; + var GRABBER_STRETCH_SPHERE_OFFSET = 0.2; + var GRABBER_SCALE_CUBE_OFFSET = 0.2; + + var GRABBER_SCALE_CUBE_IDLE_COLOR = { red:120, green:120, blue:120 }; + var GRABBER_SCALE_CUBE_SELECTED_COLOR = { red:0, green:0, blue:0 }; + var GRABBER_SCALE_EDGE_COLOR = { red:120, green:120, blue:120 }; + + var SCALE_MINIMUM_DIMENSION = 0.02; // These are multipliers for sizing the rotation degrees display while rotating an entity - var ROTATION_DISPLAY_DISTANCE_MULTIPLIER = 1.2; + var ROTATION_DISPLAY_DISTANCE_MULTIPLIER = 1.0; var ROTATION_DISPLAY_SIZE_X_MULTIPLIER = 0.6; var ROTATION_DISPLAY_SIZE_Y_MULTIPLIER = 0.18; var ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER = 0.14; - var ROTATE_ARROW_WEST_NORTH_URL = HIFI_PUBLIC_BUCKET + "images/rotate-arrow-west-north.svg"; - var ROTATE_ARROW_WEST_SOUTH_URL = HIFI_PUBLIC_BUCKET + "images/rotate-arrow-west-south.svg"; + var TRANSLATE_DIRECTION = { + X : 0, + Y : 1, + Z : 2 + } - var showExtendedStretchHandles = false; + var ROTATE_DIRECTION = { + PITCH : 0, + YAW : 1, + ROLL : 2 + } + + var SCALE_DIRECTION = { + LBN : 0, + RBN : 1, + LBF : 2, + RBF : 3, + LTN : 4, + RTN : 5, + LTF : 6, + RTF : 7 + } var spaceMode = SPACE_LOCAL; var overlayNames = []; @@ -259,185 +279,95 @@ SelectionDisplay = (function() { getControllerWorldLocation(Controller.Standard.RightHand, true) ]; - var handleHoverColor = { - red: 224, - green: 67, - blue: 36 - }; - var handleHoverAlpha = 1.0; - - var innerSnapAngle = 22.5; // the angle which we snap to on the inner rotation tool - var innerRadius; - var outerRadius; - var yawHandleRotation; - var pitchHandleRotation; - var rollHandleRotation; - var yawCenter; - var pitchCenter; - var rollCenter; var rotZero; var rotationNormal; + var worldRotationX; + var worldRotationY; + var worldRotationZ; - var handleColor = { - red: 255, - green: 255, - blue: 255 - }; - var handleAlpha = 0.7; + var activeTool = null; + var grabberTools = {}; - var highlightedHandleColor = { - red: 183, - green: 64, - blue: 44 - }; - var highlightedHandleAlpha = 0.9; - - var previousHandle = false; - var previousHandleColor; - var previousHandleAlpha; - - var grabberSizeCorner = 0.025; // These get resized by updateHandleSizes(). - var grabberSizeEdge = 0.015; - var grabberSizeFace = 0.025; - var grabberAlpha = 1; - var grabberColorCorner = { - red: 120, - green: 120, - blue: 120 - }; - var grabberColorEdge = { - red: 0, - green: 0, - blue: 0 - }; - var grabberColorFace = { - red: 120, - green: 120, - blue: 120 - }; - var grabberColorCloner = { - red: 0, - green: 155, - blue: 0 - }; - var grabberLineWidth = 0.5; - var grabberSolid = true; - var grabberMoveUpPosition = Vec3.ZERO; - - var lightOverlayColor = { - red: 255, - green: 153, - blue: 0 - }; - - var grabberPropertiesCorner = { - position: Vec3.ZERO, - size: grabberSizeCorner, - color: grabberColorCorner, - alpha: 1, - solid: grabberSolid, + var grabberPropertiesTranslateArrowCones = { + shape: "Cone", + dimensions: { x:0.05, y:0.05, z:0.05 }, + solid: true, visible: false, - dashed: false, - drawInFront: true, - borderSize: 1.4 + ignoreRayIntersection: false, + drawInFront: true }; - - var grabberPropertiesEdge = { - position: Vec3.ZERO, - size: grabberSizeEdge, - color: grabberColorEdge, - alpha: 1, - solid: grabberSolid, + var grabberPropertiesTranslateArrowCylinders = { + shape: "Cylinder", + dimensions: { x:0.01, y:0.075, z:0.01 }, + solid: true, visible: false, - dashed: false, - drawInFront: true, - borderSize: 1.4 + ignoreRayIntersection: false, + drawInFront: true }; + var grabberTranslateXCone = Overlays.addOverlay("shape", grabberPropertiesTranslateArrowCones); + var grabberTranslateXCylinder = Overlays.addOverlay("shape", grabberPropertiesTranslateArrowCylinders); + Overlays.editOverlay(grabberTranslateXCone, { color : COLOR_RED }); + Overlays.editOverlay(grabberTranslateXCylinder, { color : COLOR_RED }); + var grabberTranslateYCone = Overlays.addOverlay("shape", grabberPropertiesTranslateArrowCones); + var grabberTranslateYCylinder = Overlays.addOverlay("shape", grabberPropertiesTranslateArrowCylinders); + Overlays.editOverlay(grabberTranslateYCone, { color : COLOR_GREEN }); + Overlays.editOverlay(grabberTranslateYCylinder, { color : COLOR_GREEN }); + var grabberTranslateZCone = Overlays.addOverlay("shape", grabberPropertiesTranslateArrowCones); + var grabberTranslateZCylinder = Overlays.addOverlay("shape", grabberPropertiesTranslateArrowCylinders); + Overlays.editOverlay(grabberTranslateZCone, { color : COLOR_BLUE }); + Overlays.editOverlay(grabberTranslateZCylinder, { color : COLOR_BLUE }); - var grabberPropertiesFace = { - position: Vec3.ZERO, - size: grabberSizeFace, - color: grabberColorFace, + var grabberPropertiesRotateRings = { + size: 0.5, alpha: 1, - solid: grabberSolid, + innerRadius: 0.9, + startAt: 0, + endAt: 360, + majorTickMarksAngle: 5, + majorTickMarksLength: 0.1, visible: false, - dashed: false, - drawInFront: true, - borderSize: 1.4 + ignoreRayIntersection: false, + drawInFront: true }; + var grabberRotatePitchRing = Overlays.addOverlay("circle3d", grabberPropertiesRotateRings); + Overlays.editOverlay(grabberRotatePitchRing, { + color : COLOR_RED, + majorTickMarksColor: COLOR_RED, + }); + var grabberRotateYawRing = Overlays.addOverlay("circle3d", grabberPropertiesRotateRings); + Overlays.editOverlay(grabberRotateYawRing, { + color : COLOR_GREEN, + majorTickMarksColor: COLOR_GREEN, + }); + var grabberRotateRollRing = Overlays.addOverlay("circle3d", grabberPropertiesRotateRings); + Overlays.editOverlay(grabberRotateRollRing, { + color : COLOR_BLUE, + majorTickMarksColor: COLOR_BLUE, + }); - var grabberPropertiesCloner = { - position: Vec3.ZERO, - size: grabberSizeCorner, - color: grabberColorCloner, + var grabberRotateCurrentRing = Overlays.addOverlay("circle3d", { + size: 0.5, alpha: 1, - solid: grabberSolid, + color: { red: 224, green: 67, blue: 36 }, + solid: true, + innerRadius: 0.9, visible: false, - dashed: false, - drawInFront: true, - borderSize: 1.4 - }; - - var spotLightLineProperties = { - color: lightOverlayColor - }; - - var highlightBox = Overlays.addOverlay("cube", { - position: Vec3.ZERO, - size: 1, - color: { - red: 90, - green: 90, - blue: 90 - }, - alpha: 1, - solid: false, - visible: false, - dashed: true, - ignoreRayIntersection: true, // this never ray intersects + ignoreRayIntersection: true, drawInFront: true }); - var selectionBox = Overlays.addOverlay("cube", { - position: Vec3.ZERO, - size: 1, - color: { - red: 255, - green: 0, - blue: 0 - }, - alpha: 1, - solid: false, - visible: false, - dashed: false - }); - - var selectionBoxes = []; - var rotationDegreesDisplay = Overlays.addOverlay("text3d", { - position: Vec3.ZERO, text: "", - color: { - red: 0, - green: 0, - blue: 0 - }, - backgroundColor: { - red: 255, - green: 255, - blue: 255 - }, + color: { red: 0, green: 0, blue: 0 }, + backgroundColor: { red: 255, green: 255, blue: 255 }, alpha: 0.7, backgroundAlpha: 0.7, visible: false, isFacingAvatar: true, drawInFront: true, ignoreRayIntersection: true, - dimensions: { - x: 0, - y: 0 - }, + dimensions: { x: 0, y: 0 }, lineHeight: 0.0, topMargin: 0, rightMargin: 0, @@ -445,461 +375,152 @@ SelectionDisplay = (function() { leftMargin: 0 }); - var grabberMoveUp = Overlays.addOverlay("image3d", { - url: HIFI_PUBLIC_BUCKET + "images/up-arrow.svg", - position: Vec3.ZERO, - color: handleColor, - alpha: handleAlpha, + var grabberPropertiesStretchSpheres = { + shape: "Sphere", + dimensions: { x:0.02, y:0.02, z:0.02 }, + solid: true, visible: false, - size: 0.1, - scale: 0.1, - isFacingAvatar: true, + ignoreRayIntersection: false, drawInFront: true - }); - - // var normalLine = Overlays.addOverlay("line3d", { - // visible: true, - // start: { x: 0, y: 0, z: 0 }, - // end: { x: 0, y: 0, z: 0 }, - // color: { red: 255, green: 255, blue: 0 }, - // ignoreRayIntersection: true, - // }); - - var grabberLBN = Overlays.addOverlay("cube", grabberPropertiesCorner); - var grabberRBN = Overlays.addOverlay("cube", grabberPropertiesCorner); - var grabberLBF = Overlays.addOverlay("cube", grabberPropertiesCorner); - var grabberRBF = Overlays.addOverlay("cube", grabberPropertiesCorner); - var grabberLTN = Overlays.addOverlay("cube", grabberPropertiesCorner); - var grabberRTN = Overlays.addOverlay("cube", grabberPropertiesCorner); - var grabberLTF = Overlays.addOverlay("cube", grabberPropertiesCorner); - var grabberRTF = Overlays.addOverlay("cube", grabberPropertiesCorner); - - var grabberTOP = Overlays.addOverlay("cube", grabberPropertiesFace); - var grabberBOTTOM = Overlays.addOverlay("cube", grabberPropertiesFace); - var grabberLEFT = Overlays.addOverlay("cube", grabberPropertiesFace); - var grabberRIGHT = Overlays.addOverlay("cube", grabberPropertiesFace); - var grabberNEAR = Overlays.addOverlay("cube", grabberPropertiesFace); - var grabberFAR = Overlays.addOverlay("cube", grabberPropertiesFace); - - var grabberEdgeTR = Overlays.addOverlay("cube", grabberPropertiesEdge); - var grabberEdgeTL = Overlays.addOverlay("cube", grabberPropertiesEdge); - var grabberEdgeTF = Overlays.addOverlay("cube", grabberPropertiesEdge); - var grabberEdgeTN = Overlays.addOverlay("cube", grabberPropertiesEdge); - var grabberEdgeBR = Overlays.addOverlay("cube", grabberPropertiesEdge); - var grabberEdgeBL = Overlays.addOverlay("cube", grabberPropertiesEdge); - var grabberEdgeBF = Overlays.addOverlay("cube", grabberPropertiesEdge); - var grabberEdgeBN = Overlays.addOverlay("cube", grabberPropertiesEdge); - var grabberEdgeNR = Overlays.addOverlay("cube", grabberPropertiesEdge); - var grabberEdgeNL = Overlays.addOverlay("cube", grabberPropertiesEdge); - var grabberEdgeFR = Overlays.addOverlay("cube", grabberPropertiesEdge); - var grabberEdgeFL = Overlays.addOverlay("cube", grabberPropertiesEdge); - - var grabberSpotLightCircle = Overlays.addOverlay("circle3d", { - color: lightOverlayColor, - isSolid: false, - visible: false - }); - var grabberSpotLightLineT = Overlays.addOverlay("line3d", spotLightLineProperties); - var grabberSpotLightLineB = Overlays.addOverlay("line3d", spotLightLineProperties); - var grabberSpotLightLineL = Overlays.addOverlay("line3d", spotLightLineProperties); - var grabberSpotLightLineR = Overlays.addOverlay("line3d", spotLightLineProperties); - - var grabberSpotLightCenter = Overlays.addOverlay("cube", grabberPropertiesEdge); - var grabberSpotLightRadius = Overlays.addOverlay("cube", grabberPropertiesEdge); - var grabberSpotLightL = Overlays.addOverlay("cube", grabberPropertiesEdge); - var grabberSpotLightR = Overlays.addOverlay("cube", grabberPropertiesEdge); - var grabberSpotLightT = Overlays.addOverlay("cube", grabberPropertiesEdge); - var grabberSpotLightB = Overlays.addOverlay("cube", grabberPropertiesEdge); - - var spotLightGrabberHandles = [ - grabberSpotLightCircle, grabberSpotLightCenter, grabberSpotLightRadius, - grabberSpotLightLineT, grabberSpotLightLineB, grabberSpotLightLineL, grabberSpotLightLineR, - grabberSpotLightT, grabberSpotLightB, grabberSpotLightL, grabberSpotLightR - ]; - - var grabberPointLightCircleX = Overlays.addOverlay("circle3d", { - rotation: Quat.fromPitchYawRollDegrees(0, 90, 0), - color: lightOverlayColor, - isSolid: false, - visible: false - }); - var grabberPointLightCircleY = Overlays.addOverlay("circle3d", { - rotation: Quat.fromPitchYawRollDegrees(90, 0, 0), - color: lightOverlayColor, - isSolid: false, - visible: false - }); - var grabberPointLightCircleZ = Overlays.addOverlay("circle3d", { - rotation: Quat.fromPitchYawRollDegrees(0, 0, 0), - color: lightOverlayColor, - isSolid: false, - visible: false - }); - var grabberPointLightT = Overlays.addOverlay("cube", grabberPropertiesEdge); - var grabberPointLightB = Overlays.addOverlay("cube", grabberPropertiesEdge); - var grabberPointLightL = Overlays.addOverlay("cube", grabberPropertiesEdge); - var grabberPointLightR = Overlays.addOverlay("cube", grabberPropertiesEdge); - var grabberPointLightF = Overlays.addOverlay("cube", grabberPropertiesEdge); - var grabberPointLightN = Overlays.addOverlay("cube", grabberPropertiesEdge); - - var pointLightGrabberHandles = [ - grabberPointLightCircleX, grabberPointLightCircleY, grabberPointLightCircleZ, - grabberPointLightT, grabberPointLightB, grabberPointLightL, - grabberPointLightR, grabberPointLightF, grabberPointLightN - ]; - - var grabberCloner = Overlays.addOverlay("cube", grabberPropertiesCloner); - - var stretchHandles = [ - grabberLBN, - grabberRBN, - grabberLBF, - grabberRBF, - grabberLTN, - grabberRTN, - grabberLTF, - grabberRTF, - grabberTOP, - grabberBOTTOM, - grabberLEFT, - grabberRIGHT, - grabberNEAR, - grabberFAR, - grabberEdgeTR, - grabberEdgeTL, - grabberEdgeTF, - grabberEdgeTN, - grabberEdgeBR, - grabberEdgeBL, - grabberEdgeBF, - grabberEdgeBN, - grabberEdgeNR, - grabberEdgeNL, - grabberEdgeFR, - grabberEdgeFL, - - grabberSpotLightLineT, - grabberSpotLightLineB, - grabberSpotLightLineL, - grabberSpotLightLineR, - - grabberSpotLightCenter, - grabberSpotLightRadius, - grabberSpotLightL, - grabberSpotLightR, - grabberSpotLightT, - grabberSpotLightB, - - grabberPointLightT, - grabberPointLightB, - grabberPointLightL, - grabberPointLightR, - grabberPointLightF, - grabberPointLightN, - - grabberCloner - ]; - - - var baseOverlayAngles = { - x: 0, - y: 0, - z: 0 }; - var baseOverlayRotation = Quat.fromVec3Degrees(baseOverlayAngles); - var baseOfEntityProjectionOverlay = Overlays.addOverlay("rectangle3d", { - position: { - x: 1, - y: 0, - z: 0 - }, - color: { - red: 51, - green: 152, - blue: 203 - }, + var grabberStretchXSphere = Overlays.addOverlay("shape", grabberPropertiesStretchSpheres); + Overlays.editOverlay(grabberStretchXSphere, { color : COLOR_RED }); + var grabberStretchYSphere = Overlays.addOverlay("shape", grabberPropertiesStretchSpheres); + Overlays.editOverlay(grabberStretchYSphere, { color : COLOR_GREEN }); + var grabberStretchZSphere = Overlays.addOverlay("shape", grabberPropertiesStretchSpheres); + Overlays.editOverlay(grabberStretchZSphere, { color : COLOR_BLUE }); + + var grabberPropertiesStretchPanel = { + shape: "Quad", alpha: 0.5, + dimensions: { x:GRABBER_SCALE_CUBE_OFFSET * 2, y:GRABBER_SCALE_CUBE_OFFSET * 2, z:0.01 }, solid: true, visible: false, - width: 300, - height: 200, - rotation: baseOverlayRotation, - ignoreRayIntersection: true // always ignore this - }); + ignoreRayIntersection: true, + drawInFront: true, + } + var grabberStretchXPanel = Overlays.addOverlay("shape", grabberPropertiesStretchPanel); + Overlays.editOverlay(grabberStretchXPanel, { color : COLOR_RED }); + var grabberStretchYPanel = Overlays.addOverlay("shape", grabberPropertiesStretchPanel); + Overlays.editOverlay(grabberStretchYPanel, { color : COLOR_GREEN }); + var grabberStretchZPanel = Overlays.addOverlay("shape", grabberPropertiesStretchPanel); + Overlays.editOverlay(grabberStretchZPanel, { color : COLOR_BLUE }); - var yawOverlayAngles = { - x: 90, - y: 0, - z: 0 + var grabberPropertiesScaleCubes = { + size: 0.025, + color: GRABBER_SCALE_CUBE_IDLE_COLOR, + solid: true, + visible: false, + ignoreRayIntersection: false, + drawInFront: true, + borderSize: 1.4 }; - var yawOverlayRotation = Quat.fromVec3Degrees(yawOverlayAngles); - var pitchOverlayAngles = { - x: 0, - y: 90, - z: 0 - }; - var pitchOverlayRotation = Quat.fromVec3Degrees(pitchOverlayAngles); - var rollOverlayAngles = { - x: 0, - y: 180, - z: 0 - }; - var rollOverlayRotation = Quat.fromVec3Degrees(rollOverlayAngles); + var grabberScaleLBNCube = Overlays.addOverlay("cube", grabberPropertiesScaleCubes); // (-x, -y, -z) + var grabberScaleRBNCube = Overlays.addOverlay("cube", grabberPropertiesScaleCubes); // (-x, -y, z) + var grabberScaleLBFCube = Overlays.addOverlay("cube", grabberPropertiesScaleCubes); // ( x, -y, -z) + var grabberScaleRBFCube = Overlays.addOverlay("cube", grabberPropertiesScaleCubes); // ( x, -y, z) + var grabberScaleLTNCube = Overlays.addOverlay("cube", grabberPropertiesScaleCubes); // (-x, y, -z) + var grabberScaleRTNCube = Overlays.addOverlay("cube", grabberPropertiesScaleCubes); // (-x, y, z) + var grabberScaleLTFCube = Overlays.addOverlay("cube", grabberPropertiesScaleCubes); // ( x, y, -z) + var grabberScaleRTFCube = Overlays.addOverlay("cube", grabberPropertiesScaleCubes); // ( x, y, z) - var xRailOverlay = Overlays.addOverlay("line3d", { + var grabberPropertiesScaleEdge = { + color: GRABBER_SCALE_EDGE_COLOR, visible: false, - start: Vec3.ZERO, - end: Vec3.ZERO, - color: { - red: 255, - green: 0, - blue: 0 - }, - ignoreRayIntersection: true // always ignore this - }); - var yRailOverlay = Overlays.addOverlay("line3d", { - visible: false, - start: Vec3.ZERO, - end: Vec3.ZERO, - color: { - red: 0, - green: 255, - blue: 0 - }, - ignoreRayIntersection: true // always ignore this - }); - var zRailOverlay = Overlays.addOverlay("line3d", { - visible: false, - start: Vec3.ZERO, - end: Vec3.ZERO, - color: { - red: 0, - green: 0, - blue: 255 - }, - ignoreRayIntersection: true // always ignore this - }); - - var rotateZeroOverlay = Overlays.addOverlay("line3d", { - visible: false, - start: Vec3.ZERO, - end: Vec3.ZERO, - color: { - red: 255, - green: 0, - blue: 0 - }, - ignoreRayIntersection: true // always ignore this - }); - - var rotateCurrentOverlay = Overlays.addOverlay("line3d", { - visible: false, - start: Vec3.ZERO, - end: Vec3.ZERO, - color: { - red: 0, - green: 0, - blue: 255 - }, - ignoreRayIntersection: true // always ignore this - }); - - - var rotateOverlayInner = Overlays.addOverlay("circle3d", { - position: Vec3.ZERO, - size: 1, - color: { - red: 51, - green: 152, - blue: 203 - }, - alpha: 0.2, - solid: true, - visible: false, - rotation: yawOverlayRotation, - hasTickMarks: true, - majorTickMarksAngle: innerSnapAngle, - minorTickMarksAngle: 0, - majorTickMarksLength: -0.25, - minorTickMarksLength: 0, - majorTickMarksColor: { - red: 0, - green: 0, - blue: 0 - }, - minorTickMarksColor: { - red: 0, - green: 0, - blue: 0 - }, - ignoreRayIntersection: true // always ignore this - }); - - var rotateOverlayOuter = Overlays.addOverlay("circle3d", { - position: Vec3.ZERO, - size: 1, - color: { - red: 51, - green: 152, - blue: 203 - }, - alpha: 0.2, - solid: true, - visible: false, - rotation: yawOverlayRotation, - - hasTickMarks: true, - majorTickMarksAngle: 45.0, - minorTickMarksAngle: 5, - majorTickMarksLength: 0.25, - minorTickMarksLength: 0.1, - majorTickMarksColor: { - red: 0, - green: 0, - blue: 0 - }, - minorTickMarksColor: { - red: 0, - green: 0, - blue: 0 - }, - ignoreRayIntersection: true // always ignore this - }); - - var rotateOverlayCurrent = Overlays.addOverlay("circle3d", { - position: Vec3.ZERO, - size: 1, - color: { - red: 224, - green: 67, - blue: 36 - }, - alpha: 0.8, - solid: true, - visible: false, - rotation: yawOverlayRotation, - ignoreRayIntersection: true, // always ignore this - hasTickMarks: true, - majorTickMarksColor: { - red: 0, - green: 0, - blue: 0 - }, - minorTickMarksColor: { - red: 0, - green: 0, - blue: 0 - } - }); - - var yawHandle = Overlays.addOverlay("image3d", { - url: ROTATE_ARROW_WEST_NORTH_URL, - position: Vec3.ZERO, - color: handleColor, - alpha: handleAlpha, - visible: false, - size: 0.1, - scale: 0.1, - isFacingAvatar: false, - drawInFront: true - }); - - - var pitchHandle = Overlays.addOverlay("image3d", { - url: ROTATE_ARROW_WEST_NORTH_URL, - position: Vec3.ZERO, - color: handleColor, - alpha: handleAlpha, - visible: false, - size: 0.1, - scale: 0.1, - isFacingAvatar: false, - drawInFront: true - }); - - - var rollHandle = Overlays.addOverlay("image3d", { - url: ROTATE_ARROW_WEST_NORTH_URL, - position: Vec3.ZERO, - color: handleColor, - alpha: handleAlpha, - visible: false, - size: 0.1, - scale: 0.1, - isFacingAvatar: false, - drawInFront: true - }); + ignoreRayIntersection: true, + drawInFront: true, + lineWidth: 0.2 + } + var grabberScaleTREdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); + var grabberScaleTLEdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); + var grabberScaleTFEdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); + var grabberScaleTNEdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); + var grabberScaleBREdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); + var grabberScaleBLEdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); + var grabberScaleBFEdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); + var grabberScaleBNEdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); + var grabberScaleNREdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); + var grabberScaleNLEdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); + var grabberScaleFREdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); + var grabberScaleFLEdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); var allOverlays = [ - highlightBox, - selectionBox, - grabberMoveUp, - yawHandle, - pitchHandle, - rollHandle, - rotateOverlayInner, - rotateOverlayOuter, - rotateOverlayCurrent, - rotateZeroOverlay, - rotateCurrentOverlay, + grabberTranslateXCone, + grabberTranslateXCylinder, + grabberTranslateYCone, + grabberTranslateYCylinder, + grabberTranslateZCone, + grabberTranslateZCylinder, + grabberRotatePitchRing, + grabberRotateYawRing, + grabberRotateRollRing, + grabberRotateCurrentRing, rotationDegreesDisplay, - xRailOverlay, - yRailOverlay, - zRailOverlay, - baseOfEntityProjectionOverlay, - grabberSpotLightCircle, - grabberPointLightCircleX, - grabberPointLightCircleY, - grabberPointLightCircleZ + grabberStretchXSphere, + grabberStretchYSphere, + grabberStretchZSphere, + grabberStretchXPanel, + grabberStretchYPanel, + grabberStretchZPanel, + grabberScaleLBNCube, + grabberScaleRBNCube, + grabberScaleLBFCube, + grabberScaleRBFCube, + grabberScaleLTNCube, + grabberScaleRTNCube, + grabberScaleLTFCube, + grabberScaleRTFCube, + grabberScaleTREdge, + grabberScaleTLEdge, + grabberScaleTFEdge, + grabberScaleTNEdge, + grabberScaleBREdge, + grabberScaleBLEdge, + grabberScaleBFEdge, + grabberScaleBNEdge, + grabberScaleNREdge, + grabberScaleNLEdge, + grabberScaleFREdge, + grabberScaleFLEdge + ]; - ].concat(stretchHandles); - - overlayNames[highlightBox] = "highlightBox"; - overlayNames[selectionBox] = "selectionBox"; - overlayNames[baseOfEntityProjectionOverlay] = "baseOfEntityProjectionOverlay"; - overlayNames[grabberMoveUp] = "grabberMoveUp"; - overlayNames[grabberLBN] = "grabberLBN"; - overlayNames[grabberLBF] = "grabberLBF"; - overlayNames[grabberRBN] = "grabberRBN"; - overlayNames[grabberRBF] = "grabberRBF"; - overlayNames[grabberLTN] = "grabberLTN"; - overlayNames[grabberLTF] = "grabberLTF"; - overlayNames[grabberRTN] = "grabberRTN"; - overlayNames[grabberRTF] = "grabberRTF"; - - overlayNames[grabberTOP] = "grabberTOP"; - overlayNames[grabberBOTTOM] = "grabberBOTTOM"; - overlayNames[grabberLEFT] = "grabberLEFT"; - overlayNames[grabberRIGHT] = "grabberRIGHT"; - overlayNames[grabberNEAR] = "grabberNEAR"; - overlayNames[grabberFAR] = "grabberFAR"; - - overlayNames[grabberEdgeTR] = "grabberEdgeTR"; - overlayNames[grabberEdgeTL] = "grabberEdgeTL"; - overlayNames[grabberEdgeTF] = "grabberEdgeTF"; - overlayNames[grabberEdgeTN] = "grabberEdgeTN"; - overlayNames[grabberEdgeBR] = "grabberEdgeBR"; - overlayNames[grabberEdgeBL] = "grabberEdgeBL"; - overlayNames[grabberEdgeBF] = "grabberEdgeBF"; - overlayNames[grabberEdgeBN] = "grabberEdgeBN"; - overlayNames[grabberEdgeNR] = "grabberEdgeNR"; - overlayNames[grabberEdgeNL] = "grabberEdgeNL"; - overlayNames[grabberEdgeFR] = "grabberEdgeFR"; - overlayNames[grabberEdgeFL] = "grabberEdgeFL"; - - overlayNames[yawHandle] = "yawHandle"; - overlayNames[pitchHandle] = "pitchHandle"; - overlayNames[rollHandle] = "rollHandle"; - - overlayNames[rotateOverlayInner] = "rotateOverlayInner"; - overlayNames[rotateOverlayOuter] = "rotateOverlayOuter"; - overlayNames[rotateOverlayCurrent] = "rotateOverlayCurrent"; - - overlayNames[rotateZeroOverlay] = "rotateZeroOverlay"; - overlayNames[rotateCurrentOverlay] = "rotateCurrentOverlay"; - overlayNames[grabberCloner] = "grabberCloner"; - var activeTool = null; - var grabberTools = {}; + overlayNames[grabberTranslateXCone] = "grabberTranslateXCone"; + overlayNames[grabberTranslateXCylinder] = "grabberTranslateXCylinder"; + overlayNames[grabberTranslateYCone] = "grabberTranslateYCone"; + overlayNames[grabberTranslateYCylinder] = "grabberTranslateYCylinder"; + overlayNames[grabberTranslateZCone] = "grabberTranslateZCone"; + overlayNames[grabberTranslateZCylinder] = "grabberTranslateZCylinder"; + overlayNames[grabberRotatePitchRing] = "grabberRotatePitchRing"; + overlayNames[grabberRotateYawRing] = "grabberRotateYawRing"; + overlayNames[grabberRotateRollRing] = "grabberRotateRollRing"; + overlayNames[grabberRotateCurrentRing] = "grabberRotateCurrentRing"; + overlayNames[rotationDegreesDisplay] = "rotationDegreesDisplay"; + overlayNames[grabberStretchXSphere] = "grabberStretchXSphere"; + overlayNames[grabberStretchYSphere] = "grabberStretchYSphere"; + overlayNames[grabberStretchZSphere] = "grabberStretchZSphere"; + overlayNames[grabberStretchXPanel] = "grabberStretchXPanel"; + overlayNames[grabberStretchYPanel] = "grabberStretchYPanel"; + overlayNames[grabberStretchZPanel] = "grabberStretchZPanel"; + overlayNames[grabberScaleLBNCube] = "grabberScaleLBNCube"; + overlayNames[grabberScaleRBNCube] = "grabberScaleRBNCube"; + overlayNames[grabberScaleLBFCube] = "grabberScaleLBFCube"; + overlayNames[grabberScaleRBFCube] = "grabberScaleRBFCube"; + overlayNames[grabberScaleLTNCube] = "grabberScaleLTNCube"; + overlayNames[grabberScaleRTNCube] = "grabberScaleRTNCube"; + overlayNames[grabberScaleLTFCube] = "grabberScaleLTFCube"; + overlayNames[grabberScaleRTFCube] = "grabberScaleRTFCube"; + overlayNames[grabberScaleTREdge] = "grabberScaleTREdge"; + overlayNames[grabberScaleTLEdge] = "grabberScaleTLEdge"; + overlayNames[grabberScaleTFEdge] = "grabberScaleTFEdge"; + overlayNames[grabberScaleTNEdge] = "grabberScaleTNEdge"; + overlayNames[grabberScaleBREdge] = "grabberScaleBREdge"; + overlayNames[grabberScaleBLEdge] = "grabberScaleBLEdge"; + overlayNames[grabberScaleBFEdge] = "grabberScaleBFEdge"; + overlayNames[grabberScaleBNEdge] = "grabberScaleBNEdge"; + overlayNames[grabberScaleNREdge] = "grabberScaleNREdge"; + overlayNames[grabberScaleNLEdge] = "grabberScaleNLEdge"; + overlayNames[grabberScaleFREdge] = "grabberScaleFREdge"; + overlayNames[grabberScaleFLEdge] = "grabberScaleFLEdge"; // We get mouseMoveEvents from the handControllers, via handControllerPointer. // But we dont' get mousePressEvents. @@ -932,7 +553,6 @@ SelectionDisplay = (function() { that.triggerMapping.from(Controller.Standard.RT).peek().to(makeTriggerHandler(Controller.Standard.RightHand)); that.triggerMapping.from(Controller.Standard.LT).peek().to(makeTriggerHandler(Controller.Standard.LeftHand)); - function controllerComputePickRay() { var controllerPose = getControllerWorldLocation(activeHand, true); if (controllerPose.valid && that.triggered) { @@ -942,2352 +562,389 @@ SelectionDisplay = (function() { return {origin: controllerPosition, direction: controllerDirection}; } } + function generalComputePickRay(x, y) { return controllerComputePickRay() || Camera.computePickRay(x, y); } + function addGrabberTool(overlay, tool) { grabberTools[overlay] = tool; return tool; } - // @param: toolHandle: The overlayID associated with the tool - // that correlates to the tool you wish to query. - // @note: If toolHandle is null or undefined then activeTool - // will be checked against those values as opposed to - // the tool registered under toolHandle. Null & Undefined - // are treated as separate values. - // @return: bool - Indicates if the activeTool is that queried. - function isActiveTool(toolHandle) { - if (!toolHandle) { - // Allow isActiveTool(null) and similar to return true if there's - // no active tool - return (activeTool === toolHandle); - } - - if (!grabberTools.hasOwnProperty(toolHandle)) { - print("WARNING: entitySelectionTool.isActiveTool - Encountered unknown grabberToolHandle: " + toolHandle + ". Tools should be egistered via addGrabberTool."); - // EARLY EXIT - return false; - } - - return (activeTool === grabberTools[ toolHandle ]); - } - - // @return string - The mode of the currently active tool; - // otherwise, "UNKNOWN" if there's no active tool. - function getMode() { - return (activeTool ? activeTool.mode : "UNKNOWN"); - } - - - that.cleanup = function() { - for (var i = 0; i < allOverlays.length; i++) { - Overlays.deleteOverlay(allOverlays[i]); - } - for (var j = 0; j < selectionBoxes.length; j++) { - Overlays.deleteOverlay(selectionBoxes[j]); - } - }; - - that.highlightSelectable = function(entityID) { - var properties = Entities.getEntityProperties(entityID); - Overlays.editOverlay(highlightBox, { - visible: true, - position: properties.boundingBox.center, - dimensions: properties.dimensions, - rotation: properties.rotation - }); - }; - - that.unhighlightSelectable = function(entityID) { - Overlays.editOverlay(highlightBox, { - visible: false - }); - }; - - that.select = function(entityID, event) { - var properties = Entities.getEntityProperties(SelectionManager.selections[0]); - - lastCameraPosition = Camera.getPosition(); - lastCameraOrientation = Camera.getOrientation(); - - if (event !== false) { - pickRay = generalComputePickRay(event.x, event.y); - - var wantDebug = false; - if (wantDebug) { - print("select() with EVENT...... "); - print(" event.y:" + event.y); - Vec3.print(" current position:", properties.position); - } - - - } - - Overlays.editOverlay(highlightBox, { - visible: false - }); - - that.updateHandles(); - }; - - // Function: Calculate New Bound Extremes - // uses dot product to discover new top and bottom on the new referential (max and min) - that.calculateNewBoundExtremes = function(boundPointList, referenceVector) { - - if (boundPointList.length < 2) { - return [null, null]; - } - - var refMax = boundPointList[0]; - var refMin = boundPointList[1]; - - var dotMax = Vec3.dot(boundPointList[0], referenceVector); - var dotMin = Vec3.dot(boundPointList[1], referenceVector); - - if (dotMin > dotMax) { - dotMax = dotMin; - dotMin = Vec3.dot(boundPointList[0], referenceVector); - refMax = boundPointList[1]; - refMin = boundPointList[0]; - } - - for (var i = 2; i < boundPointList.length ; i++) { - var dotAux = Vec3.dot(boundPointList[i], referenceVector); - if (dotAux > dotMax) { - dotMax = dotAux; - refMax = boundPointList[i]; - } else if (dotAux < dotMin) { - dotMin = dotAux; - refMin = boundPointList[i]; - } - } - return [refMin, refMax]; - } - - // Function: Project Bounding Box Points - // Projects all 6 bounding box points: Top, Bottom, Left, Right, Near, Far (assumes center 0,0,0) onto - // one of the basis of the new avatar referencial - // dimensions - dimensions of the AABB (axis aligned bounding box) on the standard basis - // [1, 0, 0], [0, 1, 0], [0, 0, 1] - // v - projection vector - // rotateHandleOffset - offset for the rotation handle gizmo position - that.projectBoundingBoxPoints = function(dimensions, v, rotateHandleOffset) { - var projT_v = Vec3.dot(Vec3.multiply((dimensions.y / 2) + rotateHandleOffset, Vec3.UNIT_Y), v); - projT_v = Vec3.multiply(projT_v, v); - - var projB_v = Vec3.dot(Vec3.multiply(-(dimensions.y / 2) - rotateHandleOffset, Vec3.UNIT_Y), v); - projB_v = Vec3.multiply(projB_v, v); - - var projL_v = Vec3.dot(Vec3.multiply((dimensions.x / 2) + rotateHandleOffset, Vec3.UNIT_X), v); - projL_v = Vec3.multiply(projL_v, v); - - var projR_v = Vec3.dot(Vec3.multiply(-1.0 * (dimensions.x / 2) - 1.0 * rotateHandleOffset, Vec3.UNIT_X), v); - projR_v = Vec3.multiply(projR_v, v); - - var projN_v = Vec3.dot(Vec3.multiply((dimensions.z / 2) + rotateHandleOffset, Vec3.FRONT), v); - projN_v = Vec3.multiply(projN_v, v); - - var projF_v = Vec3.dot(Vec3.multiply(-1.0 * (dimensions.z / 2) - 1.0 * rotateHandleOffset, Vec3.FRONT), v); - projF_v = Vec3.multiply(projF_v, v); - - var projList = [projT_v, projB_v, projL_v, projR_v, projN_v, projF_v]; - - return that.calculateNewBoundExtremes(projList, v); - }; - - // FUNCTION: UPDATE ROTATION HANDLES - that.updateRotationHandles = function() { - var diagonal = (Vec3.length(SelectionManager.worldDimensions) / 2) * 1.1; - var halfDimensions = Vec3.multiply(SelectionManager.worldDimensions, 0.5); - var innerActive = false; - var innerAlpha = 0.2; - var outerAlpha = 0.2; - if (innerActive) { - innerAlpha = 0.5; - } else { - outerAlpha = 0.5; - } - // prev 0.05 - var rotateHandleOffset = 0.05; - - var boundsCenter, objectCenter; - - var dimensions, rotation; - if (spaceMode === SPACE_LOCAL) { - rotation = SelectionManager.localRotation; - } else { - rotation = SelectionManager.worldRotation; - } - objectCenter = SelectionManager.worldPosition; - dimensions = SelectionManager.worldDimensions; - var position = objectCenter; - - boundsCenter = objectCenter; - - var yawCorner; - var pitchCorner; - var rollCorner; - - var cameraPosition = Camera.getPosition(); - var look = Vec3.normalize(Vec3.subtract(cameraPosition, objectCenter)); - - // place yaw, pitch and roll rotations on the avatar referential - - var avatarReferential = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({ - x: 0, - y: 180, - z: 0 - })); - var upVector = Quat.getUp(avatarReferential); - var rightVector = Vec3.multiply(-1, Quat.getRight(avatarReferential)); - var frontVector = Quat.getFront(avatarReferential); - - // project all 6 bounding box points: Top, Bottom, Left, Right, Near, Far (assumes center 0,0,0) - // onto the new avatar referential - - // UP - var projUP = that.projectBoundingBoxPoints(dimensions, upVector, rotateHandleOffset); - // RIGHT - var projRIGHT = that.projectBoundingBoxPoints(dimensions, rightVector, rotateHandleOffset); - // FRONT - var projFRONT = that.projectBoundingBoxPoints(dimensions, frontVector, rotateHandleOffset); - - // YAW - yawCenter = Vec3.sum(boundsCenter, projUP[0]); - yawCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(projUP[0], projRIGHT[1]), projFRONT[1])); - - yawHandleRotation = Quat.lookAt( - yawCorner, - Vec3.sum(yawCorner, upVector), - Vec3.subtract(yawCenter,yawCorner)); - yawHandleRotation = Quat.multiply(Quat.angleAxis(45, upVector), yawHandleRotation); - - // PTCH - pitchCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(projUP[1], projRIGHT[0]), projFRONT[1])); - pitchCenter = Vec3.sum(boundsCenter, projRIGHT[0]); - - pitchHandleRotation = Quat.lookAt( - pitchCorner, - Vec3.sum(pitchCorner, rightVector), - Vec3.subtract(pitchCenter,pitchCorner)); - pitchHandleRotation = Quat.multiply(Quat.angleAxis(45, rightVector), pitchHandleRotation); - - // ROLL - rollCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(projUP[1], projRIGHT[1]), projFRONT[0])); - rollCenter = Vec3.sum(boundsCenter, projFRONT[0]); - - rollHandleRotation = Quat.lookAt( - rollCorner, - Vec3.sum(rollCorner, frontVector), - Vec3.subtract(rollCenter,rollCorner)); - rollHandleRotation = Quat.multiply(Quat.angleAxis(45, frontVector), rollHandleRotation); - - - var rotateHandlesVisible = true; - var rotationOverlaysVisible = false; - // note: Commented out as these are currently unused here; however, - // leaving them around as they document intent of state as it - // relates to modes that may be useful later. - // var translateHandlesVisible = true; - // var selectionBoxVisible = true; - var isPointLight = false; - if (SelectionManager.selections.length === 1) { - var properties = Entities.getEntityProperties(SelectionManager.selections[0]); - isPointLight = (properties.type === "Light") && !properties.isSpotlight; - } - - if (isActiveTool(yawHandle) || isActiveTool(pitchHandle) || - isActiveTool(rollHandle) || isActiveTool(selectionBox) || isActiveTool(grabberCloner)) { - rotationOverlaysVisible = true; - rotateHandlesVisible = false; - // translateHandlesVisible = false; - // selectionBoxVisible = false; - } else if (isActiveTool(grabberMoveUp) || isPointLight) { - rotateHandlesVisible = false; - } else if (activeTool) { - // every other mode is a stretch mode... - rotateHandlesVisible = false; - // translateHandlesVisible = false; - } - - Overlays.editOverlay(rotateZeroOverlay, { - visible: rotationOverlaysVisible - }); - Overlays.editOverlay(rotateCurrentOverlay, { - visible: rotationOverlaysVisible - }); - - Overlays.editOverlay(yawHandle, { - visible: rotateHandlesVisible, - position: yawCorner, - rotation: yawHandleRotation - }); - Overlays.editOverlay(pitchHandle, { - visible: rotateHandlesVisible, - position: pitchCorner, - rotation: pitchHandleRotation - }); - Overlays.editOverlay(rollHandle, { - visible: rotateHandlesVisible, - position: rollCorner, - rotation: rollHandleRotation - }); - - - }; - - // FUNCTION: UPDATE HANDLE SIZES - that.updateHandleSizes = function() { - if (SelectionManager.hasSelection()) { - var diff = Vec3.subtract(SelectionManager.worldPosition, Camera.getPosition()); - var grabberSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 5; - var dimensions = SelectionManager.worldDimensions; - var avgDimension = (dimensions.x + dimensions.y + dimensions.z) / 3; - grabberSize = Math.min(grabberSize, avgDimension / 10); - - for (var i = 0; i < stretchHandles.length; i++) { - Overlays.editOverlay(stretchHandles[i], { - size: grabberSize - }); - } - var handleSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 7; - handleSize = Math.min(handleSize, avgDimension / 3); - - Overlays.editOverlay(yawHandle, { - scale: handleSize - }); - Overlays.editOverlay(pitchHandle, { - scale: handleSize - }); - Overlays.editOverlay(rollHandle, { - scale: handleSize - }); - var upDiff = Vec3.multiply(( - Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 3), - Quat.getUp(MyAvatar.orientation) - ); - var pos = Vec3.sum(grabberMoveUpPosition, upDiff); - Overlays.editOverlay(grabberMoveUp, { - position: pos, - scale: handleSize / 1.25 - }); - } - }; - Script.update.connect(that.updateHandleSizes); - - // FUNCTION: SET SPACE MODE - that.setSpaceMode = function(newSpaceMode) { - var wantDebug = false; - if (wantDebug) { - print("======> SetSpaceMode called. ========"); - } - - if (spaceMode !== newSpaceMode) { - if (wantDebug) { - print(" Updating SpaceMode From: " + spaceMode + " To: " + newSpaceMode); - } - spaceMode = newSpaceMode; - that.updateHandles(); - } else if (wantDebug) { - print("WARNING: entitySelectionTool.setSpaceMode - Can't update SpaceMode. CurrentMode: " + spaceMode + " DesiredMode: " + newSpaceMode); - } - if (wantDebug) { - print("====== SetSpaceMode called. <========"); - } - }; - - // FUNCTION: TOGGLE SPACE MODE - that.toggleSpaceMode = function() { - var wantDebug = false; - if (wantDebug) { - print("========> ToggleSpaceMode called. ========="); - } - if ((spaceMode === SPACE_WORLD) && (SelectionManager.selections.length > 1)) { - if (wantDebug) { - print("Local space editing is not available with multiple selections"); - } - return; - } - if (wantDebug) { - print("PreToggle: " + spaceMode); - } - spaceMode = (spaceMode === SPACE_LOCAL) ? SPACE_WORLD : SPACE_LOCAL; - that.updateHandles(); - if (wantDebug) { - print("PostToggle: " + spaceMode); - print("======== ToggleSpaceMode called. <========="); - } - }; - - // FUNCTION: UNSELECT ALL - // TODO?: Needs implementation - that.unselectAll = function() {}; - - // FUNCTION: UPDATE HANDLES - that.updateHandles = function() { - var wantDebug = false; - if (wantDebug) { - print("======> Update Handles ======="); - print(" Selections Count: " + SelectionManager.selections.length); - print(" SpaceMode: " + spaceMode); - print(" DisplayMode: " + getMode()); - } - if (SelectionManager.selections.length === 0) { - that.setOverlaysVisible(false); - return; - } - - // print(" Triggering updateRotationHandles"); - that.updateRotationHandles(); - - var rotation, dimensions, position, registrationPoint; - - if (spaceMode === SPACE_LOCAL) { - rotation = SelectionManager.localRotation; - dimensions = SelectionManager.localDimensions; - position = SelectionManager.localPosition; - registrationPoint = SelectionManager.localRegistrationPoint; - } else { - rotation = Quat.IDENTITY; - dimensions = SelectionManager.worldDimensions; - position = SelectionManager.worldPosition; - registrationPoint = SelectionManager.worldRegistrationPoint; - } - - var registrationPointDimensions = { - x: dimensions.x * registrationPoint.x, - y: dimensions.y * registrationPoint.y, - z: dimensions.z * registrationPoint.z - }; - - // Center of entity, relative to registration point - var center = getRelativeCenterPosition(dimensions, registrationPoint); - - // Distances in world coordinates relative to the registration point - var left = -registrationPointDimensions.x; - var right = dimensions.x - registrationPointDimensions.x; - var bottom = -registrationPointDimensions.y; - var top = dimensions.y - registrationPointDimensions.y; - var near = -registrationPointDimensions.z; - var far = dimensions.z - registrationPointDimensions.z; - var front = far; - - var worldTop = SelectionManager.worldDimensions.y / 2; - - var LBN = { - x: left, - y: bottom, - z: near - }; - var RBN = { - x: right, - y: bottom, - z: near - }; - var LBF = { - x: left, - y: bottom, - z: far - }; - var RBF = { - x: right, - y: bottom, - z: far - }; - var LTN = { - x: left, - y: top, - z: near - }; - var RTN = { - x: right, - y: top, - z: near - }; - var LTF = { - x: left, - y: top, - z: far - }; - var RTF = { - x: right, - y: top, - z: far - }; - - var TOP = { - x: center.x, - y: top, - z: center.z - }; - var BOTTOM = { - x: center.x, - y: bottom, - z: center.z - }; - var LEFT = { - x: left, - y: center.y, - z: center.z - }; - var RIGHT = { - x: right, - y: center.y, - z: center.z - }; - var NEAR = { - x: center.x, - y: center.y, - z: near - }; - var FAR = { - x: center.x, - y: center.y, - z: far - }; - - var EdgeTR = { - x: right, - y: top, - z: center.z - }; - var EdgeTL = { - x: left, - y: top, - z: center.z - }; - var EdgeTF = { - x: center.x, - y: top, - z: front - }; - var EdgeTN = { - x: center.x, - y: top, - z: near - }; - var EdgeBR = { - x: right, - y: bottom, - z: center.z - }; - var EdgeBL = { - x: left, - y: bottom, - z: center.z - }; - var EdgeBF = { - x: center.x, - y: bottom, - z: front - }; - var EdgeBN = { - x: center.x, - y: bottom, - z: near - }; - var EdgeNR = { - x: right, - y: center.y, - z: near - }; - var EdgeNL = { - x: left, - y: center.y, - z: near - }; - var EdgeFR = { - x: right, - y: center.y, - z: front - }; - var EdgeFL = { - x: left, - y: center.y, - z: front - }; - - LBN = Vec3.multiplyQbyV(rotation, LBN); - RBN = Vec3.multiplyQbyV(rotation, RBN); - LBF = Vec3.multiplyQbyV(rotation, LBF); - RBF = Vec3.multiplyQbyV(rotation, RBF); - LTN = Vec3.multiplyQbyV(rotation, LTN); - RTN = Vec3.multiplyQbyV(rotation, RTN); - LTF = Vec3.multiplyQbyV(rotation, LTF); - RTF = Vec3.multiplyQbyV(rotation, RTF); - - TOP = Vec3.multiplyQbyV(rotation, TOP); - BOTTOM = Vec3.multiplyQbyV(rotation, BOTTOM); - LEFT = Vec3.multiplyQbyV(rotation, LEFT); - RIGHT = Vec3.multiplyQbyV(rotation, RIGHT); - NEAR = Vec3.multiplyQbyV(rotation, NEAR); - FAR = Vec3.multiplyQbyV(rotation, FAR); - - EdgeTR = Vec3.multiplyQbyV(rotation, EdgeTR); - EdgeTL = Vec3.multiplyQbyV(rotation, EdgeTL); - EdgeTF = Vec3.multiplyQbyV(rotation, EdgeTF); - EdgeTN = Vec3.multiplyQbyV(rotation, EdgeTN); - EdgeBR = Vec3.multiplyQbyV(rotation, EdgeBR); - EdgeBL = Vec3.multiplyQbyV(rotation, EdgeBL); - EdgeBF = Vec3.multiplyQbyV(rotation, EdgeBF); - EdgeBN = Vec3.multiplyQbyV(rotation, EdgeBN); - EdgeNR = Vec3.multiplyQbyV(rotation, EdgeNR); - EdgeNL = Vec3.multiplyQbyV(rotation, EdgeNL); - EdgeFR = Vec3.multiplyQbyV(rotation, EdgeFR); - EdgeFL = Vec3.multiplyQbyV(rotation, EdgeFL); - - LBN = Vec3.sum(position, LBN); - RBN = Vec3.sum(position, RBN); - LBF = Vec3.sum(position, LBF); - RBF = Vec3.sum(position, RBF); - LTN = Vec3.sum(position, LTN); - RTN = Vec3.sum(position, RTN); - LTF = Vec3.sum(position, LTF); - RTF = Vec3.sum(position, RTF); - - TOP = Vec3.sum(position, TOP); - BOTTOM = Vec3.sum(position, BOTTOM); - LEFT = Vec3.sum(position, LEFT); - RIGHT = Vec3.sum(position, RIGHT); - NEAR = Vec3.sum(position, NEAR); - FAR = Vec3.sum(position, FAR); - - EdgeTR = Vec3.sum(position, EdgeTR); - EdgeTL = Vec3.sum(position, EdgeTL); - EdgeTF = Vec3.sum(position, EdgeTF); - EdgeTN = Vec3.sum(position, EdgeTN); - EdgeBR = Vec3.sum(position, EdgeBR); - EdgeBL = Vec3.sum(position, EdgeBL); - EdgeBF = Vec3.sum(position, EdgeBF); - EdgeBN = Vec3.sum(position, EdgeBN); - EdgeNR = Vec3.sum(position, EdgeNR); - EdgeNL = Vec3.sum(position, EdgeNL); - EdgeFR = Vec3.sum(position, EdgeFR); - EdgeFL = Vec3.sum(position, EdgeFL); - - var inModeRotate = (isActiveTool(yawHandle) || isActiveTool(pitchHandle) || isActiveTool(rollHandle)); - var inModeTranslate = (isActiveTool(selectionBox) || isActiveTool(grabberCloner) || isActiveTool(grabberMoveUp)); - var stretchHandlesVisible = !(inModeRotate || inModeTranslate) && (spaceMode === SPACE_LOCAL); - var extendedStretchHandlesVisible = (stretchHandlesVisible && showExtendedStretchHandles); - var cloneHandleVisible = !(inModeRotate || inModeTranslate); - if (wantDebug) { - print(" Set Non-Light Grabbers Visible - Norm: " + stretchHandlesVisible + " Ext: " + extendedStretchHandlesVisible); - } - var isSingleSelection = (SelectionManager.selections.length === 1); - - if (isSingleSelection) { - var properties = Entities.getEntityProperties(SelectionManager.selections[0]); - var isLightSelection = (properties.type === "Light"); - if (isLightSelection) { - if (wantDebug) { - print(" Light Selection revoking Non-Light Grabbers Visibility!"); - } - stretchHandlesVisible = false; - extendedStretchHandlesVisible = false; - cloneHandleVisible = false; - if (properties.isSpotlight) { - that.setPointLightHandlesVisible(false); - - var distance = (properties.dimensions.z / 2) * Math.sin(properties.cutoff * (Math.PI / 180)); - var showEdgeSpotGrabbers = !(inModeTranslate || inModeRotate); - Overlays.editOverlay(grabberSpotLightCenter, { - position: position, - visible: false - }); - Overlays.editOverlay(grabberSpotLightRadius, { - position: NEAR, - rotation: rotation, - visible: showEdgeSpotGrabbers - }); - - Overlays.editOverlay(grabberSpotLightL, { - position: EdgeNL, - rotation: rotation, - visible: showEdgeSpotGrabbers - }); - Overlays.editOverlay(grabberSpotLightR, { - position: EdgeNR, - rotation: rotation, - visible: showEdgeSpotGrabbers - }); - Overlays.editOverlay(grabberSpotLightT, { - position: EdgeTN, - rotation: rotation, - visible: showEdgeSpotGrabbers - }); - Overlays.editOverlay(grabberSpotLightB, { - position: EdgeBN, - rotation: rotation, - visible: showEdgeSpotGrabbers - }); - Overlays.editOverlay(grabberSpotLightCircle, { - position: NEAR, - dimensions: { - x: distance, - y: distance, - z: 1 - }, - rotation: rotation, - visible: true - }); - - Overlays.editOverlay(grabberSpotLightLineT, { - start: position, - end: EdgeTN, - visible: true - }); - Overlays.editOverlay(grabberSpotLightLineB, { - start: position, - end: EdgeBN, - visible: true - }); - Overlays.editOverlay(grabberSpotLightLineR, { - start: position, - end: EdgeNR, - visible: true - }); - Overlays.editOverlay(grabberSpotLightLineL, { - start: position, - end: EdgeNL, - visible: true - }); - - } else { // ..it's a PointLight - that.setSpotLightHandlesVisible(false); - - var showEdgePointGrabbers = !inModeTranslate; - Overlays.editOverlay(grabberPointLightT, { - position: TOP, - rotation: rotation, - visible: showEdgePointGrabbers - }); - Overlays.editOverlay(grabberPointLightB, { - position: BOTTOM, - rotation: rotation, - visible: showEdgePointGrabbers - }); - Overlays.editOverlay(grabberPointLightL, { - position: LEFT, - rotation: rotation, - visible: showEdgePointGrabbers - }); - Overlays.editOverlay(grabberPointLightR, { - position: RIGHT, - rotation: rotation, - visible: showEdgePointGrabbers - }); - Overlays.editOverlay(grabberPointLightF, { - position: FAR, - rotation: rotation, - visible: showEdgePointGrabbers - }); - Overlays.editOverlay(grabberPointLightN, { - position: NEAR, - rotation: rotation, - visible: showEdgePointGrabbers - }); - Overlays.editOverlay(grabberPointLightCircleX, { - position: position, - rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(0, 90, 0)), - dimensions: { - x: properties.dimensions.z / 2.0, - y: properties.dimensions.z / 2.0, - z: 1 - }, - visible: true - }); - Overlays.editOverlay(grabberPointLightCircleY, { - position: position, - rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(90, 0, 0)), - dimensions: { - x: properties.dimensions.z / 2.0, - y: properties.dimensions.z / 2.0, - z: 1 - }, - visible: true - }); - Overlays.editOverlay(grabberPointLightCircleZ, { - position: position, - rotation: rotation, - dimensions: { - x: properties.dimensions.z / 2.0, - y: properties.dimensions.z / 2.0, - z: 1 - }, - visible: true - }); - } - } else { // ..it's not a light at all - that.setSpotLightHandlesVisible(false); - that.setPointLightHandlesVisible(false); - } - }// end of isSingleSelection - - - Overlays.editOverlay(grabberLBN, { - visible: stretchHandlesVisible, - rotation: rotation, - position: LBN - }); - Overlays.editOverlay(grabberRBN, { - visible: stretchHandlesVisible, - rotation: rotation, - position: RBN - }); - Overlays.editOverlay(grabberLBF, { - visible: stretchHandlesVisible, - rotation: rotation, - position: LBF - }); - Overlays.editOverlay(grabberRBF, { - visible: stretchHandlesVisible, - rotation: rotation, - position: RBF - }); - - Overlays.editOverlay(grabberLTN, { - visible: extendedStretchHandlesVisible, - rotation: rotation, - position: LTN - }); - Overlays.editOverlay(grabberRTN, { - visible: extendedStretchHandlesVisible, - rotation: rotation, - position: RTN - }); - Overlays.editOverlay(grabberLTF, { - visible: extendedStretchHandlesVisible, - rotation: rotation, - position: LTF - }); - Overlays.editOverlay(grabberRTF, { - visible: extendedStretchHandlesVisible, - rotation: rotation, - position: RTF - }); - - Overlays.editOverlay(grabberTOP, { - visible: stretchHandlesVisible, - rotation: rotation, - position: TOP - }); - Overlays.editOverlay(grabberBOTTOM, { - visible: stretchHandlesVisible, - rotation: rotation, - position: BOTTOM - }); - Overlays.editOverlay(grabberLEFT, { - visible: extendedStretchHandlesVisible, - rotation: rotation, - position: LEFT - }); - Overlays.editOverlay(grabberRIGHT, { - visible: extendedStretchHandlesVisible, - rotation: rotation, - position: RIGHT - }); - Overlays.editOverlay(grabberNEAR, { - visible: extendedStretchHandlesVisible, - rotation: rotation, - position: NEAR - }); - Overlays.editOverlay(grabberFAR, { - visible: extendedStretchHandlesVisible, - rotation: rotation, - position: FAR - }); - - Overlays.editOverlay(grabberCloner, { - visible: cloneHandleVisible, - rotation: rotation, - position: EdgeTR - }); - - var selectionBoxPosition = Vec3.multiplyQbyV(rotation, center); - selectionBoxPosition = Vec3.sum(position, selectionBoxPosition); - Overlays.editOverlay(selectionBox, { - position: selectionBoxPosition, - dimensions: dimensions, - rotation: rotation, - visible: !inModeRotate - }); - - // Create more selection box overlays if we don't have enough - var overlaysNeeded = SelectionManager.selections.length - selectionBoxes.length; - for (var i = 0; i < overlaysNeeded; i++) { - selectionBoxes.push( - Overlays.addOverlay("cube", { - position: { - x: 0, - y: 0, - z: 0 - }, - size: 1, - color: { - red: 255, - green: 153, - blue: 0 - }, - alpha: 1, - solid: false, - visible: false, - dashed: false, - ignoreRayIntersection: true - })); - } - - i = 0; - // Only show individual selections boxes if there is more than 1 selection - if (SelectionManager.selections.length > 1) { - for (; i < SelectionManager.selections.length; i++) { - var props = Entities.getEntityProperties(SelectionManager.selections[i]); - - // Adjust overlay position to take registrationPoint into account - // centeredRP = registrationPoint with range [-0.5, 0.5] - var centeredRP = Vec3.subtract(props.registrationPoint, { - x: 0.5, - y: 0.5, - z: 0.5 - }); - var offset = vec3Mult(props.dimensions, centeredRP); - offset = Vec3.multiply(-1, offset); - offset = Vec3.multiplyQbyV(props.rotation, offset); - var curBoxPosition = Vec3.sum(props.position, offset); - - var color = {red: 255, green: 128, blue: 0}; - if (i >= SelectionManager.selections.length - 1) { - color = {red: 255, green: 255, blue: 64}; - } - - Overlays.editOverlay(selectionBoxes[i], { - position: curBoxPosition, - color: color, - rotation: props.rotation, - dimensions: props.dimensions, - visible: true - }); - } - } - // Hide any remaining selection boxes - for (; i < selectionBoxes.length; i++) { - Overlays.editOverlay(selectionBoxes[i], { - visible: false - }); - } - - Overlays.editOverlay(grabberEdgeTR, { - visible: extendedStretchHandlesVisible, - rotation: rotation, - position: EdgeTR - }); - Overlays.editOverlay(grabberEdgeTL, { - visible: extendedStretchHandlesVisible, - rotation: rotation, - position: EdgeTL - }); - Overlays.editOverlay(grabberEdgeTF, { - visible: extendedStretchHandlesVisible, - rotation: rotation, - position: EdgeTF - }); - Overlays.editOverlay(grabberEdgeTN, { - visible: extendedStretchHandlesVisible, - rotation: rotation, - position: EdgeTN - }); - Overlays.editOverlay(grabberEdgeBR, { - visible: stretchHandlesVisible, - rotation: rotation, - position: EdgeBR - }); - Overlays.editOverlay(grabberEdgeBL, { - visible: stretchHandlesVisible, - rotation: rotation, - position: EdgeBL - }); - Overlays.editOverlay(grabberEdgeBF, { - visible: stretchHandlesVisible, - rotation: rotation, - position: EdgeBF - }); - Overlays.editOverlay(grabberEdgeBN, { - visible: stretchHandlesVisible, - rotation: rotation, - position: EdgeBN - }); - Overlays.editOverlay(grabberEdgeNR, { - visible: extendedStretchHandlesVisible, - rotation: rotation, - position: EdgeNR - }); - Overlays.editOverlay(grabberEdgeNL, { - visible: extendedStretchHandlesVisible, - rotation: rotation, - position: EdgeNL - }); - Overlays.editOverlay(grabberEdgeFR, { - visible: extendedStretchHandlesVisible, - rotation: rotation, - position: EdgeFR - }); - Overlays.editOverlay(grabberEdgeFL, { - visible: extendedStretchHandlesVisible, - rotation: rotation, - position: EdgeFL - }); - - var grabberMoveUpOffset = 0.1; - var upVec = Quat.getUp(MyAvatar.orientation); - grabberMoveUpPosition = { - x: position.x + (grabberMoveUpOffset + worldTop) * upVec.x , - y: position.y+ (grabberMoveUpOffset + worldTop) * upVec.y, - z: position.z + (grabberMoveUpOffset + worldTop) * upVec.z - }; - Overlays.editOverlay(grabberMoveUp, { - visible: (!activeTool) || isActiveTool(grabberMoveUp) - }); - - Overlays.editOverlay(baseOfEntityProjectionOverlay, { - visible: !inModeRotate, - solid: true, - position: { - x: SelectionManager.worldPosition.x, - y: grid.getOrigin().y, - z: SelectionManager.worldPosition.z - }, - dimensions: { - x: SelectionManager.worldDimensions.x, - y: SelectionManager.worldDimensions.z - }, - rotation: Quat.fromPitchYawRollDegrees(90, 0, 0) - }); - - if (wantDebug) { - print("====== Update Handles <======="); - } - - }; - - function helperSetOverlaysVisibility(handleArray, isVisible) { - var numHandles = handleArray.length; - var visibilityUpdate = { visible: isVisible }; - for (var handleIndex = 0; handleIndex < numHandles; ++handleIndex) { - Overlays.editOverlay(handleArray[ handleIndex ], visibilityUpdate); - } - } - - // FUNCTION: SET OVERLAYS VISIBLE - that.setOverlaysVisible = function(isVisible) { - helperSetOverlaysVisibility(allOverlays, isVisible); - helperSetOverlaysVisibility(selectionBoxes, isVisible); - }; - - // FUNCTION: SET ROTATION HANDLES VISIBLE - that.setRotationHandlesVisible = function(isVisible) { - var visibilityUpdate = { visible: isVisible }; - Overlays.editOverlay(yawHandle, visibilityUpdate); - Overlays.editOverlay(pitchHandle, visibilityUpdate); - Overlays.editOverlay(rollHandle, visibilityUpdate); - }; - - // FUNCTION: SET STRETCH HANDLES VISIBLE - that.setStretchHandlesVisible = function(isVisible) { - helperSetOverlaysVisibility(stretchHandles, isVisible); - }; - - // FUNCTION: SET GRABBER MOVE UP VISIBLE - that.setGrabberMoveUpVisible = function(isVisible) { - Overlays.editOverlay(grabberMoveUp, { visible: isVisible }); - }; - - // FUNCTION: SET GRABBER TOOLS UP VISIBLE - that.setGrabberToolsVisible = function(isVisible) { - var visibilityUpdate = { visible: isVisible }; - for (var toolKey in grabberTools) { - if (!grabberTools.hasOwnProperty(toolKey)) { - // EARLY ITERATION EXIT--(On to the next one) - continue; - } - - Overlays.editOverlay(toolKey, visibilityUpdate); - } - }; - - // FUNCTION: SET POINT LIGHT HANDLES VISIBLE - that.setPointLightHandlesVisible = function(isVisible) { - helperSetOverlaysVisibility(pointLightGrabberHandles, isVisible); - }; - - // FUNCTION: SET SPOT LIGHT HANDLES VISIBLE - that.setSpotLightHandlesVisible = function(isVisible) { - helperSetOverlaysVisibility(spotLightGrabberHandles, isVisible); - }; - - // FUNCTION: UNSELECT - // TODO?: Needs implementation - that.unselect = function(entityID) {}; - - var initialXZPick = null; - var isConstrained = false; - var constrainMajorOnly = false; - var startPosition = null; - var duplicatedEntityIDs = null; - - // TOOL DEFINITION: TRANSLATE XZ TOOL - var translateXZTool = addGrabberTool(selectionBox,{ - mode: 'TRANSLATE_XZ', - pickPlanePosition: { x: 0, y: 0, z: 0 }, - greatestDimension: 0.0, - startingDistance: 0.0, - startingElevation: 0.0, - onBegin: function(event, pickRay, pickResult, doClone) { - var wantDebug = false; - if (wantDebug) { - print("================== TRANSLATE_XZ(Beg) -> ======================="); - Vec3.print(" pickRay", pickRay); - Vec3.print(" pickRay.origin", pickRay.origin); - Vec3.print(" pickResult.intersection", pickResult.intersection); - } - - SelectionManager.saveProperties(); - that.setRotationHandlesVisible(false); - that.setStretchHandlesVisible(false); - that.setGrabberMoveUpVisible(false); - - startPosition = SelectionManager.worldPosition; - - translateXZTool.pickPlanePosition = pickResult.intersection; - translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), SelectionManager.worldDimensions.z); - translateXZTool.startingDistance = Vec3.distance(pickRay.origin, SelectionManager.position); - translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition); - if (wantDebug) { - print(" longest dimension: " + translateXZTool.greatestDimension); - print(" starting distance: " + translateXZTool.startingDistance); - print(" starting elevation: " + translateXZTool.startingElevation); - } - - initialXZPick = rayPlaneIntersection(pickRay, translateXZTool.pickPlanePosition, { - x: 0, - y: 1, - z: 0 - }); - - // Duplicate entities if alt is pressed. This will make a - // copy of the selected entities and move the _original_ entities, not - // the new ones. - if (event.isAlt || doClone) { - duplicatedEntityIDs = []; - for (var otherEntityID in SelectionManager.savedProperties) { - var properties = SelectionManager.savedProperties[otherEntityID]; - if (!properties.locked) { - var entityID = Entities.addEntity(properties); - duplicatedEntityIDs.push({ - entityID: entityID, - properties: properties - }); - } - } - } else { - duplicatedEntityIDs = null; - } - - isConstrained = false; - if (wantDebug) { - print("================== TRANSLATE_XZ(End) <- ======================="); - } - }, - onEnd: function(event, reason) { - pushCommandForSelections(duplicatedEntityIDs); - - Overlays.editOverlay(xRailOverlay, { - visible: false - }); - Overlays.editOverlay(zRailOverlay, { - visible: false - }); - }, - elevation: function(origin, intersection) { - return (origin.y - intersection.y) / Vec3.distance(origin, intersection); - }, - onMove: function(event) { - var wantDebug = false; - pickRay = generalComputePickRay(event.x, event.y); - - var pick = rayPlaneIntersection2(pickRay, translateXZTool.pickPlanePosition, { - x: 0, - y: 1, - z: 0 - }); - - // If the pick ray doesn't hit the pick plane in this direction, do nothing. - // this will happen when someone drags across the horizon from the side they started on. - if (!pick) { - if (wantDebug) { - print(" "+ translateXZTool.mode + "Pick ray does not intersect XZ plane."); - } - - // EARLY EXIT--(Invalid ray detected.) - return; - } - - var vector = Vec3.subtract(pick, initialXZPick); - - // If the mouse is too close to the horizon of the pick plane, stop moving - var MIN_ELEVATION = 0.02; // largest dimension of object divided by distance to it - var elevation = translateXZTool.elevation(pickRay.origin, pick); - if (wantDebug) { - print("Start Elevation: " + translateXZTool.startingElevation + ", elevation: " + elevation); - } - if ((translateXZTool.startingElevation > 0.0 && elevation < MIN_ELEVATION) || - (translateXZTool.startingElevation < 0.0 && elevation > -MIN_ELEVATION)) { - if (wantDebug) { - print(" "+ translateXZTool.mode + " - too close to horizon!"); - } - - // EARLY EXIT--(Don't proceed past the reached limit.) - return; - } - - // If the angular size of the object is too small, stop moving - var MIN_ANGULAR_SIZE = 0.01; // Radians - if (translateXZTool.greatestDimension > 0) { - var angularSize = Math.atan(translateXZTool.greatestDimension / Vec3.distance(pickRay.origin, pick)); - if (wantDebug) { - print("Angular size = " + angularSize); - } - if (angularSize < MIN_ANGULAR_SIZE) { - return; - } - } - - // If shifted, constrain to one axis - if (event.isShifted) { - if (Math.abs(vector.x) > Math.abs(vector.z)) { - vector.z = 0; - } else { - vector.x = 0; - } - if (!isConstrained) { - Overlays.editOverlay(xRailOverlay, { - visible: true - }); - var xStart = Vec3.sum(startPosition, { - x: -10000, - y: 0, - z: 0 - }); - var xEnd = Vec3.sum(startPosition, { - x: 10000, - y: 0, - z: 0 - }); - var zStart = Vec3.sum(startPosition, { - x: 0, - y: 0, - z: -10000 - }); - var zEnd = Vec3.sum(startPosition, { - x: 0, - y: 0, - z: 10000 - }); - Overlays.editOverlay(xRailOverlay, { - start: xStart, - end: xEnd, - visible: true - }); - Overlays.editOverlay(zRailOverlay, { - start: zStart, - end: zEnd, - visible: true - }); - isConstrained = true; - } - } else { - if (isConstrained) { - Overlays.editOverlay(xRailOverlay, { - visible: false - }); - Overlays.editOverlay(zRailOverlay, { - visible: false - }); - isConstrained = false; - } - } - - constrainMajorOnly = event.isControl; - var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(-0.5, SelectionManager.worldDimensions)); - vector = Vec3.subtract( - grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly), - cornerPosition); - - - for (var i = 0; i < SelectionManager.selections.length; i++) { - var properties = SelectionManager.savedProperties[SelectionManager.selections[i]]; - if (!properties) { - continue; - } - var newPosition = Vec3.sum(properties.position, { - x: vector.x, - y: 0, - z: vector.z - }); - Entities.editEntity(SelectionManager.selections[i], { - position: newPosition - }); - - if (wantDebug) { - print("translateXZ... "); - Vec3.print(" vector:", vector); - Vec3.print(" newPosition:", properties.position); - Vec3.print(" newPosition:", newPosition); - } - } - - SelectionManager._update(); - } - }); - - // GRABBER TOOL: GRABBER MOVE UP - var lastXYPick = null; - var upDownPickNormal = null; - addGrabberTool(grabberMoveUp, { - mode: "TRANSLATE_UP_DOWN", - onBegin: function(event, pickRay, pickResult) { - upDownPickNormal = Quat.getForward(lastCameraOrientation); - lastXYPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, upDownPickNormal); - - SelectionManager.saveProperties(); - that.setGrabberMoveUpVisible(true); - that.setStretchHandlesVisible(false); - that.setRotationHandlesVisible(false); - - // Duplicate entities if alt is pressed. This will make a - // copy of the selected entities and move the _original_ entities, not - // the new ones. - if (event.isAlt) { - duplicatedEntityIDs = []; - for (var otherEntityID in SelectionManager.savedProperties) { - var properties = SelectionManager.savedProperties[otherEntityID]; - if (!properties.locked) { - var entityID = Entities.addEntity(properties); - duplicatedEntityIDs.push({ - entityID: entityID, - properties: properties - }); - } - } - } else { - duplicatedEntityIDs = null; - } - }, - onEnd: function(event, reason) { - pushCommandForSelections(duplicatedEntityIDs); - }, - onMove: function(event) { - pickRay = generalComputePickRay(event.x, event.y); - - // translate mode left/right based on view toward entity - var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, upDownPickNormal); - - var vector = Vec3.subtract(newIntersection, lastXYPick); - - // project vector onto avatar up vector - // we want the avatar referential not the camera. - var avatarUpVector = Quat.getUp(MyAvatar.orientation); - var dotVectorUp = Vec3.dot(vector, avatarUpVector); - vector = Vec3.multiply(dotVectorUp, avatarUpVector); - - - vector = grid.snapToGrid(vector); - - - - var wantDebug = false; - if (wantDebug) { - print("translateUpDown... "); - print(" event.y:" + event.y); - Vec3.print(" newIntersection:", newIntersection); - Vec3.print(" vector:", vector); - // Vec3.print(" newPosition:", newPosition); - } - for (var i = 0; i < SelectionManager.selections.length; i++) { - var id = SelectionManager.selections[i]; - var properties = SelectionManager.savedProperties[id]; - - var original = properties.position; - var newPosition = Vec3.sum(properties.position, vector); - - Entities.editEntity(id, { - position: newPosition - }); - } - - SelectionManager._update(); - } - }); - - // GRABBER TOOL: GRABBER CLONER - addGrabberTool(grabberCloner, { - mode: "CLONE", - onBegin: function(event, pickRay, pickResult) { - var doClone = true; - translateXZTool.onBegin(event,pickRay,pickResult,doClone); - }, - elevation: function (event) { - translateXZTool.elevation(event); - }, - - onEnd: function (event) { - translateXZTool.onEnd(event); - }, - - onMove: function (event) { - translateXZTool.onMove(event); - } - }); - - - // FUNCTION: VEC 3 MULT - var vec3Mult = function(v1, v2) { - return { - x: v1.x * v2.x, - y: v1.y * v2.y, - z: v1.z * v2.z - }; - }; - - // FUNCTION: MAKE STRETCH TOOL - // stretchMode - name of mode - // direction - direction to stretch in - // pivot - point to use as a pivot - // offset - the position of the overlay tool relative to the selections center position - // @return: tool obj - var makeStretchTool = function(stretchMode, direction, pivot, offset, customOnMove) { - // directionFor3DStretch - direction and pivot for 3D stretch - // distanceFor3DStretch - distance from the intersection point and the handController - // used to increase the scale taking into account the distance to the object - // DISTANCE_INFLUENCE_THRESHOLD - constant that holds the minimum distance where the - // distance to the object will influence the stretch/resize/scale - var directionFor3DStretch = getDirectionsFor3DStretch(stretchMode); - var distanceFor3DStretch = 0; - var DISTANCE_INFLUENCE_THRESHOLD = 1.2; - - - var signs = { - x: direction.x < 0 ? -1 : (direction.x > 0 ? 1 : 0), - y: direction.y < 0 ? -1 : (direction.y > 0 ? 1 : 0), - z: direction.z < 0 ? -1 : (direction.z > 0 ? 1 : 0) - }; - - var mask = { - x: Math.abs(direction.x) > 0 ? 1 : 0, - y: Math.abs(direction.y) > 0 ? 1 : 0, - z: Math.abs(direction.z) > 0 ? 1 : 0 - }; - - - var numDimensions = mask.x + mask.y + mask.z; - - var planeNormal = null; + function addGrabberTranslateTool(overlay, mode, direction) { + var pickNormal = null; var lastPick = null; - var lastPick3D = null; - var initialPosition = null; - var initialDimensions = null; - var initialIntersection = null; - var initialProperties = null; - var registrationPoint = null; - var deltaPivot = null; - var deltaPivot3D = null; - var pickRayPosition = null; - var pickRayPosition3D = null; - var rotation = null; - - var onBegin = function(event, pickRay, pickResult) { - var properties = Entities.getEntityProperties(SelectionManager.selections[0]); - initialProperties = properties; - rotation = (spaceMode === SPACE_LOCAL) ? properties.rotation : Quat.IDENTITY; - - if (spaceMode === SPACE_LOCAL) { - rotation = SelectionManager.localRotation; - initialPosition = SelectionManager.localPosition; - initialDimensions = SelectionManager.localDimensions; - registrationPoint = SelectionManager.localRegistrationPoint; - } else { - rotation = SelectionManager.worldRotation; - initialPosition = SelectionManager.worldPosition; - initialDimensions = SelectionManager.worldDimensions; - registrationPoint = SelectionManager.worldRegistrationPoint; - } - - // Modify range of registrationPoint to be [-0.5, 0.5] - var centeredRP = Vec3.subtract(registrationPoint, { - x: 0.5, - y: 0.5, - z: 0.5 - }); - - // Scale pivot to be in the same range as registrationPoint - var scaledPivot = Vec3.multiply(0.5, pivot); - deltaPivot = Vec3.subtract(centeredRP, scaledPivot); - - var scaledOffset = Vec3.multiply(0.5, offset); - - // Offset from the registration point - offsetRP = Vec3.subtract(scaledOffset, centeredRP); - - // Scaled offset in world coordinates - var scaledOffsetWorld = vec3Mult(initialDimensions, offsetRP); - - pickRayPosition = Vec3.sum(initialPosition, Vec3.multiplyQbyV(rotation, scaledOffsetWorld)); - - if (directionFor3DStretch) { - // pivot, offset and pickPlanePosition for 3D manipulation - var scaledPivot3D = Vec3.multiply(0.5, Vec3.multiply(1.0, directionFor3DStretch)); - deltaPivot3D = Vec3.subtract(centeredRP, scaledPivot3D); - - var scaledOffsetWorld3D = vec3Mult(initialDimensions, - Vec3.subtract(Vec3.multiply(0.5, Vec3.multiply(-1.0, directionFor3DStretch)), centeredRP)); - - pickRayPosition3D = Vec3.sum(initialPosition, Vec3.multiplyQbyV(rotation, scaledOffsetWorld)); - } - var start = null; - var end = null; - if ((numDimensions === 1) && mask.x) { - start = Vec3.multiplyQbyV(rotation, { - x: -10000, - y: 0, - z: 0 - }); - start = Vec3.sum(start, properties.position); - end = Vec3.multiplyQbyV(rotation, { - x: 10000, - y: 0, - z: 0 - }); - end = Vec3.sum(end, properties.position); - Overlays.editOverlay(xRailOverlay, { - start: start, - end: end, - visible: true - }); - } - if ((numDimensions === 1) && mask.y) { - start = Vec3.multiplyQbyV(rotation, { - x: 0, - y: -10000, - z: 0 - }); - start = Vec3.sum(start, properties.position); - end = Vec3.multiplyQbyV(rotation, { - x: 0, - y: 10000, - z: 0 - }); - end = Vec3.sum(end, properties.position); - Overlays.editOverlay(yRailOverlay, { - start: start, - end: end, - visible: true - }); - } - if ((numDimensions === 1) && mask.z) { - start = Vec3.multiplyQbyV(rotation, { - x: 0, - y: 0, - z: -10000 - }); - start = Vec3.sum(start, properties.position); - end = Vec3.multiplyQbyV(rotation, { - x: 0, - y: 0, - z: 10000 - }); - end = Vec3.sum(end, properties.position); - Overlays.editOverlay(zRailOverlay, { - start: start, - end: end, - visible: true - }); - } - if (numDimensions === 1) { - if (mask.x === 1) { - planeNormal = { - x: 0, - y: 1, - z: 0 - }; - } else if (mask.y === 1) { - planeNormal = { - x: 1, - y: 0, - z: 0 - }; - } else { - planeNormal = { - x: 0, - y: 1, - z: 0 - }; + var projectionVector = null; + addGrabberTool(overlay, { + mode: mode, + onBegin: function(event, pickRay, pickResult) { + if (direction === TRANSLATE_DIRECTION.X) { + pickNormal = { x:0, y:0, z:1 }; + } else if (direction === TRANSLATE_DIRECTION.Y) { + pickNormal = { x:1, y:0, z:0 }; + } else if (direction === TRANSLATE_DIRECTION.Z) { + pickNormal = { x:0, y:1, z:0 }; } - } else if (numDimensions === 2) { - if (mask.x === 0) { - planeNormal = { - x: 1, - y: 0, - z: 0 - }; - } else if (mask.y === 0) { - planeNormal = { - x: 0, - y: 1, - z: 0 - }; - } else { - planeNormal = { - x: 0, - y: 0, - z: 1 - }; - } - } - - planeNormal = Vec3.multiplyQbyV(rotation, planeNormal); - lastPick = rayPlaneIntersection(pickRay, - pickRayPosition, - planeNormal); - - var planeNormal3D = { - x: 0, - y: 0, - z: 0 - }; - if (directionFor3DStretch) { - lastPick3D = rayPlaneIntersection(pickRay, - pickRayPosition3D, - planeNormal3D); - distanceFor3DStretch = Vec3.length(Vec3.subtract(pickRayPosition3D, pickRay.origin)); - } - - SelectionManager.saveProperties(); - }; - var onEnd = function(event, reason) { - Overlays.editOverlay(xRailOverlay, { - visible: false - }); - Overlays.editOverlay(yRailOverlay, { - visible: false - }); - Overlays.editOverlay(zRailOverlay, { - visible: false - }); + var rotation = Entities.getEntityProperties(SelectionManager.selections[0]).rotation; + pickNormal = Vec3.multiplyQbyV(rotation, pickNormal); - pushCommandForSelections(); - }; + lastPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal); + + SelectionManager.saveProperties(); - var onMove = function(event) { - var proportional = (spaceMode === SPACE_WORLD) || event.isShifted || isActiveTool(grabberSpotLightRadius); - - var position, dimensions, rotation; - if (spaceMode === SPACE_LOCAL) { - position = SelectionManager.localPosition; - dimensions = SelectionManager.localDimensions; - rotation = SelectionManager.localRotation; - } else { - position = SelectionManager.worldPosition; - dimensions = SelectionManager.worldDimensions; - rotation = SelectionManager.worldRotation; - } - - var localDeltaPivot = deltaPivot; - var localSigns = signs; - - var pickRay = generalComputePickRay(event.x, event.y); - - // Are we using handControllers or Mouse - only relevant for 3D tools - var controllerPose = getControllerWorldLocation(activeHand, true); - var vector = null; - if (HMD.isHMDAvailable() && HMD.isHandControllerAvailable() && - controllerPose.valid && that.triggered && directionFor3DStretch) { - localDeltaPivot = deltaPivot3D; - - newPick = pickRay.origin; - - vector = Vec3.subtract(newPick, lastPick3D); - - vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector); - - if (distanceFor3DStretch > DISTANCE_INFLUENCE_THRESHOLD) { - // Range of Motion - vector = Vec3.multiply(distanceFor3DStretch , vector); - } - - localSigns = directionFor3DStretch; - - } else { - newPick = rayPlaneIntersection(pickRay, - pickRayPosition, - planeNormal); - vector = Vec3.subtract(newPick, lastPick); - - vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector); - - vector = vec3Mult(mask, vector); - - } - - if (customOnMove) { - var change = Vec3.multiply(-1, vec3Mult(localSigns, vector)); - customOnMove(vector, change); - } else { - vector = grid.snapToSpacing(vector); - - var changeInDimensions = Vec3.multiply(-1, vec3Mult(localSigns, vector)); - var newDimensions; - if (proportional) { - var absX = Math.abs(changeInDimensions.x); - var absY = Math.abs(changeInDimensions.y); - var absZ = Math.abs(changeInDimensions.z); - var pctChange = 0; - if (absX > absY && absX > absZ) { - pctChange = changeInDimensions.x / initialProperties.dimensions.x; - pctChange = changeInDimensions.x / initialDimensions.x; - } else if (absY > absZ) { - pctChange = changeInDimensions.y / initialProperties.dimensions.y; - pctChange = changeInDimensions.y / initialDimensions.y; - } else { - pctChange = changeInDimensions.z / initialProperties.dimensions.z; - pctChange = changeInDimensions.z / initialDimensions.z; + that.setGrabberTranslateXVisible(direction === TRANSLATE_DIRECTION.X); + that.setGrabberTranslateYVisible(direction === TRANSLATE_DIRECTION.Y); + that.setGrabberTranslateZVisible(direction === TRANSLATE_DIRECTION.Z); + that.setGrabberRotateVisible(false); + that.setGrabberStretchVisible(false); + that.setGrabberScaleVisible(false); + + // Duplicate entities if alt is pressed. This will make a + // copy of the selected entities and move the _original_ entities, not + // the new ones. + if (event.isAlt) { + duplicatedEntityIDs = []; + for (var otherEntityID in SelectionManager.savedProperties) { + var properties = SelectionManager.savedProperties[otherEntityID]; + if (!properties.locked) { + var entityID = Entities.addEntity(properties); + duplicatedEntityIDs.push({ + entityID: entityID, + properties: properties + }); + } } - pctChange += 1.0; - newDimensions = Vec3.multiply(pctChange, initialDimensions); } else { - newDimensions = Vec3.sum(initialDimensions, changeInDimensions); + duplicatedEntityIDs = null; + } + }, + onEnd: function(event, reason) { + pushCommandForSelections(duplicatedEntityIDs); + }, + onMove: function(event) { + pickRay = generalComputePickRay(event.x, event.y); + + // translate mode left/right based on view toward entity + var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal); + var vector = Vec3.subtract(newIntersection, lastPick); + + if (direction === TRANSLATE_DIRECTION.X) { + projectionVector = { x:1, y:0, z:0 }; + } else if (direction === TRANSLATE_DIRECTION.Y) { + projectionVector = { x:0, y:1, z:0 }; + } else if (direction === TRANSLATE_DIRECTION.Z) { + projectionVector = { x:0, y:0, z:1 }; } - newDimensions.x = Math.max(newDimensions.x, MINIMUM_DIMENSION); - newDimensions.y = Math.max(newDimensions.y, MINIMUM_DIMENSION); - newDimensions.z = Math.max(newDimensions.z, MINIMUM_DIMENSION); - - var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(localDeltaPivot, changeInDimensions)); - var newPosition = Vec3.sum(initialPosition, changeInPosition); + var rotation = Entities.getEntityProperties(SelectionManager.selections[0]).rotation; + projectionVector = Vec3.multiplyQbyV(rotation, projectionVector); + var dotVector = Vec3.dot(vector, projectionVector); + vector = Vec3.multiply(dotVector, projectionVector); + vector = grid.snapToGrid(vector); + + var wantDebug = false; + if (wantDebug) { + print("translateUpDown... "); + print(" event.y:" + event.y); + Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + // Vec3.print(" newPosition:", newPosition); + } + for (var i = 0; i < SelectionManager.selections.length; i++) { - Entities.editEntity(SelectionManager.selections[i], { - position: newPosition, + var id = SelectionManager.selections[i]; + var properties = SelectionManager.savedProperties[id]; + var newPosition = Vec3.sum(properties.position, vector); + Entities.editEntity(id, { position: newPosition }); + } + + SelectionManager._update(); + } + }); + } + + function addGrabberStretchTool(overlay, mode, direction) { + var pickNormal = null; + var lastPick = null; + var projectionVector = null; + var stretchPanel = null; + addGrabberTool(overlay, { + mode: mode, + onBegin: function(event, pickRay, pickResult) { + if (direction === TRANSLATE_DIRECTION.X) { + stretchPanel = grabberStretchXPanel; + pickNormal = { x:0, y:0, z:1 }; + } else if (direction === TRANSLATE_DIRECTION.Y) { + stretchPanel = grabberStretchYPanel; + pickNormal = { x:1, y:0, z:0 }; + } else if (direction === TRANSLATE_DIRECTION.Z) { + stretchPanel = grabberStretchZPanel; + pickNormal = { x:0, y:1, z:0 }; + } + + Overlays.editOverlay(stretchPanel, { visible:true }); + + var rotation = Entities.getEntityProperties(SelectionManager.selections[0]).rotation; + pickNormal = Vec3.multiplyQbyV(rotation, pickNormal); + + lastPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal); + + SelectionManager.saveProperties(); + + that.setGrabberTranslateVisible(false); + that.setGrabberRotateVisible(false); + that.setGrabberStretchXVisible(direction === TRANSLATE_DIRECTION.X); + that.setGrabberStretchYVisible(direction === TRANSLATE_DIRECTION.Y); + that.setGrabberStretchZVisible(direction === TRANSLATE_DIRECTION.Z); + + // Duplicate entities if alt is pressed. This will make a + // copy of the selected entities and move the _original_ entities, not + // the new ones. + if (event.isAlt) { + duplicatedEntityIDs = []; + for (var otherEntityID in SelectionManager.savedProperties) { + var properties = SelectionManager.savedProperties[otherEntityID]; + if (!properties.locked) { + var entityID = Entities.addEntity(properties); + duplicatedEntityIDs.push({ + entityID: entityID, + properties: properties + }); + } + } + } else { + duplicatedEntityIDs = null; + } + }, + onEnd: function(event, reason) { + Overlays.editOverlay(stretchPanel, { visible:false }); + pushCommandForSelections(duplicatedEntityIDs); + }, + onMove: function(event) { + pickRay = generalComputePickRay(event.x, event.y); + + // translate mode left/right based on view toward entity + var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal); + var vector = Vec3.subtract(newIntersection, lastPick); + + if (direction === TRANSLATE_DIRECTION.X) { + projectionVector = { x:1, y:0, z:0 }; + } else if (direction === TRANSLATE_DIRECTION.Y) { + projectionVector = { x:0, y:1, z:0 }; + } else if (direction === TRANSLATE_DIRECTION.Z) { + projectionVector = { x:0, y:0, z:1 }; + } + + var rotation = Entities.getEntityProperties(SelectionManager.selections[0]).rotation; + projectionVector = Vec3.multiplyQbyV(rotation, projectionVector); + + var dotVector = Vec3.dot(vector, projectionVector); + vector = Vec3.multiply(dotVector, projectionVector); + vector = grid.snapToGrid(vector); + + var wantDebug = false; + if (wantDebug) { + print("translateUpDown... "); + print(" event.y:" + event.y); + Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + // Vec3.print(" newPosition:", newPosition); + } + + for (var i = 0; i < SelectionManager.selections.length; i++) { + var id = SelectionManager.selections[i]; + var properties = SelectionManager.savedProperties[id]; + var newPosition = Vec3.sum(properties.position, vector); + var difference = Vec3.subtract(newPosition, SelectionManager.worldPosition); + var halfDifference = Vec3.multiply(difference, 0.5); + var quarterDifference = Vec3.multiply(halfDifference, 0.5); + var newDimensions = properties.dimensions; + var actualNewPosition = properties.position; + + if (direction == TRANSLATE_DIRECTION.X) { + newDimensions.x += halfDifference.x; + actualNewPosition.x += quarterDifference.x; + } else if (direction == TRANSLATE_DIRECTION.Y) { + newDimensions.y += halfDifference.y; + actualNewPosition.y += quarterDifference.y; + } else if (direction == TRANSLATE_DIRECTION.Z) { + newDimensions.z += halfDifference.z; + actualNewPosition.z += quarterDifference.z; + } + + Entities.editEntity(id, { + position: actualNewPosition, dimensions: newDimensions }); } - + + SelectionManager._update(); + } + }); + } + + function addGrabberScaleTool(overlay, mode, direction) { + var pickNormal = null; + var lastPick = null; + var selectedGrabber = null; + addGrabberTool(overlay, { + mode: mode, + onBegin: function(event, pickRay, pickResult) { + pickNormal = { x:-1, y:1, z:-1 }; + + if (direction === SCALE_DIRECTION.LBN) { + selectedGrabber = grabberScaleLBNCube; + } else if (direction === SCALE_DIRECTION.RBN) { + selectedGrabber = grabberScaleRBNCube; + } else if (direction === SCALE_DIRECTION.LBF) { + selectedGrabber = grabberScaleLBFCube; + } else if (direction === SCALE_DIRECTION.RBF) { + selectedGrabber = grabberScaleRBFCube; + } else if (direction === SCALE_DIRECTION.LTN) { + selectedGrabber = grabberScaleLTNCube; + } else if (direction === SCALE_DIRECTION.RTN) { + selectedGrabber = grabberScaleRTNCube; + } else if (direction === SCALE_DIRECTION.LTF) { + selectedGrabber = grabberScaleLTFCube; + } else if (direction === SCALE_DIRECTION.RTF) { + selectedGrabber = grabberScaleRTFCube; + } + Overlays.editOverlay(selectedGrabber, { color: GRABBER_SCALE_CUBE_SELECTED_COLOR }); + + lastPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal); + + SelectionManager.saveProperties(); + + that.setGrabberTranslateVisible(false); + that.setGrabberRotateVisible(false); + that.setGrabberStretchVisible(false); + that.setGrabberScaleVisible(true); + + // Duplicate entities if alt is pressed. This will make a + // copy of the selected entities and move the _original_ entities, not + // the new ones. + if (event.isAlt) { + duplicatedEntityIDs = []; + for (var otherEntityID in SelectionManager.savedProperties) { + var properties = SelectionManager.savedProperties[otherEntityID]; + if (!properties.locked) { + var entityID = Entities.addEntity(properties); + duplicatedEntityIDs.push({ + entityID: entityID, + properties: properties + }); + } + } + } else { + duplicatedEntityIDs = null; + } + }, + onEnd: function(event, reason) { + Overlays.editOverlay(selectedGrabber, { color: GRABBER_SCALE_CUBE_IDLE_COLOR }); + pushCommandForSelections(duplicatedEntityIDs); + }, + onMove: function(event) { + pickRay = generalComputePickRay(event.x, event.y); + + // translate mode left/right based on view toward entity + var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal); + var vector = Vec3.subtract(newIntersection, lastPick); + + var projectionVector; + if (direction === SCALE_DIRECTION.LBN) { + projectionVector = { x:-1, y:-1, z:-1 }; + } else if (direction === SCALE_DIRECTION.RBN) { + projectionVector = { x:-1, y:-1, z:1 }; + } else if (direction === SCALE_DIRECTION.LBF) { + projectionVector = { x:1, y:-1, z:-1 }; + } else if (direction === SCALE_DIRECTION.RBF) { + projectionVector = { x:1, y:-1, z:1 }; + } else if (direction === SCALE_DIRECTION.LTN) { + projectionVector = { x:-1, y:1, z:-1 }; + } else if (direction === SCALE_DIRECTION.RTN) { + projectionVector = { x:-1, y:1, z:1 }; + } else if (direction === SCALE_DIRECTION.LTF) { + projectionVector = { x:1, y:1, z:-1 }; + } else if (direction === SCALE_DIRECTION.RTF) { + projectionVector = { x:1, y:1, z:1 }; + } + + var dotVector = Vec3.dot(vector, projectionVector); + vector = Vec3.multiply(dotVector, projectionVector); + vector = grid.snapToGrid(vector); var wantDebug = false; if (wantDebug) { - print(stretchMode); - // Vec3.print(" newIntersection:", newIntersection); + print("translateUpDown... "); + print(" event.y:" + event.y); + Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); - // Vec3.print(" oldPOS:", oldPOS); - // Vec3.print(" newPOS:", newPOS); - Vec3.print(" changeInDimensions:", changeInDimensions); - Vec3.print(" newDimensions:", newDimensions); - - Vec3.print(" changeInPosition:", changeInPosition); - Vec3.print(" newPosition:", newPosition); + // Vec3.print(" newPosition:", newPosition); } + + for (var i = 0; i < SelectionManager.selections.length; i++) { + var id = SelectionManager.selections[i]; + var properties = SelectionManager.savedProperties[id]; + var newPosition = Vec3.sum(properties.position, vector); + var difference = Vec3.subtract(newPosition, SelectionManager.worldPosition); + var differenceAvg = (difference.x + difference.y + difference.z) / 3; + var newDimensionsX = properties.dimensions.x + differenceAvg; + var newDimensionsY = properties.dimensions.y + differenceAvg; + var newDimensionsZ = properties.dimensions.z + differenceAvg; + if (newDimensionsX < SCALE_MINIMUM_DIMENSION) { + var differenceBelow = SCALE_MINIMUM_DIMENSION - newDimensionsX; + newDimensionsX += differenceBelow; + newDimensionsY += differenceBelow; + newDimensionsZ += differenceBelow; + } + if (newDimensionsY < SCALE_MINIMUM_DIMENSION) { + var differenceBelow = SCALE_MINIMUM_DIMENSION - newDimensionsY; + newDimensionsX += differenceBelow; + newDimensionsY += differenceBelow; + newDimensionsZ += differenceBelow; + } + if (newDimensionsZ < SCALE_MINIMUM_DIMENSION) { + var differenceBelow = SCALE_MINIMUM_DIMENSION - newDimensionsZ; + newDimensionsX += differenceBelow; + newDimensionsY += differenceBelow; + newDimensionsZ += differenceBelow; + } + Entities.editEntity(id, { dimensions: { x:newDimensionsX, y:newDimensionsY, z:newDimensionsZ }}); + } + + SelectionManager._update(); } - - SelectionManager._update(); - };// End of onMove def - - return { - mode: stretchMode, - onBegin: onBegin, - onMove: onMove, - onEnd: onEnd - }; - }; - - // Direction for the stretch tool when using hand controller - var directionsFor3DGrab = { - LBN: { - x: 1, - y: 1, - z: 1 - }, - RBN: { - x: -1, - y: 1, - z: 1 - }, - LBF: { - x: 1, - y: 1, - z: -1 - }, - RBF: { - x: -1, - y: 1, - z: -1 - }, - LTN: { - x: 1, - y: -1, - z: 1 - }, - RTN: { - x: -1, - y: -1, - z: 1 - }, - LTF: { - x: 1, - y: -1, - z: -1 - }, - RTF: { - x: -1, - y: -1, - z: -1 - } - }; - - // FUNCTION: GET DIRECTION FOR 3D STRETCH - // Returns a vector with directions for the stretch tool in 3D using hand controllers - function getDirectionsFor3DStretch(mode) { - if (mode === "STRETCH_LBN") { - return directionsFor3DGrab.LBN; - } else if (mode === "STRETCH_RBN") { - return directionsFor3DGrab.RBN; - } else if (mode === "STRETCH_LBF") { - return directionsFor3DGrab.LBF; - } else if (mode === "STRETCH_RBF") { - return directionsFor3DGrab.RBF; - } else if (mode === "STRETCH_LTN") { - return directionsFor3DGrab.LTN; - } else if (mode === "STRETCH_RTN") { - return directionsFor3DGrab.RTN; - } else if (mode === "STRETCH_LTF") { - return directionsFor3DGrab.LTF; - } else if (mode === "STRETCH_RTF") { - return directionsFor3DGrab.RTF; - } else { - return null; - } - } - - - // FUNCTION: ADD STRETCH TOOL - function addStretchTool(overlay, mode, pivot, direction, offset, handleMove) { - if (!pivot) { - pivot = direction; - } - var tool = makeStretchTool(mode, direction, pivot, offset, handleMove); - - return addGrabberTool(overlay, tool); - } - - // FUNCTION: CUTOFF STRETCH FUNC - function cutoffStretchFunc(vector, change) { - vector = change; - var wantDebug = false; - if (wantDebug) { - Vec3.print("Radius stretch: ", vector); - } - var length = vector.x + vector.y + vector.z; - var props = SelectionManager.savedProperties[SelectionManager.selections[0]]; - - var radius = props.dimensions.z / 2; - var originalCutoff = props.cutoff; - - var originalSize = radius * Math.tan(originalCutoff * (Math.PI / 180)); - var newSize = originalSize + length; - var cutoff = Math.atan2(newSize, radius) * 180 / Math.PI; - - Entities.editEntity(SelectionManager.selections[0], { - cutoff: cutoff }); - - SelectionManager._update(); } - // FUNCTION: RADIUS STRETCH FUNC - function radiusStretchFunc(vector, change) { - var props = SelectionManager.savedProperties[SelectionManager.selections[0]]; - - // Find the axis being adjusted - var size; - if (Math.abs(change.x) > 0) { - size = props.dimensions.x + change.x; - } else if (Math.abs(change.y) > 0) { - size = props.dimensions.y + change.y; - } else if (Math.abs(change.z) > 0) { - size = props.dimensions.z + change.z; - } - - var newDimensions = { - x: size, - y: size, - z: size - }; - - Entities.editEntity(SelectionManager.selections[0], { - dimensions: newDimensions - }); - - SelectionManager._update(); - } - - // STRETCH TOOL DEF SECTION - addStretchTool(grabberNEAR, "STRETCH_NEAR", { - x: 0, - y: 0, - z: 1 - }, { - x: 0, - y: 0, - z: 1 - }, { - x: 0, - y: 0, - z: -1 - }); - addStretchTool(grabberFAR, "STRETCH_FAR", { - x: 0, - y: 0, - z: -1 - }, { - x: 0, - y: 0, - z: -1 - }, { - x: 0, - y: 0, - z: 1 - }); - addStretchTool(grabberTOP, "STRETCH_TOP", { - x: 0, - y: -1, - z: 0 - }, { - x: 0, - y: -1, - z: 0 - }, { - x: 0, - y: 1, - z: 0 - }); - addStretchTool(grabberBOTTOM, "STRETCH_BOTTOM", { - x: 0, - y: 1, - z: 0 - }, { - x: 0, - y: 1, - z: 0 - }, { - x: 0, - y: -1, - z: 0 - }); - addStretchTool(grabberRIGHT, "STRETCH_RIGHT", { - x: -1, - y: 0, - z: 0 - }, { - x: -1, - y: 0, - z: 0 - }, { - x: 1, - y: 0, - z: 0 - }); - addStretchTool(grabberLEFT, "STRETCH_LEFT", { - x: 1, - y: 0, - z: 0 - }, { - x: 1, - y: 0, - z: 0 - }, { - x: -1, - y: 0, - z: 0 - }); - - addStretchTool(grabberSpotLightRadius, "STRETCH_RADIUS", { - x: 0, - y: 0, - z: 0 - }, { - x: 0, - y: 0, - z: 1 - }, { - x: 0, - y: 0, - z: -1 - }); - addStretchTool(grabberSpotLightT, "STRETCH_CUTOFF_T", { - x: 0, - y: 0, - z: 0 - }, { - x: 0, - y: -1, - z: 0 - }, { - x: 0, - y: 1, - z: 0 - }, cutoffStretchFunc); - addStretchTool(grabberSpotLightB, "STRETCH_CUTOFF_B", { - x: 0, - y: 0, - z: 0 - }, { - x: 0, - y: 1, - z: 0 - }, { - x: 0, - y: -1, - z: 0 - }, cutoffStretchFunc); - addStretchTool(grabberSpotLightL, "STRETCH_CUTOFF_L", { - x: 0, - y: 0, - z: 0 - }, { - x: 1, - y: 0, - z: 0 - }, { - x: -1, - y: 0, - z: 0 - }, cutoffStretchFunc); - addStretchTool(grabberSpotLightR, "STRETCH_CUTOFF_R", { - x: 0, - y: 0, - z: 0 - }, { - x: -1, - y: 0, - z: 0 - }, { - x: 1, - y: 0, - z: 0 - }, cutoffStretchFunc); - - addStretchTool(grabberPointLightT, "STRETCH_RADIUS_T", { - x: 0, - y: 0, - z: 0 - }, { - x: 0, - y: -1, - z: 0 - }, { - x: 0, - y: 0, - z: 1 - }, radiusStretchFunc); - addStretchTool(grabberPointLightB, "STRETCH_RADIUS_B", { - x: 0, - y: 0, - z: 0 - }, { - x: 0, - y: 1, - z: 0 - }, { - x: 0, - y: 0, - z: 1 - }, radiusStretchFunc); - addStretchTool(grabberPointLightL, "STRETCH_RADIUS_L", { - x: 0, - y: 0, - z: 0 - }, { - x: 1, - y: 0, - z: 0 - }, { - x: 0, - y: 0, - z: 1 - }, radiusStretchFunc); - addStretchTool(grabberPointLightR, "STRETCH_RADIUS_R", { - x: 0, - y: 0, - z: 0 - }, { - x: -1, - y: 0, - z: 0 - }, { - x: 0, - y: 0, - z: 1 - }, radiusStretchFunc); - addStretchTool(grabberPointLightF, "STRETCH_RADIUS_F", { - x: 0, - y: 0, - z: 0 - }, { - x: 0, - y: 0, - z: -1 - }, { - x: 0, - y: 0, - z: 1 - }, radiusStretchFunc); - addStretchTool(grabberPointLightN, "STRETCH_RADIUS_N", { - x: 0, - y: 0, - z: 0 - }, { - x: 0, - y: 0, - z: 1 - }, { - x: 0, - y: 0, - z: -1 - }, radiusStretchFunc); - - addStretchTool(grabberLBN, "STRETCH_LBN", null, { - x: 1, - y: 0, - z: 1 - }, { - x: -1, - y: -1, - z: -1 - }); - addStretchTool(grabberRBN, "STRETCH_RBN", null, { - x: -1, - y: 0, - z: 1 - }, { - x: 1, - y: -1, - z: -1 - }); - addStretchTool(grabberLBF, "STRETCH_LBF", null, { - x: 1, - y: 0, - z: -1 - }, { - x: -1, - y: -1, - z: 1 - }); - addStretchTool(grabberRBF, "STRETCH_RBF", null, { - x: -1, - y: 0, - z: -1 - }, { - x: 1, - y: -1, - z: 1 - }); - addStretchTool(grabberLTN, "STRETCH_LTN", null, { - x: 1, - y: 0, - z: 1 - }, { - x: -1, - y: 1, - z: -1 - }); - addStretchTool(grabberRTN, "STRETCH_RTN", null, { - x: -1, - y: 0, - z: 1 - }, { - x: 1, - y: 1, - z: -1 - }); - addStretchTool(grabberLTF, "STRETCH_LTF", null, { - x: 1, - y: 0, - z: -1 - }, { - x: -1, - y: 1, - z: 1 - }); - addStretchTool(grabberRTF, "STRETCH_RTF", null, { - x: -1, - y: 0, - z: -1 - }, { - x: 1, - y: 1, - z: 1 - }); - - addStretchTool(grabberEdgeTR, "STRETCH_EdgeTR", null, { - x: 1, - y: 1, - z: 0 - }, { - x: 1, - y: 1, - z: 0 - }); - addStretchTool(grabberEdgeTL, "STRETCH_EdgeTL", null, { - x: -1, - y: 1, - z: 0 - }, { - x: -1, - y: 1, - z: 0 - }); - addStretchTool(grabberEdgeTF, "STRETCH_EdgeTF", null, { - x: 0, - y: 1, - z: -1 - }, { - x: 0, - y: 1, - z: -1 - }); - addStretchTool(grabberEdgeTN, "STRETCH_EdgeTN", null, { - x: 0, - y: 1, - z: 1 - }, { - x: 0, - y: 1, - z: 1 - }); - addStretchTool(grabberEdgeBR, "STRETCH_EdgeBR", null, { - x: -1, - y: 0, - z: 0 - }, { - x: 1, - y: -1, - z: 0 - }); - addStretchTool(grabberEdgeBL, "STRETCH_EdgeBL", null, { - x: 1, - y: 0, - z: 0 - }, { - x: -1, - y: -1, - z: 0 - }); - addStretchTool(grabberEdgeBF, "STRETCH_EdgeBF", null, { - x: 0, - y: 0, - z: -1 - }, { - x: 0, - y: -1, - z: -1 - }); - addStretchTool(grabberEdgeBN, "STRETCH_EdgeBN", null, { - x: 0, - y: 0, - z: 1 - }, { - x: 0, - y: -1, - z: 1 - }); - addStretchTool(grabberEdgeNR, "STRETCH_EdgeNR", null, { - x: -1, - y: 0, - z: 1 - }, { - x: 1, - y: 0, - z: -1 - }); - addStretchTool(grabberEdgeNL, "STRETCH_EdgeNL", null, { - x: 1, - y: 0, - z: 1 - }, { - x: -1, - y: 0, - z: -1 - }); - addStretchTool(grabberEdgeFR, "STRETCH_EdgeFR", null, { - x: -1, - y: 0, - z: -1 - }, { - x: 1, - y: 0, - z: 1 - }); - addStretchTool(grabberEdgeFL, "STRETCH_EdgeFL", null, { - x: 1, - y: 0, - z: -1 - }, { - x: -1, - y: 0, - z: 1 - }); - // FUNCTION: UPDATE ROTATION DEGREES OVERLAY - function updateRotationDegreesOverlay(angleFromZero, handleRotation, centerPosition) { - var wantDebug = false; - if (wantDebug) { - print("---> updateRotationDegreesOverlay ---"); - print(" AngleFromZero: " + angleFromZero); - print(" HandleRotation - X: " + handleRotation.x + " Y: " + handleRotation.y + " Z: " + handleRotation.z); - print(" CenterPos - " + centerPosition.x + " Y: " + centerPosition.y + " Z: " + centerPosition.z); - } - + function updateRotationDegreesOverlay(angleFromZero, direction, centerPosition) { var angle = angleFromZero * (Math.PI / 180); var position = { - x: Math.cos(angle) * outerRadius * ROTATION_DISPLAY_DISTANCE_MULTIPLIER, - y: Math.sin(angle) * outerRadius * ROTATION_DISPLAY_DISTANCE_MULTIPLIER, + x: Math.cos(angle) * ROTATION_DISPLAY_DISTANCE_MULTIPLIER, + y: Math.sin(angle) * ROTATION_DISPLAY_DISTANCE_MULTIPLIER, z: 0 }; - if (wantDebug) { - print(" Angle: " + angle); - print(" InitialPos: " + position.x + ", " + position.y + ", " + position.z); - } - - position = Vec3.multiplyQbyV(handleRotation, position); + if (direction === ROTATE_DIRECTION.PITCH) + position = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, -90, 0), position); + else if (direction === ROTATE_DIRECTION.YAW) + position = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(90, 0, 0), position); + else if (direction === ROTATE_DIRECTION.ROLL) + position = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, 180, 0), position); position = Vec3.sum(centerPosition, position); var overlayProps = { position: position, dimensions: { - x: innerRadius * ROTATION_DISPLAY_SIZE_X_MULTIPLIER, - y: innerRadius * ROTATION_DISPLAY_SIZE_Y_MULTIPLIER + x: ROTATION_DISPLAY_SIZE_X_MULTIPLIER, + y: ROTATION_DISPLAY_SIZE_Y_MULTIPLIER }, - lineHeight: innerRadius * ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER, + lineHeight: ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER, text: normalizeDegrees(-angleFromZero) + "°" }; - if (wantDebug) { - print(" TranslatedPos: " + position.x + ", " + position.y + ", " + position.z); - print(" OverlayDim - X: " + overlayProps.dimensions.x + " Y: " + overlayProps.dimensions.y + " Z: " + overlayProps.dimensions.z); - print(" OverlayLineHeight: " + overlayProps.lineHeight); - print(" OverlayText: " + overlayProps.text); - } - Overlays.editOverlay(rotationDegreesDisplay, overlayProps); - if (wantDebug) { - print("<--- updateRotationDegreesOverlay ---"); - } } // FUNCTION DEF: updateSelectionsRotation @@ -3322,260 +979,422 @@ SelectionDisplay = (function() { } } - function helperRotationHandleOnBegin(event, pickRay, rotAroundAxis, rotCenter, handleRotation) { + function addGrabberRotateTool(overlay, mode, direction) { + var initialPosition = SelectionManager.worldPosition; + var selectedGrabber = null; + addGrabberTool(overlay, { + mode: mode, + onBegin: function(event, pickRay, pickResult) { + SelectionManager.saveProperties(); + + that.setGrabberTranslateVisible(false); + that.setGrabberRotatePitchVisible(direction === ROTATE_DIRECTION.PITCH); + that.setGrabberRotateYawVisible(direction === ROTATE_DIRECTION.YAW); + that.setGrabberRotateRollVisible(direction === ROTATE_DIRECTION.ROLL); + that.setGrabberStretchVisible(false); + that.setGrabberScaleVisible(false); + + initialPosition = SelectionManager.worldPosition; + + if (direction === ROTATE_DIRECTION.PITCH) { + rotationNormal = { x: 1, y: 0, z: 0 }; + selectedGrabber = grabberRotatePitchRing; + } else if (direction === ROTATE_DIRECTION.YAW) { + rotationNormal = { x: 0, y: 1, z: 0 }; + selectedGrabber = grabberRotateYawRing; + } else if (direction === ROTATE_DIRECTION.ROLL) { + rotationNormal = { x: 0, y: 0, z: 1 }; + selectedGrabber = grabberRotateRollRing; + } + + Overlays.editOverlay(selectedGrabber, { hasTickMarks: true }); + + var rotation = Entities.getEntityProperties(SelectionManager.selections[0]).rotation; + rotationNormal = Vec3.multiplyQbyV(rotation, rotationNormal); + + var rotCenter = SelectionManager.worldPosition; + + Overlays.editOverlay(rotationDegreesDisplay, { visible: true }); + Overlays.editOverlay(grabberRotateCurrentRing, { visible: true }); + updateRotationDegreesOverlay(0, direction, rotCenter); + + // editOverlays may not have committed rotation changes. + // Compute zero position based on where the overlay will be eventually. + var result = rayPlaneIntersection(pickRay, rotCenter, rotationNormal); + // In case of a parallel ray, this will be null, which will cause early-out + // in the onMove helper. + rotZero = result; + }, + onEnd: function(event, reason) { + Overlays.editOverlay(rotationDegreesDisplay, { visible: false }); + Overlays.editOverlay(selectedGrabber, { hasTickMarks: false }); + Overlays.editOverlay(grabberRotateCurrentRing, { visible: false }); + pushCommandForSelections(); + }, + onMove: function(event) { + if (!rotZero) { + print("ERROR: entitySelectionTool.handleRotationHandleOnMove - Invalid RotationZero Specified (missed rotation target plane?)"); + + // EARLY EXIT + return; + } + var pickRay = generalComputePickRay(event.x, event.y); + var rotCenter = SelectionManager.worldPosition; + var result = rayPlaneIntersection(pickRay, rotCenter, rotationNormal); + if (result) { + var centerToZero = Vec3.subtract(rotZero, rotCenter); + var centerToIntersect = Vec3.subtract(result, rotCenter); + // Note: orientedAngle which wants normalized centerToZero and centerToIntersect + // handles that internally, so it's to pass unnormalized vectors here. + var angleFromZero = Math.floor((Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal))); + var rotChange = Quat.angleAxis(angleFromZero, rotationNormal); + updateSelectionsRotation(rotChange); + updateRotationDegreesOverlay(-angleFromZero, direction, rotCenter); + + var worldRotation; + if (direction === ROTATE_DIRECTION.PITCH) { + worldRotation = worldRotationY; + } else if (direction === ROTATE_DIRECTION.YAW) { + worldRotation = worldRotationZ; + } else if (direction === ROTATE_DIRECTION.ROLL) { + worldRotation = worldRotationX; + } + + var startAtCurrent = 0; + var endAtCurrent = angleFromZero; + if (angleFromZero < 0) { + startAtCurrent = 360 + angleFromZero; + endAtCurrent = 360; + } + Overlays.editOverlay(grabberRotateCurrentRing, { + position: rotCenter, + rotation: worldRotation, + startAt: startAtCurrent, + endAt: endAtCurrent + }); + } + } + }); + } + + // @param: toolHandle: The overlayID associated with the tool + // that correlates to the tool you wish to query. + // @note: If toolHandle is null or undefined then activeTool + // will be checked against those values as opposed to + // the tool registered under toolHandle. Null & Undefined + // are treated as separate values. + // @return: bool - Indicates if the activeTool is that queried. + function isActiveTool(toolHandle) { + if (!toolHandle) { + // Allow isActiveTool(null) and similar to return true if there's + // no active tool + return (activeTool === toolHandle); + } + + if (!grabberTools.hasOwnProperty(toolHandle)) { + print("WARNING: entitySelectionTool.isActiveTool - Encountered unknown grabberToolHandle: " + toolHandle + ". Tools should be registered via addGrabberTool."); + // EARLY EXIT + return false; + } + + return (activeTool === grabberTools[ toolHandle ]); + } + + // @return string - The mode of the currently active tool; + // otherwise, "UNKNOWN" if there's no active tool. + function getMode() { + return (activeTool ? activeTool.mode : "UNKNOWN"); + } + + that.cleanup = function() { + for (var i = 0; i < allOverlays.length; i++) { + Overlays.deleteOverlay(allOverlays[i]); + } + }; + + that.highlightSelectable = function(entityID) { + var properties = Entities.getEntityProperties(entityID); + }; + + that.unhighlightSelectable = function(entityID) { + }; + + that.select = function(entityID, event) { + var properties = Entities.getEntityProperties(SelectionManager.selections[0]); + + lastCameraPosition = Camera.getPosition(); + lastCameraOrientation = Camera.getOrientation(); + + if (event !== false) { + pickRay = generalComputePickRay(event.x, event.y); + + var wantDebug = false; + if (wantDebug) { + print("select() with EVENT...... "); + print(" event.y:" + event.y); + Vec3.print(" current position:", properties.position); + } + } + + /* + Overlays.editOverlay(highlightBox, { + visible: false + }); + */ + + that.updateHandles(); + }; + + // FUNCTION: UPDATE HANDLE POSITION ROTATION + that.updateHandlePositionRotation = function() { + if (SelectionManager.hasSelection()) { + var worldPosition = SelectionManager.worldPosition; + var worldRotation = Entities.getEntityProperties(SelectionManager.selections[0]).rotation; + + var localRotationX = Quat.fromPitchYawRollDegrees(0, 0, -90); + worldRotationX = Quat.multiply(worldRotation, localRotationX); + var localRotationY = Quat.fromPitchYawRollDegrees(0, 90, 0); + worldRotationY = Quat.multiply(worldRotation, localRotationY); + var localRotationZ = Quat.fromPitchYawRollDegrees(90, 0, 0); + worldRotationZ = Quat.multiply(worldRotation, localRotationZ); + + var cylinderXPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET, y:0, z:0 })); + Overlays.editOverlay(grabberTranslateXCylinder, { position: cylinderXPos, rotation:worldRotationX }); + var coneXPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:GRABBER_TRANSLATE_ARROW_CONE_OFFSET, y:0, z:0 })); + Overlays.editOverlay(grabberTranslateXCone, { position: coneXPos, rotation:worldRotationX }); + var stretchXPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:GRABBER_STRETCH_SPHERE_OFFSET, y:0, z:0 })); + Overlays.editOverlay(grabberStretchXSphere, { position: stretchXPos }); + Overlays.editOverlay(grabberStretchXPanel, { position: stretchXPos, rotation:worldRotationY }); + + var cylinderYPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:0, y:GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET, z:0 })); + Overlays.editOverlay(grabberTranslateYCylinder, { position: cylinderYPos, rotation:worldRotationY }); + var coneYPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:0, y:GRABBER_TRANSLATE_ARROW_CONE_OFFSET, z:0 })); + Overlays.editOverlay(grabberTranslateYCone, { position: coneYPos, rotation:worldRotationY }); + var stretchYPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:0, y:GRABBER_STRETCH_SPHERE_OFFSET, z:0 })); + Overlays.editOverlay(grabberStretchYSphere, { position: stretchYPos }); + Overlays.editOverlay(grabberStretchYPanel, { position: stretchYPos, rotation:worldRotationZ }); + + var cylinderZPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:0, y:0, z:GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET })); + Overlays.editOverlay(grabberTranslateZCylinder, { position: cylinderZPos, rotation:worldRotationZ }); + var coneZPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:0, y:0, z:GRABBER_TRANSLATE_ARROW_CONE_OFFSET })); + Overlays.editOverlay(grabberTranslateZCone, { position: coneZPos, rotation:worldRotationZ }); + var stretchZPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:0, y:0, z:GRABBER_STRETCH_SPHERE_OFFSET })); + Overlays.editOverlay(grabberStretchZSphere, { position: stretchZPos }); + Overlays.editOverlay(grabberStretchZPanel, { position: stretchZPos, rotation:worldRotationX }); + + if (!isActiveTool(grabberRotatePitchRing)) { + Overlays.editOverlay(grabberRotatePitchRing, { position: SelectionManager.worldPosition, rotation: worldRotationY }); + } + if (!isActiveTool(grabberRotateYawRing)) { + Overlays.editOverlay(grabberRotateYawRing, { position: SelectionManager.worldPosition, rotation: worldRotationZ }); + } + if (!isActiveTool(grabberRotateRollRing)) { + Overlays.editOverlay(grabberRotateRollRing, { position: SelectionManager.worldPosition, rotation: worldRotationX }); + } + + var grabberScaleLBNCubePos = Vec3.sum(worldPosition, { x:-GRABBER_SCALE_CUBE_OFFSET, y:-GRABBER_SCALE_CUBE_OFFSET, z:-GRABBER_SCALE_CUBE_OFFSET }); + Overlays.editOverlay(grabberScaleLBNCube, { position:grabberScaleLBNCubePos }); + var grabberScaleRBNCubePos = Vec3.sum(worldPosition, { x:-GRABBER_SCALE_CUBE_OFFSET, y:-GRABBER_SCALE_CUBE_OFFSET, z:GRABBER_SCALE_CUBE_OFFSET }); + Overlays.editOverlay(grabberScaleRBNCube, { position:grabberScaleRBNCubePos }); + var grabberScaleLBFCubePos = Vec3.sum(worldPosition, { x:GRABBER_SCALE_CUBE_OFFSET, y:-GRABBER_SCALE_CUBE_OFFSET, z:-GRABBER_SCALE_CUBE_OFFSET }); + Overlays.editOverlay(grabberScaleLBFCube, { position:grabberScaleLBFCubePos }); + var grabberScaleRBFCubePos = Vec3.sum(worldPosition, { x:GRABBER_SCALE_CUBE_OFFSET, y:-GRABBER_SCALE_CUBE_OFFSET, z:GRABBER_SCALE_CUBE_OFFSET }); + Overlays.editOverlay(grabberScaleRBFCube, { position:grabberScaleRBFCubePos }); + var grabberScaleLTNCubePos = Vec3.sum(worldPosition, { x:-GRABBER_SCALE_CUBE_OFFSET, y:GRABBER_SCALE_CUBE_OFFSET, z:-GRABBER_SCALE_CUBE_OFFSET }); + Overlays.editOverlay(grabberScaleLTNCube, { position:grabberScaleLTNCubePos }); + var grabberScaleRTNCubePos = Vec3.sum(worldPosition, { x:-GRABBER_SCALE_CUBE_OFFSET, y:GRABBER_SCALE_CUBE_OFFSET, z:GRABBER_SCALE_CUBE_OFFSET }); + Overlays.editOverlay(grabberScaleRTNCube, { position:grabberScaleRTNCubePos }); + var grabberScaleLTFCubePos = Vec3.sum(worldPosition, { x:GRABBER_SCALE_CUBE_OFFSET, y:GRABBER_SCALE_CUBE_OFFSET, z:-GRABBER_SCALE_CUBE_OFFSET }); + Overlays.editOverlay(grabberScaleLTFCube, { position:grabberScaleLTFCubePos }); + var grabberScaleRTFCubePos = Vec3.sum(worldPosition, { x:GRABBER_SCALE_CUBE_OFFSET, y:GRABBER_SCALE_CUBE_OFFSET, z:GRABBER_SCALE_CUBE_OFFSET }); + Overlays.editOverlay(grabberScaleRTFCube, { position:grabberScaleRTFCubePos }); + + Overlays.editOverlay(grabberScaleTREdge, { start: grabberScaleRTNCubePos, end:grabberScaleRTFCubePos }); + Overlays.editOverlay(grabberScaleTLEdge, { start: grabberScaleLTNCubePos, end:grabberScaleLTFCubePos }); + Overlays.editOverlay(grabberScaleTFEdge, { start: grabberScaleLTFCubePos, end:grabberScaleRTFCubePos }); + Overlays.editOverlay(grabberScaleTNEdge, { start: grabberScaleLTNCubePos, end:grabberScaleRTNCubePos }); + Overlays.editOverlay(grabberScaleBREdge, { start: grabberScaleRBNCubePos, end:grabberScaleRBFCubePos }); + Overlays.editOverlay(grabberScaleBLEdge, { start: grabberScaleLBNCubePos, end:grabberScaleLBFCubePos }); + Overlays.editOverlay(grabberScaleBFEdge, { start: grabberScaleLBFCubePos, end:grabberScaleRBFCubePos }); + Overlays.editOverlay(grabberScaleBNEdge, { start: grabberScaleLBNCubePos, end:grabberScaleRBNCubePos }); + Overlays.editOverlay(grabberScaleNREdge, { start: grabberScaleRTNCubePos, end:grabberScaleRBNCubePos }); + Overlays.editOverlay(grabberScaleNLEdge, { start: grabberScaleLTNCubePos, end:grabberScaleLBNCubePos }); + Overlays.editOverlay(grabberScaleFREdge, { start: grabberScaleRTFCubePos, end:grabberScaleRBFCubePos }); + Overlays.editOverlay(grabberScaleFLEdge, { start: grabberScaleLTFCubePos, end:grabberScaleLBFCubePos }); + } + }; + Script.update.connect(that.updateHandlePositionRotation); + + // FUNCTION: SET SPACE MODE + that.setSpaceMode = function(newSpaceMode) { var wantDebug = false; if (wantDebug) { - print("================== " + getMode() + "(rotation helper onBegin) -> ======================="); + print("======> SetSpaceMode called. ========"); } - SelectionManager.saveProperties(); - that.setRotationHandlesVisible(false); - that.setStretchHandlesVisible(false); - that.setGrabberMoveUpVisible(false); - - initialPosition = SelectionManager.worldPosition; - rotationNormal = { x: 0, y: 0, z: 0 }; - rotationNormal[rotAroundAxis] = 1; - //get the correct axis according to the avatar referencial - var avatarReferential = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({ - x: 0, - y: 0, - z: 0 - })); - rotationNormal = Vec3.multiplyQbyV(avatarReferential, rotationNormal); - - // Size the overlays to the current selection size - var diagonal = (Vec3.length(SelectionManager.worldDimensions) / 2) * 1.1; - var halfDimensions = Vec3.multiply(SelectionManager.worldDimensions, 0.5); - innerRadius = diagonal; - outerRadius = diagonal * 1.15; - var innerAlpha = 0.2; - var outerAlpha = 0.2; - Overlays.editOverlay(rotateOverlayInner, { - visible: true, - rotation: handleRotation, - position: rotCenter, - size: innerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: innerAlpha - }); - - Overlays.editOverlay(rotateOverlayOuter, { - visible: true, - rotation: handleRotation, - position: rotCenter, - size: outerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: outerAlpha - }); - - Overlays.editOverlay(rotateOverlayCurrent, { - visible: true, - rotation: handleRotation, - position: rotCenter, - size: outerRadius, - startAt: 0, - endAt: 0, - innerRadius: 0.9 - }); - - Overlays.editOverlay(rotationDegreesDisplay, { - visible: true - }); - - updateRotationDegreesOverlay(0, handleRotation, rotCenter); - - // editOverlays may not have committed rotation changes. - // Compute zero position based on where the overlay will be eventually. - var result = rayPlaneIntersection(pickRay, rotCenter, rotationNormal); - // In case of a parallel ray, this will be null, which will cause early-out - // in the onMove helper. - rotZero = result; - + if (spaceMode !== newSpaceMode) { + if (wantDebug) { + print(" Updating SpaceMode From: " + spaceMode + " To: " + newSpaceMode); + } + spaceMode = newSpaceMode; + that.updateHandles(); + } else if (wantDebug) { + print("WARNING: entitySelectionTool.setSpaceMode - Can't update SpaceMode. CurrentMode: " + spaceMode + " DesiredMode: " + newSpaceMode); + } if (wantDebug) { - print("================== " + getMode() + "(rotation helper onBegin) <- ======================="); + print("====== SetSpaceMode called. <========"); } - }// End_Function(helperRotationHandleOnBegin) + }; - function helperRotationHandleOnMove(event, rotAroundAxis, rotCenter, handleRotation) { - - if (!rotZero) { - print("ERROR: entitySelectionTool.handleRotationHandleOnMove - Invalid RotationZero Specified (missed rotation target plane?)"); - - // EARLY EXIT + // FUNCTION: UPDATE HANDLES + that.updateHandles = function() { + var wantDebug = false; + if (wantDebug) { + print("======> Update Handles ======="); + print(" Selections Count: " + SelectionManager.selections.length); + print(" SpaceMode: " + spaceMode); + print(" DisplayMode: " + getMode()); + } + if (SelectionManager.selections.length === 0) { + that.setOverlaysVisible(false); return; } - var wantDebug = false; - if (wantDebug) { - print("================== "+ getMode() + "(rotation helper onMove) -> ======================="); - Vec3.print(" rotZero: ", rotZero); - } - var pickRay = generalComputePickRay(event.x, event.y); - Overlays.editOverlay(selectionBox, { - visible: false - }); - Overlays.editOverlay(baseOfEntityProjectionOverlay, { - visible: false - }); + var isSingleSelection = (SelectionManager.selections.length === 1); + if (isSingleSelection) { + }// end of isSingleSelection - var result = rayPlaneIntersection(pickRay, rotCenter, rotationNormal); - if (result) { - var centerToZero = Vec3.subtract(rotZero, rotCenter); - var centerToIntersect = Vec3.subtract(result, rotCenter); - if (wantDebug) { - Vec3.print(" RotationNormal: ", rotationNormal); - Vec3.print(" rotZero: ", rotZero); - Vec3.print(" rotCenter: ", rotCenter); - Vec3.print(" intersect: ", result); - Vec3.print(" centerToZero: ", centerToZero); - Vec3.print(" centerToIntersect: ", centerToIntersect); - } - // Note: orientedAngle which wants normalized centerToZero and centerToIntersect - // handles that internally, so it's to pass unnormalized vectors here. - var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); - - var distanceFromCenter = Vec3.length(centerToIntersect); - var snapToInner = distanceFromCenter < innerRadius; - var snapAngle = snapToInner ? innerSnapAngle : 1.0; - angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; - - - var rotChange = Quat.angleAxis(angleFromZero, rotationNormal); - updateSelectionsRotation(rotChange); - //present angle in avatar referencial - angleFromZero = -angleFromZero; - updateRotationDegreesOverlay(angleFromZero, handleRotation, rotCenter); - - // update the rotation display accordingly... - var startAtCurrent = 0; - var endAtCurrent = angleFromZero; - var startAtRemainder = angleFromZero; - var endAtRemainder = 360; - if (angleFromZero < 0) { - startAtCurrent = 360 + angleFromZero; - endAtCurrent = 360; - startAtRemainder = 0; - endAtRemainder = startAtCurrent; - } - if (snapToInner) { - Overlays.editOverlay(rotateOverlayOuter, { - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayInner, { - startAt: startAtRemainder, - endAt: endAtRemainder - }); - Overlays.editOverlay(rotateOverlayCurrent, { - startAt: startAtCurrent, - endAt: endAtCurrent, - size: innerRadius, - majorTickMarksAngle: innerSnapAngle, - minorTickMarksAngle: 0, - majorTickMarksLength: -0.25, - minorTickMarksLength: 0 - }); - } else { - Overlays.editOverlay(rotateOverlayInner, { - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayOuter, { - startAt: startAtRemainder, - endAt: endAtRemainder - }); - Overlays.editOverlay(rotateOverlayCurrent, { - startAt: startAtCurrent, - endAt: endAtCurrent, - size: outerRadius, - majorTickMarksAngle: 45.0, - minorTickMarksAngle: 5, - majorTickMarksLength: 0.25, - minorTickMarksLength: 0.1 - }); - } - }// End_If(results.intersects) + that.setGrabberTranslateXVisible(!activeTool || isActiveTool(grabberTranslateXCone) || isActiveTool(grabberTranslateXCylinder)); + that.setGrabberTranslateYVisible(!activeTool || isActiveTool(grabberTranslateYCone) || isActiveTool(grabberTranslateYCylinder)); + that.setGrabberTranslateZVisible(!activeTool || isActiveTool(grabberTranslateZCone) || isActiveTool(grabberTranslateZCylinder)); + that.setGrabberRotatePitchVisible(!activeTool || isActiveTool(grabberRotatePitchRing)); + that.setGrabberRotateYawVisible(!activeTool || isActiveTool(grabberRotateYawRing)); + that.setGrabberRotateRollVisible(!activeTool || isActiveTool(grabberRotateRollRing)); + that.setGrabberStretchXVisible(!activeTool || isActiveTool(grabberStretchXSphere)); + that.setGrabberStretchYVisible(!activeTool || isActiveTool(grabberStretchYSphere)); + that.setGrabberStretchZVisible(!activeTool || isActiveTool(grabberStretchZSphere)); + that.setGrabberScaleVisible(!activeTool || isActiveTool(grabberScaleLBNCube) || isActiveTool(grabberScaleRBNCube) || isActiveTool(grabberScaleLBFCube) || isActiveTool(grabberScaleRBFCube) + || isActiveTool(grabberScaleLTNCube) || isActiveTool(grabberScaleRTNCube) || isActiveTool(grabberScaleLTFCube) || isActiveTool(grabberScaleRTFCube)); if (wantDebug) { - print("================== "+ getMode() + "(rotation helper onMove) <- ======================="); + print("====== Update Handles <======="); } - }// End_Function(helperRotationHandleOnMove) + }; - function helperRotationHandleOnEnd() { - var wantDebug = false; - if (wantDebug) { - print("================== " + getMode() + "(onEnd) -> ======================="); + // FUNCTION: SET OVERLAYS VISIBLE + that.setOverlaysVisible = function(isVisible) { + for (var i = 0; i < allOverlays.length; i++) { + Overlays.editOverlay(allOverlays[i], { visible: isVisible }); } - Overlays.editOverlay(rotateOverlayInner, { - visible: false - }); - Overlays.editOverlay(rotateOverlayOuter, { - visible: false - }); - Overlays.editOverlay(rotateOverlayCurrent, { - visible: false - }); - Overlays.editOverlay(rotationDegreesDisplay, { - visible: false - }); + }; - pushCommandForSelections(); + // FUNCTION: SET GRABBER TRANSLATE VISIBLE + that.setGrabberTranslateVisible = function(isVisible) { + that.setGrabberTranslateXVisible(isVisible); + that.setGrabberTranslateYVisible(isVisible); + that.setGrabberTranslateZVisible(isVisible); + }; - if (wantDebug) { - print("================== " + getMode() + "(onEnd) <- ======================="); - } - }// End_Function(helperRotationHandleOnEnd) + that.setGrabberTranslateXVisible = function(isVisible) { + Overlays.editOverlay(grabberTranslateXCone, { visible: isVisible }); + Overlays.editOverlay(grabberTranslateXCylinder, { visible: isVisible }); + }; + that.setGrabberTranslateYVisible = function(isVisible) { + Overlays.editOverlay(grabberTranslateYCone, { visible: isVisible }); + Overlays.editOverlay(grabberTranslateYCylinder, { visible: isVisible }); + }; - // YAW GRABBER TOOL DEFINITION - var initialPosition = SelectionManager.worldPosition; - addGrabberTool(yawHandle, { - mode: "ROTATE_YAW", - onBegin: function(event, pickRay, pickResult) { - helperRotationHandleOnBegin(event, pickRay, "y", yawCenter, yawHandleRotation); - }, - onEnd: function(event, reason) { - helperRotationHandleOnEnd(); - }, - onMove: function(event) { - helperRotationHandleOnMove(event, "y", yawCenter, yawHandleRotation); - } - }); + that.setGrabberTranslateZVisible = function(isVisible) { + Overlays.editOverlay(grabberTranslateZCone, { visible: isVisible }); + Overlays.editOverlay(grabberTranslateZCylinder, { visible: isVisible }); + }; + // FUNCTION: SET GRABBER ROTATION VISIBLE + that.setGrabberRotateVisible = function(isVisible) { + that.setGrabberRotatePitchVisible(isVisible); + that.setGrabberRotateYawVisible(isVisible); + that.setGrabberRotateRollVisible(isVisible); + }; - // PITCH GRABBER TOOL DEFINITION - addGrabberTool(pitchHandle, { - mode: "ROTATE_PITCH", - onBegin: function(event, pickRay, pickResult) { - helperRotationHandleOnBegin(event, pickRay, "x", pitchCenter, pitchHandleRotation); - }, - onEnd: function(event, reason) { - helperRotationHandleOnEnd(); - }, - onMove: function (event) { - helperRotationHandleOnMove(event, "x", pitchCenter, pitchHandleRotation); - } - }); + that.setGrabberRotatePitchVisible = function(isVisible) { + Overlays.editOverlay(grabberRotatePitchRing, { visible: isVisible }); + }; + that.setGrabberRotateYawVisible = function(isVisible) { + Overlays.editOverlay(grabberRotateYawRing, { visible: isVisible }); + }; - // ROLL GRABBER TOOL DEFINITION - addGrabberTool(rollHandle, { - mode: "ROTATE_ROLL", - onBegin: function(event, pickRay, pickResult) { - helperRotationHandleOnBegin(event, pickRay, "z", rollCenter, rollHandleRotation); - }, - onEnd: function (event, reason) { - helperRotationHandleOnEnd(); - }, - onMove: function(event) { - helperRotationHandleOnMove(event, "z", rollCenter, rollHandleRotation); - } - }); + that.setGrabberRotateRollVisible = function(isVisible) { + Overlays.editOverlay(grabberRotateRollRing, { visible: isVisible }); + }; + + // FUNCTION: SET GRABBER STRETCH VISIBLE + that.setGrabberStretchVisible = function(isVisible) { + that.setGrabberStretchXVisible(isVisible); + that.setGrabberStretchYVisible(isVisible); + that.setGrabberStretchZVisible(isVisible); + }; + + that.setGrabberStretchXVisible = function(isVisible) { + Overlays.editOverlay(grabberStretchXSphere, { visible: isVisible }); + }; + + that.setGrabberStretchYVisible = function(isVisible) { + Overlays.editOverlay(grabberStretchYSphere, { visible: isVisible }); + }; + + that.setGrabberStretchZVisible = function(isVisible) { + Overlays.editOverlay(grabberStretchZSphere, { visible: isVisible }); + }; + + // FUNCTION: SET GRABBER SCALE VISIBLE + that.setGrabberScaleVisible = function(isVisible) { + Overlays.editOverlay(grabberScaleLBNCube, { visible: isVisible }); + Overlays.editOverlay(grabberScaleRBNCube, { visible: isVisible }); + Overlays.editOverlay(grabberScaleLBFCube, { visible: isVisible }); + Overlays.editOverlay(grabberScaleRBFCube, { visible: isVisible }); + Overlays.editOverlay(grabberScaleLTNCube, { visible: isVisible }); + Overlays.editOverlay(grabberScaleRTNCube, { visible: isVisible }); + Overlays.editOverlay(grabberScaleLTFCube, { visible: isVisible }); + Overlays.editOverlay(grabberScaleRTFCube, { visible: isVisible }); + Overlays.editOverlay(grabberScaleTREdge, { visible: isVisible }); + Overlays.editOverlay(grabberScaleTLEdge, { visible: isVisible }); + Overlays.editOverlay(grabberScaleTFEdge, { visible: isVisible }); + Overlays.editOverlay(grabberScaleTNEdge, { visible: isVisible }); + Overlays.editOverlay(grabberScaleBREdge, { visible: isVisible }); + Overlays.editOverlay(grabberScaleBLEdge, { visible: isVisible }); + Overlays.editOverlay(grabberScaleBFEdge, { visible: isVisible }); + Overlays.editOverlay(grabberScaleBNEdge, { visible: isVisible }); + Overlays.editOverlay(grabberScaleNREdge, { visible: isVisible }); + Overlays.editOverlay(grabberScaleNLEdge, { visible: isVisible }); + Overlays.editOverlay(grabberScaleFREdge, { visible: isVisible }); + Overlays.editOverlay(grabberScaleFLEdge, { visible: isVisible }); + }; + + addGrabberTranslateTool(grabberTranslateXCone, "TRANSLATE_X", TRANSLATE_DIRECTION.X); + addGrabberTranslateTool(grabberTranslateXCylinder, "TRANSLATE_X", TRANSLATE_DIRECTION.X); + addGrabberTranslateTool(grabberTranslateYCone, "TRANSLATE_Y", TRANSLATE_DIRECTION.Y); + addGrabberTranslateTool(grabberTranslateYCylinder, "TRANSLATE_Y", TRANSLATE_DIRECTION.Y); + addGrabberTranslateTool(grabberTranslateZCone, "TRANSLATE_Z", TRANSLATE_DIRECTION.Z); + addGrabberTranslateTool(grabberTranslateZCylinder, "TRANSLATE_Z", TRANSLATE_DIRECTION.Z); + + addGrabberRotateTool(grabberRotatePitchRing, "ROTATE_PITCH", ROTATE_DIRECTION.PITCH); + addGrabberRotateTool(grabberRotateYawRing, "ROTATE_YAW", ROTATE_DIRECTION.YAW); + addGrabberRotateTool(grabberRotateRollRing, "ROTATE_ROLL", ROTATE_DIRECTION.ROLL); + + addGrabberStretchTool(grabberStretchXSphere, "STRETCH_X", TRANSLATE_DIRECTION.X); + addGrabberStretchTool(grabberStretchYSphere, "STRETCH_Y", TRANSLATE_DIRECTION.Y); + addGrabberStretchTool(grabberStretchZSphere, "STRETCH_Z", TRANSLATE_DIRECTION.Z); + + addGrabberScaleTool(grabberScaleLBNCube, "SCALE_LBN", SCALE_DIRECTION.LBN); + addGrabberScaleTool(grabberScaleRBNCube, "SCALE_RBN", SCALE_DIRECTION.RBN); + addGrabberScaleTool(grabberScaleLBFCube, "SCALE_LBF", SCALE_DIRECTION.LBF); + addGrabberScaleTool(grabberScaleRBFCube, "SCALE_RBF", SCALE_DIRECTION.RBF); + addGrabberScaleTool(grabberScaleLTNCube, "SCALE_LTN", SCALE_DIRECTION.LTN); + addGrabberScaleTool(grabberScaleRTNCube, "SCALE_RTN", SCALE_DIRECTION.RTN); + addGrabberScaleTool(grabberScaleLTFCube, "SCALE_LTF", SCALE_DIRECTION.LTF); + addGrabberScaleTool(grabberScaleRTFCube, "SCALE_RTF", SCALE_DIRECTION.RTF); // FUNCTION: CHECK MOVE that.checkMove = function() { @@ -3587,7 +1406,7 @@ SelectionDisplay = (function() { if (!Vec3.equal(Camera.getPosition(), lastCameraPosition) || !Quat.equal(Camera.getOrientation(), lastCameraOrientation)) { - that.updateRotationHandles(); + //that.updateRotationHandles(); } } }; @@ -3606,7 +1425,6 @@ SelectionDisplay = (function() { } }; - // FUNCTION DEF(s): Intersection Check Helpers function testRayIntersect(queryRay, overlayIncludes, overlayExcludes) { var wantDebug = false; @@ -3650,7 +1468,7 @@ SelectionDisplay = (function() { var pickRay = generalComputePickRay(event.x, event.y); // TODO_Case6491: Move this out to setup just to make it once - var interactiveOverlays = [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID, selectionBox]; + var interactiveOverlays = [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]; for (var key in grabberTools) { if (grabberTools.hasOwnProperty(key)) { interactiveOverlays.push(key); @@ -3717,125 +1535,6 @@ SelectionDisplay = (function() { return true; } - // if no tool is active, then just look for handles to highlight... - var pickRay = generalComputePickRay(event.x, event.y); - var result = Overlays.findRayIntersection(pickRay); - var pickedColor; - var pickedAlpha; - var highlightNeeded = false; - - if (result.intersects) { - switch (result.overlayID) { - case yawHandle: - case pitchHandle: - case rollHandle: - pickedColor = handleColor; - pickedAlpha = handleAlpha; - highlightNeeded = true; - break; - - case grabberMoveUp: - pickedColor = handleColor; - pickedAlpha = handleAlpha; - highlightNeeded = true; - break; - - case grabberLBN: - case grabberLBF: - case grabberRBN: - case grabberRBF: - case grabberLTN: - case grabberLTF: - case grabberRTN: - case grabberRTF: - pickedColor = grabberColorCorner; - pickedAlpha = grabberAlpha; - highlightNeeded = true; - break; - - case grabberTOP: - case grabberBOTTOM: - case grabberLEFT: - case grabberRIGHT: - case grabberNEAR: - case grabberFAR: - pickedColor = grabberColorFace; - pickedAlpha = grabberAlpha; - highlightNeeded = true; - break; - - case grabberEdgeTR: - case grabberEdgeTL: - case grabberEdgeTF: - case grabberEdgeTN: - case grabberEdgeBR: - case grabberEdgeBL: - case grabberEdgeBF: - case grabberEdgeBN: - case grabberEdgeNR: - case grabberEdgeNL: - case grabberEdgeFR: - case grabberEdgeFL: - case grabberSpotLightRadius: - case grabberSpotLightT: - case grabberSpotLightB: - case grabberSpotLightL: - case grabberSpotLightR: - case grabberPointLightT: - case grabberPointLightB: - case grabberPointLightR: - case grabberPointLightL: - case grabberPointLightN: - case grabberPointLightF: - pickedColor = grabberColorEdge; - pickedAlpha = grabberAlpha; - highlightNeeded = true; - break; - - case grabberCloner: - pickedColor = grabberColorCloner; - pickedAlpha = grabberAlpha; - highlightNeeded = true; - break; - - default: - if (previousHandle) { - Overlays.editOverlay(previousHandle, { - color: previousHandleColor, - alpha: previousHandleAlpha - }); - previousHandle = false; - } - break; - } - - if (highlightNeeded) { - if (previousHandle) { - Overlays.editOverlay(previousHandle, { - color: previousHandleColor, - alpha: previousHandleAlpha - }); - previousHandle = false; - } - Overlays.editOverlay(result.overlayID, { - color: highlightedHandleColor, - alpha: highlightedHandleAlpha - }); - previousHandle = result.overlayID; - previousHandleColor = pickedColor; - previousHandleAlpha = pickedAlpha; - } - - } else { - if (previousHandle) { - Overlays.editOverlay(previousHandle, { - color: previousHandleColor, - alpha: previousHandleAlpha - }); - previousHandle = false; - } - } - if (wantDebug) { print("=============== eST::MouseMoveEvent END ======================="); } @@ -3859,23 +1558,6 @@ SelectionDisplay = (function() { print(" ActiveTool(" + activeTool.mode + ")'s missing onEnd"); } } - - // hide our rotation overlays..., and show our handles - if (isActiveTool(yawHandle) || isActiveTool(pitchHandle) || isActiveTool(rollHandle)) { - if (wantDebug) { - print(" Triggering hide of RotateOverlays"); - } - Overlays.editOverlay(rotateOverlayInner, { - visible: false - }); - Overlays.editOverlay(rotateOverlayOuter, { - visible: false - }); - Overlays.editOverlay(rotateOverlayCurrent, { - visible: false - }); - - } showHandles = activeTool; // base on prior tool value activeTool = null; @@ -3900,7 +1582,5 @@ SelectionDisplay = (function() { // Controller.mouseMoveEvent.connect(that.mouseMoveEvent); Controller.mouseReleaseEvent.connect(that.mouseReleaseEvent); - return that; - }()); From 25f5eb6b4fc3288d23b193e274996f72153cdec5 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 18 Jan 2018 15:08:13 -0800 Subject: [PATCH 036/569] named property example --- libraries/entities/src/EntityEditFilters.cpp | 3 +++ scripts/tutorials/entity_edit_filters/position-example.js | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index 2b0dcc9f73..a69a8ce7d1 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -299,6 +299,9 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { filterData.wantsZoneProperties = !stringValue.isEmpty(); if (filterData.wantsZoneProperties) { EntityPropertyFlagsFromScriptValue(wantsZonePropertiesValue, filterData.includedZoneProperties); + if (stringValue == "boundingBox") { + filterData.wantsZoneBoundingBox = true; + } } } else if (wantsZonePropertiesValue.isArray()) { auto length = wantsZonePropertiesValue.property("length").toInteger(); diff --git a/scripts/tutorials/entity_edit_filters/position-example.js b/scripts/tutorials/entity_edit_filters/position-example.js index 01eabee7db..314ba0fd9f 100644 --- a/scripts/tutorials/entity_edit_filters/position-example.js +++ b/scripts/tutorials/entity_edit_filters/position-example.js @@ -41,5 +41,5 @@ function filter(properties, type, originalProperties) { return properties; } -filter.wantsOriginalProperties = true; +filter.wantsOriginalProperties = "position"; filter; \ No newline at end of file From c4359de859a0cf0e1edd112ea828f807cbb7b99e Mon Sep 17 00:00:00 2001 From: David Back Date: Fri, 19 Jan 2018 18:20:36 -0800 Subject: [PATCH 037/569] entity edit tools wip update --- scripts/system/edit.js | 2 +- .../system/libraries/entitySelectionTool.js | 1269 ++++++++++++----- 2 files changed, 883 insertions(+), 388 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 87cd3e0faf..2b886d2540 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -65,7 +65,7 @@ gridTool.setVisible(false); var entityListTool = new EntityListTool(); selectionManager.addEventListener(function () { - selectionDisplay.updateHandles(); + selectionDisplay.updateGrabbers(); entityIconOverlayManager.updatePositions(); // Update particle explorer diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 220a7b7c70..253c67bfca 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -138,16 +138,16 @@ SelectionManager = (function() { that.localPosition = null; that.worldDimensions = null; that.worldPosition = null; + that.worldRotation = null; } else if (that.selections.length === 1) { properties = Entities.getEntityProperties(that.selections[0]); that.localDimensions = properties.dimensions; that.localPosition = properties.position; that.localRotation = properties.rotation; that.localRegistrationPoint = properties.registrationPoint; - - that.worldDimensions = properties.boundingBox.dimensions; - that.worldPosition = properties.boundingBox.center; - + that.worldDimensions = properties.dimensions; // properties.boundingbox.dimensions; + that.worldPosition = properties.position; + that.worldRotation = properties.rotation; SelectionDisplay.setSpaceMode(SPACE_LOCAL); } else { that.localRotation = null; @@ -194,7 +194,6 @@ SelectionManager = (function() { print("ERROR: entitySelectionTool.update got exception: " + JSON.stringify(e)); } } - }; return that; @@ -230,16 +229,23 @@ SelectionDisplay = (function() { var COLOR_BLUE = { red:0, green:0, blue:255 }; var COLOR_RED = { red:255, green:0, blue:0 }; - var GRABBER_TRANSLATE_ARROW_CONE_OFFSET = 0.3625; - var GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET = 0.3; - var GRABBER_STRETCH_SPHERE_OFFSET = 0.2; - var GRABBER_SCALE_CUBE_OFFSET = 0.2; + var GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET = 1.35; + var GRABBER_TRANSLATE_ARROW_CYLINDER_DIMENSION_MULTIPLE = 0.05; + var GRABBER_TRANSLATE_ARROW_CYLINDER_Y_MULTIPLE = 7.5; + var GRABBER_TRANSLATE_ARROW_CONE_DIMENSION_MULTIPLE = 0.25; + var GRABBER_ROTATE_RINGS_DIMENSION_MULTIPLE = 2.0; + var GRABBER_STRETCH_SPHERE_OFFSET = 1.0; + var GRABBER_STRETCH_SPHERE_DIMENSION_MULTIPLE = 0.1; + var GRABBER_SCALE_CUBE_OFFSET = 1.0; + var GRABBER_SCALE_CUBE_DIMENSION_MULTIPLE = 0.15; + var GRABBER_CLONER_OFFSET = { x:0.9, y:-0.9, z:0.9 }; var GRABBER_SCALE_CUBE_IDLE_COLOR = { red:120, green:120, blue:120 }; var GRABBER_SCALE_CUBE_SELECTED_COLOR = { red:0, green:0, blue:0 }; var GRABBER_SCALE_EDGE_COLOR = { red:120, green:120, blue:120 }; var SCALE_MINIMUM_DIMENSION = 0.02; + var STRETCH_MINIMUM_DIMENSION = 0.001; // These are multipliers for sizing the rotation degrees display while rotating an entity var ROTATION_DISPLAY_DISTANCE_MULTIPLIER = 1.0; @@ -253,6 +259,13 @@ SelectionDisplay = (function() { Z : 2 } + var STRETCH_DIRECTION = { + X : 0, + Y : 1, + Z : 2, + ALL : 3 + } + var ROTATE_DIRECTION = { PITCH : 0, YAW : 1, @@ -291,7 +304,6 @@ SelectionDisplay = (function() { var grabberPropertiesTranslateArrowCones = { shape: "Cone", - dimensions: { x:0.05, y:0.05, z:0.05 }, solid: true, visible: false, ignoreRayIntersection: false, @@ -299,7 +311,6 @@ SelectionDisplay = (function() { }; var grabberPropertiesTranslateArrowCylinders = { shape: "Cylinder", - dimensions: { x:0.01, y:0.075, z:0.01 }, solid: true, visible: false, ignoreRayIntersection: false, @@ -319,7 +330,6 @@ SelectionDisplay = (function() { Overlays.editOverlay(grabberTranslateZCylinder, { color : COLOR_BLUE }); var grabberPropertiesRotateRings = { - size: 0.5, alpha: 1, innerRadius: 0.9, startAt: 0, @@ -347,9 +357,8 @@ SelectionDisplay = (function() { }); var grabberRotateCurrentRing = Overlays.addOverlay("circle3d", { - size: 0.5, alpha: 1, - color: { red: 224, green: 67, blue: 36 }, + color: { red: 255, green: 99, blue: 9 }, solid: true, innerRadius: 0.9, visible: false, @@ -377,7 +386,6 @@ SelectionDisplay = (function() { var grabberPropertiesStretchSpheres = { shape: "Sphere", - dimensions: { x:0.02, y:0.02, z:0.02 }, solid: true, visible: false, ignoreRayIntersection: false, @@ -393,7 +401,6 @@ SelectionDisplay = (function() { var grabberPropertiesStretchPanel = { shape: "Quad", alpha: 0.5, - dimensions: { x:GRABBER_SCALE_CUBE_OFFSET * 2, y:GRABBER_SCALE_CUBE_OFFSET * 2, z:0.01 }, solid: true, visible: false, ignoreRayIntersection: true, @@ -444,6 +451,25 @@ SelectionDisplay = (function() { var grabberScaleFREdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); var grabberScaleFLEdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); + var grabberCloner = Overlays.addOverlay("cube", { + size: 0.05, + color: COLOR_GREEN, + solid: true, + visible: false, + ignoreRayIntersection: false, + drawInFront: true, + borderSize: 1.4 + }); + + var selectionBox = Overlays.addOverlay("cube", { + size: 1, + color: COLOR_RED, + alpha: 1, + solid: false, + visible: false, + dashed: false + }); + var allOverlays = [ grabberTranslateXCone, grabberTranslateXCylinder, @@ -481,7 +507,9 @@ SelectionDisplay = (function() { grabberScaleNREdge, grabberScaleNLEdge, grabberScaleFREdge, - grabberScaleFLEdge + grabberScaleFLEdge, + grabberCloner, + selectionBox ]; overlayNames[grabberTranslateXCone] = "grabberTranslateXCone"; @@ -521,6 +549,8 @@ SelectionDisplay = (function() { overlayNames[grabberScaleNLEdge] = "grabberScaleNLEdge"; overlayNames[grabberScaleFREdge] = "grabberScaleFREdge"; overlayNames[grabberScaleFLEdge] = "grabberScaleFLEdge"; + overlayNames[grabberCloner] = "grabberCloner"; + overlayNames[selectionBox] = "selectionBox"; // We get mouseMoveEvents from the handControllers, via handControllerPointer. // But we dont' get mousePressEvents. @@ -587,7 +617,7 @@ SelectionDisplay = (function() { pickNormal = { x:0, y:1, z:0 }; } - var rotation = Entities.getEntityProperties(SelectionManager.selections[0]).rotation; + var rotation = SelectionManager.worldRotation; pickNormal = Vec3.multiplyQbyV(rotation, pickNormal); lastPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal); @@ -600,6 +630,7 @@ SelectionDisplay = (function() { that.setGrabberRotateVisible(false); that.setGrabberStretchVisible(false); that.setGrabberScaleVisible(false); + that.setGrabberClonerVisible(false); // Duplicate entities if alt is pressed. This will make a // copy of the selected entities and move the _original_ entities, not @@ -638,7 +669,7 @@ SelectionDisplay = (function() { projectionVector = { x:0, y:0, z:1 }; } - var rotation = Entities.getEntityProperties(SelectionManager.selections[0]).rotation; + var rotation = SelectionManager.worldRotation; projectionVector = Vec3.multiplyQbyV(rotation, projectionVector); var dotVector = Vec3.dot(vector, projectionVector); @@ -666,258 +697,388 @@ SelectionDisplay = (function() { }); } - function addGrabberStretchTool(overlay, mode, direction) { - var pickNormal = null; + // FUNCTION: VEC 3 MULT + var vec3Mult = function(v1, v2) { + return { + x: v1.x * v2.x, + y: v1.y * v2.y, + z: v1.z * v2.z + }; + }; + + function makeStretchTool(stretchMode, directionEnum, directionVec, pivot, offset) { + var directionFor3DStretch = directionVec; + var distanceFor3DStretch = 0; + var DISTANCE_INFLUENCE_THRESHOLD = 1.2; + + var signs = { + x: directionVec.x < 0 ? -1 : (directionVec.x > 0 ? 1 : 0), + y: directionVec.y < 0 ? -1 : (directionVec.y > 0 ? 1 : 0), + z: directionVec.z < 0 ? -1 : (directionVec.z > 0 ? 1 : 0) + }; + + var mask = { + x: Math.abs(directionVec.x) > 0 ? 1 : 0, + y: Math.abs(directionVec.y) > 0 ? 1 : 0, + z: Math.abs(directionVec.z) > 0 ? 1 : 0 + }; + + var numDimensions = mask.x + mask.y + mask.z; + + var planeNormal = null; var lastPick = null; - var projectionVector = null; - var stretchPanel = null; - addGrabberTool(overlay, { - mode: mode, - onBegin: function(event, pickRay, pickResult) { - if (direction === TRANSLATE_DIRECTION.X) { - stretchPanel = grabberStretchXPanel; - pickNormal = { x:0, y:0, z:1 }; - } else if (direction === TRANSLATE_DIRECTION.Y) { - stretchPanel = grabberStretchYPanel; - pickNormal = { x:1, y:0, z:0 }; - } else if (direction === TRANSLATE_DIRECTION.Z) { - stretchPanel = grabberStretchZPanel; - pickNormal = { x:0, y:1, z:0 }; - } + var lastPick3D = null; + var initialPosition = null; + var initialDimensions = null; + var initialIntersection = null; + var initialProperties = null; + var registrationPoint = null; + var deltaPivot = null; + var deltaPivot3D = null; + var pickRayPosition = null; + var pickRayPosition3D = null; + var rotation = null; - Overlays.editOverlay(stretchPanel, { visible:true }); + var onBegin = function(event, pickRay, pickResult) { + var properties = Entities.getEntityProperties(SelectionManager.selections[0]); + initialProperties = properties; + rotation = (spaceMode === SPACE_LOCAL) ? properties.rotation : Quat.IDENTITY; - var rotation = Entities.getEntityProperties(SelectionManager.selections[0]).rotation; - pickNormal = Vec3.multiplyQbyV(rotation, pickNormal); - - lastPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal); - - SelectionManager.saveProperties(); - - that.setGrabberTranslateVisible(false); - that.setGrabberRotateVisible(false); - that.setGrabberStretchXVisible(direction === TRANSLATE_DIRECTION.X); - that.setGrabberStretchYVisible(direction === TRANSLATE_DIRECTION.Y); - that.setGrabberStretchZVisible(direction === TRANSLATE_DIRECTION.Z); - - // Duplicate entities if alt is pressed. This will make a - // copy of the selected entities and move the _original_ entities, not - // the new ones. - if (event.isAlt) { - duplicatedEntityIDs = []; - for (var otherEntityID in SelectionManager.savedProperties) { - var properties = SelectionManager.savedProperties[otherEntityID]; - if (!properties.locked) { - var entityID = Entities.addEntity(properties); - duplicatedEntityIDs.push({ - entityID: entityID, - properties: properties - }); - } - } - } else { - duplicatedEntityIDs = null; - } - }, - onEnd: function(event, reason) { - Overlays.editOverlay(stretchPanel, { visible:false }); - pushCommandForSelections(duplicatedEntityIDs); - }, - onMove: function(event) { - pickRay = generalComputePickRay(event.x, event.y); - - // translate mode left/right based on view toward entity - var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal); - var vector = Vec3.subtract(newIntersection, lastPick); - - if (direction === TRANSLATE_DIRECTION.X) { - projectionVector = { x:1, y:0, z:0 }; - } else if (direction === TRANSLATE_DIRECTION.Y) { - projectionVector = { x:0, y:1, z:0 }; - } else if (direction === TRANSLATE_DIRECTION.Z) { - projectionVector = { x:0, y:0, z:1 }; - } - - var rotation = Entities.getEntityProperties(SelectionManager.selections[0]).rotation; - projectionVector = Vec3.multiplyQbyV(rotation, projectionVector); - - var dotVector = Vec3.dot(vector, projectionVector); - vector = Vec3.multiply(dotVector, projectionVector); - vector = grid.snapToGrid(vector); - - var wantDebug = false; - if (wantDebug) { - print("translateUpDown... "); - print(" event.y:" + event.y); - Vec3.print(" newIntersection:", newIntersection); - Vec3.print(" vector:", vector); - // Vec3.print(" newPosition:", newPosition); - } - - for (var i = 0; i < SelectionManager.selections.length; i++) { - var id = SelectionManager.selections[i]; - var properties = SelectionManager.savedProperties[id]; - var newPosition = Vec3.sum(properties.position, vector); - var difference = Vec3.subtract(newPosition, SelectionManager.worldPosition); - var halfDifference = Vec3.multiply(difference, 0.5); - var quarterDifference = Vec3.multiply(halfDifference, 0.5); - var newDimensions = properties.dimensions; - var actualNewPosition = properties.position; - - if (direction == TRANSLATE_DIRECTION.X) { - newDimensions.x += halfDifference.x; - actualNewPosition.x += quarterDifference.x; - } else if (direction == TRANSLATE_DIRECTION.Y) { - newDimensions.y += halfDifference.y; - actualNewPosition.y += quarterDifference.y; - } else if (direction == TRANSLATE_DIRECTION.Z) { - newDimensions.z += halfDifference.z; - actualNewPosition.z += quarterDifference.z; - } - - Entities.editEntity(id, { - position: actualNewPosition, - dimensions: newDimensions - }); - } - - SelectionManager._update(); + if (spaceMode === SPACE_LOCAL) { + rotation = SelectionManager.localRotation; + initialPosition = SelectionManager.localPosition; + initialDimensions = SelectionManager.localDimensions; + registrationPoint = SelectionManager.localRegistrationPoint; + } else { + rotation = SelectionManager.worldRotation; + initialPosition = SelectionManager.worldPosition; + initialDimensions = SelectionManager.worldDimensions; + registrationPoint = SelectionManager.worldRegistrationPoint; } - }); + + // Modify range of registrationPoint to be [-0.5, 0.5] + var centeredRP = Vec3.subtract(registrationPoint, { + x: 0.5, + y: 0.5, + z: 0.5 + }); + + // Scale pivot to be in the same range as registrationPoint + var scaledPivot = Vec3.multiply(0.5, pivot); + deltaPivot = Vec3.subtract(centeredRP, scaledPivot); + + var scaledOffset = Vec3.multiply(0.5, offset); + + // Offset from the registration point + offsetRP = Vec3.subtract(scaledOffset, centeredRP); + + // Scaled offset in world coordinates + var scaledOffsetWorld = vec3Mult(initialDimensions, offsetRP); + + pickRayPosition = Vec3.sum(initialPosition, Vec3.multiplyQbyV(rotation, scaledOffsetWorld)); + + if (directionFor3DStretch) { + // pivot, offset and pickPlanePosition for 3D manipulation + var scaledPivot3D = Vec3.multiply(0.5, Vec3.multiply(1.0, directionFor3DStretch)); + deltaPivot3D = Vec3.subtract(centeredRP, scaledPivot3D); + + var scaledOffsetWorld3D = vec3Mult(initialDimensions, + Vec3.subtract(Vec3.multiply(0.5, Vec3.multiply(-1.0, directionFor3DStretch)), centeredRP)); + + pickRayPosition3D = Vec3.sum(initialPosition, Vec3.multiplyQbyV(rotation, scaledOffsetWorld)); + } + var start = null; + var end = null; + if ((numDimensions === 1) && mask.x) { + start = Vec3.multiplyQbyV(rotation, { + x: -10000, + y: 0, + z: 0 + }); + start = Vec3.sum(start, properties.position); + end = Vec3.multiplyQbyV(rotation, { + x: 10000, + y: 0, + z: 0 + }); + end = Vec3.sum(end, properties.position); + } + if ((numDimensions === 1) && mask.y) { + start = Vec3.multiplyQbyV(rotation, { + x: 0, + y: -10000, + z: 0 + }); + start = Vec3.sum(start, properties.position); + end = Vec3.multiplyQbyV(rotation, { + x: 0, + y: 10000, + z: 0 + }); + end = Vec3.sum(end, properties.position); + } + if ((numDimensions === 1) && mask.z) { + start = Vec3.multiplyQbyV(rotation, { + x: 0, + y: 0, + z: -10000 + }); + start = Vec3.sum(start, properties.position); + end = Vec3.multiplyQbyV(rotation, { + x: 0, + y: 0, + z: 10000 + }); + end = Vec3.sum(end, properties.position); + } + if (numDimensions === 1) { + if (mask.x === 1) { + planeNormal = { + x: 0, + y: 1, + z: 0 + }; + } else if (mask.y === 1) { + planeNormal = { + x: 1, + y: 0, + z: 0 + }; + } else { + planeNormal = { + x: 0, + y: 1, + z: 0 + }; + } + } else if (numDimensions === 2) { + if (mask.x === 0) { + planeNormal = { + x: 1, + y: 0, + z: 0 + }; + } else if (mask.y === 0) { + planeNormal = { + x: 0, + y: 1, + z: 0 + }; + } else { + planeNormal = { + x: 0, + y: 0, + z: 1 + }; + } + } + + planeNormal = Vec3.multiplyQbyV(rotation, planeNormal); + lastPick = rayPlaneIntersection(pickRay, + pickRayPosition, + planeNormal); + + var planeNormal3D = { + x: 0, + y: 0, + z: 0 + }; + if (directionFor3DStretch) { + lastPick3D = rayPlaneIntersection(pickRay, + pickRayPosition3D, + planeNormal3D); + distanceFor3DStretch = Vec3.length(Vec3.subtract(pickRayPosition3D, pickRay.origin)); + } + + that.setGrabberTranslateVisible(false); + that.setGrabberRotateVisible(false); + that.setGrabberScaleVisible(directionEnum === STRETCH_DIRECTION.ALL); + that.setGrabberStretchXVisible(directionEnum === STRETCH_DIRECTION.X); + that.setGrabberStretchYVisible(directionEnum === STRETCH_DIRECTION.Y); + that.setGrabberStretchZVisible(directionEnum === STRETCH_DIRECTION.Z); + that.setGrabberClonerVisible(false); + + SelectionManager.saveProperties(); + }; + + var onEnd = function(event, reason) { + pushCommandForSelections(); + }; + + var onMove = function(event) { + var proportional = (spaceMode === SPACE_WORLD) || event.isShifted || directionEnum === STRETCH_DIRECTION.ALL; + + var position, dimensions, rotation; + if (spaceMode === SPACE_LOCAL) { + position = SelectionManager.localPosition; + dimensions = SelectionManager.localDimensions; + rotation = SelectionManager.localRotation; + } else { + position = SelectionManager.worldPosition; + dimensions = SelectionManager.worldDimensions; + rotation = SelectionManager.worldRotation; + } + + var localDeltaPivot = deltaPivot; + var localSigns = signs; + + var pickRay = generalComputePickRay(event.x, event.y); + + // Are we using handControllers or Mouse - only relevant for 3D tools + var controllerPose = getControllerWorldLocation(activeHand, true); + var vector = null; + if (HMD.isHMDAvailable() && HMD.isHandControllerAvailable() && + controllerPose.valid && that.triggered && directionFor3DStretch) { + localDeltaPivot = deltaPivot3D; + + newPick = pickRay.origin; + + vector = Vec3.subtract(newPick, lastPick3D); + + vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector); + + if (distanceFor3DStretch > DISTANCE_INFLUENCE_THRESHOLD) { + // Range of Motion + vector = Vec3.multiply(distanceFor3DStretch , vector); + } + + localSigns = directionFor3DStretch; + + } else { + newPick = rayPlaneIntersection(pickRay, + pickRayPosition, + planeNormal); + vector = Vec3.subtract(newPick, lastPick); + + vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector); + + vector = vec3Mult(mask, vector); + + } + + vector = grid.snapToSpacing(vector); + + var changeInDimensions = Vec3.multiply(-1, vec3Mult(localSigns, vector)); + var newDimensions; + if (proportional) { + var absX = Math.abs(changeInDimensions.x); + var absY = Math.abs(changeInDimensions.y); + var absZ = Math.abs(changeInDimensions.z); + var pctChange = 0; + if (absX > absY && absX > absZ) { + pctChange = changeInDimensions.x / initialProperties.dimensions.x; + pctChange = changeInDimensions.x / initialDimensions.x; + } else if (absY > absZ) { + pctChange = changeInDimensions.y / initialProperties.dimensions.y; + pctChange = changeInDimensions.y / initialDimensions.y; + } else { + pctChange = changeInDimensions.z / initialProperties.dimensions.z; + pctChange = changeInDimensions.z / initialDimensions.z; + } + pctChange += 1.0; + newDimensions = Vec3.multiply(pctChange, initialDimensions); + } else { + newDimensions = Vec3.sum(initialDimensions, changeInDimensions); + } + + newDimensions.x = Math.max(newDimensions.x, STRETCH_MINIMUM_DIMENSION); + newDimensions.y = Math.max(newDimensions.y, STRETCH_MINIMUM_DIMENSION); + newDimensions.z = Math.max(newDimensions.z, STRETCH_MINIMUM_DIMENSION); + + var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(localDeltaPivot, changeInDimensions)); + if (directionEnum === STRETCH_DIRECTION.ALL) { + changeInPosition = { x:0, y:0, z:0 }; + } + var newPosition = Vec3.sum(initialPosition, changeInPosition); + + for (var i = 0; i < SelectionManager.selections.length; i++) { + Entities.editEntity(SelectionManager.selections[i], { + position: newPosition, + dimensions: newDimensions + }); + } + + var wantDebug = false; + if (wantDebug) { + print(stretchMode); + // Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + // Vec3.print(" oldPOS:", oldPOS); + // Vec3.print(" newPOS:", newPOS); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + + Vec3.print(" changeInPosition:", changeInPosition); + Vec3.print(" newPosition:", newPosition); + } + + SelectionManager._update(); + };// End of onMove def + + return { + mode: stretchMode, + onBegin: onBegin, + onMove: onMove, + onEnd: onEnd + }; } - function addGrabberScaleTool(overlay, mode, direction) { - var pickNormal = null; - var lastPick = null; - var selectedGrabber = null; - addGrabberTool(overlay, { - mode: mode, - onBegin: function(event, pickRay, pickResult) { - pickNormal = { x:-1, y:1, z:-1 }; + function addGrabberStretchTool(overlay, mode, directionEnum) { + var directionVec, pivot, offset; + if (directionEnum === STRETCH_DIRECTION.X) { + directionVec = { x:-1, y:0, z:0 }; + pivot = { x:-1, y:0, z:0 }; + offset = { x:1, y:0, z:0 }; + } else if (directionEnum === STRETCH_DIRECTION.Y) { + directionVec = { x:0, y:-1, z:0 }; + pivot = { x:0, y:-1, z:0 }; + offset = { x:0, y:1, z:0 }; + } else if (directionEnum === STRETCH_DIRECTION.Z) { + directionVec = { x:0, y:0, z:-1 }; + pivot = { x:0, y:0, z:-1 }; + offset = { x:0, y:0, z:1 }; + } + var tool = makeStretchTool(mode, directionEnum, directionVec, pivot, offset); + return addGrabberTool(overlay, tool); + } - if (direction === SCALE_DIRECTION.LBN) { - selectedGrabber = grabberScaleLBNCube; - } else if (direction === SCALE_DIRECTION.RBN) { - selectedGrabber = grabberScaleRBNCube; - } else if (direction === SCALE_DIRECTION.LBF) { - selectedGrabber = grabberScaleLBFCube; - } else if (direction === SCALE_DIRECTION.RBF) { - selectedGrabber = grabberScaleRBFCube; - } else if (direction === SCALE_DIRECTION.LTN) { - selectedGrabber = grabberScaleLTNCube; - } else if (direction === SCALE_DIRECTION.RTN) { - selectedGrabber = grabberScaleRTNCube; - } else if (direction === SCALE_DIRECTION.LTF) { - selectedGrabber = grabberScaleLTFCube; - } else if (direction === SCALE_DIRECTION.RTF) { - selectedGrabber = grabberScaleRTFCube; - } - Overlays.editOverlay(selectedGrabber, { color: GRABBER_SCALE_CUBE_SELECTED_COLOR }); - - lastPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal); - - SelectionManager.saveProperties(); - - that.setGrabberTranslateVisible(false); - that.setGrabberRotateVisible(false); - that.setGrabberStretchVisible(false); - that.setGrabberScaleVisible(true); - - // Duplicate entities if alt is pressed. This will make a - // copy of the selected entities and move the _original_ entities, not - // the new ones. - if (event.isAlt) { - duplicatedEntityIDs = []; - for (var otherEntityID in SelectionManager.savedProperties) { - var properties = SelectionManager.savedProperties[otherEntityID]; - if (!properties.locked) { - var entityID = Entities.addEntity(properties); - duplicatedEntityIDs.push({ - entityID: entityID, - properties: properties - }); - } - } - } else { - duplicatedEntityIDs = null; - } - }, - onEnd: function(event, reason) { - Overlays.editOverlay(selectedGrabber, { color: GRABBER_SCALE_CUBE_IDLE_COLOR }); - pushCommandForSelections(duplicatedEntityIDs); - }, - onMove: function(event) { - pickRay = generalComputePickRay(event.x, event.y); - - // translate mode left/right based on view toward entity - var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal); - var vector = Vec3.subtract(newIntersection, lastPick); - - var projectionVector; - if (direction === SCALE_DIRECTION.LBN) { - projectionVector = { x:-1, y:-1, z:-1 }; - } else if (direction === SCALE_DIRECTION.RBN) { - projectionVector = { x:-1, y:-1, z:1 }; - } else if (direction === SCALE_DIRECTION.LBF) { - projectionVector = { x:1, y:-1, z:-1 }; - } else if (direction === SCALE_DIRECTION.RBF) { - projectionVector = { x:1, y:-1, z:1 }; - } else if (direction === SCALE_DIRECTION.LTN) { - projectionVector = { x:-1, y:1, z:-1 }; - } else if (direction === SCALE_DIRECTION.RTN) { - projectionVector = { x:-1, y:1, z:1 }; - } else if (direction === SCALE_DIRECTION.LTF) { - projectionVector = { x:1, y:1, z:-1 }; - } else if (direction === SCALE_DIRECTION.RTF) { - projectionVector = { x:1, y:1, z:1 }; - } - - var dotVector = Vec3.dot(vector, projectionVector); - vector = Vec3.multiply(dotVector, projectionVector); - vector = grid.snapToGrid(vector); - - var wantDebug = false; - if (wantDebug) { - print("translateUpDown... "); - print(" event.y:" + event.y); - Vec3.print(" newIntersection:", newIntersection); - Vec3.print(" vector:", vector); - // Vec3.print(" newPosition:", newPosition); - } - - for (var i = 0; i < SelectionManager.selections.length; i++) { - var id = SelectionManager.selections[i]; - var properties = SelectionManager.savedProperties[id]; - var newPosition = Vec3.sum(properties.position, vector); - var difference = Vec3.subtract(newPosition, SelectionManager.worldPosition); - var differenceAvg = (difference.x + difference.y + difference.z) / 3; - var newDimensionsX = properties.dimensions.x + differenceAvg; - var newDimensionsY = properties.dimensions.y + differenceAvg; - var newDimensionsZ = properties.dimensions.z + differenceAvg; - if (newDimensionsX < SCALE_MINIMUM_DIMENSION) { - var differenceBelow = SCALE_MINIMUM_DIMENSION - newDimensionsX; - newDimensionsX += differenceBelow; - newDimensionsY += differenceBelow; - newDimensionsZ += differenceBelow; - } - if (newDimensionsY < SCALE_MINIMUM_DIMENSION) { - var differenceBelow = SCALE_MINIMUM_DIMENSION - newDimensionsY; - newDimensionsX += differenceBelow; - newDimensionsY += differenceBelow; - newDimensionsZ += differenceBelow; - } - if (newDimensionsZ < SCALE_MINIMUM_DIMENSION) { - var differenceBelow = SCALE_MINIMUM_DIMENSION - newDimensionsZ; - newDimensionsX += differenceBelow; - newDimensionsY += differenceBelow; - newDimensionsZ += differenceBelow; - } - Entities.editEntity(id, { dimensions: { x:newDimensionsX, y:newDimensionsY, z:newDimensionsZ }}); - } - - SelectionManager._update(); - } - }); + function addGrabberScaleTool(overlay, mode, directionEnum) { + var directionVec, pivot, offset; + if (directionEnum === SCALE_DIRECTION.LBN) { + directionVec = { x:1, y:1, z:1 }; + pivot = { x:1, y:1, z:1 }; + offset = { x:-1, y:-1, z:-1 }; + } else if (directionEnum === SCALE_DIRECTION.RBN) { + directionVec = { x:1, y:1, z:-1 }; + pivot = { x:1, y:1, z:-1 }; + offset = { x:-1, y:-1, z:1 }; + } else if (directionEnum === SCALE_DIRECTION.LBF) { + directionVec = { x:-1, y:1, z:1 }; + pivot = { x:-1, y:1, z:1 }; + offset = { x:1, y:-1, z:-1 }; + } else if (directionEnum === SCALE_DIRECTION.RBF) { + directionVec = { x:-1, y:1, z:-1 }; + pivot = { x:-1, y:1, z:-1 }; + offset = { x:1, y:-1, z:1 }; + } else if (directionEnum === SCALE_DIRECTION.LTN) { + directionVec = { x:1, y:-1, z:1 }; + pivot = { x:1, y:-1, z:1 }; + offset = { x:-1, y:1, z:-1 }; + } else if (directionEnum === SCALE_DIRECTION.RTN) { + directionVec = { x:1, y:-1, z:-1 }; + pivot = { x:1, y:-1, z:-1 }; + offset = { x:-1, y:1, z:1 }; + } else if (directionEnum === SCALE_DIRECTION.LTF) { + directionVec = { x:-1, y:-1, z:1 }; + pivot = { x:-1, y:-1, z:1 }; + offset = { x:1, y:1, z:-1 }; + } else if (directionEnum === SCALE_DIRECTION.RTF) { + directionVec = { x:-1, y:-1, z:-1 }; + pivot = { x:-1, y:-1, z:-1 }; + offset = { x:1, y:1, z:1 }; + } + var tool = makeStretchTool(mode, STRETCH_DIRECTION.ALL, directionVec, pivot, offset); + return addGrabberTool(overlay, tool); } // FUNCTION: UPDATE ROTATION DEGREES OVERLAY @@ -970,9 +1131,9 @@ SelectionDisplay = (function() { }; if (reposition) { - var dPos = Vec3.subtract(initialProperties.position, initialPosition); + var dPos = Vec3.subtract(initialProperties.position, SelectionManager.worldPosition); dPos = Vec3.multiplyQbyV(rotationChange, dPos); - newProperties.position = Vec3.sum(initialPosition, dPos); + newProperties.position = Vec3.sum(SelectionManager.worldPosition, dPos); } Entities.editEntity(entityID, newProperties); @@ -980,8 +1141,8 @@ SelectionDisplay = (function() { } function addGrabberRotateTool(overlay, mode, direction) { - var initialPosition = SelectionManager.worldPosition; var selectedGrabber = null; + var worldRotation = null; addGrabberTool(overlay, { mode: mode, onBegin: function(event, pickRay, pickResult) { @@ -993,29 +1154,37 @@ SelectionDisplay = (function() { that.setGrabberRotateRollVisible(direction === ROTATE_DIRECTION.ROLL); that.setGrabberStretchVisible(false); that.setGrabberScaleVisible(false); - - initialPosition = SelectionManager.worldPosition; + that.setGrabberClonerVisible(false); if (direction === ROTATE_DIRECTION.PITCH) { rotationNormal = { x: 1, y: 0, z: 0 }; + worldRotation = worldRotationY; selectedGrabber = grabberRotatePitchRing; } else if (direction === ROTATE_DIRECTION.YAW) { rotationNormal = { x: 0, y: 1, z: 0 }; + worldRotation = worldRotationZ; selectedGrabber = grabberRotateYawRing; } else if (direction === ROTATE_DIRECTION.ROLL) { rotationNormal = { x: 0, y: 0, z: 1 }; + worldRotation = worldRotationX; selectedGrabber = grabberRotateRollRing; } Overlays.editOverlay(selectedGrabber, { hasTickMarks: true }); - var rotation = Entities.getEntityProperties(SelectionManager.selections[0]).rotation; + var rotation = SelectionManager.worldRotation; rotationNormal = Vec3.multiplyQbyV(rotation, rotationNormal); var rotCenter = SelectionManager.worldPosition; Overlays.editOverlay(rotationDegreesDisplay, { visible: true }); - Overlays.editOverlay(grabberRotateCurrentRing, { visible: true }); + Overlays.editOverlay(grabberRotateCurrentRing, { + position: rotCenter, + rotation: worldRotation, + startAt: 0, + endAt: 0, + visible: true + }); updateRotationDegreesOverlay(0, direction, rotCenter); // editOverlays may not have committed rotation changes. @@ -1051,15 +1220,6 @@ SelectionDisplay = (function() { updateSelectionsRotation(rotChange); updateRotationDegreesOverlay(-angleFromZero, direction, rotCenter); - var worldRotation; - if (direction === ROTATE_DIRECTION.PITCH) { - worldRotation = worldRotationY; - } else if (direction === ROTATE_DIRECTION.YAW) { - worldRotation = worldRotationZ; - } else if (direction === ROTATE_DIRECTION.ROLL) { - worldRotation = worldRotationX; - } - var startAtCurrent = 0; var endAtCurrent = angleFromZero; if (angleFromZero < 0) { @@ -1067,11 +1227,13 @@ SelectionDisplay = (function() { endAtCurrent = 360; } Overlays.editOverlay(grabberRotateCurrentRing, { - position: rotCenter, - rotation: worldRotation, startAt: startAtCurrent, endAt: endAtCurrent }); + // not sure why but this seems to be needed to fix an reverse rotation for yaw ring only + if (direction === ROTATE_DIRECTION.YAW) { + Overlays.editOverlay(grabberRotateCurrentRing, { rotation: worldRotationZ }); + } } } }); @@ -1142,89 +1304,9 @@ SelectionDisplay = (function() { }); */ - that.updateHandles(); + that.updateGrabbers(); }; - // FUNCTION: UPDATE HANDLE POSITION ROTATION - that.updateHandlePositionRotation = function() { - if (SelectionManager.hasSelection()) { - var worldPosition = SelectionManager.worldPosition; - var worldRotation = Entities.getEntityProperties(SelectionManager.selections[0]).rotation; - - var localRotationX = Quat.fromPitchYawRollDegrees(0, 0, -90); - worldRotationX = Quat.multiply(worldRotation, localRotationX); - var localRotationY = Quat.fromPitchYawRollDegrees(0, 90, 0); - worldRotationY = Quat.multiply(worldRotation, localRotationY); - var localRotationZ = Quat.fromPitchYawRollDegrees(90, 0, 0); - worldRotationZ = Quat.multiply(worldRotation, localRotationZ); - - var cylinderXPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET, y:0, z:0 })); - Overlays.editOverlay(grabberTranslateXCylinder, { position: cylinderXPos, rotation:worldRotationX }); - var coneXPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:GRABBER_TRANSLATE_ARROW_CONE_OFFSET, y:0, z:0 })); - Overlays.editOverlay(grabberTranslateXCone, { position: coneXPos, rotation:worldRotationX }); - var stretchXPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:GRABBER_STRETCH_SPHERE_OFFSET, y:0, z:0 })); - Overlays.editOverlay(grabberStretchXSphere, { position: stretchXPos }); - Overlays.editOverlay(grabberStretchXPanel, { position: stretchXPos, rotation:worldRotationY }); - - var cylinderYPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:0, y:GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET, z:0 })); - Overlays.editOverlay(grabberTranslateYCylinder, { position: cylinderYPos, rotation:worldRotationY }); - var coneYPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:0, y:GRABBER_TRANSLATE_ARROW_CONE_OFFSET, z:0 })); - Overlays.editOverlay(grabberTranslateYCone, { position: coneYPos, rotation:worldRotationY }); - var stretchYPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:0, y:GRABBER_STRETCH_SPHERE_OFFSET, z:0 })); - Overlays.editOverlay(grabberStretchYSphere, { position: stretchYPos }); - Overlays.editOverlay(grabberStretchYPanel, { position: stretchYPos, rotation:worldRotationZ }); - - var cylinderZPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:0, y:0, z:GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET })); - Overlays.editOverlay(grabberTranslateZCylinder, { position: cylinderZPos, rotation:worldRotationZ }); - var coneZPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:0, y:0, z:GRABBER_TRANSLATE_ARROW_CONE_OFFSET })); - Overlays.editOverlay(grabberTranslateZCone, { position: coneZPos, rotation:worldRotationZ }); - var stretchZPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:0, y:0, z:GRABBER_STRETCH_SPHERE_OFFSET })); - Overlays.editOverlay(grabberStretchZSphere, { position: stretchZPos }); - Overlays.editOverlay(grabberStretchZPanel, { position: stretchZPos, rotation:worldRotationX }); - - if (!isActiveTool(grabberRotatePitchRing)) { - Overlays.editOverlay(grabberRotatePitchRing, { position: SelectionManager.worldPosition, rotation: worldRotationY }); - } - if (!isActiveTool(grabberRotateYawRing)) { - Overlays.editOverlay(grabberRotateYawRing, { position: SelectionManager.worldPosition, rotation: worldRotationZ }); - } - if (!isActiveTool(grabberRotateRollRing)) { - Overlays.editOverlay(grabberRotateRollRing, { position: SelectionManager.worldPosition, rotation: worldRotationX }); - } - - var grabberScaleLBNCubePos = Vec3.sum(worldPosition, { x:-GRABBER_SCALE_CUBE_OFFSET, y:-GRABBER_SCALE_CUBE_OFFSET, z:-GRABBER_SCALE_CUBE_OFFSET }); - Overlays.editOverlay(grabberScaleLBNCube, { position:grabberScaleLBNCubePos }); - var grabberScaleRBNCubePos = Vec3.sum(worldPosition, { x:-GRABBER_SCALE_CUBE_OFFSET, y:-GRABBER_SCALE_CUBE_OFFSET, z:GRABBER_SCALE_CUBE_OFFSET }); - Overlays.editOverlay(grabberScaleRBNCube, { position:grabberScaleRBNCubePos }); - var grabberScaleLBFCubePos = Vec3.sum(worldPosition, { x:GRABBER_SCALE_CUBE_OFFSET, y:-GRABBER_SCALE_CUBE_OFFSET, z:-GRABBER_SCALE_CUBE_OFFSET }); - Overlays.editOverlay(grabberScaleLBFCube, { position:grabberScaleLBFCubePos }); - var grabberScaleRBFCubePos = Vec3.sum(worldPosition, { x:GRABBER_SCALE_CUBE_OFFSET, y:-GRABBER_SCALE_CUBE_OFFSET, z:GRABBER_SCALE_CUBE_OFFSET }); - Overlays.editOverlay(grabberScaleRBFCube, { position:grabberScaleRBFCubePos }); - var grabberScaleLTNCubePos = Vec3.sum(worldPosition, { x:-GRABBER_SCALE_CUBE_OFFSET, y:GRABBER_SCALE_CUBE_OFFSET, z:-GRABBER_SCALE_CUBE_OFFSET }); - Overlays.editOverlay(grabberScaleLTNCube, { position:grabberScaleLTNCubePos }); - var grabberScaleRTNCubePos = Vec3.sum(worldPosition, { x:-GRABBER_SCALE_CUBE_OFFSET, y:GRABBER_SCALE_CUBE_OFFSET, z:GRABBER_SCALE_CUBE_OFFSET }); - Overlays.editOverlay(grabberScaleRTNCube, { position:grabberScaleRTNCubePos }); - var grabberScaleLTFCubePos = Vec3.sum(worldPosition, { x:GRABBER_SCALE_CUBE_OFFSET, y:GRABBER_SCALE_CUBE_OFFSET, z:-GRABBER_SCALE_CUBE_OFFSET }); - Overlays.editOverlay(grabberScaleLTFCube, { position:grabberScaleLTFCubePos }); - var grabberScaleRTFCubePos = Vec3.sum(worldPosition, { x:GRABBER_SCALE_CUBE_OFFSET, y:GRABBER_SCALE_CUBE_OFFSET, z:GRABBER_SCALE_CUBE_OFFSET }); - Overlays.editOverlay(grabberScaleRTFCube, { position:grabberScaleRTFCubePos }); - - Overlays.editOverlay(grabberScaleTREdge, { start: grabberScaleRTNCubePos, end:grabberScaleRTFCubePos }); - Overlays.editOverlay(grabberScaleTLEdge, { start: grabberScaleLTNCubePos, end:grabberScaleLTFCubePos }); - Overlays.editOverlay(grabberScaleTFEdge, { start: grabberScaleLTFCubePos, end:grabberScaleRTFCubePos }); - Overlays.editOverlay(grabberScaleTNEdge, { start: grabberScaleLTNCubePos, end:grabberScaleRTNCubePos }); - Overlays.editOverlay(grabberScaleBREdge, { start: grabberScaleRBNCubePos, end:grabberScaleRBFCubePos }); - Overlays.editOverlay(grabberScaleBLEdge, { start: grabberScaleLBNCubePos, end:grabberScaleLBFCubePos }); - Overlays.editOverlay(grabberScaleBFEdge, { start: grabberScaleLBFCubePos, end:grabberScaleRBFCubePos }); - Overlays.editOverlay(grabberScaleBNEdge, { start: grabberScaleLBNCubePos, end:grabberScaleRBNCubePos }); - Overlays.editOverlay(grabberScaleNREdge, { start: grabberScaleRTNCubePos, end:grabberScaleRBNCubePos }); - Overlays.editOverlay(grabberScaleNLEdge, { start: grabberScaleLTNCubePos, end:grabberScaleLBNCubePos }); - Overlays.editOverlay(grabberScaleFREdge, { start: grabberScaleRTFCubePos, end:grabberScaleRBFCubePos }); - Overlays.editOverlay(grabberScaleFLEdge, { start: grabberScaleLTFCubePos, end:grabberScaleLBFCubePos }); - } - }; - Script.update.connect(that.updateHandlePositionRotation); - // FUNCTION: SET SPACE MODE that.setSpaceMode = function(newSpaceMode) { var wantDebug = false; @@ -1237,7 +1319,7 @@ SelectionDisplay = (function() { print(" Updating SpaceMode From: " + spaceMode + " To: " + newSpaceMode); } spaceMode = newSpaceMode; - that.updateHandles(); + that.updateGrabbers(); } else if (wantDebug) { print("WARNING: entitySelectionTool.setSpaceMode - Can't update SpaceMode. CurrentMode: " + spaceMode + " DesiredMode: " + newSpaceMode); } @@ -1246,23 +1328,243 @@ SelectionDisplay = (function() { } }; - // FUNCTION: UPDATE HANDLES - that.updateHandles = function() { + // FUNCTION: UPDATE GRABBERS + that.updateGrabbers = function() { var wantDebug = false; if (wantDebug) { - print("======> Update Handles ======="); + print("======> Update Grabbers ======="); print(" Selections Count: " + SelectionManager.selections.length); print(" SpaceMode: " + spaceMode); print(" DisplayMode: " + getMode()); } + if (SelectionManager.selections.length === 0) { that.setOverlaysVisible(false); return; } - var isSingleSelection = (SelectionManager.selections.length === 1); - if (isSingleSelection) { - }// end of isSingleSelection + if (SelectionManager.hasSelection()) { + var worldPosition = SelectionManager.worldPosition; + var worldRotation = SelectionManager.worldRotation; + var worldDimensions = SelectionManager.worldDimensions; + + var worldDimensionsX = worldDimensions.x; + var worldDimensionsY = worldDimensions.y; + var worldDimensionsZ = worldDimensions.z; + var dimensionAverage = (worldDimensionsX + worldDimensionsY + worldDimensionsZ) / 3; + + var localRotationX = Quat.fromPitchYawRollDegrees(0, 0, -90); + worldRotationX = Quat.multiply(worldRotation, localRotationX); + var localRotationY = Quat.fromPitchYawRollDegrees(0, 90, 0); + worldRotationY = Quat.multiply(worldRotation, localRotationY); + var localRotationZ = Quat.fromPitchYawRollDegrees(90, 0, 0); + worldRotationZ = Quat.multiply(worldRotation, localRotationZ); + + var arrowCylinderDimension = dimensionAverage * GRABBER_TRANSLATE_ARROW_CYLINDER_DIMENSION_MULTIPLE; + var arrowCylinderDimensions = { x:arrowCylinderDimension, y:arrowCylinderDimension * GRABBER_TRANSLATE_ARROW_CYLINDER_Y_MULTIPLE, z:arrowCylinderDimension }; + var arrowConeDimension = dimensionAverage * GRABBER_TRANSLATE_ARROW_CONE_DIMENSION_MULTIPLE; + var arrowConeDimensions = { x:arrowConeDimension, y:arrowConeDimension, z:arrowConeDimension }; + var cylinderXPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET * dimensionAverage, y:0, z:0 })); + Overlays.editOverlay(grabberTranslateXCylinder, { + position: cylinderXPos, + rotation: worldRotationX, + dimensions: arrowCylinderDimensions + }); + var cylinderXDiff = Vec3.subtract(cylinderXPos, worldPosition); + var coneXPos = Vec3.sum(cylinderXPos, Vec3.multiply(Vec3.normalize(cylinderXDiff), arrowCylinderDimensions.y * 0.83)); + Overlays.editOverlay(grabberTranslateXCone, { + position: coneXPos, + rotation: worldRotationX, + dimensions: arrowConeDimensions + }); + var cylinderYPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:0, y:GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET * dimensionAverage, z:0 })); + Overlays.editOverlay(grabberTranslateYCylinder, { + position: cylinderYPos, + rotation: worldRotationY, + dimensions: arrowCylinderDimensions + }); + var cylinderYDiff = Vec3.subtract(cylinderYPos, worldPosition); + var coneYPos = Vec3.sum(cylinderYPos, Vec3.multiply(Vec3.normalize(cylinderYDiff), arrowCylinderDimensions.y * 0.83)); + Overlays.editOverlay(grabberTranslateYCone, { + position: coneYPos, + rotation: worldRotationY, + dimensions: arrowConeDimensions + }); + var cylinderZPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:0, y:0, z:GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET * dimensionAverage })); + Overlays.editOverlay(grabberTranslateZCylinder, { + position: cylinderZPos, + rotation: worldRotationZ, + dimensions: arrowCylinderDimensions + }); + var cylinderZDiff = Vec3.subtract(cylinderZPos, worldPosition); + var coneZPos = Vec3.sum(cylinderZPos, Vec3.multiply(Vec3.normalize(cylinderZDiff), arrowCylinderDimensions.y * 0.83)); + Overlays.editOverlay(grabberTranslateZCone, { + position: coneZPos, + rotation: worldRotationZ, + dimensions: arrowConeDimensions + }); + + var grabberScaleCubeOffsetX = GRABBER_SCALE_CUBE_OFFSET * worldDimensionsX; + var grabberScaleCubeOffsetY = GRABBER_SCALE_CUBE_OFFSET * worldDimensionsY; + var grabberScaleCubeOffsetZ = GRABBER_SCALE_CUBE_OFFSET * worldDimensionsZ; + var scaleDimension = dimensionAverage * GRABBER_SCALE_CUBE_DIMENSION_MULTIPLE; + var scaleDimensions = { x:scaleDimension, y:scaleDimension, z:scaleDimension }; + var grabberScaleLBNCubePos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:-grabberScaleCubeOffsetX, y:-grabberScaleCubeOffsetY, z:-grabberScaleCubeOffsetZ })); + Overlays.editOverlay(grabberScaleLBNCube, { + position: grabberScaleLBNCubePos, + rotation: worldRotation, + dimensions: scaleDimensions + }); + var grabberScaleRBNCubePos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:-grabberScaleCubeOffsetX, y:-grabberScaleCubeOffsetY, z:grabberScaleCubeOffsetZ })); + Overlays.editOverlay(grabberScaleRBNCube, { + position: grabberScaleRBNCubePos, + rotation: worldRotation, + dimensions: scaleDimensions + }); + var grabberScaleLBFCubePos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:grabberScaleCubeOffsetX, y:-grabberScaleCubeOffsetY, z:-grabberScaleCubeOffsetZ })); + Overlays.editOverlay(grabberScaleLBFCube, { + position: grabberScaleLBFCubePos, + rotation: worldRotation, + dimensions: scaleDimensions + }); + var grabberScaleRBFCubePos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:grabberScaleCubeOffsetX, y:-grabberScaleCubeOffsetY, z:grabberScaleCubeOffsetZ })); + Overlays.editOverlay(grabberScaleRBFCube, { + position: grabberScaleRBFCubePos, + rotation: worldRotation, + dimensions: scaleDimensions + }); + var grabberScaleLTNCubePos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:-grabberScaleCubeOffsetX, y:grabberScaleCubeOffsetY, z:-grabberScaleCubeOffsetZ })); + Overlays.editOverlay(grabberScaleLTNCube, { + position: grabberScaleLTNCubePos, + rotation: worldRotation, + dimensions: scaleDimensions + }); + var grabberScaleRTNCubePos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:-grabberScaleCubeOffsetX, y:grabberScaleCubeOffsetY, z:grabberScaleCubeOffsetZ })); + Overlays.editOverlay(grabberScaleRTNCube, { + position: grabberScaleRTNCubePos, + rotation: worldRotation, + dimensions: scaleDimensions + }); + var grabberScaleLTFCubePos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:grabberScaleCubeOffsetX, y:grabberScaleCubeOffsetY, z:-grabberScaleCubeOffsetZ })); + Overlays.editOverlay(grabberScaleLTFCube, { + position: grabberScaleLTFCubePos, + rotation: worldRotation, + dimensions: scaleDimensions + }); + var grabberScaleRTFCubePos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:grabberScaleCubeOffsetX, y:grabberScaleCubeOffsetY, z:grabberScaleCubeOffsetZ })); + Overlays.editOverlay(grabberScaleRTFCube, { + position: grabberScaleRTFCubePos, + rotation: worldRotation, + dimensions: scaleDimensions + }); + + Overlays.editOverlay(grabberScaleTREdge, { start: grabberScaleRTNCubePos, end: grabberScaleRTFCubePos }); + Overlays.editOverlay(grabberScaleTLEdge, { start: grabberScaleLTNCubePos, end: grabberScaleLTFCubePos }); + Overlays.editOverlay(grabberScaleTFEdge, { start: grabberScaleLTFCubePos, end: grabberScaleRTFCubePos }); + Overlays.editOverlay(grabberScaleTNEdge, { start: grabberScaleLTNCubePos, end: grabberScaleRTNCubePos }); + Overlays.editOverlay(grabberScaleBREdge, { start: grabberScaleRBNCubePos, end: grabberScaleRBFCubePos }); + Overlays.editOverlay(grabberScaleBLEdge, { start: grabberScaleLBNCubePos, end: grabberScaleLBFCubePos }); + Overlays.editOverlay(grabberScaleBFEdge, { start: grabberScaleLBFCubePos, end: grabberScaleRBFCubePos }); + Overlays.editOverlay(grabberScaleBNEdge, { start: grabberScaleLBNCubePos, end: grabberScaleRBNCubePos }); + Overlays.editOverlay(grabberScaleNREdge, { start: grabberScaleRTNCubePos, end: grabberScaleRBNCubePos }); + Overlays.editOverlay(grabberScaleNLEdge, { start: grabberScaleLTNCubePos, end: grabberScaleLBNCubePos }); + Overlays.editOverlay(grabberScaleFREdge, { start: grabberScaleRTFCubePos, end: grabberScaleRBFCubePos }); + Overlays.editOverlay(grabberScaleFLEdge, { start: grabberScaleLTFCubePos, end: grabberScaleLBFCubePos }); + + var stretchSphereDimension = dimensionAverage * GRABBER_STRETCH_SPHERE_DIMENSION_MULTIPLE; + var stretchSphereDimensions = { x:stretchSphereDimension, y:stretchSphereDimension, z:stretchSphereDimension }; + var stretchXPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:GRABBER_STRETCH_SPHERE_OFFSET * worldDimensionsX, y:0, z:0 })); + Overlays.editOverlay(grabberStretchXSphere, { + position: stretchXPos, + dimensions: stretchSphereDimensions + }); + var stretchPanelXDimensions = Vec3.subtract(grabberScaleLTFCubePos, grabberScaleRBFCubePos); + var tempY = Math.abs(stretchPanelXDimensions.y); + stretchPanelXDimensions.x = 0.01; + stretchPanelXDimensions.y = Math.abs(stretchPanelXDimensions.z); + stretchPanelXDimensions.z = tempY; + Overlays.editOverlay(grabberStretchXPanel, { + position: stretchXPos, + rotation: worldRotationZ, + dimensions: stretchPanelXDimensions + }); + var stretchYPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:0, y:GRABBER_STRETCH_SPHERE_OFFSET * worldDimensionsY, z:0 })); + Overlays.editOverlay(grabberStretchYSphere, { + position: stretchYPos, + dimensions: stretchSphereDimensions + }); + var stretchPanelYDimensions = Vec3.subtract(grabberScaleLTFCubePos, grabberScaleRTNCubePos); + var tempX = Math.abs(stretchPanelYDimensions.x); + stretchPanelYDimensions.x = Math.abs(stretchPanelYDimensions.z); + stretchPanelYDimensions.y = 0.01; + stretchPanelYDimensions.z = tempX; + Overlays.editOverlay(grabberStretchYPanel, { + position: stretchYPos, + rotation: worldRotationY, + dimensions: stretchPanelYDimensions + }); + var stretchZPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:0, y:0, z:GRABBER_STRETCH_SPHERE_OFFSET * worldDimensionsZ })); + Overlays.editOverlay(grabberStretchZSphere, { + position: stretchZPos, + dimensions: stretchSphereDimensions + }); + var stretchPanelZDimensions = Vec3.subtract(grabberScaleRTFCubePos, grabberScaleRBNCubePos); + var tempX = Math.abs(stretchPanelZDimensions.x); + stretchPanelZDimensions.x = Math.abs(stretchPanelZDimensions.y); + stretchPanelZDimensions.y = tempX; + stretchPanelZDimensions.z = 0.01; + Overlays.editOverlay(grabberStretchZPanel, { + position: stretchZPos, + rotation: worldRotationX, + dimensions: stretchPanelZDimensions + }); + + var rotateDimension = dimensionAverage * GRABBER_ROTATE_RINGS_DIMENSION_MULTIPLE; + var rotateDimensions = { x:rotateDimension, y:rotateDimension, z:rotateDimension }; + if (!isActiveTool(grabberRotatePitchRing)) { + Overlays.editOverlay(grabberRotatePitchRing, { + position: SelectionManager.worldPosition, + rotation: worldRotationY, + dimensions: rotateDimensions + }); + } + if (!isActiveTool(grabberRotateYawRing)) { + Overlays.editOverlay(grabberRotateYawRing, { + position: SelectionManager.worldPosition, + rotation: worldRotationZ, + dimensions: rotateDimensions + }); + } + if (!isActiveTool(grabberRotateRollRing)) { + Overlays.editOverlay(grabberRotateRollRing, { + position: SelectionManager.worldPosition, + rotation: worldRotationX, + dimensions: rotateDimensions + }); + } + Overlays.editOverlay(grabberRotateCurrentRing, { dimensions: rotateDimensions }); + + var inModeRotate = isActiveTool(grabberRotatePitchRing) || isActiveTool(grabberRotateYawRing) || isActiveTool(grabberRotateRollRing); + var inModeTranslate = isActiveTool(grabberTranslateXCone) || isActiveTool(grabberTranslateXCylinder) || + isActiveTool(grabberTranslateYCone) || isActiveTool(grabberTranslateYCylinder) || + isActiveTool(grabberTranslateZCone) || isActiveTool(grabberTranslateZCylinder) || + isActiveTool(grabberCloner) || isActiveTool(selectionBox); + + Overlays.editOverlay(selectionBox, { + position: worldPosition, + rotation: worldRotation, + dimensions: worldDimensions, + visible: !inModeRotate + }); + + var grabberClonerOffset = { x:GRABBER_CLONER_OFFSET.x * worldDimensionsX, y:GRABBER_CLONER_OFFSET.y * worldDimensionsY, z:GRABBER_CLONER_OFFSET.z * worldDimensionsZ }; + var grabberClonerPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, grabberClonerOffset)); + Overlays.editOverlay(grabberCloner, { + position: grabberClonerPos, + rotation: worldRotation, + dimensions: scaleDimensions, + }); + } that.setGrabberTranslateXVisible(!activeTool || isActiveTool(grabberTranslateXCone) || isActiveTool(grabberTranslateXCylinder)); that.setGrabberTranslateYVisible(!activeTool || isActiveTool(grabberTranslateYCone) || isActiveTool(grabberTranslateYCylinder)); @@ -1275,9 +1577,10 @@ SelectionDisplay = (function() { that.setGrabberStretchZVisible(!activeTool || isActiveTool(grabberStretchZSphere)); that.setGrabberScaleVisible(!activeTool || isActiveTool(grabberScaleLBNCube) || isActiveTool(grabberScaleRBNCube) || isActiveTool(grabberScaleLBFCube) || isActiveTool(grabberScaleRBFCube) || isActiveTool(grabberScaleLTNCube) || isActiveTool(grabberScaleRTNCube) || isActiveTool(grabberScaleLTFCube) || isActiveTool(grabberScaleRTFCube)); + that.setGrabberClonerVisible(!activeTool || isActiveTool(grabberCloner)); if (wantDebug) { - print("====== Update Handles <======="); + print("====== Update Grabbers <======="); } }; @@ -1310,7 +1613,7 @@ SelectionDisplay = (function() { Overlays.editOverlay(grabberTranslateZCylinder, { visible: isVisible }); }; - // FUNCTION: SET GRABBER ROTATION VISIBLE + // FUNCTION: SET GRABBER ROTATE VISIBLE that.setGrabberRotateVisible = function(isVisible) { that.setGrabberRotatePitchVisible(isVisible); that.setGrabberRotateYawVisible(isVisible); @@ -1372,6 +1675,189 @@ SelectionDisplay = (function() { Overlays.editOverlay(grabberScaleFLEdge, { visible: isVisible }); }; + // FUNCTION: SET GRABBER CLONER VISIBLE + that.setGrabberClonerVisible = function(isVisible) { + Overlays.editOverlay(grabberCloner, { visible: isVisible }); + }; + + var initialXZPick = null; + var isConstrained = false; + var constrainMajorOnly = false; + var startPosition = null; + var duplicatedEntityIDs = null; + + // TOOL DEFINITION: TRANSLATE XZ TOOL + var translateXZTool = addGrabberTool(selectionBox, { + mode: 'TRANSLATE_XZ', + pickPlanePosition: { x: 0, y: 0, z: 0 }, + greatestDimension: 0.0, + startingDistance: 0.0, + startingElevation: 0.0, + onBegin: function(event, pickRay, pickResult, doClone) { + var wantDebug = false; + if (wantDebug) { + print("================== TRANSLATE_XZ(Beg) -> ======================="); + Vec3.print(" pickRay", pickRay); + Vec3.print(" pickRay.origin", pickRay.origin); + Vec3.print(" pickResult.intersection", pickResult.intersection); + } + + SelectionManager.saveProperties(); + + that.setGrabberTranslateVisible(false); + that.setGrabberRotateVisible(false); + that.setGrabberScaleVisible(false); + that.setGrabberStretchVisible(false); + that.setGrabberClonerVisible(false); + + startPosition = SelectionManager.worldPosition; + + translateXZTool.pickPlanePosition = pickResult.intersection; + translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), SelectionManager.worldDimensions.z); + translateXZTool.startingDistance = Vec3.distance(pickRay.origin, SelectionManager.position); + translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition); + if (wantDebug) { + print(" longest dimension: " + translateXZTool.greatestDimension); + print(" starting distance: " + translateXZTool.startingDistance); + print(" starting elevation: " + translateXZTool.startingElevation); + } + + initialXZPick = rayPlaneIntersection(pickRay, translateXZTool.pickPlanePosition, { + x: 0, + y: 1, + z: 0 + }); + + // Duplicate entities if alt is pressed. This will make a + // copy of the selected entities and move the _original_ entities, not + // the new ones. + if (event.isAlt || doClone) { + duplicatedEntityIDs = []; + for (var otherEntityID in SelectionManager.savedProperties) { + var properties = SelectionManager.savedProperties[otherEntityID]; + if (!properties.locked) { + var entityID = Entities.addEntity(properties); + duplicatedEntityIDs.push({ + entityID: entityID, + properties: properties + }); + } + } + } else { + duplicatedEntityIDs = null; + } + + isConstrained = false; + if (wantDebug) { + print("================== TRANSLATE_XZ(End) <- ======================="); + } + }, + onEnd: function(event, reason) { + pushCommandForSelections(duplicatedEntityIDs); + }, + elevation: function(origin, intersection) { + return (origin.y - intersection.y) / Vec3.distance(origin, intersection); + }, + onMove: function(event) { + var wantDebug = false; + pickRay = generalComputePickRay(event.x, event.y); + + var pick = rayPlaneIntersection2(pickRay, translateXZTool.pickPlanePosition, { + x: 0, + y: 1, + z: 0 + }); + + // If the pick ray doesn't hit the pick plane in this direction, do nothing. + // this will happen when someone drags across the horizon from the side they started on. + if (!pick) { + if (wantDebug) { + print(" "+ translateXZTool.mode + "Pick ray does not intersect XZ plane."); + } + + // EARLY EXIT--(Invalid ray detected.) + return; + } + + var vector = Vec3.subtract(pick, initialXZPick); + + // If the mouse is too close to the horizon of the pick plane, stop moving + var MIN_ELEVATION = 0.02; // largest dimension of object divided by distance to it + var elevation = translateXZTool.elevation(pickRay.origin, pick); + if (wantDebug) { + print("Start Elevation: " + translateXZTool.startingElevation + ", elevation: " + elevation); + } + if ((translateXZTool.startingElevation > 0.0 && elevation < MIN_ELEVATION) || + (translateXZTool.startingElevation < 0.0 && elevation > -MIN_ELEVATION)) { + if (wantDebug) { + print(" "+ translateXZTool.mode + " - too close to horizon!"); + } + + // EARLY EXIT--(Don't proceed past the reached limit.) + return; + } + + // If the angular size of the object is too small, stop moving + var MIN_ANGULAR_SIZE = 0.01; // Radians + if (translateXZTool.greatestDimension > 0) { + var angularSize = Math.atan(translateXZTool.greatestDimension / Vec3.distance(pickRay.origin, pick)); + if (wantDebug) { + print("Angular size = " + angularSize); + } + if (angularSize < MIN_ANGULAR_SIZE) { + return; + } + } + + // If shifted, constrain to one axis + if (event.isShifted) { + if (Math.abs(vector.x) > Math.abs(vector.z)) { + vector.z = 0; + } else { + vector.x = 0; + } + if (!isConstrained) { + isConstrained = true; + } + } else { + if (isConstrained) { + isConstrained = false; + } + } + + constrainMajorOnly = event.isControl; + var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(-0.5, SelectionManager.worldDimensions)); + vector = Vec3.subtract( + grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly), + cornerPosition); + + + for (var i = 0; i < SelectionManager.selections.length; i++) { + var properties = SelectionManager.savedProperties[SelectionManager.selections[i]]; + if (!properties) { + continue; + } + var newPosition = Vec3.sum(properties.position, { + x: vector.x, + y: 0, + z: vector.z + }); + Entities.editEntity(SelectionManager.selections[i], { + position: newPosition + }); + + if (wantDebug) { + print("translateXZ... "); + Vec3.print(" vector:", vector); + Vec3.print(" newPosition:", properties.position); + Vec3.print(" newPosition:", newPosition); + } + } + + SelectionManager._update(); + } + }); + addGrabberTranslateTool(grabberTranslateXCone, "TRANSLATE_X", TRANSLATE_DIRECTION.X); addGrabberTranslateTool(grabberTranslateXCylinder, "TRANSLATE_X", TRANSLATE_DIRECTION.X); addGrabberTranslateTool(grabberTranslateYCone, "TRANSLATE_Y", TRANSLATE_DIRECTION.Y); @@ -1383,9 +1869,9 @@ SelectionDisplay = (function() { addGrabberRotateTool(grabberRotateYawRing, "ROTATE_YAW", ROTATE_DIRECTION.YAW); addGrabberRotateTool(grabberRotateRollRing, "ROTATE_ROLL", ROTATE_DIRECTION.ROLL); - addGrabberStretchTool(grabberStretchXSphere, "STRETCH_X", TRANSLATE_DIRECTION.X); - addGrabberStretchTool(grabberStretchYSphere, "STRETCH_Y", TRANSLATE_DIRECTION.Y); - addGrabberStretchTool(grabberStretchZSphere, "STRETCH_Z", TRANSLATE_DIRECTION.Z); + addGrabberStretchTool(grabberStretchXSphere, "STRETCH_X", STRETCH_DIRECTION.X); + addGrabberStretchTool(grabberStretchYSphere, "STRETCH_Y", STRETCH_DIRECTION.Y); + addGrabberStretchTool(grabberStretchZSphere, "STRETCH_Z", STRETCH_DIRECTION.Z); addGrabberScaleTool(grabberScaleLBNCube, "SCALE_LBN", SCALE_DIRECTION.LBN); addGrabberScaleTool(grabberScaleRBNCube, "SCALE_RBN", SCALE_DIRECTION.RBN); @@ -1396,19 +1882,28 @@ SelectionDisplay = (function() { addGrabberScaleTool(grabberScaleLTFCube, "SCALE_LTF", SCALE_DIRECTION.LTF); addGrabberScaleTool(grabberScaleRTFCube, "SCALE_RTF", SCALE_DIRECTION.RTF); + // GRABBER TOOL: GRABBER CLONER + addGrabberTool(grabberCloner, { + mode: "CLONE", + onBegin: function(event, pickRay, pickResult) { + var doClone = true; + translateXZTool.onBegin(event,pickRay,pickResult,doClone); + }, + elevation: function (event) { + translateXZTool.elevation(event); + }, + + onEnd: function (event) { + translateXZTool.onEnd(event); + }, + + onMove: function (event) { + translateXZTool.onMove(event); + } + }); + // FUNCTION: CHECK MOVE that.checkMove = function() { - if (SelectionManager.hasSelection()) { - - // FIXME - this cause problems with editing in the entity properties window - // SelectionManager._update(); - - if (!Vec3.equal(Camera.getPosition(), lastCameraPosition) || - !Quat.equal(Camera.getOrientation(), lastCameraOrientation)) { - - //that.updateRotationHandles(); - } - } }; that.checkControllerMove = function() { @@ -1583,4 +2078,4 @@ SelectionDisplay = (function() { Controller.mouseReleaseEvent.connect(that.mouseReleaseEvent); return that; -}()); +}()); \ No newline at end of file From 6a035578017935304c77ac8821a5dc5d49f3d3cf Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 23 Jan 2018 14:44:10 -0800 Subject: [PATCH 038/569] don't override sensorToWorldMatrix --- interface/src/avatar/MyAvatar.cpp | 10 ++++++---- interface/src/avatar/MyAvatar.h | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e93b897013..2943cf7fd8 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1790,7 +1790,7 @@ void MyAvatar::setAnimGraphUrl(const QUrl& url) { updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes } -void MyAvatar::initAnimGraph() { +void MyAvatar::initAnimGraph(bool updateBodySensorMat) { QUrl graphUrl; if (!_prefOverrideAnimGraphUrl.get().isEmpty()) { graphUrl = _prefOverrideAnimGraphUrl.get(); @@ -1803,8 +1803,10 @@ void MyAvatar::initAnimGraph() { _skeletonModel->getRig().initAnimGraph(graphUrl); _currentAnimGraphUrl.set(graphUrl); - _bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. - updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes + if (updateBodySensorMat) { + _bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. + updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes + } } void MyAvatar::destroyAnimGraph() { @@ -1819,7 +1821,7 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { initHeadBones(); _skeletonModel->setCauterizeBoneSet(_headBoneSet); _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); - initAnimGraph(); + initAnimGraph(false); _isAnimatingScale = true; } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 58b49b61ff..cdcd6f4607 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -730,7 +730,7 @@ private: void updatePosition(float deltaTime); void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency); void initHeadBones(); - void initAnimGraph(); + void initAnimGraph(bool updateBodySensorMat = true); // Avatar Preferences QUrl _fullAvatarURLFromPreferences; From 2cbcc28bd4c860a45e0e050ae959537c6b795a02 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 24 Jan 2018 10:44:04 -0800 Subject: [PATCH 039/569] only call init animGraph once --- interface/src/avatar/MyAvatar.cpp | 27 +++++++++++++++------------ interface/src/avatar/MyAvatar.h | 4 +++- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 1475860665..5f2cbf92b2 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1785,12 +1785,10 @@ void MyAvatar::setAnimGraphUrl(const QUrl& url) { _currentAnimGraphUrl.set(url); _skeletonModel->getRig().initAnimGraph(url); - - _bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. - updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes + connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SLOT(animGraphLoaded())); } -void MyAvatar::initAnimGraph(bool updateBodySensorMat) { +void MyAvatar::initAnimGraph() { QUrl graphUrl; if (!_prefOverrideAnimGraphUrl.get().isEmpty()) { graphUrl = _prefOverrideAnimGraphUrl.get(); @@ -1802,27 +1800,32 @@ void MyAvatar::initAnimGraph(bool updateBodySensorMat) { _skeletonModel->getRig().initAnimGraph(graphUrl); _currentAnimGraphUrl.set(graphUrl); - - if (updateBodySensorMat) { - _bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. - updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes - } + connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SLOT(animGraphLoaded())); } void MyAvatar::destroyAnimGraph() { _skeletonModel->getRig().destroyAnimGraph(); } +void MyAvatar::animGraphLoaded() { + _bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. + updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes + _isAnimatingScale = true; + disconnect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SLOT(animGraphLoaded())); +} + void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { Avatar::postUpdate(deltaTime, scene); - if (_skeletonModel->isLoaded() && !_skeletonModel->getRig().getAnimNode()) { + if (_skeletonModel->isLoaded() && !_skeletonModel->getRig().getAnimNode() && _initHeadBones) { initHeadBones(); _skeletonModel->setCauterizeBoneSet(_headBoneSet); _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); - initAnimGraph(false); - _isAnimatingScale = true; + initAnimGraph(); + _initHeadBones = false; + } else if (!_skeletonModel->isLoaded()) { + _initHeadBones = true; } if (_enableDebugDrawDefaultPose || _enableDebugDrawAnimPose) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index cdcd6f4607..6a9e0e6a38 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -566,6 +566,7 @@ public slots: void increaseSize(); void decreaseSize(); void resetSize(); + void animGraphLoaded(); void setGravity(float gravity); float getGravity(); @@ -730,7 +731,7 @@ private: void updatePosition(float deltaTime); void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency); void initHeadBones(); - void initAnimGraph(bool updateBodySensorMat = true); + void initAnimGraph(); // Avatar Preferences QUrl _fullAvatarURLFromPreferences; @@ -808,6 +809,7 @@ private: bool _enableDebugDrawIKConstraints { false }; bool _enableDebugDrawIKChains { false }; bool _enableDebugDrawDetailedCollision { false }; + bool _initHeadBones { false }; AudioListenerMode _audioListenerMode; glm::vec3 _customListenPosition; From b7ba7862aa929a501a7fec542974d6ce3ebb4d5c Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 24 Jan 2018 11:18:42 -0800 Subject: [PATCH 040/569] give animGrapgh loader a high priority --- libraries/animation/src/AnimNodeLoader.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 33f3d72756..8173845205 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -38,6 +38,8 @@ static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const Q static AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); +static const float ANIM_GRAPH_LOAD_PRIORITY = 10.0f; + // called after children have been loaded // returns node on success, nullptr on failure. static bool processDoNothing(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } @@ -653,6 +655,7 @@ AnimNodeLoader::AnimNodeLoader(const QUrl& url) : { _resource = QSharedPointer::create(url); _resource->setSelf(_resource); + _resource->setLoadPriority(this, ANIM_GRAPH_LOAD_PRIORITY); connect(_resource.data(), &Resource::loaded, this, &AnimNodeLoader::onRequestDone); connect(_resource.data(), &Resource::failed, this, &AnimNodeLoader::onRequestError); _resource->ensureLoading(); From c96e395a46df93b652a22d605f2b2ceb4bf68b64 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 25 Jan 2018 12:13:32 -0800 Subject: [PATCH 041/569] fix warning --- libraries/entities/src/EntityEditFilters.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index a69a8ce7d1..f5bf699e02 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -271,7 +271,7 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { } } else if (wantsOriginalPropertiesValue.isArray()) { auto length = wantsOriginalPropertiesValue.property("length").toInteger(); - for (int i; i < length; i++) { + for (int i = 0; i < length; i++) { auto stringValue = wantsOriginalPropertiesValue.property(i).toString(); if (!stringValue.isEmpty()) { filterData.wantsOriginalProperties = true; @@ -305,7 +305,7 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { } } else if (wantsZonePropertiesValue.isArray()) { auto length = wantsZonePropertiesValue.property("length").toInteger(); - for (int i; i < length; i++) { + for (int i = 0; i < length; i++) { auto stringValue = wantsZonePropertiesValue.property(i).toString(); if (!stringValue.isEmpty()) { filterData.wantsZoneProperties = true; From aea16fe071d1731f783d6f93482c1161e2d513c2 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 25 Jan 2018 15:01:29 -0800 Subject: [PATCH 042/569] add delete filter support --- libraries/entities/src/EntityEditFilters.cpp | 1 + libraries/entities/src/EntityTree.cpp | 18 ++++++++++++++++++ libraries/entities/src/EntityTree.h | 3 ++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index f5bf699e02..4e48c671cb 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -245,6 +245,7 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { entitiesObject.setProperty("ADD_FILTER_TYPE", EntityTree::FilterType::Add); entitiesObject.setProperty("EDIT_FILTER_TYPE", EntityTree::FilterType::Edit); entitiesObject.setProperty("PHYSICS_FILTER_TYPE", EntityTree::FilterType::Physics); + entitiesObject.setProperty("ERASE_FILTER_TYPE", EntityTree::FilterType::Erase); global.setProperty("Entities", entitiesObject); filterData.filterFn = global.property("filter"); if (!filterData.filterFn.isFunction()) { diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 9ab9f63245..629281749e 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1928,6 +1928,24 @@ int EntityTree::processEraseMessageDetails(const QByteArray& dataByteArray, cons #endif EntityItemID entityItemID(entityID); + + EntityItemPointer existingEntity; + + auto startLookup = usecTimestampNow(); + existingEntity = findEntityByEntityItemID(entityItemID); + auto endLookup = usecTimestampNow(); + _totalLookupTime += endLookup - startLookup; + + auto startFilter = usecTimestampNow(); + FilterType filterType = FilterType::Erase; + EntityItemProperties dummyProperties; + bool wasChanged = false; + + bool allowed = (sourceNode->isAllowedEditor()) || filterProperties(existingEntity, dummyProperties, dummyProperties, wasChanged, filterType); + auto endFilter = usecTimestampNow(); + + _totalFilterTime += endFilter - startFilter; + entityItemIDsToDelete << entityItemID; if (wantEditLogging() || wantTerseEditLogging()) { diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 11a747d624..ba786fbe98 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -57,7 +57,8 @@ public: enum FilterType { Add, Edit, - Physics + Physics, + Erase }; EntityTree(bool shouldReaverage = false); virtual ~EntityTree(); From 6192625942a34242688cb5e087b3fed99ed7e954 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 25 Jan 2018 15:01:50 -0800 Subject: [PATCH 043/569] add delete filter support --- .../prevent-erase-in-zone-example.js | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 scripts/tutorials/entity_edit_filters/prevent-erase-in-zone-example.js diff --git a/scripts/tutorials/entity_edit_filters/prevent-erase-in-zone-example.js b/scripts/tutorials/entity_edit_filters/prevent-erase-in-zone-example.js new file mode 100644 index 0000000000..356cc55079 --- /dev/null +++ b/scripts/tutorials/entity_edit_filters/prevent-erase-in-zone-example.js @@ -0,0 +1,24 @@ +// +// prevent-erase-in-zone-example.js +// +// +// Created by Brad Hefta-Gaub to use Entities on Jan. 25, 2018 +// Copyright 2018 High Fidelity, Inc. +// +// This sample entity edit filter script will keep prevent any entity inside the zone from being erased. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +function filter(properties, type, originalProperties, zoneProperties) { + + if (type == Entities.ERASE_FILTER_TYPE) { + return false; + } + return properties; +} + +filter.wantsOriginalProperties = true; +filter.wantsZoneProperties = true; +filter; \ No newline at end of file From 6b0b17ff633f72bb16171da4f34ad9f672ecefec Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 26 Jan 2018 17:57:20 +0100 Subject: [PATCH 044/569] Working on better adaptive bias algorithm for shadow --- libraries/render-utils/src/LightStage.cpp | 8 ++--- libraries/render-utils/src/LightStage.h | 2 +- .../render-utils/src/RenderShadowTask.cpp | 10 ++++-- libraries/render-utils/src/RenderShadowTask.h | 16 ++++++++- libraries/render-utils/src/Shadow.slh | 17 +++++---- .../src/directional_ambient_light_shadow.slf | 4 ++- .../src/directional_skybox_light_shadow.slf | 4 ++- scripts/developer/utilities/render/shadow.qml | 35 +++++++++++++++++++ 8 files changed, 77 insertions(+), 19 deletions(-) diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index f146cd6e0a..eac9dd7657 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -220,7 +220,7 @@ void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum, } void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum, - float nearDepth, float farDepth) { + float nearDepth, float farDepth, float baseBias) { assert(nearDepth < farDepth); assert(cascadeIndex < _cascades.size()); @@ -270,11 +270,7 @@ void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, co // Update the buffer auto& schema = _schemaBuffer.edit(); schema.cascades[cascadeIndex].reprojection = _biasMatrix * ortho * shadowViewInverse.getMatrix(); - // Adapt shadow bias to shadow resolution with a totally empirical formula - const auto maxShadowFrustumDim = std::max(fabsf(min.x - max.x), fabsf(min.y - max.y)); - const auto REFERENCE_TEXEL_DENSITY = 7.5f; - const auto cascadeTexelDensity = MAP_SIZE / maxShadowFrustumDim; - schema.cascades[cascadeIndex].bias = MAX_BIAS * std::min(1.0f, REFERENCE_TEXEL_DENSITY / cascadeTexelDensity); + schema.cascades[cascadeIndex].bias = baseBias; } void LightStage::Shadow::setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum) { diff --git a/libraries/render-utils/src/LightStage.h b/libraries/render-utils/src/LightStage.h index d1a8680706..c2c2df8c89 100644 --- a/libraries/render-utils/src/LightStage.h +++ b/libraries/render-utils/src/LightStage.h @@ -80,7 +80,7 @@ public: void setKeylightFrustum(const ViewFrustum& viewFrustum, float nearDepth = 1.0f, float farDepth = 1000.0f); void setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum, - float nearDepth = 1.0f, float farDepth = 1000.0f); + float nearDepth = 1.0f, float farDepth = 1000.0f, float baseBias = 0.005f); void setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum); const UniformBufferView& getBuffer() const { return _schemaBuffer; } diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index d83dfd73a5..f41860b6de 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -216,7 +216,9 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende task.addJob("ShadowSetup"); for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { - const auto setupOutput = task.addJob("ShadowCascadeSetup", i); + char jobName[64]; + sprintf(jobName, "ShadowCascadeSetup%d", i); + const auto setupOutput = task.addJob(jobName, i); const auto shadowFilter = setupOutput.getN(1); // CPU jobs: @@ -253,6 +255,10 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext) { } } +void RenderShadowCascadeSetup::configure(const Config& configuration) { + _baseBias = configuration.bias * configuration.bias * 0.01f; +} + void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderContext, Outputs& output) { auto lightStage = renderContext->_scene->getStage(); assert(lightStage); @@ -266,7 +272,7 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon if (globalShadow && _cascadeIndexgetCascadeCount()) { output.edit1() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); - globalShadow->setKeylightCascadeFrustum(_cascadeIndex, args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR); + globalShadow->setKeylightCascadeFrustum(_cascadeIndex, args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR, _baseBias); // Set the keylight render args args->pushViewFrustum(*(globalShadow->getCascade(_cascadeIndex).getFrustum())); diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index d8d4c624e7..42c279c02a 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -62,17 +62,31 @@ public: }; +class RenderShadowCascadeSetupConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(float bias MEMBER bias NOTIFY dirty) +public: + + float bias{ 0.5f }; + +signals: + void dirty(); +}; + class RenderShadowCascadeSetup { public: using Outputs = render::VaryingSet3; - using JobModel = render::Job::ModelO; + using Config = RenderShadowCascadeSetupConfig; + using JobModel = render::Job::ModelO; RenderShadowCascadeSetup(unsigned int cascadeIndex) : _cascadeIndex{ cascadeIndex } {} + void configure(const Config& configuration); void run(const render::RenderContextPointer& renderContext, Outputs& output); private: unsigned int _cascadeIndex; + float _baseBias{ 0.1f }; }; class RenderShadowCascadeTeardown { diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index a12dd0f4a4..50ae8864f4 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -83,27 +83,30 @@ float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, ve return shadowAttenuation; } -float evalShadowCascadeAttenuation(int cascadeIndex, vec3 viewNormal, ShadowSampleOffsets offsets, vec4 shadowTexcoord) { +float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float bias) { if (!isShadowCascadeProjectedOnPixel(shadowTexcoord)) { // If a point is not in the map, do not attenuate return 1.0; } - // Multiply bias if we are at a grazing angle with light - float tangentFactor = abs(dot(getShadowDirInViewSpace(), viewNormal)); - float bias = getShadowBias(cascadeIndex) * (5.0-4.0*tangentFactor); + // Add fixed bias + bias += getShadowBias(cascadeIndex); return evalShadowAttenuationPCF(cascadeIndex, offsets, shadowTexcoord, bias); } -float evalShadowAttenuation(vec4 worldPosition, float viewDepth, vec3 viewNormal) { +float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDepth, vec3 worldNormal) { ShadowSampleOffsets offsets = evalShadowFilterOffsets(worldPosition); vec4 cascadeShadowCoords[2]; ivec2 cascadeIndices; float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices); + // Adjust bias if we are at a grazing angle with light + float ndotl = dot(worldLightDir, worldNormal); + float bias = 0.5*(1.0/(ndotl*ndotl)-1.0); + bias *= getShadowScale(); vec2 cascadeAttenuations = vec2(1.0, 1.0); - cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, viewNormal, offsets, cascadeShadowCoords[0]); + cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, offsets, cascadeShadowCoords[0], bias); if (cascadeMix > 0.0 && cascadeIndices.y < getShadowCascadeCount()) { - cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, viewNormal, offsets, cascadeShadowCoords[1]); + cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, offsets, cascadeShadowCoords[1], bias); } float attenuation = mix(cascadeAttenuations.x, cascadeAttenuations.y, cascadeMix); // Falloff to max distance diff --git a/libraries/render-utils/src/directional_ambient_light_shadow.slf b/libraries/render-utils/src/directional_ambient_light_shadow.slf index f7ea8c5966..eead392fd8 100644 --- a/libraries/render-utils/src/directional_ambient_light_shadow.slf +++ b/libraries/render-utils/src/directional_ambient_light_shadow.slf @@ -28,7 +28,9 @@ void main(void) { vec4 viewPos = vec4(frag.position.xyz, 1.0); vec4 worldPos = getViewInverse() * viewPos; - float shadowAttenuation = evalShadowAttenuation(worldPos, -viewPos.z, frag.normal); + Light shadowLight = getKeyLight(); + vec3 worldLightDirection = getLightDirection(shadowLight); + float shadowAttenuation = evalShadowAttenuation(worldLightDirection, worldPos, -viewPos.z, frag.normal); if (frag.mode == FRAG_MODE_UNLIT) { discard; diff --git a/libraries/render-utils/src/directional_skybox_light_shadow.slf b/libraries/render-utils/src/directional_skybox_light_shadow.slf index 37c9ae7fba..2a2f8f65a3 100644 --- a/libraries/render-utils/src/directional_skybox_light_shadow.slf +++ b/libraries/render-utils/src/directional_skybox_light_shadow.slf @@ -28,7 +28,9 @@ void main(void) { vec4 viewPos = vec4(frag.position.xyz, 1.0); vec4 worldPos = getViewInverse() * viewPos; - float shadowAttenuation = evalShadowAttenuation(worldPos, -viewPos.z, frag.normal); + Light shadowLight = getKeyLight(); + vec3 worldLightDirection = getLightDirection(shadowLight); + float shadowAttenuation = evalShadowAttenuation(worldLightDirection, worldPos, -viewPos.z, frag.normal); // Light mapped or not ? if (frag.mode == FRAG_MODE_UNLIT) { diff --git a/scripts/developer/utilities/render/shadow.qml b/scripts/developer/utilities/render/shadow.qml index a8fcf1aec7..32405a5260 100644 --- a/scripts/developer/utilities/render/shadow.qml +++ b/scripts/developer/utilities/render/shadow.qml @@ -10,6 +10,9 @@ // import QtQuick 2.5 import QtQuick.Controls 1.4 +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls +import "configSlider" Column { id: root @@ -68,4 +71,36 @@ Column { font.italic: true } } + ConfigSlider { + label: qsTr("Cascade 0 bias") + integral: false + config: Render.getConfig("RenderMainView.ShadowCascadeSetup0") + property: "bias" + max: 1.0 + min: 0.01 + } + ConfigSlider { + label: qsTr("Cascade 1 bias") + integral: false + config: Render.getConfig("RenderMainView.ShadowCascadeSetup1") + property: "bias" + max: 1.0 + min: 0.01 + } + ConfigSlider { + label: qsTr("Cascade 2 bias") + integral: false + config: Render.getConfig("RenderMainView.ShadowCascadeSetup2") + property: "bias" + max: 1.0 + min: 0.01 + } + ConfigSlider { + label: qsTr("Cascade 3 bias") + integral: false + config: Render.getConfig("RenderMainView.ShadowCascadeSetup3") + property: "bias" + max: 1.0 + min: 0.01 + } } From aa82ad885561411daf1d97778193f22cd18b612b Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 26 Jan 2018 14:57:36 -0800 Subject: [PATCH 045/569] adjust client to only delete entities on server echo --- libraries/entities/src/EntityScriptingInterface.cpp | 5 ++++- libraries/entities/src/EntityTree.cpp | 12 ++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 206065beeb..877225de77 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -585,7 +585,10 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { if (entity->getLocked()) { shouldDelete = false; } else { - _entityTree->deleteEntity(entityID); + // only delete local entities, server entities will round trip through the server filters + if (entity->getClientOnly()) { + _entityTree->deleteEntity(entityID); + } } } }); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 629281749e..8071a8f9b7 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1946,12 +1946,16 @@ int EntityTree::processEraseMessageDetails(const QByteArray& dataByteArray, cons _totalFilterTime += endFilter - startFilter; - entityItemIDsToDelete << entityItemID; - - if (wantEditLogging() || wantTerseEditLogging()) { - qCDebug(entities) << "User [" << sourceNode->getUUID() << "] deleting entity. ID:" << entityItemID; + if (allowed) { + entityItemIDsToDelete << entityItemID; + if (wantEditLogging() || wantTerseEditLogging()) { + qCDebug(entities) << "User [" << sourceNode->getUUID() << "] deleting entity. ID:" << entityItemID; + } + } else if (wantEditLogging() || wantTerseEditLogging()) { + qCDebug(entities) << "User [" << sourceNode->getUUID() << "] attempted to deleteentity. ID:" << entityItemID << " Filter rejected erase."; } + } deleteEntities(entityItemIDsToDelete, true, true); } From a3d86a02423eeaa22fe373b29531d0fc1f77a52b Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 26 Jan 2018 17:37:24 -0800 Subject: [PATCH 046/569] cleanup --- libraries/entities/src/EntityEditFilters.cpp | 2 +- libraries/entities/src/EntityTree.cpp | 62 ++++++++++--------- libraries/entities/src/EntityTree.h | 4 +- ...e.js => prevent-delete-in-zone-example.js} | 6 +- 4 files changed, 41 insertions(+), 33 deletions(-) rename scripts/tutorials/entity_edit_filters/{prevent-erase-in-zone-example.js => prevent-delete-in-zone-example.js} (81%) diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index 4e48c671cb..4b66b61d93 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -245,7 +245,7 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { entitiesObject.setProperty("ADD_FILTER_TYPE", EntityTree::FilterType::Add); entitiesObject.setProperty("EDIT_FILTER_TYPE", EntityTree::FilterType::Edit); entitiesObject.setProperty("PHYSICS_FILTER_TYPE", EntityTree::FilterType::Physics); - entitiesObject.setProperty("ERASE_FILTER_TYPE", EntityTree::FilterType::Erase); + entitiesObject.setProperty("DELETE_FILTER_TYPE", EntityTree::FilterType::Delete); global.setProperty("Entities", entitiesObject); filterData.filterFn = global.property("filter"); if (!filterData.filterFn.isFunction()) { diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 8071a8f9b7..57540092da 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1850,6 +1850,37 @@ void EntityTree::forgetEntitiesDeletedBefore(quint64 sinceTime) { } +bool EntityTree::shouldEraseEntity(EntityItemID entityID, const SharedNodePointer& sourceNode) { + EntityItemPointer existingEntity; + + auto startLookup = usecTimestampNow(); + existingEntity = findEntityByEntityItemID(entityID); + auto endLookup = usecTimestampNow(); + _totalLookupTime += endLookup - startLookup; + + auto startFilter = usecTimestampNow(); + FilterType filterType = FilterType::Delete; + EntityItemProperties dummyProperties; + bool wasChanged = false; + + bool allowed = (sourceNode->isAllowedEditor()) || filterProperties(existingEntity, dummyProperties, dummyProperties, wasChanged, filterType); + auto endFilter = usecTimestampNow(); + + _totalFilterTime += endFilter - startFilter; + + if (allowed) { + if (wantEditLogging() || wantTerseEditLogging()) { + qCDebug(entities) << "User [" << sourceNode->getUUID() << "] deleting entity. ID:" << entityID; + } + } + else if (wantEditLogging() || wantTerseEditLogging()) { + qCDebug(entities) << "User [" << sourceNode->getUUID() << "] attempted to deleteentity. ID:" << entityID << " Filter rejected erase."; + } + + return allowed; +} + + // TODO: consider consolidating processEraseMessageDetails() and processEraseMessage() int EntityTree::processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode) { #ifdef EXTRA_ERASE_DEBUGGING @@ -1877,12 +1908,10 @@ int EntityTree::processEraseMessage(ReceivedMessage& message, const SharedNodePo #endif EntityItemID entityItemID(entityID); - entityItemIDsToDelete << entityItemID; - if (wantEditLogging() || wantTerseEditLogging()) { - qCDebug(entities) << "User [" << sourceNode->getUUID() << "] deleting entity. ID:" << entityItemID; + if (shouldEraseEntity(entityID, sourceNode)) { + entityItemIDsToDelete << entityItemID; } - } deleteEntities(entityItemIDsToDelete, true, true); } @@ -1929,33 +1958,10 @@ int EntityTree::processEraseMessageDetails(const QByteArray& dataByteArray, cons EntityItemID entityItemID(entityID); - EntityItemPointer existingEntity; - - auto startLookup = usecTimestampNow(); - existingEntity = findEntityByEntityItemID(entityItemID); - auto endLookup = usecTimestampNow(); - _totalLookupTime += endLookup - startLookup; - - auto startFilter = usecTimestampNow(); - FilterType filterType = FilterType::Erase; - EntityItemProperties dummyProperties; - bool wasChanged = false; - - bool allowed = (sourceNode->isAllowedEditor()) || filterProperties(existingEntity, dummyProperties, dummyProperties, wasChanged, filterType); - auto endFilter = usecTimestampNow(); - - _totalFilterTime += endFilter - startFilter; - - if (allowed) { + if (shouldEraseEntity(entityID, sourceNode)) { entityItemIDsToDelete << entityItemID; - if (wantEditLogging() || wantTerseEditLogging()) { - qCDebug(entities) << "User [" << sourceNode->getUUID() << "] deleting entity. ID:" << entityItemID; - } - } else if (wantEditLogging() || wantTerseEditLogging()) { - qCDebug(entities) << "User [" << sourceNode->getUUID() << "] attempted to deleteentity. ID:" << entityItemID << " Filter rejected erase."; } - } deleteEntities(entityItemIDsToDelete, true, true); } diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index ba786fbe98..4c5fcdb0f7 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -58,7 +58,7 @@ public: Add, Edit, Physics, - Erase + Delete }; EntityTree(bool shouldReaverage = false); virtual ~EntityTree(); @@ -194,6 +194,8 @@ public: int processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode); int processEraseMessageDetails(const QByteArray& buffer, const SharedNodePointer& sourceNode); + bool shouldEraseEntity(EntityItemID entityID, const SharedNodePointer& sourceNode); + EntityTreeElementPointer getContainingElement(const EntityItemID& entityItemID) /*const*/; void addEntityMapEntry(EntityItemPointer entity); diff --git a/scripts/tutorials/entity_edit_filters/prevent-erase-in-zone-example.js b/scripts/tutorials/entity_edit_filters/prevent-delete-in-zone-example.js similarity index 81% rename from scripts/tutorials/entity_edit_filters/prevent-erase-in-zone-example.js rename to scripts/tutorials/entity_edit_filters/prevent-delete-in-zone-example.js index 356cc55079..94ee58738d 100644 --- a/scripts/tutorials/entity_edit_filters/prevent-erase-in-zone-example.js +++ b/scripts/tutorials/entity_edit_filters/prevent-delete-in-zone-example.js @@ -1,11 +1,11 @@ // -// prevent-erase-in-zone-example.js +// prevent-delete-in-zone-example.js // // // Created by Brad Hefta-Gaub to use Entities on Jan. 25, 2018 // Copyright 2018 High Fidelity, Inc. // -// This sample entity edit filter script will keep prevent any entity inside the zone from being erased. +// This sample entity edit filter script will keep prevent any entity inside the zone from being deleted. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -13,7 +13,7 @@ function filter(properties, type, originalProperties, zoneProperties) { - if (type == Entities.ERASE_FILTER_TYPE) { + if (type == Entities.DELETE_FILTER_TYPE) { return false; } return properties; From b883d006c83f6c333d74be14a4b22a90950a8cd1 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 26 Jan 2018 18:26:48 -0800 Subject: [PATCH 047/569] add flags to support asking for specific messages, update examples --- libraries/entities/src/EntityEditFilters.cpp | 26 +++++++++++++++++++ libraries/entities/src/EntityEditFilters.h | 6 +++++ .../prevent-all-deletes.js | 22 ++++++++++++++++ .../prevent-delete-in-zone-example.js | 8 +++--- 4 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 scripts/tutorials/entity_edit_filters/prevent-all-deletes.js diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index 4b66b61d93..cd1982bdb4 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -62,6 +62,16 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper return false; } + // check to see if this filter wants to filter this message type + if ((!filterData.wantsToFilterEdit && filterType == EntityTree::FilterType::Edit) || + (!filterData.wantsToFilterPhysics && filterType == EntityTree::FilterType::Physics) || + (!filterData.wantsToFilterDelete && filterType == EntityTree::FilterType::Delete) || + (!filterData.wantsToFilterAdd && filterType == EntityTree::FilterType::Add)) { + + wasChanged = false; + return true; // accept the message + } + auto oldProperties = propertiesIn.getDesiredProperties(); auto specifiedProperties = propertiesIn.getChangedProperties(); propertiesIn.setDesiredProperties(specifiedProperties); @@ -254,6 +264,22 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { filterData.rejectAll=true; } + // if the wantsToFilterEdit is a boolean evaluate as a boolean, otherwise assume true + QScriptValue wantsToFilterAddValue = filterData.filterFn.property("wantsToFilterAdd"); + filterData.wantsToFilterAdd = wantsToFilterAddValue.isBool() ? wantsToFilterAddValue.toBool() : true; + + // if the wantsToFilterEdit is a boolean evaluate as a boolean, otherwise assume true + QScriptValue wantsToFilterEditValue = filterData.filterFn.property("wantsToFilterEdit"); + filterData.wantsToFilterEdit = wantsToFilterEditValue.isBool() ? wantsToFilterEditValue.toBool() : true; + + // if the wantsToFilterPhysics is a boolean evaluate as a boolean, otherwise assume true + QScriptValue wantsToFilterPhysicsValue = filterData.filterFn.property("wantsToFilterPhysics"); + filterData.wantsToFilterPhysics = wantsToFilterPhysicsValue.isBool() ? wantsToFilterPhysicsValue.toBool() : true; + + // if the wantsToFilterDelete is a boolean evaluate as a boolean, otherwise assume false + QScriptValue wantsToFilterDeleteValue = filterData.filterFn.property("wantsToFilterDelete"); + filterData.wantsToFilterDelete = wantsToFilterDeleteValue.isBool() ? wantsToFilterDeleteValue.toBool() : false; + // check to see if the filterFn has properties asking for Original props QScriptValue wantsOriginalPropertiesValue = filterData.filterFn.property("wantsOriginalProperties"); // if the wantsOriginalProperties is a boolean, or a string, or list of strings, then evaluate as follows: diff --git a/libraries/entities/src/EntityEditFilters.h b/libraries/entities/src/EntityEditFilters.h index be3df50d3f..cb99c97762 100644 --- a/libraries/entities/src/EntityEditFilters.h +++ b/libraries/entities/src/EntityEditFilters.h @@ -30,6 +30,12 @@ public: QScriptValue filterFn; bool wantsOriginalProperties { false }; bool wantsZoneProperties { false }; + + bool wantsToFilterAdd { true }; + bool wantsToFilterEdit { true }; + bool wantsToFilterPhysics { true }; + bool wantsToFilterDelete { true }; + EntityPropertyFlags includedOriginalProperties; EntityPropertyFlags includedZoneProperties; bool wantsZoneBoundingBox { false }; diff --git a/scripts/tutorials/entity_edit_filters/prevent-all-deletes.js b/scripts/tutorials/entity_edit_filters/prevent-all-deletes.js new file mode 100644 index 0000000000..0e2c54a04a --- /dev/null +++ b/scripts/tutorials/entity_edit_filters/prevent-all-deletes.js @@ -0,0 +1,22 @@ +// +// prevent-all-deletes.js +// +// +// Created by Brad Hefta-Gaub to use Entities on Jan. 25, 2018 +// Copyright 2018 High Fidelity, Inc. +// +// This sample entity edit filter script will prevent deletes of any entities. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +function filter() { + return false; // all deletes are blocked +} + +filter.wantsToFilterAdd = false; // don't run on adds +filter.wantsToFilterEdit = false; // don't run on edits +filter.wantsToFilterPhysics = false; // don't run on physics +filter.wantsToFilterDelete = true; // do run on deletes +filter; \ No newline at end of file diff --git a/scripts/tutorials/entity_edit_filters/prevent-delete-in-zone-example.js b/scripts/tutorials/entity_edit_filters/prevent-delete-in-zone-example.js index 94ee58738d..521bb94d00 100644 --- a/scripts/tutorials/entity_edit_filters/prevent-delete-in-zone-example.js +++ b/scripts/tutorials/entity_edit_filters/prevent-delete-in-zone-example.js @@ -5,13 +5,14 @@ // Created by Brad Hefta-Gaub to use Entities on Jan. 25, 2018 // Copyright 2018 High Fidelity, Inc. // -// This sample entity edit filter script will keep prevent any entity inside the zone from being deleted. +// This sample entity edit filter script will get all edits, adds, physcis, and deletes, but will only block +// deletes, and will pass through all others. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -function filter(properties, type, originalProperties, zoneProperties) { +function filter(properties, type) { if (type == Entities.DELETE_FILTER_TYPE) { return false; @@ -19,6 +20,5 @@ function filter(properties, type, originalProperties, zoneProperties) { return properties; } -filter.wantsOriginalProperties = true; -filter.wantsZoneProperties = true; +filter.wantsToFilterDelete = true; // do run on deletes filter; \ No newline at end of file From efc63a41e9551583c3eeb62c41e52e37789594c9 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 26 Jan 2018 18:49:55 -0800 Subject: [PATCH 048/569] make sure true results for delete actually work, add more examples --- libraries/entities/src/EntityEditFilters.cpp | 13 +++++++++- ...event-add-of-entities-named-bob-example.js | 26 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 scripts/tutorials/entity_edit_filters/prevent-add-of-entities-named-bob-example.js diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index cd1982bdb4..12bf1ac231 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -125,7 +125,7 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper return false; } - if (result.isObject()){ + if (result.isObject()) { // make propertiesIn reflect the changes, for next filter... propertiesIn.copyFromScriptValue(result, false); @@ -134,6 +134,17 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper // Javascript objects are == only if they are the same object. To compare arbitrary values, we need to use JSON. auto out = QJsonValue::fromVariant(result.toVariant()); wasChanged |= (in != out); + } else if (result.isBool()) { + + // if the filter returned false, then it's authoritative + if (!result.toBool()) { + return false; + } + + // otherwise, assume it wants to pass all properties + propertiesOut = propertiesIn; + wasChanged = false; + } else { return false; } diff --git a/scripts/tutorials/entity_edit_filters/prevent-add-of-entities-named-bob-example.js b/scripts/tutorials/entity_edit_filters/prevent-add-of-entities-named-bob-example.js new file mode 100644 index 0000000000..03fbe97430 --- /dev/null +++ b/scripts/tutorials/entity_edit_filters/prevent-add-of-entities-named-bob-example.js @@ -0,0 +1,26 @@ +// +// prevent-add-of-entities-named-bob-example.js +// +// +// Created by Brad Hefta-Gaub to use Entities on Jan. 25, 2018 +// Copyright 2018 High Fidelity, Inc. +// +// This sample entity edit filter script will get all edits, adds, physcis, and deletes, but will only block +// deletes, and will pass through all others. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +function filter(properties, type) { + if (properties.name == "bob") { + return false; + } + return properties; +} + +filter.wantsToFilterAdd = true; // do run on add +filter.wantsToFilterEdit = false; // do not run on edit +filter.wantsToFilterPhysics = false; // do not run on physics +filter.wantsToFilterDelete = false; // do not run on delete +filter; \ No newline at end of file From 0324f41565c77acdda2d326560aeba2a2f260498 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Mon, 29 Jan 2018 17:23:35 +0100 Subject: [PATCH 049/569] Trying to improve adaptive shadow bias --- libraries/render-utils/src/LightStage.cpp | 35 ++++++++++++++----- libraries/render-utils/src/LightStage.h | 3 ++ .../render-utils/src/RenderShadowTask.cpp | 4 ++- libraries/render-utils/src/RenderShadowTask.h | 2 +- libraries/render-utils/src/Shadow.slh | 15 +++++--- libraries/render-utils/src/ShadowCore.slh | 14 ++++++-- libraries/render-utils/src/Shadows_shared.slh | 8 ++--- 7 files changed, 58 insertions(+), 23 deletions(-) diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index eac9dd7657..e06d24f1b1 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -23,8 +23,6 @@ const glm::mat4 LightStage::Shadow::_biasMatrix{ 0.5, 0.5, 0.5, 1.0 }; const int LightStage::Shadow::MAP_SIZE = 1024; -static const auto MAX_BIAS = 0.006f; - const LightStage::Index LightStage::INVALID_INDEX { render::indexed_container::INVALID_INDEX }; LightStage::LightStage() { @@ -63,7 +61,7 @@ LightStage::LightStage() { LightStage::Shadow::Schema::Schema() { ShadowTransform defaultTransform; - defaultTransform.bias = MAX_BIAS; + defaultTransform.fixedBias = 0.005f; std::fill(cascades, cascades + SHADOW_CASCADE_MAX_COUNT, defaultTransform); invMapSize = 1.0f / MAP_SIZE; cascadeCount = 1; @@ -72,6 +70,18 @@ LightStage::Shadow::Schema::Schema() { maxDistance = 20.0f; } +void LightStage::Shadow::Schema::updateCascade(int cascadeIndex, const ViewFrustum& shadowFrustum) { + auto& cascade = cascades[cascadeIndex]; + cascade.frustumPosition = shadowFrustum.getPosition(); + // The adaptative bias is computing how much depth offset we have to add to + // push back a coarsely sampled surface perpendicularly to the shadow direction, + // to not have shadow acnee. The final computation is done in the shader, based on the + // surface normal. + auto maxWorldFrustumSize = glm::max(shadowFrustum.getWidth(), shadowFrustum.getHeight()); + cascade.adaptiveBiasUnitScale = maxWorldFrustumSize * 0.5f * invMapSize; + cascade.adaptiveBiasTransformScale = shadowFrustum.getNearClip() * shadowFrustum.getFarClip() / (shadowFrustum.getFarClip() - shadowFrustum.getNearClip()); +} + LightStage::Shadow::Cascade::Cascade() : _frustum{ std::make_shared() }, _minDistance{ 0.0f }, @@ -210,13 +220,15 @@ void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum, // Position the keylight frustum auto position = viewFrustum.getPosition() - (nearDepth + farDepth)*lightDirection; + // Update the buffer + auto& schema = _schemaBuffer.edit(); + auto cascadeIndex = 0; for (auto& cascade : _cascades) { cascade._frustum->setOrientation(orientation); cascade._frustum->setPosition(position); + schema.cascades[cascadeIndex].frustumPosition = position; + cascadeIndex++; } - // Update the buffer - auto& schema = _schemaBuffer.edit(); - schema.lightDirInViewSpace = glm::inverse(viewFrustum.getView()) * glm::vec4(lightDirection, 0.f); } void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum, @@ -269,8 +281,10 @@ void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, co // Update the buffer auto& schema = _schemaBuffer.edit(); - schema.cascades[cascadeIndex].reprojection = _biasMatrix * ortho * shadowViewInverse.getMatrix(); - schema.cascades[cascadeIndex].bias = baseBias; + auto& schemaCascade = schema.cascades[cascadeIndex]; + schemaCascade.reprojection = _biasMatrix * ortho * shadowViewInverse.getMatrix(); + schemaCascade.fixedBias = baseBias; + schema.updateCascade(cascadeIndex, *cascade._frustum); } void LightStage::Shadow::setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum) { @@ -281,7 +295,10 @@ void LightStage::Shadow::setCascadeFrustum(unsigned int cascadeIndex, const View *cascade._frustum = shadowFrustum; // Update the buffer - _schemaBuffer.edit().cascades[cascadeIndex].reprojection = _biasMatrix * shadowFrustum.getProjection() * viewInverse.getMatrix(); + auto& schema = _schemaBuffer.edit(); + auto& schemaCascade = schema.cascades[cascadeIndex]; + schemaCascade.reprojection = _biasMatrix * shadowFrustum.getProjection() * viewInverse.getMatrix(); + schema.updateCascade(cascadeIndex, shadowFrustum); } LightStage::Index LightStage::findLight(const LightPointer& light) const { diff --git a/libraries/render-utils/src/LightStage.h b/libraries/render-utils/src/LightStage.h index c2c2df8c89..922ec5eb4a 100644 --- a/libraries/render-utils/src/LightStage.h +++ b/libraries/render-utils/src/LightStage.h @@ -110,6 +110,8 @@ public: Schema(); + void updateCascade(int cascadeIndex, const ViewFrustum& shadowFrustum); + }; UniformBufferView _schemaBuffer = nullptr; }; @@ -213,6 +215,7 @@ protected: Index _sunOffLightId; Index _defaultLightId; + }; using LightStagePointer = std::shared_ptr; diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index f41860b6de..2172dda3e3 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -256,7 +256,9 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext) { } void RenderShadowCascadeSetup::configure(const Config& configuration) { - _baseBias = configuration.bias * configuration.bias * 0.01f; + // I'm not very proud of this empirical adjustment + auto cascadeBias = configuration.bias * powf(1.1f, _cascadeIndex); + _baseBias = cascadeBias * cascadeBias * 0.01f; } void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderContext, Outputs& output) { diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index 42c279c02a..c4f0c65bfc 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -67,7 +67,7 @@ class RenderShadowCascadeSetupConfig : public render::Job::Config { Q_PROPERTY(float bias MEMBER bias NOTIFY dirty) public: - float bias{ 0.5f }; + float bias{ 0.25f }; signals: void dirty(); diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index 50ae8864f4..c11f5fa6a7 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -83,11 +83,17 @@ float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, ve return shadowAttenuation; } -float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float bias) { +float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float bias, + vec3 worldPosition, vec3 worldLightDir) { if (!isShadowCascadeProjectedOnPixel(shadowTexcoord)) { // If a point is not in the map, do not attenuate return 1.0; } + // After this, bias is in view units + bias *= getShadowAdaptiveBiasUnitScale(cascadeIndex); + // Transform to texcoord space (between 0 and 1) + float shadowDepth = abs(dot(worldPosition-getShadowFrustumPosition(cascadeIndex), worldLightDir)); + bias = transformShadowAdaptiveBias(cascadeIndex, shadowDepth, bias); // Add fixed bias bias += getShadowBias(cascadeIndex); return evalShadowAttenuationPCF(cascadeIndex, offsets, shadowTexcoord, bias); @@ -101,12 +107,11 @@ float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDe // Adjust bias if we are at a grazing angle with light float ndotl = dot(worldLightDir, worldNormal); - float bias = 0.5*(1.0/(ndotl*ndotl)-1.0); - bias *= getShadowScale(); + float bias = 1.0/(ndotl*ndotl)-1.0; vec2 cascadeAttenuations = vec2(1.0, 1.0); - cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, offsets, cascadeShadowCoords[0], bias); + cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, offsets, cascadeShadowCoords[0], bias, worldPosition.xyz, worldLightDir); if (cascadeMix > 0.0 && cascadeIndices.y < getShadowCascadeCount()) { - cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, offsets, cascadeShadowCoords[1], bias); + cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, offsets, cascadeShadowCoords[1], bias, worldPosition.xyz, worldLightDir); } float attenuation = mix(cascadeAttenuations.x, cascadeAttenuations.y, cascadeMix); // Falloff to max distance diff --git a/libraries/render-utils/src/ShadowCore.slh b/libraries/render-utils/src/ShadowCore.slh index 9b3b086598..2d48e16ef4 100644 --- a/libraries/render-utils/src/ShadowCore.slh +++ b/libraries/render-utils/src/ShadowCore.slh @@ -38,11 +38,19 @@ float getShadowScale() { } float getShadowBias(int cascadeIndex) { - return shadow.cascades[cascadeIndex].bias; + return shadow.cascades[cascadeIndex].fixedBias; } -vec3 getShadowDirInViewSpace() { - return shadow.lightDirInViewSpace; +vec3 getShadowFrustumPosition(int cascadeIndex) { + return shadow.cascades[cascadeIndex].frustumPosition; +} + +float getShadowAdaptiveBiasUnitScale(int cascadeIndex) { + return shadow.cascades[cascadeIndex].adaptiveBiasUnitScale; +} + +float transformShadowAdaptiveBias(int cascadeIndex, float shadowDepth, float depthBias) { + return shadow.cascades[cascadeIndex].adaptiveBiasTransformScale * depthBias / (shadowDepth*shadowDepth); } // Compute the texture coordinates from world coordinates diff --git a/libraries/render-utils/src/Shadows_shared.slh b/libraries/render-utils/src/Shadows_shared.slh index abb226421c..def6b1b4d4 100644 --- a/libraries/render-utils/src/Shadows_shared.slh +++ b/libraries/render-utils/src/Shadows_shared.slh @@ -11,16 +11,16 @@ struct ShadowTransform { MAT4 reprojection; - - float bias; + VEC3 frustumPosition; + float fixedBias; + float adaptiveBiasUnitScale; + float adaptiveBiasTransformScale; float _padding1; float _padding2; - float _padding3; }; struct ShadowParameters { ShadowTransform cascades[SHADOW_CASCADE_MAX_COUNT]; - VEC3 lightDirInViewSpace; int cascadeCount; float invMapSize; float invCascadeBlendWidth; From 9ee715364185b913bf928864867278a11ac35af0 Mon Sep 17 00:00:00 2001 From: David Back Date: Mon, 29 Jan 2018 16:53:02 -0800 Subject: [PATCH 050/569] fix scale speed, fix stretch panels, fix scale cube highlight --- .../system/libraries/entitySelectionTool.js | 96 +++++++++++-------- 1 file changed, 58 insertions(+), 38 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 253c67bfca..3da7d1f083 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -246,6 +246,7 @@ SelectionDisplay = (function() { var SCALE_MINIMUM_DIMENSION = 0.02; var STRETCH_MINIMUM_DIMENSION = 0.001; + var STRETCH_DIRECTION_ALL_FACTOR = 15; // These are multipliers for sizing the rotation degrees display while rotating an entity var ROTATION_DISPLAY_DISTANCE_MULTIPLIER = 1.0; @@ -266,12 +267,6 @@ SelectionDisplay = (function() { ALL : 3 } - var ROTATE_DIRECTION = { - PITCH : 0, - YAW : 1, - ROLL : 2 - } - var SCALE_DIRECTION = { LBN : 0, RBN : 1, @@ -283,6 +278,12 @@ SelectionDisplay = (function() { RTF : 7 } + var ROTATE_DIRECTION = { + PITCH : 0, + YAW : 1, + ROLL : 2 + } + var spaceMode = SPACE_LOCAL; var overlayNames = []; var lastCameraPosition = Camera.getPosition(); @@ -706,7 +707,7 @@ SelectionDisplay = (function() { }; }; - function makeStretchTool(stretchMode, directionEnum, directionVec, pivot, offset) { + function makeStretchTool(stretchMode, directionEnum, directionVec, pivot, offset, stretchPanel, scaleGrabber) { var directionFor3DStretch = directionVec; var distanceFor3DStretch = 0; var DISTANCE_INFLUENCE_THRESHOLD = 1.2; @@ -892,16 +893,29 @@ SelectionDisplay = (function() { that.setGrabberTranslateVisible(false); that.setGrabberRotateVisible(false); - that.setGrabberScaleVisible(directionEnum === STRETCH_DIRECTION.ALL); + that.setGrabberScaleVisible(true); that.setGrabberStretchXVisible(directionEnum === STRETCH_DIRECTION.X); that.setGrabberStretchYVisible(directionEnum === STRETCH_DIRECTION.Y); that.setGrabberStretchZVisible(directionEnum === STRETCH_DIRECTION.Z); that.setGrabberClonerVisible(false); + + if (stretchPanel != null) { + Overlays.editOverlay(stretchPanel, { visible: true }); + } + if (scaleGrabber != null) { + Overlays.editOverlay(scaleGrabber, { color: GRABBER_SCALE_CUBE_SELECTED_COLOR }); + } SelectionManager.saveProperties(); }; var onEnd = function(event, reason) { + if (stretchPanel != null) { + Overlays.editOverlay(stretchPanel, { visible: false }); + } + if (scaleGrabber != null) { + Overlays.editOverlay(scaleGrabber, { color: GRABBER_SCALE_CUBE_IDLE_COLOR }); + } pushCommandForSelections(); }; @@ -959,6 +973,11 @@ SelectionDisplay = (function() { vector = grid.snapToSpacing(vector); var changeInDimensions = Vec3.multiply(-1, vec3Mult(localSigns, vector)); + + if (directionEnum === STRETCH_DIRECTION.ALL) { + changeInDimensions = Vec3.multiply(changeInDimensions, STRETCH_DIRECTION_ALL_FACTOR); + } + var newDimensions; if (proportional) { var absX = Math.abs(changeInDimensions.x); @@ -1024,60 +1043,53 @@ SelectionDisplay = (function() { } function addGrabberStretchTool(overlay, mode, directionEnum) { - var directionVec, pivot, offset; + var directionVec, pivot, offset, stretchPanel; if (directionEnum === STRETCH_DIRECTION.X) { + stretchPanel = grabberStretchXPanel; directionVec = { x:-1, y:0, z:0 }; - pivot = { x:-1, y:0, z:0 }; - offset = { x:1, y:0, z:0 }; } else if (directionEnum === STRETCH_DIRECTION.Y) { + stretchPanel = grabberStretchYPanel; directionVec = { x:0, y:-1, z:0 }; - pivot = { x:0, y:-1, z:0 }; - offset = { x:0, y:1, z:0 }; } else if (directionEnum === STRETCH_DIRECTION.Z) { + stretchPanel = grabberStretchZPanel directionVec = { x:0, y:0, z:-1 }; - pivot = { x:0, y:0, z:-1 }; - offset = { x:0, y:0, z:1 }; } - var tool = makeStretchTool(mode, directionEnum, directionVec, pivot, offset); + pivot = directionVec; + offset = Vec3.multiply(directionVec, -1); + var tool = makeStretchTool(mode, directionEnum, directionVec, pivot, offset, stretchPanel, null); return addGrabberTool(overlay, tool); } function addGrabberScaleTool(overlay, mode, directionEnum) { - var directionVec, pivot, offset; + var directionVec, pivot, offset, selectedGrabber; if (directionEnum === SCALE_DIRECTION.LBN) { directionVec = { x:1, y:1, z:1 }; - pivot = { x:1, y:1, z:1 }; - offset = { x:-1, y:-1, z:-1 }; + selectedGrabber = grabberScaleLBNCube; } else if (directionEnum === SCALE_DIRECTION.RBN) { directionVec = { x:1, y:1, z:-1 }; - pivot = { x:1, y:1, z:-1 }; - offset = { x:-1, y:-1, z:1 }; + selectedGrabber = grabberScaleRBNCube; } else if (directionEnum === SCALE_DIRECTION.LBF) { directionVec = { x:-1, y:1, z:1 }; - pivot = { x:-1, y:1, z:1 }; - offset = { x:1, y:-1, z:-1 }; + selectedGrabber = grabberScaleLBFCube; } else if (directionEnum === SCALE_DIRECTION.RBF) { directionVec = { x:-1, y:1, z:-1 }; - pivot = { x:-1, y:1, z:-1 }; - offset = { x:1, y:-1, z:1 }; + selectedGrabber = grabberScaleRBFCube; } else if (directionEnum === SCALE_DIRECTION.LTN) { directionVec = { x:1, y:-1, z:1 }; - pivot = { x:1, y:-1, z:1 }; - offset = { x:-1, y:1, z:-1 }; + selectedGrabber = grabberScaleLTNCube; } else if (directionEnum === SCALE_DIRECTION.RTN) { directionVec = { x:1, y:-1, z:-1 }; - pivot = { x:1, y:-1, z:-1 }; - offset = { x:-1, y:1, z:1 }; + selectedGrabber = grabberScaleRTNCube; } else if (directionEnum === SCALE_DIRECTION.LTF) { directionVec = { x:-1, y:-1, z:1 }; - pivot = { x:-1, y:-1, z:1 }; - offset = { x:1, y:1, z:-1 }; + selectedGrabber = grabberScaleLTFCube; } else if (directionEnum === SCALE_DIRECTION.RTF) { directionVec = { x:-1, y:-1, z:-1 }; - pivot = { x:-1, y:-1, z:-1 }; - offset = { x:1, y:1, z:1 }; + selectedGrabber = grabberScaleRTFCube; } - var tool = makeStretchTool(mode, STRETCH_DIRECTION.ALL, directionVec, pivot, offset); + pivot = directionVec; + offset = Vec3.multiply(directionVec, -1); + var tool = makeStretchTool(mode, STRETCH_DIRECTION.ALL, directionVec, pivot, offset, null, selectedGrabber); return addGrabberTool(overlay, tool); } @@ -1346,6 +1358,7 @@ SelectionDisplay = (function() { if (SelectionManager.hasSelection()) { var worldPosition = SelectionManager.worldPosition; var worldRotation = SelectionManager.worldRotation; + var worldRotationInverse = Quat.inverse(worldRotation); var worldDimensions = SelectionManager.worldDimensions; var worldDimensionsX = worldDimensions.x; @@ -1478,7 +1491,9 @@ SelectionDisplay = (function() { position: stretchXPos, dimensions: stretchSphereDimensions }); - var stretchPanelXDimensions = Vec3.subtract(grabberScaleLTFCubePos, grabberScaleRBFCubePos); + var grabberScaleLTFCubePosRot = Vec3.multiplyQbyV(worldRotationInverse, grabberScaleLTFCubePos); + var grabberScaleRBFCubePosRot = Vec3.multiplyQbyV(worldRotationInverse, grabberScaleRBFCubePos); + var stretchPanelXDimensions = Vec3.subtract(grabberScaleLTFCubePosRot, grabberScaleRBFCubePosRot); var tempY = Math.abs(stretchPanelXDimensions.y); stretchPanelXDimensions.x = 0.01; stretchPanelXDimensions.y = Math.abs(stretchPanelXDimensions.z); @@ -1493,7 +1508,9 @@ SelectionDisplay = (function() { position: stretchYPos, dimensions: stretchSphereDimensions }); - var stretchPanelYDimensions = Vec3.subtract(grabberScaleLTFCubePos, grabberScaleRTNCubePos); + var grabberScaleLTFCubePosRot = Vec3.multiplyQbyV(worldRotationInverse, grabberScaleLTNCubePos); + var grabberScaleRTNCubePosRot = Vec3.multiplyQbyV(worldRotationInverse, grabberScaleRTFCubePos); + var stretchPanelYDimensions = Vec3.subtract(grabberScaleLTFCubePosRot, grabberScaleRTNCubePosRot); var tempX = Math.abs(stretchPanelYDimensions.x); stretchPanelYDimensions.x = Math.abs(stretchPanelYDimensions.z); stretchPanelYDimensions.y = 0.01; @@ -1508,7 +1525,9 @@ SelectionDisplay = (function() { position: stretchZPos, dimensions: stretchSphereDimensions }); - var stretchPanelZDimensions = Vec3.subtract(grabberScaleRTFCubePos, grabberScaleRBNCubePos); + var grabberScaleRTFCubePosRot = Vec3.multiplyQbyV(worldRotationInverse, grabberScaleRTFCubePos); + var grabberScaleRBNCubePosRot = Vec3.multiplyQbyV(worldRotationInverse, grabberScaleRBNCubePos); + var stretchPanelZDimensions = Vec3.subtract(grabberScaleRTFCubePosRot, grabberScaleRBNCubePosRot); var tempX = Math.abs(stretchPanelZDimensions.x); stretchPanelZDimensions.x = Math.abs(stretchPanelZDimensions.y); stretchPanelZDimensions.y = tempX; @@ -1576,7 +1595,8 @@ SelectionDisplay = (function() { that.setGrabberStretchYVisible(!activeTool || isActiveTool(grabberStretchYSphere)); that.setGrabberStretchZVisible(!activeTool || isActiveTool(grabberStretchZSphere)); that.setGrabberScaleVisible(!activeTool || isActiveTool(grabberScaleLBNCube) || isActiveTool(grabberScaleRBNCube) || isActiveTool(grabberScaleLBFCube) || isActiveTool(grabberScaleRBFCube) - || isActiveTool(grabberScaleLTNCube) || isActiveTool(grabberScaleRTNCube) || isActiveTool(grabberScaleLTFCube) || isActiveTool(grabberScaleRTFCube)); + || isActiveTool(grabberScaleLTNCube) || isActiveTool(grabberScaleRTNCube) || isActiveTool(grabberScaleLTFCube) || isActiveTool(grabberScaleRTFCube) + || isActiveTool(grabberStretchXSphere) || isActiveTool(grabberStretchYSphere) || isActiveTool(grabberStretchZSphere)); that.setGrabberClonerVisible(!activeTool || isActiveTool(grabberCloner)); if (wantDebug) { From 2f0d92c3cde394770e6613ce2e434ad5d45e9908 Mon Sep 17 00:00:00 2001 From: David Back Date: Mon, 29 Jan 2018 18:15:01 -0800 Subject: [PATCH 051/569] ctrl 22.5 snapping --- .../system/libraries/entitySelectionTool.js | 62 +++++++++++++++---- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 3da7d1f083..d843b6282b 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -254,6 +254,10 @@ SelectionDisplay = (function() { var ROTATION_DISPLAY_SIZE_Y_MULTIPLIER = 0.18; var ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER = 0.14; + var ROTATION_CTRL_SNAP_ANGLE = 22.5; + var ROTATION_DEFAULT_SNAP_ANGLE = 1; + var ROTATION_DEFAULT_TICK_MARKS_ANGLE = 5; + var TRANSLATE_DIRECTION = { X : 0, Y : 1, @@ -300,6 +304,8 @@ SelectionDisplay = (function() { var worldRotationY; var worldRotationZ; + var ctrlPressed = false; + var activeTool = null; var grabberTools = {}; @@ -335,7 +341,7 @@ SelectionDisplay = (function() { innerRadius: 0.9, startAt: 0, endAt: 360, - majorTickMarksAngle: 5, + majorTickMarksAngle: ROTATION_DEFAULT_TICK_MARKS_ANGLE, majorTickMarksLength: 0.1, visible: false, ignoreRayIntersection: false, @@ -584,6 +590,25 @@ SelectionDisplay = (function() { that.triggerMapping.from(Controller.Standard.RT).peek().to(makeTriggerHandler(Controller.Standard.RightHand)); that.triggerMapping.from(Controller.Standard.LT).peek().to(makeTriggerHandler(Controller.Standard.LeftHand)); + // Control key remains active only while key is held down + function keyReleaseEvent(key) { + if (key.key === 16777249) { + ctrlPressed = false; + that.updateActiveRotateRing(); + } + } + + // Triggers notification on specific key driven events + function keyPressEvent(key) { + if (key.key === 16777249) { + ctrlPressed = true; + that.updateActiveRotateRing(); + } + } + + Controller.keyPressEvent.connect(keyPressEvent); + Controller.keyReleaseEvent.connect(keyReleaseEvent); + function controllerComputePickRay() { var controllerPose = getControllerWorldLocation(activeHand, true); if (controllerPose.valid && that.triggered) { @@ -1227,7 +1252,9 @@ SelectionDisplay = (function() { var centerToIntersect = Vec3.subtract(result, rotCenter); // Note: orientedAngle which wants normalized centerToZero and centerToIntersect // handles that internally, so it's to pass unnormalized vectors here. - var angleFromZero = Math.floor((Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal))); + var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); + var snapAngle = ctrlPressed ? ROTATION_CTRL_SNAP_ANGLE : ROTATION_DEFAULT_SNAP_ANGLE; + angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; var rotChange = Quat.angleAxis(angleFromZero, rotationNormal); updateSelectionsRotation(rotChange); updateRotationDegreesOverlay(-angleFromZero, direction, rotCenter); @@ -1310,12 +1337,6 @@ SelectionDisplay = (function() { } } - /* - Overlays.editOverlay(highlightBox, { - visible: false - }); - */ - that.updateGrabbers(); }; @@ -1544,24 +1565,28 @@ SelectionDisplay = (function() { Overlays.editOverlay(grabberRotatePitchRing, { position: SelectionManager.worldPosition, rotation: worldRotationY, - dimensions: rotateDimensions + dimensions: rotateDimensions, + majorTickMarksAngle: ROTATION_DEFAULT_TICK_MARKS_ANGLE }); } if (!isActiveTool(grabberRotateYawRing)) { Overlays.editOverlay(grabberRotateYawRing, { position: SelectionManager.worldPosition, rotation: worldRotationZ, - dimensions: rotateDimensions + dimensions: rotateDimensions, + majorTickMarksAngle: ROTATION_DEFAULT_TICK_MARKS_ANGLE }); } if (!isActiveTool(grabberRotateRollRing)) { Overlays.editOverlay(grabberRotateRollRing, { position: SelectionManager.worldPosition, rotation: worldRotationX, - dimensions: rotateDimensions + dimensions: rotateDimensions, + majorTickMarksAngle: ROTATION_DEFAULT_TICK_MARKS_ANGLE }); } Overlays.editOverlay(grabberRotateCurrentRing, { dimensions: rotateDimensions }); + that.updateActiveRotateRing(); var inModeRotate = isActiveTool(grabberRotatePitchRing) || isActiveTool(grabberRotateYawRing) || isActiveTool(grabberRotateRollRing); var inModeTranslate = isActiveTool(grabberTranslateXCone) || isActiveTool(grabberTranslateXCylinder) || @@ -1604,6 +1629,21 @@ SelectionDisplay = (function() { } }; + that.updateActiveRotateRing = function() { + var activeRotateRing = null; + if (isActiveTool(grabberRotatePitchRing)) { + activeRotateRing = grabberRotatePitchRing; + } else if (isActiveTool(grabberRotateYawRing)) { + activeRotateRing = grabberRotateYawRing; + } else if (isActiveTool(grabberRotateRollRing)) { + activeRotateRing = grabberRotateRollRing; + } + if (activeRotateRing != null) { + var tickMarksAngle = ctrlPressed ? ROTATION_CTRL_SNAP_ANGLE : ROTATION_DEFAULT_TICK_MARKS_ANGLE; + Overlays.editOverlay(activeRotateRing, { majorTickMarksAngle: tickMarksAngle }); + } + }; + // FUNCTION: SET OVERLAYS VISIBLE that.setOverlaysVisible = function(isVisible) { for (var i = 0; i < allOverlays.length; i++) { From e679b75e99a53f45a5d50619c0716339b1b82778 Mon Sep 17 00:00:00 2001 From: David Back Date: Mon, 29 Jan 2018 19:02:36 -0800 Subject: [PATCH 052/569] fix degree display position --- .../system/libraries/entitySelectionTool.js | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index d843b6282b..fbda9e9eba 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -299,6 +299,7 @@ SelectionDisplay = (function() { var rotZero; var rotationNormal; + var rotDegreePos; var worldRotationX; var worldRotationY; @@ -1119,20 +1120,8 @@ SelectionDisplay = (function() { } // FUNCTION: UPDATE ROTATION DEGREES OVERLAY - function updateRotationDegreesOverlay(angleFromZero, direction, centerPosition) { + function updateRotationDegreesOverlay(angleFromZero, position) { var angle = angleFromZero * (Math.PI / 180); - var position = { - x: Math.cos(angle) * ROTATION_DISPLAY_DISTANCE_MULTIPLIER, - y: Math.sin(angle) * ROTATION_DISPLAY_DISTANCE_MULTIPLIER, - z: 0 - }; - if (direction === ROTATE_DIRECTION.PITCH) - position = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, -90, 0), position); - else if (direction === ROTATE_DIRECTION.YAW) - position = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(90, 0, 0), position); - else if (direction === ROTATE_DIRECTION.ROLL) - position = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, 180, 0), position); - position = Vec3.sum(centerPosition, position); var overlayProps = { position: position, dimensions: { @@ -1222,7 +1211,6 @@ SelectionDisplay = (function() { endAt: 0, visible: true }); - updateRotationDegreesOverlay(0, direction, rotCenter); // editOverlays may not have committed rotation changes. // Compute zero position based on where the overlay will be eventually. @@ -1230,6 +1218,11 @@ SelectionDisplay = (function() { // In case of a parallel ray, this will be null, which will cause early-out // in the onMove helper. rotZero = result; + + var rotCenterToZero = Vec3.subtract(rotZero, rotCenter); + var rotCenterToZeroLength = Vec3.length(rotCenterToZero); + rotDegreePos = Vec3.sum(rotCenter, Vec3.multiply(Vec3.normalize(rotCenterToZero), rotCenterToZeroLength * 1.2)); + updateRotationDegreesOverlay(0, rotDegreePos); }, onEnd: function(event, reason) { Overlays.editOverlay(rotationDegreesDisplay, { visible: false }); @@ -1257,7 +1250,7 @@ SelectionDisplay = (function() { angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; var rotChange = Quat.angleAxis(angleFromZero, rotationNormal); updateSelectionsRotation(rotChange); - updateRotationDegreesOverlay(-angleFromZero, direction, rotCenter); + updateRotationDegreesOverlay(-angleFromZero, rotDegreePos); var startAtCurrent = 0; var endAtCurrent = angleFromZero; From 5a771e3a168357708d646dcba70c79ceb7eb4cb4 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Fri, 26 Jan 2018 17:08:53 -0500 Subject: [PATCH 053/569] [Case 10865] Ctrl/Cmd/Shift+Click Multi-Select for Asset Browser. * Removes manual selectionModel update from controls-uit/Tree.qml * This was interfering with the Key+Click functionality. It was introduced via Commit 99617600c47 to address a selection issue; however, it's unclear what that issue was. Selection within the Asset Browser works without it at present. * Amends the ContextMenu within the AssetBrowser to work in conjunction with the multi-selection changes. * ContextMenu will _only_ display when triggered within the current selection be it one or more items as opposed to previous behavior of selecting the item the menu was triggered on. * CopyURL is only enabled when the selection size is 1 * Rename is only enabled when the selection size is 1 * Delete is enabled when the selection size is greater than 0 Changes Committed: modified: interface/resources/qml/controls-uit/Tree.qml modified: interface/resources/qml/hifi/AssetServer.qml --- interface/resources/qml/controls-uit/Tree.qml | 4 -- interface/resources/qml/hifi/AssetServer.qml | 41 +++++++++++-------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/interface/resources/qml/controls-uit/Tree.qml b/interface/resources/qml/controls-uit/Tree.qml index 6bd11295b1..5199a10a27 100644 --- a/interface/resources/qml/controls-uit/Tree.qml +++ b/interface/resources/qml/controls-uit/Tree.qml @@ -202,8 +202,4 @@ TreeView { } onDoubleClicked: isExpanded(index) ? collapse(index) : expand(index) - - onClicked: { - selectionModel.setCurrentIndex(index, ItemSelectionModel.ClearAndSelect); - } } diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 37c3c2adab..7c16b19865 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -694,7 +694,7 @@ Windows.ScrollingWindow { } } } - } + }// End_OF( itemLoader ) Rectangle { id: treeLabelToolTip @@ -731,50 +731,59 @@ Windows.ScrollingWindow { showTimer.stop(); treeLabelToolTip.visible = false; } - } + }// End_OF( treeLabelToolTip ) MouseArea { propagateComposedEvents: true anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: { - if (!HMD.active) { // Popup only displays properly on desktop - var index = treeView.indexAt(mouse.x, mouse.y); - treeView.selection.setCurrentIndex(index, 0x0002); - contextMenu.currentIndex = index; - contextMenu.popup(); + if (treeView.selection.hasSelection && !HMD.active) { // Popup only displays properly on desktop + // Only display the popup if the click triggered within + // the selection. + var clickedIndex = treeView.indexAt(mouse.x, mouse.y); + var displayContextMenu = false; + for ( var i = 0; i < selectedItems; ++i) { + var currentSelectedIndex = treeView.selection.selectedIndexes[i]; + if (clickedIndex === currentSelectedIndex) { + contextMenu.popup(); + break; + } + } } } } - + Menu { id: contextMenu title: "Edit" property var url: "" - property var currentIndex: null MenuItem { text: "Copy URL" + enabled: (selectedItems == 1) onTriggered: { - copyURLToClipboard(contextMenu.currentIndex); + copyURLToClipboard(treeView.selection.currentIndex); } } MenuItem { text: "Rename" + enabled: (selectedItems == 1) onTriggered: { - renameFile(contextMenu.currentIndex); + renameFile(treeView.selection.currentIndex); } } MenuItem { text: "Delete" + enabled: (selectedItems > 0) onTriggered: { - deleteFile(contextMenu.currentIndex); + deleteFile(); } } - } - } + }// End_OF( contextMenu ) + }// End_OF( treeView ) Row { id: infoRow @@ -885,7 +894,7 @@ Windows.ScrollingWindow { "Baking compresses and optimizes files for faster network transfer and display. We recommend you bake your content to reduce initial load times for your visitors."); } } - } + }// End_OF( infoRow ) HifiControls.ContentSection { id: uploadSection @@ -945,7 +954,7 @@ Windows.ScrollingWindow { } } } - } + }// End_OF( uploadSection ) } } From dfdf28f37e4572bb95b6354be850356ad8bffa27 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Fri, 26 Jan 2018 17:34:05 -0500 Subject: [PATCH 054/569] [Case 10865] Some cleanup for multi-selection (details below). * Remove setCurrentIndex call when looping over current selection. * Changed selectedItems var name to selectedItemCount to clarify what it represents. * Change path arg name to paths to clarify that there can be more than a single path contained within it. Changes Committed: modified: interface/resources/qml/hifi/AssetServer.qml --- interface/resources/qml/hifi/AssetServer.qml | 49 ++++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 7c16b19865..30f76fecd6 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -37,7 +37,7 @@ Windows.ScrollingWindow { property var assetProxyModel: Assets.proxyModel; property var assetMappingsModel: Assets.mappingModel; property var currentDirectory; - property var selectedItems: treeView.selection.selectedIndexes.length; + property var selectedItemCount: treeView.selection.selectedIndexes.length; Settings { category: "Overlay.AssetServer" @@ -75,17 +75,17 @@ Windows.ScrollingWindow { }); } - function doDeleteFile(path) { - console.log("Deleting " + path); + function doDeleteFile(paths) { + console.log("Deleting " + paths); - Assets.deleteMappings(path, function(err) { + Assets.deleteMappings(paths, function(err) { if (err) { - console.log("Asset browser - error deleting path: ", path, err); + console.log("Asset browser - error deleting paths: ", paths, err); - box = errorMessageBox("There was an error deleting:\n" + path + "\n" + err); + box = errorMessageBox("There was an error deleting:\n" + paths + "\n" + err); box.selected.connect(reload); } else { - console.log("Asset browser - finished deleting path: ", path); + console.log("Asset browser - finished deleting paths: ", paths); reload(); } }); @@ -145,7 +145,7 @@ Windows.ScrollingWindow { function canAddToWorld(path) { var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i]; - if (selectedItems > 1) { + if (selectedItemCount > 1) { return false; } @@ -155,7 +155,7 @@ Windows.ScrollingWindow { } function canRename() { - if (treeView.selection.hasSelection && selectedItems == 1) { + if (treeView.selection.hasSelection && selectedItemCount == 1) { return true; } else { return false; @@ -333,29 +333,28 @@ Windows.ScrollingWindow { }); } function deleteFile(index) { - var path = []; + var paths = []; if (!index) { - for (var i = 0; i < selectedItems; i++) { - treeView.selection.setCurrentIndex(treeView.selection.selectedIndexes[i], 0x100); - index = treeView.selection.currentIndex; - path[i] = assetProxyModel.data(index, 0x100); + for (var i = 0; i < selectedItemCount; ++i) { + index = treeView.selection.selectedIndexes[i]; + paths[i] = assetProxyModel.data(index, 0x100); } } - if (!path) { + if (!paths) { return; } var modalMessage = ""; - var items = selectedItems.toString(); + var items = selectedItemCount.toString(); var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101); var typeString = isFolder ? 'folder' : 'file'; - if (selectedItems > 1) { + if (selectedItemCount > 1) { modalMessage = "You are about to delete " + items + " items \nDo you want to continue?"; } else { - modalMessage = "You are about to delete the following " + typeString + ":\n" + path + "\nDo you want to continue?"; + modalMessage = "You are about to delete the following " + typeString + ":\n" + paths + "\nDo you want to continue?"; } var object = desktop.messageBox({ @@ -367,7 +366,7 @@ Windows.ScrollingWindow { }); object.selected.connect(function(button) { if (button === OriginalDialogs.StandardButton.Yes) { - doDeleteFile(path); + doDeleteFile(paths); } }); } @@ -743,7 +742,7 @@ Windows.ScrollingWindow { // the selection. var clickedIndex = treeView.indexAt(mouse.x, mouse.y); var displayContextMenu = false; - for ( var i = 0; i < selectedItems; ++i) { + for ( var i = 0; i < selectedItemCount; ++i) { var currentSelectedIndex = treeView.selection.selectedIndexes[i]; if (clickedIndex === currentSelectedIndex) { contextMenu.popup(); @@ -761,7 +760,7 @@ Windows.ScrollingWindow { MenuItem { text: "Copy URL" - enabled: (selectedItems == 1) + enabled: (selectedItemCount == 1) onTriggered: { copyURLToClipboard(treeView.selection.currentIndex); } @@ -769,7 +768,7 @@ Windows.ScrollingWindow { MenuItem { text: "Rename" - enabled: (selectedItems == 1) + enabled: (selectedItemCount == 1) onTriggered: { renameFile(treeView.selection.currentIndex); } @@ -777,7 +776,7 @@ Windows.ScrollingWindow { MenuItem { text: "Delete" - enabled: (selectedItems > 0) + enabled: (selectedItemCount > 0) onTriggered: { deleteFile(); } @@ -796,8 +795,8 @@ Windows.ScrollingWindow { function makeText() { var numPendingBakes = assetMappingsModel.numPendingBakes; - if (selectedItems > 1 || numPendingBakes === 0) { - return selectedItems + " items selected"; + if (selectedItemCount > 1 || numPendingBakes === 0) { + return selectedItemCount + " items selected"; } else { return numPendingBakes + " bakes pending" } From 70e23f3ce86f5837f24eaea39c422aeea23c64f0 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Tue, 30 Jan 2018 14:00:09 -0500 Subject: [PATCH 055/569] [Case 10865] Bringing in multi-select fix from Desktop AssetServer. Changes Committed: modified: interface/resources/qml/hifi/dialogs/TabletAssetServer.qml --- .../qml/hifi/dialogs/TabletAssetServer.qml | 82 ++++++++++--------- 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index a02496a252..9c61206592 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -39,7 +39,7 @@ Rectangle { property var assetProxyModel: Assets.proxyModel; property var assetMappingsModel: Assets.mappingModel; property var currentDirectory; - property var selectedItems: treeView.selection.selectedIndexes.length; + property var selectedItemCount: treeView.selection.selectedIndexes.length; Settings { category: "Overlay.AssetServer" @@ -76,17 +76,17 @@ Rectangle { }); } - function doDeleteFile(path) { - console.log("Deleting " + path); + function doDeleteFile(paths) { + console.log("Deleting " + paths); - Assets.deleteMappings(path, function(err) { + Assets.deleteMappings(paths, function(err) { if (err) { - console.log("Asset browser - error deleting path: ", path, err); + console.log("Asset browser - error deleting paths: ", paths, err); - box = errorMessageBox("There was an error deleting:\n" + path + "\n" + err); + box = errorMessageBox("There was an error deleting:\n" + paths + "\n" + err); box.selected.connect(reload); } else { - console.log("Asset browser - finished deleting path: ", path); + console.log("Asset browser - finished deleting paths: ", paths); reload(); } }); @@ -146,7 +146,7 @@ Rectangle { function canAddToWorld(path) { var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i]; - if (selectedItems > 1) { + if (selectedItemCount > 1) { return false; } @@ -156,7 +156,7 @@ Rectangle { } function canRename() { - if (treeView.selection.hasSelection && selectedItems == 1) { + if (treeView.selection.hasSelection && selectedItemCount == 1) { return true; } else { return false; @@ -334,29 +334,28 @@ Rectangle { }); } function deleteFile(index) { - var path = []; + var paths = []; if (!index) { - for (var i = 0; i < selectedItems; i++) { - treeView.selection.setCurrentIndex(treeView.selection.selectedIndexes[i], 0x100); - index = treeView.selection.currentIndex; - path[i] = assetProxyModel.data(index, 0x100); + for (var i = 0; i < selectedItemCount; ++i) { + index = treeView.selection.selectedIndexes[i]; + paths[i] = assetProxyModel.data(index, 0x100); } } - if (!path) { + if (!paths) { return; } var modalMessage = ""; - var items = selectedItems.toString(); + var items = selectedItemCount.toString(); var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101); var typeString = isFolder ? 'folder' : 'file'; - if (selectedItems > 1) { + if (selectedItemCount > 1) { modalMessage = "You are about to delete " + items + " items \nDo you want to continue?"; } else { - modalMessage = "You are about to delete the following " + typeString + ":\n" + path + "\nDo you want to continue?"; + modalMessage = "You are about to delete the following " + typeString + ":\n" + paths + "\nDo you want to continue?"; } var object = tabletRoot.messageBox({ @@ -368,7 +367,7 @@ Rectangle { }); object.selected.connect(function(button) { if (button === OriginalDialogs.StandardButton.Yes) { - doDeleteFile(path); + doDeleteFile(paths); } }); } @@ -693,7 +692,7 @@ Rectangle { } } } - } + }// End_OF( itemLoader ) Rectangle { id: treeLabelToolTip @@ -730,50 +729,59 @@ Rectangle { showTimer.stop(); treeLabelToolTip.visible = false; } - } + }// End_OF( treeLabelToolTip ) MouseArea { propagateComposedEvents: true anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: { - if (!HMD.active) { // Popup only displays properly on desktop - var index = treeView.indexAt(mouse.x, mouse.y); - treeView.selection.setCurrentIndex(index, 0x0002); - contextMenu.currentIndex = index; - contextMenu.popup(); + if (treeView.selection.hasSelection && !HMD.active) { // Popup only displays properly on desktop + // Only display the popup if the click triggered within + // the selection. + var clickedIndex = treeView.indexAt(mouse.x, mouse.y); + var displayContextMenu = false; + for ( var i = 0; i < selectedItemCount; ++i) { + var currentSelectedIndex = treeView.selection.selectedIndexes[i]; + if (clickedIndex === currentSelectedIndex) { + contextMenu.popup(); + break; + } + } } } } - + Menu { id: contextMenu title: "Edit" property var url: "" - property var currentIndex: null MenuItem { text: "Copy URL" + enabled: (selectedItemCount == 1) onTriggered: { - copyURLToClipboard(contextMenu.currentIndex); + copyURLToClipboard(treeView.selection.currentIndex); } } MenuItem { text: "Rename" + enabled: (selectedItemCount == 1) onTriggered: { - renameFile(contextMenu.currentIndex); + renameFile(treeView.selection.currentIndex); } } MenuItem { text: "Delete" + enabled: (selectedItemCount > 0) onTriggered: { - deleteFile(contextMenu.currentIndex); + deleteFile(); } } - } - } + }// End_OF( contextMenu ) + }// End_OF( treeView ) Row { id: infoRow @@ -786,8 +794,8 @@ Rectangle { function makeText() { var numPendingBakes = assetMappingsModel.numPendingBakes; - if (selectedItems > 1 || numPendingBakes === 0) { - return selectedItems + " items selected"; + if (selectedItemCount > 1 || numPendingBakes === 0) { + return selectedItemCount + " items selected"; } else { return numPendingBakes + " bakes pending" } @@ -884,7 +892,7 @@ Rectangle { "Baking compresses and optimizes files for faster network transfer and display. We recommend you bake your content to reduce initial load times for your visitors."); } } - } + }// End_OF( infoRow ) HifiControls.TabletContentSection { id: uploadSection @@ -961,7 +969,7 @@ Rectangle { } } } - } + }// End_OF( uploadSection ) } } From f344e44d26bf8d307a00593e34481bd25954bebf Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Wed, 31 Jan 2018 10:19:17 +0100 Subject: [PATCH 056/569] Switched to a simpler manual fixed/slope based shadow bias system. Automatic stuff fail most of the time --- libraries/render-utils/src/LightStage.cpp | 22 +------ libraries/render-utils/src/LightStage.h | 4 +- .../render-utils/src/RenderShadowTask.cpp | 8 +-- libraries/render-utils/src/RenderShadowTask.h | 9 ++- libraries/render-utils/src/Shadow.slh | 18 ++--- libraries/render-utils/src/ShadowCore.slh | 13 +--- libraries/render-utils/src/Shadows_shared.slh | 4 +- .../developer/utilities/render/debugShadow.js | 4 +- scripts/developer/utilities/render/shadow.qml | 65 +++++++++++++++---- 9 files changed, 78 insertions(+), 69 deletions(-) diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index e06d24f1b1..259d0dd665 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -70,18 +70,6 @@ LightStage::Shadow::Schema::Schema() { maxDistance = 20.0f; } -void LightStage::Shadow::Schema::updateCascade(int cascadeIndex, const ViewFrustum& shadowFrustum) { - auto& cascade = cascades[cascadeIndex]; - cascade.frustumPosition = shadowFrustum.getPosition(); - // The adaptative bias is computing how much depth offset we have to add to - // push back a coarsely sampled surface perpendicularly to the shadow direction, - // to not have shadow acnee. The final computation is done in the shader, based on the - // surface normal. - auto maxWorldFrustumSize = glm::max(shadowFrustum.getWidth(), shadowFrustum.getHeight()); - cascade.adaptiveBiasUnitScale = maxWorldFrustumSize * 0.5f * invMapSize; - cascade.adaptiveBiasTransformScale = shadowFrustum.getNearClip() * shadowFrustum.getFarClip() / (shadowFrustum.getFarClip() - shadowFrustum.getNearClip()); -} - LightStage::Shadow::Cascade::Cascade() : _frustum{ std::make_shared() }, _minDistance{ 0.0f }, @@ -222,17 +210,14 @@ void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum, auto position = viewFrustum.getPosition() - (nearDepth + farDepth)*lightDirection; // Update the buffer auto& schema = _schemaBuffer.edit(); - auto cascadeIndex = 0; for (auto& cascade : _cascades) { cascade._frustum->setOrientation(orientation); cascade._frustum->setPosition(position); - schema.cascades[cascadeIndex].frustumPosition = position; - cascadeIndex++; } } void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum, - float nearDepth, float farDepth, float baseBias) { + float nearDepth, float farDepth, float fixedBias, float slopeBias) { assert(nearDepth < farDepth); assert(cascadeIndex < _cascades.size()); @@ -283,8 +268,8 @@ void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, co auto& schema = _schemaBuffer.edit(); auto& schemaCascade = schema.cascades[cascadeIndex]; schemaCascade.reprojection = _biasMatrix * ortho * shadowViewInverse.getMatrix(); - schemaCascade.fixedBias = baseBias; - schema.updateCascade(cascadeIndex, *cascade._frustum); + schemaCascade.fixedBias = fixedBias; + schemaCascade.slopeBias = slopeBias; } void LightStage::Shadow::setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum) { @@ -298,7 +283,6 @@ void LightStage::Shadow::setCascadeFrustum(unsigned int cascadeIndex, const View auto& schema = _schemaBuffer.edit(); auto& schemaCascade = schema.cascades[cascadeIndex]; schemaCascade.reprojection = _biasMatrix * shadowFrustum.getProjection() * viewInverse.getMatrix(); - schema.updateCascade(cascadeIndex, shadowFrustum); } LightStage::Index LightStage::findLight(const LightPointer& light) const { diff --git a/libraries/render-utils/src/LightStage.h b/libraries/render-utils/src/LightStage.h index 922ec5eb4a..9812426fa6 100644 --- a/libraries/render-utils/src/LightStage.h +++ b/libraries/render-utils/src/LightStage.h @@ -80,7 +80,7 @@ public: void setKeylightFrustum(const ViewFrustum& viewFrustum, float nearDepth = 1.0f, float farDepth = 1000.0f); void setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum, - float nearDepth = 1.0f, float farDepth = 1000.0f, float baseBias = 0.005f); + float nearDepth = 1.0f, float farDepth = 1000.0f, float fixedBias = 0.005f, float slopeBias = 0.005f); void setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum); const UniformBufferView& getBuffer() const { return _schemaBuffer; } @@ -110,8 +110,6 @@ public: Schema(); - void updateCascade(int cascadeIndex, const ViewFrustum& shadowFrustum); - }; UniformBufferView _schemaBuffer = nullptr; }; diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 2172dda3e3..2b5ab09c06 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -256,9 +256,8 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext) { } void RenderShadowCascadeSetup::configure(const Config& configuration) { - // I'm not very proud of this empirical adjustment - auto cascadeBias = configuration.bias * powf(1.1f, _cascadeIndex); - _baseBias = cascadeBias * cascadeBias * 0.01f; + _fixedBias = configuration.fixedBias * configuration.fixedBias * configuration.fixedBias * 0.004f; + _slopeBias = configuration.slopeBias * configuration.slopeBias * configuration.slopeBias * 0.01f; } void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderContext, Outputs& output) { @@ -274,7 +273,8 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon if (globalShadow && _cascadeIndexgetCascadeCount()) { output.edit1() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); - globalShadow->setKeylightCascadeFrustum(_cascadeIndex, args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR, _baseBias); + globalShadow->setKeylightCascadeFrustum(_cascadeIndex, args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR, + _fixedBias, _slopeBias); // Set the keylight render args args->pushViewFrustum(*(globalShadow->getCascade(_cascadeIndex).getFrustum())); diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index c4f0c65bfc..7e5655b375 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -64,10 +64,12 @@ public: class RenderShadowCascadeSetupConfig : public render::Job::Config { Q_OBJECT - Q_PROPERTY(float bias MEMBER bias NOTIFY dirty) + Q_PROPERTY(float fixedBias MEMBER fixedBias NOTIFY dirty) + Q_PROPERTY(float slopeBias MEMBER slopeBias NOTIFY dirty) public: - float bias{ 0.25f }; + float fixedBias{ 0.15f }; + float slopeBias{ 0.55f }; signals: void dirty(); @@ -86,7 +88,8 @@ public: private: unsigned int _cascadeIndex; - float _baseBias{ 0.1f }; + float _fixedBias{ 0.1f }; + float _slopeBias{ 0.1f }; }; class RenderShadowCascadeTeardown { diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index c11f5fa6a7..6575e68090 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -83,19 +83,12 @@ float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, ve return shadowAttenuation; } -float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float bias, - vec3 worldPosition, vec3 worldLightDir) { +float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float oneMinusNdotL) { if (!isShadowCascadeProjectedOnPixel(shadowTexcoord)) { // If a point is not in the map, do not attenuate return 1.0; } - // After this, bias is in view units - bias *= getShadowAdaptiveBiasUnitScale(cascadeIndex); - // Transform to texcoord space (between 0 and 1) - float shadowDepth = abs(dot(worldPosition-getShadowFrustumPosition(cascadeIndex), worldLightDir)); - bias = transformShadowAdaptiveBias(cascadeIndex, shadowDepth, bias); - // Add fixed bias - bias += getShadowBias(cascadeIndex); + float bias = getShadowFixedBias(cascadeIndex) + getShadowSlopeBias(cascadeIndex) * oneMinusNdotL; return evalShadowAttenuationPCF(cascadeIndex, offsets, shadowTexcoord, bias); } @@ -106,12 +99,11 @@ float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDe float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices); // Adjust bias if we are at a grazing angle with light - float ndotl = dot(worldLightDir, worldNormal); - float bias = 1.0/(ndotl*ndotl)-1.0; + float oneMinusNdotL = 1.0 - clamp(dot(worldLightDir, worldNormal), 0, 1); vec2 cascadeAttenuations = vec2(1.0, 1.0); - cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, offsets, cascadeShadowCoords[0], bias, worldPosition.xyz, worldLightDir); + cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, offsets, cascadeShadowCoords[0], oneMinusNdotL); if (cascadeMix > 0.0 && cascadeIndices.y < getShadowCascadeCount()) { - cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, offsets, cascadeShadowCoords[1], bias, worldPosition.xyz, worldLightDir); + cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, offsets, cascadeShadowCoords[1], oneMinusNdotL); } float attenuation = mix(cascadeAttenuations.x, cascadeAttenuations.y, cascadeMix); // Falloff to max distance diff --git a/libraries/render-utils/src/ShadowCore.slh b/libraries/render-utils/src/ShadowCore.slh index 2d48e16ef4..782e2bc2b8 100644 --- a/libraries/render-utils/src/ShadowCore.slh +++ b/libraries/render-utils/src/ShadowCore.slh @@ -37,21 +37,14 @@ float getShadowScale() { return shadow.invMapSize; } -float getShadowBias(int cascadeIndex) { +float getShadowFixedBias(int cascadeIndex) { return shadow.cascades[cascadeIndex].fixedBias; } -vec3 getShadowFrustumPosition(int cascadeIndex) { - return shadow.cascades[cascadeIndex].frustumPosition; +float getShadowSlopeBias(int cascadeIndex) { + return shadow.cascades[cascadeIndex].slopeBias; } -float getShadowAdaptiveBiasUnitScale(int cascadeIndex) { - return shadow.cascades[cascadeIndex].adaptiveBiasUnitScale; -} - -float transformShadowAdaptiveBias(int cascadeIndex, float shadowDepth, float depthBias) { - return shadow.cascades[cascadeIndex].adaptiveBiasTransformScale * depthBias / (shadowDepth*shadowDepth); -} // Compute the texture coordinates from world coordinates vec4 evalShadowTexcoord(int cascadeIndex, vec4 position) { diff --git a/libraries/render-utils/src/Shadows_shared.slh b/libraries/render-utils/src/Shadows_shared.slh index def6b1b4d4..0d49fc037e 100644 --- a/libraries/render-utils/src/Shadows_shared.slh +++ b/libraries/render-utils/src/Shadows_shared.slh @@ -11,10 +11,8 @@ struct ShadowTransform { MAT4 reprojection; - VEC3 frustumPosition; float fixedBias; - float adaptiveBiasUnitScale; - float adaptiveBiasTransformScale; + float slopeBias; float _padding1; float _padding2; }; diff --git a/scripts/developer/utilities/render/debugShadow.js b/scripts/developer/utilities/render/debugShadow.js index a0d2142258..1f1d00e6b4 100644 --- a/scripts/developer/utilities/render/debugShadow.js +++ b/scripts/developer/utilities/render/debugShadow.js @@ -14,7 +14,7 @@ var qml = Script.resolvePath('shadow.qml'); var window = new OverlayWindow({ title: 'Shadow Debug', source: qml, - width: 200, - height: 90 + width: 250, + height: 300 }); window.closed.connect(function() { Script.stop(); }); \ No newline at end of file diff --git a/scripts/developer/utilities/render/shadow.qml b/scripts/developer/utilities/render/shadow.qml index 32405a5260..a077ab9f50 100644 --- a/scripts/developer/utilities/render/shadow.qml +++ b/scripts/developer/utilities/render/shadow.qml @@ -36,6 +36,14 @@ Column { shadow1Config.enabled = false; shadow2Config.enabled = false; shadow3Config.enabled = false; + shadow0Config.isFrozen = false; + shadow1Config.isFrozen = false; + shadow2Config.isFrozen = false; + shadow3Config.isFrozen = false; + shadow0BoundConfig.isFrozen = false; + shadow1BoundConfig.isFrozen = false; + shadow2BoundConfig.isFrozen = false; + shadow3BoundConfig.isFrozen = false; } CheckBox { @@ -72,35 +80,68 @@ Column { } } ConfigSlider { - label: qsTr("Cascade 0 bias") + label: qsTr("Cascade 0 fixed bias") integral: false config: Render.getConfig("RenderMainView.ShadowCascadeSetup0") - property: "bias" + property: "fixedBias" max: 1.0 - min: 0.01 + min: 0.0 } ConfigSlider { - label: qsTr("Cascade 1 bias") + label: qsTr("Cascade 1 fixed bias") integral: false config: Render.getConfig("RenderMainView.ShadowCascadeSetup1") - property: "bias" + property: "fixedBias" max: 1.0 - min: 0.01 + min: 0.0 } ConfigSlider { - label: qsTr("Cascade 2 bias") + label: qsTr("Cascade 2 fixed bias") integral: false config: Render.getConfig("RenderMainView.ShadowCascadeSetup2") - property: "bias" + property: "fixedBias" max: 1.0 - min: 0.01 + min: 0.0 } ConfigSlider { - label: qsTr("Cascade 3 bias") + label: qsTr("Cascade 3 fixed bias") integral: false config: Render.getConfig("RenderMainView.ShadowCascadeSetup3") - property: "bias" + property: "fixedBias" max: 1.0 - min: 0.01 + min: 0.0 + } + + ConfigSlider { + label: qsTr("Cascade 0 slope bias") + integral: false + config: Render.getConfig("RenderMainView.ShadowCascadeSetup0") + property: "slopeBias" + max: 1.0 + min: 0.0 + } + ConfigSlider { + label: qsTr("Cascade 1 slope bias") + integral: false + config: Render.getConfig("RenderMainView.ShadowCascadeSetup1") + property: "slopeBias" + max: 1.0 + min: 0.0 + } + ConfigSlider { + label: qsTr("Cascade 2 slope bias") + integral: false + config: Render.getConfig("RenderMainView.ShadowCascadeSetup2") + property: "slopeBias" + max: 1.0 + min: 0.0 + } + ConfigSlider { + label: qsTr("Cascade 3 slope bias") + integral: false + config: Render.getConfig("RenderMainView.ShadowCascadeSetup3") + property: "slopeBias" + max: 1.0 + min: 0.0 } } From 3fa2babec2ea97178ae2030982459f6b511366b7 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Wed, 31 Jan 2018 11:55:46 +0100 Subject: [PATCH 057/569] Moved cascade frustum pre-computation to single ShadowSetup job --- .../render-utils/src/RenderShadowTask.cpp | 75 ++++++++++++++++--- libraries/render-utils/src/RenderShadowTask.h | 61 ++++++++++----- scripts/developer/utilities/render/shadow.qml | 41 +++++----- 3 files changed, 128 insertions(+), 49 deletions(-) diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 2b5ab09c06..b83911582c 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -213,7 +213,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende initZPassPipelines(*shapePlumber, state); } - task.addJob("ShadowSetup"); + const auto coarseFrustum = task.addJob("ShadowSetup"); for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { char jobName[64]; @@ -243,7 +243,31 @@ void RenderShadowTask::configure(const Config& configuration) { // Task::configure(configuration); } -void RenderShadowSetup::run(const render::RenderContextPointer& renderContext) { +RenderShadowSetup::RenderShadowSetup() : + _coarseShadowFrustum{ std::make_shared() } { + +} + +void RenderShadowSetup::configure(const Config& configuration) { + setConstantBias(0, configuration.constantBias0); + setConstantBias(1, configuration.constantBias1); + setConstantBias(2, configuration.constantBias2); + setConstantBias(3, configuration.constantBias3); + setSlopeBias(0, configuration.slopeBias0); + setSlopeBias(1, configuration.slopeBias1); + setSlopeBias(2, configuration.slopeBias2); + setSlopeBias(3, configuration.slopeBias3); +} + +void RenderShadowSetup::setConstantBias(int cascadeIndex, float value) { + _bias[cascadeIndex]._constant = value * value * value * 0.004f; +} + +void RenderShadowSetup::setSlopeBias(int cascadeIndex, float value) { + _bias[cascadeIndex]._slope = value * value * value * 0.01f; +} + +void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, Output& output) { auto lightStage = renderContext->_scene->getStage(); assert(lightStage); // Cache old render args @@ -252,12 +276,46 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext) { const auto globalShadow = lightStage->getCurrentKeyShadow(); if (globalShadow) { globalShadow->setKeylightFrustum(args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR); - } -} + auto firstCascadeFrustum = globalShadow->getCascade(0).getFrustum(); + unsigned int cascadeIndex; + _coarseShadowFrustum->setPosition(firstCascadeFrustum->getPosition()); + _coarseShadowFrustum->setOrientation(firstCascadeFrustum->getOrientation()); -void RenderShadowCascadeSetup::configure(const Config& configuration) { - _fixedBias = configuration.fixedBias * configuration.fixedBias * configuration.fixedBias * 0.004f; - _slopeBias = configuration.slopeBias * configuration.slopeBias * configuration.slopeBias * 0.01f; + // Adjust each cascade frustum + for (cascadeIndex = 0; cascadeIndex < globalShadow->getCascadeCount(); ++cascadeIndex) { + auto& bias = _bias[cascadeIndex]; + globalShadow->setKeylightCascadeFrustum(cascadeIndex, args->getViewFrustum(), + SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR, + bias._constant, bias._slope); + } + // Now adjust coarse frustum bounds + auto left = glm::dot(firstCascadeFrustum->getFarTopLeft(), firstCascadeFrustum->getRight()); + auto right = glm::dot(firstCascadeFrustum->getFarTopRight(), firstCascadeFrustum->getRight()); + auto top = glm::dot(firstCascadeFrustum->getFarTopLeft(), firstCascadeFrustum->getUp()); + auto bottom = glm::dot(firstCascadeFrustum->getFarBottomRight(), firstCascadeFrustum->getUp()); + auto near = firstCascadeFrustum->getNearClip(); + auto far = firstCascadeFrustum->getFarClip(); + for (cascadeIndex = 1; cascadeIndex < globalShadow->getCascadeCount(); ++cascadeIndex) { + auto cascadeLeft = glm::dot(firstCascadeFrustum->getFarTopLeft(), firstCascadeFrustum->getRight()); + auto cascadeRight = glm::dot(firstCascadeFrustum->getFarTopRight(), firstCascadeFrustum->getRight()); + auto cascadeTop = glm::dot(firstCascadeFrustum->getFarTopLeft(), firstCascadeFrustum->getUp()); + auto cascadeBottom = glm::dot(firstCascadeFrustum->getFarBottomRight(), firstCascadeFrustum->getUp()); + auto cascadeNear = firstCascadeFrustum->getNearClip(); + auto cascadeFar = firstCascadeFrustum->getFarClip(); + left = glm::min(left, cascadeLeft); + right = glm::max(right, cascadeRight); + bottom = glm::min(bottom, cascadeBottom); + top = glm::max(top, cascadeTop); + near = glm::min(near, cascadeNear); + far = glm::max(far, cascadeFar); + } + _coarseShadowFrustum->setProjection(glm::ortho(left, right, bottom, top, near, far)); + _coarseShadowFrustum->calculate(); + + output = _coarseShadowFrustum; + } else { + output = nullptr; + } } void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderContext, Outputs& output) { @@ -273,9 +331,6 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon if (globalShadow && _cascadeIndexgetCascadeCount()) { output.edit1() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); - globalShadow->setKeylightCascadeFrustum(_cascadeIndex, args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR, - _fixedBias, _slopeBias); - // Set the keylight render args args->pushViewFrustum(*(globalShadow->getCascade(_cascadeIndex).getFrustum())); args->_renderMode = RenderArgs::SHADOW_RENDER_MODE; diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index 7e5655b375..f488c46b8e 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -17,6 +17,8 @@ #include +#include "Shadows_shared.slh" + class ViewFrustum; class RenderShadowMap { @@ -53,43 +55,64 @@ public: void configure(const Config& configuration); }; -class RenderShadowSetup { -public: - using JobModel = render::Job::Model; - - RenderShadowSetup() {} - void run(const render::RenderContextPointer& renderContext); - -}; - -class RenderShadowCascadeSetupConfig : public render::Job::Config { +class RenderShadowSetupConfig : public render::Job::Config { Q_OBJECT - Q_PROPERTY(float fixedBias MEMBER fixedBias NOTIFY dirty) - Q_PROPERTY(float slopeBias MEMBER slopeBias NOTIFY dirty) + Q_PROPERTY(float constantBias0 MEMBER constantBias0 NOTIFY dirty) + Q_PROPERTY(float constantBias1 MEMBER constantBias1 NOTIFY dirty) + Q_PROPERTY(float constantBias2 MEMBER constantBias2 NOTIFY dirty) + Q_PROPERTY(float constantBias3 MEMBER constantBias3 NOTIFY dirty) + Q_PROPERTY(float slopeBias0 MEMBER slopeBias0 NOTIFY dirty) + Q_PROPERTY(float slopeBias1 MEMBER slopeBias1 NOTIFY dirty) + Q_PROPERTY(float slopeBias2 MEMBER slopeBias2 NOTIFY dirty) + Q_PROPERTY(float slopeBias3 MEMBER slopeBias3 NOTIFY dirty) public: - float fixedBias{ 0.15f }; - float slopeBias{ 0.55f }; + float constantBias0{ 0.15f }; + float constantBias1{ 0.15f }; + float constantBias2{ 0.15f }; + float constantBias3{ 0.15f }; + float slopeBias0{ 0.55f }; + float slopeBias1{ 0.55f }; + float slopeBias2{ 0.55f }; + float slopeBias3{ 0.55f }; signals: void dirty(); }; +class RenderShadowSetup { +public: + using Output = ViewFrustumPointer; + using Config = RenderShadowSetupConfig; + using JobModel = render::Job::ModelO; + + RenderShadowSetup(); + void configure(const Config& configuration); + void run(const render::RenderContextPointer& renderContext, Output& output); + +private: + + ViewFrustumPointer _coarseShadowFrustum; + struct { + float _constant; + float _slope; + } _bias[SHADOW_CASCADE_MAX_COUNT]; + + void setConstantBias(int cascadeIndex, float value); + void setSlopeBias(int cascadeIndex, float value); +}; + class RenderShadowCascadeSetup { public: using Outputs = render::VaryingSet3; - using Config = RenderShadowCascadeSetupConfig; - using JobModel = render::Job::ModelO; + using JobModel = render::Job::ModelO; RenderShadowCascadeSetup(unsigned int cascadeIndex) : _cascadeIndex{ cascadeIndex } {} - void configure(const Config& configuration); void run(const render::RenderContextPointer& renderContext, Outputs& output); private: unsigned int _cascadeIndex; - float _fixedBias{ 0.1f }; - float _slopeBias{ 0.1f }; }; class RenderShadowCascadeTeardown { diff --git a/scripts/developer/utilities/render/shadow.qml b/scripts/developer/utilities/render/shadow.qml index a077ab9f50..3400dcd847 100644 --- a/scripts/developer/utilities/render/shadow.qml +++ b/scripts/developer/utilities/render/shadow.qml @@ -18,6 +18,7 @@ Column { id: root spacing: 8 property var viewConfig: Render.getConfig("RenderMainView.DrawViewFrustum"); + property var shadowConfig : Render.getConfig("RenderMainView.ShadowSetup"); property var shadow0Config: Render.getConfig("RenderMainView.DrawShadowFrustum0"); property var shadow1Config: Render.getConfig("RenderMainView.DrawShadowFrustum1"); property var shadow2Config: Render.getConfig("RenderMainView.DrawShadowFrustum2"); @@ -80,34 +81,34 @@ Column { } } ConfigSlider { - label: qsTr("Cascade 0 fixed bias") + label: qsTr("Cascade 0 constant bias") integral: false - config: Render.getConfig("RenderMainView.ShadowCascadeSetup0") - property: "fixedBias" + config: shadowConfig + property: "constantBias0" max: 1.0 min: 0.0 } ConfigSlider { - label: qsTr("Cascade 1 fixed bias") + label: qsTr("Cascade 1 constant bias") integral: false - config: Render.getConfig("RenderMainView.ShadowCascadeSetup1") - property: "fixedBias" + config: shadowConfig + property: "constantBias1" max: 1.0 min: 0.0 } ConfigSlider { - label: qsTr("Cascade 2 fixed bias") + label: qsTr("Cascade 2 constant bias") integral: false - config: Render.getConfig("RenderMainView.ShadowCascadeSetup2") - property: "fixedBias" + config: shadowConfig + property: "constantBias2" max: 1.0 min: 0.0 } ConfigSlider { - label: qsTr("Cascade 3 fixed bias") + label: qsTr("Cascade 3 constant bias") integral: false - config: Render.getConfig("RenderMainView.ShadowCascadeSetup3") - property: "fixedBias" + config: shadowConfig + property: "constantBias3" max: 1.0 min: 0.0 } @@ -115,32 +116,32 @@ Column { ConfigSlider { label: qsTr("Cascade 0 slope bias") integral: false - config: Render.getConfig("RenderMainView.ShadowCascadeSetup0") - property: "slopeBias" + config: shadowConfig + property: "slopeBias0" max: 1.0 min: 0.0 } ConfigSlider { label: qsTr("Cascade 1 slope bias") integral: false - config: Render.getConfig("RenderMainView.ShadowCascadeSetup1") - property: "slopeBias" + config: shadowConfig + property: "slopeBias1" max: 1.0 min: 0.0 } ConfigSlider { label: qsTr("Cascade 2 slope bias") integral: false - config: Render.getConfig("RenderMainView.ShadowCascadeSetup2") - property: "slopeBias" + config: shadowConfig + property: "slopeBias2" max: 1.0 min: 0.0 } ConfigSlider { label: qsTr("Cascade 3 slope bias") integral: false - config: Render.getConfig("RenderMainView.ShadowCascadeSetup3") - property: "slopeBias" + config: shadowConfig + property: "slopeBias3" max: 1.0 min: 0.0 } From d422545c78a999041f956e02a5d2e74203e79493 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Wed, 31 Jan 2018 17:13:06 +0100 Subject: [PATCH 058/569] Changed shadow task to do a single octree query as well as pipeline/depth sort for all cascades. Still issue with disapearing objects from shadow map with viewpoint --- .../render-utils/src/RenderShadowTask.cpp | 105 +++++---- libraries/render-utils/src/RenderShadowTask.h | 17 +- libraries/render-utils/src/RenderViewTask.cpp | 9 +- libraries/render/src/render/CullTask.cpp | 203 ++++++++++++++---- libraries/render/src/render/CullTask.h | 40 +++- .../src/render/RenderFetchCullSortTask.cpp | 6 +- 6 files changed, 273 insertions(+), 107 deletions(-) diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index b83911582c..c9df67dad1 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -213,28 +213,34 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende initZPassPipelines(*shapePlumber, state); } - const auto coarseFrustum = task.addJob("ShadowSetup"); + const auto setupOutput = task.addJob("ShadowSetup"); + // Fetch and cull the items from the scene + static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); + const auto fetchInput = render::Varying(shadowCasterFilter); + const auto shadowSelection = task.addJob("FetchShadowTree", fetchInput); + const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterFilter).asVarying(); + const auto shadowItems = task.addJob("FetchShadowSelection", selectionInputs); + + // Sort + const auto sortedPipelines = task.addJob("PipelineSortShadow", shadowItems); + const auto sortedShapes = task.addJob("DepthSortShadow", sortedPipelines, true); for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { char jobName[64]; sprintf(jobName, "ShadowCascadeSetup%d", i); - const auto setupOutput = task.addJob(jobName, i); - const auto shadowFilter = setupOutput.getN(1); + const auto shadowFilter = task.addJob(jobName, i); - // CPU jobs: - // Fetch and cull the items from the scene - const auto shadowSelection = task.addJob("FetchShadowSelection", shadowFilter); - const auto cullInputs = CullSpatialSelection::Inputs(shadowSelection, shadowFilter).asVarying(); - const auto culledShadowSelection = task.addJob("CullShadowSelection", cullInputs, cullFunctor, RenderDetails::SHADOW); - - // Sort - const auto sortedPipelines = task.addJob("PipelineSortShadowSort", culledShadowSelection); - const auto sortedShapesAndBounds = task.addJob("DepthSortShadowMap", sortedPipelines, true); + // CPU jobs: finer grained culling + const auto cullInputs = CullShapeBounds::Inputs(sortedShapes, shadowFilter).asVarying(); + const auto culledShadowItemsAndBounds = task.addJob("CullShadowCascade", cullInputs, cullFunctor, RenderDetails::SHADOW); // GPU jobs: Render to shadow map - task.addJob("RenderShadowMap", sortedShapesAndBounds, shapePlumber, i); - task.addJob("ShadowCascadeTeardown", setupOutput); + sprintf(jobName, "RenderShadowMap%d", i); + task.addJob(jobName, culledShadowItemsAndBounds, shapePlumber, i); + task.addJob("ShadowCascadeTeardown", shadowFilter); } + + task.addJob("ShadowTeardown", setupOutput); } void RenderShadowTask::configure(const Config& configuration) { @@ -267,16 +273,19 @@ void RenderShadowSetup::setSlopeBias(int cascadeIndex, float value) { _bias[cascadeIndex]._slope = value * value * value * 0.01f; } -void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, Output& output) { +void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, Outputs& output) { auto lightStage = renderContext->_scene->getStage(); assert(lightStage); // Cache old render args RenderArgs* args = renderContext->args; + output.edit0() = args->_renderMode; + output.edit1() = args->_sizeScale; + const auto globalShadow = lightStage->getCurrentKeyShadow(); if (globalShadow) { globalShadow->setKeylightFrustum(args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR); - auto firstCascadeFrustum = globalShadow->getCascade(0).getFrustum(); + auto& firstCascadeFrustum = globalShadow->getCascade(0).getFrustum(); unsigned int cascadeIndex; _coarseShadowFrustum->setPosition(firstCascadeFrustum->getPosition()); _coarseShadowFrustum->setOrientation(firstCascadeFrustum->getOrientation()); @@ -296,12 +305,13 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O auto near = firstCascadeFrustum->getNearClip(); auto far = firstCascadeFrustum->getFarClip(); for (cascadeIndex = 1; cascadeIndex < globalShadow->getCascadeCount(); ++cascadeIndex) { - auto cascadeLeft = glm::dot(firstCascadeFrustum->getFarTopLeft(), firstCascadeFrustum->getRight()); - auto cascadeRight = glm::dot(firstCascadeFrustum->getFarTopRight(), firstCascadeFrustum->getRight()); - auto cascadeTop = glm::dot(firstCascadeFrustum->getFarTopLeft(), firstCascadeFrustum->getUp()); - auto cascadeBottom = glm::dot(firstCascadeFrustum->getFarBottomRight(), firstCascadeFrustum->getUp()); - auto cascadeNear = firstCascadeFrustum->getNearClip(); - auto cascadeFar = firstCascadeFrustum->getFarClip(); + auto& cascadeFrustum = globalShadow->getCascade(cascadeIndex).getFrustum(); + auto cascadeLeft = glm::dot(cascadeFrustum->getFarTopLeft(), cascadeFrustum->getRight()); + auto cascadeRight = glm::dot(cascadeFrustum->getFarTopRight(), cascadeFrustum->getRight()); + auto cascadeTop = glm::dot(cascadeFrustum->getFarTopLeft(), cascadeFrustum->getUp()); + auto cascadeBottom = glm::dot(cascadeFrustum->getFarBottomRight(), cascadeFrustum->getUp()); + auto cascadeNear = cascadeFrustum->getNearClip(); + auto cascadeFar = cascadeFrustum->getFarClip(); left = glm::min(left, cascadeLeft); right = glm::max(right, cascadeRight); bottom = glm::min(bottom, cascadeBottom); @@ -312,9 +322,14 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O _coarseShadowFrustum->setProjection(glm::ortho(left, right, bottom, top, near, far)); _coarseShadowFrustum->calculate(); - output = _coarseShadowFrustum; - } else { - output = nullptr; + // Push frustum for further culling and selection + args->pushViewFrustum(*_coarseShadowFrustum); + + args->_renderMode = RenderArgs::SHADOW_RENDER_MODE; + if (lightStage->getCurrentKeyLight()->getType() == graphics::Light::SUN) { + // Set to ridiculously high amount to prevent solid angle culling in octree selection + args->_sizeScale = 1e16f; + } } } @@ -324,37 +339,41 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon // Cache old render args RenderArgs* args = renderContext->args; - output.edit0() = args->_renderMode; - output.edit2() = args->_sizeScale; - const auto globalShadow = lightStage->getCurrentKeyShadow(); if (globalShadow && _cascadeIndexgetCascadeCount()) { - output.edit1() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); + output = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); // Set the keylight render args - args->pushViewFrustum(*(globalShadow->getCascade(_cascadeIndex).getFrustum())); - args->_renderMode = RenderArgs::SHADOW_RENDER_MODE; - if (lightStage->getCurrentKeyLight()->getType() == graphics::Light::SUN) { - const float shadowSizeScale = 1e16f; - // Set the size scale to a ridiculously high value to prevent small object culling which assumes - // the view frustum is a perspective projection. But this isn't the case for the sun which - // is an orthographic projection. - args->_sizeScale = shadowSizeScale; - } - + auto& cascade = globalShadow->getCascade(_cascadeIndex); + auto& cascadeFrustum = cascade.getFrustum(); + args->pushViewFrustum(*cascadeFrustum); + // Set the cull threshold to 2 shadow texels. + auto texelSize = glm::max(cascadeFrustum->getHeight(), cascadeFrustum->getWidth()) / cascade.framebuffer->getSize().x; + texelSize *= 2.0f; + // SizeScale is used in the shadow cull function defined ine RenderViewTask + args->_sizeScale = texelSize * texelSize; } else { - output.edit1() = ItemFilter::Builder::nothing(); + output = ItemFilter::Builder::nothing(); } } void RenderShadowCascadeTeardown::run(const render::RenderContextPointer& renderContext, const Input& input) { RenderArgs* args = renderContext->args; - if (args->_renderMode == RenderArgs::SHADOW_RENDER_MODE && !input.get1().selectsNothing()) { + if (args->_renderMode == RenderArgs::SHADOW_RENDER_MODE && !input.selectsNothing()) { + args->popViewFrustum(); + } + assert(args->hasViewFrustum()); +} + +void RenderShadowTeardown::run(const render::RenderContextPointer& renderContext, const Input& input) { + RenderArgs* args = renderContext->args; + + if (args->_renderMode == RenderArgs::SHADOW_RENDER_MODE) { args->popViewFrustum(); } assert(args->hasViewFrustum()); // Reset the render args args->_renderMode = input.get0(); - args->_sizeScale = input.get2(); -}; + args->_sizeScale = input.get1(); +} diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index f488c46b8e..1736d07fd5 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -82,13 +82,13 @@ signals: class RenderShadowSetup { public: - using Output = ViewFrustumPointer; + using Outputs = render::VaryingSet2; using Config = RenderShadowSetupConfig; - using JobModel = render::Job::ModelO; + using JobModel = render::Job::ModelO; RenderShadowSetup(); void configure(const Config& configuration); - void run(const render::RenderContextPointer& renderContext, Output& output); + void run(const render::RenderContextPointer& renderContext, Outputs& output); private: @@ -104,7 +104,7 @@ private: class RenderShadowCascadeSetup { public: - using Outputs = render::VaryingSet3; + using Outputs = render::ItemFilter; using JobModel = render::Job::ModelO; RenderShadowCascadeSetup(unsigned int cascadeIndex) : _cascadeIndex{ cascadeIndex } {} @@ -117,9 +117,16 @@ private: class RenderShadowCascadeTeardown { public: - using Input = RenderShadowCascadeSetup::Outputs; + using Input = render::ItemFilter; using JobModel = render::Job::ModelI; void run(const render::RenderContextPointer& renderContext, const Input& input); }; +class RenderShadowTeardown { +public: + using Input = RenderShadowSetup::Outputs; + using JobModel = render::Job::ModelI; + void run(const render::RenderContextPointer& renderContext, const Input& input); +}; + #endif // hifi_RenderShadowTask_h diff --git a/libraries/render-utils/src/RenderViewTask.cpp b/libraries/render-utils/src/RenderViewTask.cpp index dc6c66e058..c2e43582cd 100644 --- a/libraries/render-utils/src/RenderViewTask.cpp +++ b/libraries/render-utils/src/RenderViewTask.cpp @@ -21,13 +21,8 @@ void RenderViewTask::build(JobModel& task, const render::Varying& input, render: // but the cullFunctor passed is probably tailored for perspective projection and culls too much. task.addJob("RenderShadowTask", [](const RenderArgs* args, const AABox& bounds) { // Cull only objects that are too small relatively to shadow frustum - auto& frustum = args->getViewFrustum(); - auto frustumSize = std::max(frustum.getHeight(), frustum.getWidth()); - const auto boundsRadius = bounds.getDimensions().length(); - const auto relativeBoundRadius = boundsRadius / frustumSize; - const auto threshold = 1e-3f; - return relativeBoundRadius > threshold; - return true; + const auto boundsSquareRadius = glm::dot(bounds.getDimensions(), bounds.getDimensions()); + return boundsSquareRadius > args->_sizeScale; }); const auto items = task.addJob("FetchCullSort", cullFunctor); diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index 70331cdb47..c6ff224560 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -19,6 +19,50 @@ using namespace render; +// Culling Frustum / solidAngle test helper class +struct Test { + CullFunctor _functor; + RenderArgs* _args; + RenderDetails::Item& _renderDetails; + glm::vec3 _eyePos; + float _squareTanAlpha; + + Test(CullFunctor& functor, RenderArgs* pargs, RenderDetails::Item& renderDetails) : + _functor(functor), + _args(pargs), + _renderDetails(renderDetails) { + // FIXME: Keep this code here even though we don't use it yet + /*_eyePos = _args->getViewFrustum().getPosition(); + float a = glm::degrees(Octree::getAccuracyAngle(_args->_sizeScale, _args->_boundaryLevelAdjust)); + auto angle = std::min(glm::radians(45.0f), a); // no worse than 45 degrees + angle = std::max(glm::radians(1.0f / 60.0f), a); // no better than 1 minute of degree + auto tanAlpha = tan(angle); + _squareTanAlpha = (float)(tanAlpha * tanAlpha); + */ + } + + bool frustumTest(const AABox& bound) { + if (!_args->getViewFrustum().boxIntersectsFrustum(bound)) { + _renderDetails._outOfView++; + return false; + } + return true; + } + + bool solidAngleTest(const AABox& bound) { + // FIXME: Keep this code here even though we don't use it yet + //auto eyeToPoint = bound.calcCenter() - _eyePos; + //auto boundSize = bound.getDimensions(); + //float test = (glm::dot(boundSize, boundSize) / glm::dot(eyeToPoint, eyeToPoint)) - squareTanAlpha; + //if (test < 0.0f) { + if (!_functor(_args, bound)) { + _renderDetails._tooSmall++; + return false; + } + return true; + } +}; + void render::cullItems(const RenderContextPointer& renderContext, const CullFunctor& cullFunctor, RenderDetails::Item& details, const ItemBounds& inItems, ItemBounds& outItems) { assert(renderContext->args); @@ -82,18 +126,20 @@ void FetchSpatialTree::configure(const Config& config) { _lodAngle = config.lodAngle; } -void FetchSpatialTree::run(const RenderContextPointer& renderContext, const ItemFilter& filter, ItemSpatialTree::ItemSelection& outSelection) { +void FetchSpatialTree::run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemSpatialTree::ItemSelection& outSelection) { // start fresh outSelection.clear(); + auto& filter = inputs; + if (!filter.selectsNothing()) { assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; auto& scene = renderContext->_scene; - // Eventually use a frozen frustum auto queryFrustum = args->getViewFrustum(); + // Eventually use a frozen frustum if (_freezeFrustum) { if (_justFrozeFrustum) { _justFrozeFrustum = false; @@ -134,50 +180,6 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, args->pushViewFrustum(_frozenFrustum); // replace the true view frustum by the frozen one } - // Culling Frustum / solidAngle test helper class - struct Test { - CullFunctor _functor; - RenderArgs* _args; - RenderDetails::Item& _renderDetails; - glm::vec3 _eyePos; - float _squareTanAlpha; - - Test(CullFunctor& functor, RenderArgs* pargs, RenderDetails::Item& renderDetails) : - _functor(functor), - _args(pargs), - _renderDetails(renderDetails) - { - // FIXME: Keep this code here even though we don't use it yet - /*_eyePos = _args->getViewFrustum().getPosition(); - float a = glm::degrees(Octree::getAccuracyAngle(_args->_sizeScale, _args->_boundaryLevelAdjust)); - auto angle = std::min(glm::radians(45.0f), a); // no worse than 45 degrees - angle = std::max(glm::radians(1.0f / 60.0f), a); // no better than 1 minute of degree - auto tanAlpha = tan(angle); - _squareTanAlpha = (float)(tanAlpha * tanAlpha); - */ - } - - bool frustumTest(const AABox& bound) { - if (!_args->getViewFrustum().boxIntersectsFrustum(bound)) { - _renderDetails._outOfView++; - return false; - } - return true; - } - - bool solidAngleTest(const AABox& bound) { - // FIXME: Keep this code here even though we don't use it yet - //auto eyeToPoint = bound.calcCenter() - _eyePos; - //auto boundSize = bound.getDimensions(); - //float test = (glm::dot(boundSize, boundSize) / glm::dot(eyeToPoint, eyeToPoint)) - squareTanAlpha; - //if (test < 0.0f) { - if (!_functor(_args, bound)) { - _renderDetails._tooSmall++; - return false; - } - return true; - } - }; Test test(_cullFunctor, args, details); // Now we have a selection of items to render @@ -309,3 +311,112 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, std::static_pointer_cast(renderContext->jobConfig)->numItems = (int)outItems.size(); } + +void CullShapeBounds::run(const RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + RenderArgs* args = renderContext->args; + + const auto& inShapes = inputs.get0(); + const auto& filter = inputs.get1(); + auto& outShapes = outputs.edit0(); + auto& outBounds = outputs.edit1(); + + outShapes.clear(); + outBounds = AABox(); + + if (!filter.selectsNothing()) { + auto& details = args->_details.edit(_detailType); + Test test(_cullFunctor, args, details); + + for (auto& inItems : inShapes) { + auto key = inItems.first; + auto outItems = outShapes.find(key); + if (outItems == outShapes.end()) { + outItems = outShapes.insert(std::make_pair(key, ItemBounds{})).first; + outItems->second.reserve(inItems.second.size()); + } + + details._considered += (int)inItems.second.size(); + + for (auto& item : inItems.second) { + if (test.frustumTest(item.bound) && test.solidAngleTest(item.bound)) { + outItems->second.emplace_back(item); + outBounds += item.bound; + } + } + + details._rendered += (int)outItems->second.size(); + } + + for (auto& items : outShapes) { + items.second.shrink_to_fit(); + } + } +} + +void FetchSpatialSelection::run(const RenderContextPointer& renderContext, + const Inputs& inputs, ItemBounds& outItems) { + assert(renderContext->args); + RenderArgs* args = renderContext->args; + auto& scene = renderContext->_scene; + auto& inSelection = inputs.get0(); + + // Now we have a selection of items to render + outItems.clear(); + outItems.reserve(inSelection.numItems()); + + const auto filter = inputs.get1(); + if (!filter.selectsNothing()) { + // Now get the bound, and + // filter individually against the _filter + + // inside & fit items: filter only, culling is disabled + { + PerformanceTimer perfTimer("insideFitItems"); + for (auto id : inSelection.insideItems) { + auto& item = scene->getItem(id); + if (filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + outItems.emplace_back(itemBound); + } + } + } + + // inside & subcell items: filter only, culling is disabled + { + PerformanceTimer perfTimer("insideSmallItems"); + for (auto id : inSelection.insideSubcellItems) { + auto& item = scene->getItem(id); + if (filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + outItems.emplace_back(itemBound); + } + } + } + + // partial & fit items: filter only, culling is disabled + { + PerformanceTimer perfTimer("partialFitItems"); + for (auto id : inSelection.partialItems) { + auto& item = scene->getItem(id); + if (filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + outItems.emplace_back(itemBound); + } + } + } + + // partial & subcell items: filter only, culling is disabled + { + PerformanceTimer perfTimer("partialSmallItems"); + for (auto id : inSelection.partialSubcellItems) { + auto& item = scene->getItem(id); + if (filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + outItems.emplace_back(itemBound); + } + } + } + } +} diff --git a/libraries/render/src/render/CullTask.h b/libraries/render/src/render/CullTask.h index 486c4f4cdf..a140a86aee 100644 --- a/libraries/render/src/render/CullTask.h +++ b/libraries/render/src/render/CullTask.h @@ -55,12 +55,13 @@ namespace render { float _lodAngle; public: using Config = FetchSpatialTreeConfig; - using JobModel = Job::ModelIO; + using Inputs = ItemFilter; + using JobModel = Job::ModelIO; FetchSpatialTree() {} void configure(const Config& config); - void run(const RenderContextPointer& renderContext, const ItemFilter& filter, ItemSpatialTree::ItemSelection& outSelection); + void run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemSpatialTree::ItemSelection& outSelection); }; class CullSpatialSelectionConfig : public Job::Config { @@ -96,7 +97,8 @@ namespace render { _detailType(type) {} CullSpatialSelection(CullFunctor cullFunctor) : - _cullFunctor{ cullFunctor } {} + _cullFunctor{ cullFunctor } { + } CullFunctor _cullFunctor; RenderDetails::Type _detailType{ RenderDetails::OTHER }; @@ -105,6 +107,38 @@ namespace render { void run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems); }; + class CullShapeBounds { + public: + using Inputs = render::VaryingSet2; + using Outputs = render::VaryingSet2; + using JobModel = Job::ModelIO; + + CullShapeBounds(CullFunctor cullFunctor, RenderDetails::Type type) : + _cullFunctor{ cullFunctor }, + _detailType(type) {} + + CullShapeBounds(CullFunctor cullFunctor) : + _cullFunctor{ cullFunctor } { + } + + void run(const RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); + + private: + + CullFunctor _cullFunctor; + RenderDetails::Type _detailType{ RenderDetails::OTHER }; + + }; + + class FetchSpatialSelection { + public: + using Inputs = render::VaryingSet2; + using JobModel = Job::ModelIO; + + FetchSpatialSelection() {} + void run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems); + }; + } #endif // hifi_render_CullTask_h; \ No newline at end of file diff --git a/libraries/render/src/render/RenderFetchCullSortTask.cpp b/libraries/render/src/render/RenderFetchCullSortTask.cpp index d7294fa2bd..a1b4f079e7 100644 --- a/libraries/render/src/render/RenderFetchCullSortTask.cpp +++ b/libraries/render/src/render/RenderFetchCullSortTask.cpp @@ -23,9 +23,9 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin // CPU jobs: // Fetch and cull the items from the scene const ItemFilter filter = ItemFilter::Builder::visibleWorldItems().withoutLayered(); - const auto spatialFilter = render::Varying(filter); - const auto spatialSelection = task.addJob("FetchSceneSelection", spatialFilter); - const auto cullInputs = CullSpatialSelection::Inputs(spatialSelection, spatialFilter).asVarying(); + const auto fetchInput = render::Varying(filter); + const auto spatialSelection = task.addJob("FetchSceneSelection", fetchInput); + const auto cullInputs = CullSpatialSelection::Inputs(spatialSelection, render::Varying(filter)).asVarying(); const auto culledSpatialSelection = task.addJob("CullSceneSelection", cullInputs, cullFunctor, RenderDetails::ITEM); // Overlays are not culled From c01790bd3e2deefe8789bfeceebff1182a0da76f Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 31 Jan 2018 18:27:15 -0800 Subject: [PATCH 059/569] punch list updates and various fixes --- .../system/libraries/entitySelectionTool.js | 342 ++++++++++++------ 1 file changed, 231 insertions(+), 111 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index fbda9e9eba..87dc089c1a 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -94,6 +94,7 @@ SelectionManager = (function() { for (var i = 0; i < entityIDs.length; i++) { var entityID = entityIDs[i]; that.selections.push(entityID); + //Selection.addToSelectedItemsList("contextOverlayHighlightList", "entity", entityID); } that._update(true); @@ -110,8 +111,10 @@ SelectionManager = (function() { } if (idx === -1) { that.selections.push(entityID); + //Selection.addToSelectedItemsList("contextOverlayHighlightList", "entity", entityID); } else if (toggleSelection) { that.selections.splice(idx, 1); + //Selection.removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID); } } @@ -122,6 +125,7 @@ SelectionManager = (function() { var idx = that.selections.indexOf(entityID); if (idx >= 0) { that.selections.splice(idx, 1); + //Selection.removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID); } that._update(true); }; @@ -145,9 +149,9 @@ SelectionManager = (function() { that.localPosition = properties.position; that.localRotation = properties.rotation; that.localRegistrationPoint = properties.registrationPoint; - that.worldDimensions = properties.dimensions; // properties.boundingbox.dimensions; - that.worldPosition = properties.position; - that.worldRotation = properties.rotation; + that.worldDimensions = properties.boundingBox.dimensions; + that.worldPosition = properties.boundingBox.center; + that.worldRotation = properties.boundingBox.rotation; SelectionDisplay.setSpaceMode(SPACE_LOCAL); } else { that.localRotation = null; @@ -225,39 +229,43 @@ function getRelativeCenterPosition(dimensions, registrationPoint) { SelectionDisplay = (function() { var that = {}; - var COLOR_GREEN = { red:0, green:255, blue:0 }; - var COLOR_BLUE = { red:0, green:0, blue:255 }; - var COLOR_RED = { red:255, green:0, blue:0 }; + var COLOR_GREEN = { red:44, green:142, blue:14 }; + var COLOR_BLUE = { red:0, green:147, blue:197 }; + var COLOR_RED = { red:183, green:10, blue:55 }; - var GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET = 1.35; - var GRABBER_TRANSLATE_ARROW_CYLINDER_DIMENSION_MULTIPLE = 0.05; + var GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET = 0.1; + var GRABBER_TRANSLATE_ARROW_CYLINDER_CAMERA_DISTANCE_MULTIPLE = 0.005; var GRABBER_TRANSLATE_ARROW_CYLINDER_Y_MULTIPLE = 7.5; - var GRABBER_TRANSLATE_ARROW_CONE_DIMENSION_MULTIPLE = 0.25; - var GRABBER_ROTATE_RINGS_DIMENSION_MULTIPLE = 2.0; - var GRABBER_STRETCH_SPHERE_OFFSET = 1.0; - var GRABBER_STRETCH_SPHERE_DIMENSION_MULTIPLE = 0.1; - var GRABBER_SCALE_CUBE_OFFSET = 1.0; - var GRABBER_SCALE_CUBE_DIMENSION_MULTIPLE = 0.15; + var GRABBER_TRANSLATE_ARROW_CONE_CAMERA_DISTANCE_MULTIPLE = 0.025; + var GRABBER_ROTATE_RINGS_CAMERA_DISTANCE_MULTIPLE = 0.15; + var GRABBER_STRETCH_SPHERE_OFFSET = 0.06; + var GRABBER_STRETCH_SPHERE_CAMERA_DISTANCE_MULTIPLE = 0.01; + var GRABBER_SCALE_CUBE_OFFSET = 0.5; + var GRABBER_SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.015; var GRABBER_CLONER_OFFSET = { x:0.9, y:-0.9, z:0.9 }; - var GRABBER_SCALE_CUBE_IDLE_COLOR = { red:120, green:120, blue:120 }; - var GRABBER_SCALE_CUBE_SELECTED_COLOR = { red:0, green:0, blue:0 }; - var GRABBER_SCALE_EDGE_COLOR = { red:120, green:120, blue:120 }; + var GRABBER_SCALE_CUBE_IDLE_COLOR = { red:106, green:106, blue:106 }; + var GRABBER_SCALE_CUBE_SELECTED_COLOR = { red:18, green:18, blue:18 }; + var GRABBER_SCALE_EDGE_COLOR = { red:87, green:87, blue:87 }; + var GRABBER_HOVER_COLOR = { red:227, green:227, blue:227 }; var SCALE_MINIMUM_DIMENSION = 0.02; var STRETCH_MINIMUM_DIMENSION = 0.001; - var STRETCH_DIRECTION_ALL_FACTOR = 15; + var STRETCH_DIRECTION_ALL_FACTOR = 25; // These are multipliers for sizing the rotation degrees display while rotating an entity var ROTATION_DISPLAY_DISTANCE_MULTIPLIER = 1.0; - var ROTATION_DISPLAY_SIZE_X_MULTIPLIER = 0.6; - var ROTATION_DISPLAY_SIZE_Y_MULTIPLIER = 0.18; - var ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER = 0.14; + var ROTATION_DISPLAY_SIZE_X_MULTIPLIER = 0.3; + var ROTATION_DISPLAY_SIZE_Y_MULTIPLIER = 0.09; + var ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER = 0.07; var ROTATION_CTRL_SNAP_ANGLE = 22.5; var ROTATION_DEFAULT_SNAP_ANGLE = 1; var ROTATION_DEFAULT_TICK_MARKS_ANGLE = 5; + var ROTATION_RING_IDLE_INNER_RADIUS = 0.97; + var ROTATION_RING_SELECTED_INNER_RADIUS = 0.9; + var TRANSLATE_DIRECTION = { X : 0, Y : 1, @@ -307,6 +315,10 @@ SelectionDisplay = (function() { var ctrlPressed = false; + var previousHandle = null; + var previousHandleHelper = null; + var previousHandleColor; + var activeTool = null; var grabberTools = {}; @@ -339,9 +351,10 @@ SelectionDisplay = (function() { var grabberPropertiesRotateRings = { alpha: 1, - innerRadius: 0.9, + solid: true, startAt: 0, endAt: 360, + innerRadius: ROTATION_RING_IDLE_INNER_RADIUS, majorTickMarksAngle: ROTATION_DEFAULT_TICK_MARKS_ANGLE, majorTickMarksLength: 0.1, visible: false, @@ -472,7 +485,7 @@ SelectionDisplay = (function() { var selectionBox = Overlays.addOverlay("cube", { size: 1, color: COLOR_RED, - alpha: 1, + alpha: 0, // setting to 0 alpha for now to keep this hidden vs using visible false because its used as the translate xz tool overlay solid: false, visible: false, dashed: false @@ -644,12 +657,13 @@ SelectionDisplay = (function() { pickNormal = { x:0, y:1, z:0 }; } - var rotation = SelectionManager.worldRotation; + var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation; pickNormal = Vec3.multiplyQbyV(rotation, pickNormal); lastPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal); SelectionManager.saveProperties(); + that.resetPreviousHandleColor(); that.setGrabberTranslateXVisible(direction === TRANSLATE_DIRECTION.X); that.setGrabberTranslateYVisible(direction === TRANSLATE_DIRECTION.Y); @@ -696,7 +710,7 @@ SelectionDisplay = (function() { projectionVector = { x:0, y:0, z:1 }; } - var rotation = SelectionManager.worldRotation; + var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation; projectionVector = Vec3.multiplyQbyV(rotation, projectionVector); var dotVector = Vec3.dot(vector, projectionVector); @@ -924,6 +938,9 @@ SelectionDisplay = (function() { that.setGrabberStretchYVisible(directionEnum === STRETCH_DIRECTION.Y); that.setGrabberStretchZVisible(directionEnum === STRETCH_DIRECTION.Z); that.setGrabberClonerVisible(false); + + SelectionManager.saveProperties(); + that.resetPreviousHandleColor(); if (stretchPanel != null) { Overlays.editOverlay(stretchPanel, { visible: true }); @@ -931,8 +948,6 @@ SelectionDisplay = (function() { if (scaleGrabber != null) { Overlays.editOverlay(scaleGrabber, { color: GRABBER_SCALE_CUBE_SELECTED_COLOR }); } - - SelectionManager.saveProperties(); }; var onEnd = function(event, reason) { @@ -1122,13 +1137,15 @@ SelectionDisplay = (function() { // FUNCTION: UPDATE ROTATION DEGREES OVERLAY function updateRotationDegreesOverlay(angleFromZero, position) { var angle = angleFromZero * (Math.PI / 180); + var cameraPosition = Camera.getPosition(); + var entityToCameraDistance = Vec3.length(Vec3.subtract(cameraPosition, position)); var overlayProps = { position: position, dimensions: { - x: ROTATION_DISPLAY_SIZE_X_MULTIPLIER, - y: ROTATION_DISPLAY_SIZE_Y_MULTIPLIER + x: entityToCameraDistance * ROTATION_DISPLAY_SIZE_X_MULTIPLIER, + y: entityToCameraDistance * ROTATION_DISPLAY_SIZE_Y_MULTIPLIER }, - lineHeight: ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER, + lineHeight: entityToCameraDistance * ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER, text: normalizeDegrees(-angleFromZero) + "°" }; Overlays.editOverlay(rotationDegreesDisplay, overlayProps); @@ -1173,6 +1190,7 @@ SelectionDisplay = (function() { mode: mode, onBegin: function(event, pickRay, pickResult) { SelectionManager.saveProperties(); + that.resetPreviousHandleColor(); that.setGrabberTranslateVisible(false); that.setGrabberRotatePitchVisible(direction === ROTATE_DIRECTION.PITCH); @@ -1196,9 +1214,13 @@ SelectionDisplay = (function() { selectedGrabber = grabberRotateRollRing; } - Overlays.editOverlay(selectedGrabber, { hasTickMarks: true }); + Overlays.editOverlay(selectedGrabber, { + hasTickMarks: true, + solid: false, + innerRadius: ROTATION_RING_SELECTED_INNER_RADIUS + }); - var rotation = SelectionManager.worldRotation; + var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation; rotationNormal = Vec3.multiplyQbyV(rotation, rotationNormal); var rotCenter = SelectionManager.worldPosition; @@ -1221,12 +1243,16 @@ SelectionDisplay = (function() { var rotCenterToZero = Vec3.subtract(rotZero, rotCenter); var rotCenterToZeroLength = Vec3.length(rotCenterToZero); - rotDegreePos = Vec3.sum(rotCenter, Vec3.multiply(Vec3.normalize(rotCenterToZero), rotCenterToZeroLength * 1.2)); + rotDegreePos = Vec3.sum(rotCenter, Vec3.multiply(Vec3.normalize(rotCenterToZero), rotCenterToZeroLength * 1.75)); updateRotationDegreesOverlay(0, rotDegreePos); }, onEnd: function(event, reason) { Overlays.editOverlay(rotationDegreesDisplay, { visible: false }); - Overlays.editOverlay(selectedGrabber, { hasTickMarks: false }); + Overlays.editOverlay(selectedGrabber, { + hasTickMarks: false, + solid: true, + innerRadius: ROTATION_RING_IDLE_INNER_RADIUS + }); Overlays.editOverlay(grabberRotateCurrentRing, { visible: false }); pushCommandForSelections(); }, @@ -1266,6 +1292,7 @@ SelectionDisplay = (function() { if (direction === ROTATE_DIRECTION.YAW) { Overlays.editOverlay(grabberRotateCurrentRing, { rotation: worldRotationZ }); } + } } }); @@ -1307,7 +1334,6 @@ SelectionDisplay = (function() { }; that.highlightSelectable = function(entityID) { - var properties = Entities.getEntityProperties(entityID); }; that.unhighlightSelectable = function(entityID) { @@ -1370,118 +1396,119 @@ SelectionDisplay = (function() { } if (SelectionManager.hasSelection()) { - var worldPosition = SelectionManager.worldPosition; - var worldRotation = SelectionManager.worldRotation; - var worldRotationInverse = Quat.inverse(worldRotation); - var worldDimensions = SelectionManager.worldDimensions; + var position = SelectionManager.worldPosition; + var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation; + var dimensions = spaceMode === SPACE_LOCAL ? SelectionManager.localDimensions : SelectionManager.worldDimensions; + var rotationInverse = Quat.inverse(rotation); - var worldDimensionsX = worldDimensions.x; - var worldDimensionsY = worldDimensions.y; - var worldDimensionsZ = worldDimensions.z; - var dimensionAverage = (worldDimensionsX + worldDimensionsY + worldDimensionsZ) / 3; + var cameraPosition = Camera.getPosition(); + var entityToCameraDistance = Vec3.length(Vec3.subtract(cameraPosition, position)); var localRotationX = Quat.fromPitchYawRollDegrees(0, 0, -90); - worldRotationX = Quat.multiply(worldRotation, localRotationX); + rotationX = Quat.multiply(rotation, localRotationX); + worldRotationX = rotationX; var localRotationY = Quat.fromPitchYawRollDegrees(0, 90, 0); - worldRotationY = Quat.multiply(worldRotation, localRotationY); + rotationY = Quat.multiply(rotation, localRotationY); + worldRotationY = rotationY; var localRotationZ = Quat.fromPitchYawRollDegrees(90, 0, 0); - worldRotationZ = Quat.multiply(worldRotation, localRotationZ); + rotationZ = Quat.multiply(rotation, localRotationZ); + worldRotationZ = rotationZ; - var arrowCylinderDimension = dimensionAverage * GRABBER_TRANSLATE_ARROW_CYLINDER_DIMENSION_MULTIPLE; + var arrowCylinderDimension = entityToCameraDistance * GRABBER_TRANSLATE_ARROW_CYLINDER_CAMERA_DISTANCE_MULTIPLE; var arrowCylinderDimensions = { x:arrowCylinderDimension, y:arrowCylinderDimension * GRABBER_TRANSLATE_ARROW_CYLINDER_Y_MULTIPLE, z:arrowCylinderDimension }; - var arrowConeDimension = dimensionAverage * GRABBER_TRANSLATE_ARROW_CONE_DIMENSION_MULTIPLE; + var arrowConeDimension = entityToCameraDistance * GRABBER_TRANSLATE_ARROW_CONE_CAMERA_DISTANCE_MULTIPLE; var arrowConeDimensions = { x:arrowConeDimension, y:arrowConeDimension, z:arrowConeDimension }; - var cylinderXPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET * dimensionAverage, y:0, z:0 })); + var cylinderXPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET * entityToCameraDistance, y:0, z:0 })); Overlays.editOverlay(grabberTranslateXCylinder, { position: cylinderXPos, - rotation: worldRotationX, + rotation: rotationX, dimensions: arrowCylinderDimensions }); - var cylinderXDiff = Vec3.subtract(cylinderXPos, worldPosition); + var cylinderXDiff = Vec3.subtract(cylinderXPos, position); var coneXPos = Vec3.sum(cylinderXPos, Vec3.multiply(Vec3.normalize(cylinderXDiff), arrowCylinderDimensions.y * 0.83)); Overlays.editOverlay(grabberTranslateXCone, { position: coneXPos, - rotation: worldRotationX, + rotation: rotationX, dimensions: arrowConeDimensions }); - var cylinderYPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:0, y:GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET * dimensionAverage, z:0 })); + var cylinderYPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET * entityToCameraDistance, z:0 })); Overlays.editOverlay(grabberTranslateYCylinder, { position: cylinderYPos, - rotation: worldRotationY, + rotation: rotationY, dimensions: arrowCylinderDimensions }); - var cylinderYDiff = Vec3.subtract(cylinderYPos, worldPosition); + var cylinderYDiff = Vec3.subtract(cylinderYPos, position); var coneYPos = Vec3.sum(cylinderYPos, Vec3.multiply(Vec3.normalize(cylinderYDiff), arrowCylinderDimensions.y * 0.83)); Overlays.editOverlay(grabberTranslateYCone, { position: coneYPos, - rotation: worldRotationY, + rotation: rotationY, dimensions: arrowConeDimensions }); - var cylinderZPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:0, y:0, z:GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET * dimensionAverage })); + var cylinderZPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:0, z:GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET * entityToCameraDistance })); Overlays.editOverlay(grabberTranslateZCylinder, { position: cylinderZPos, - rotation: worldRotationZ, + rotation: rotationZ, dimensions: arrowCylinderDimensions }); - var cylinderZDiff = Vec3.subtract(cylinderZPos, worldPosition); + var cylinderZDiff = Vec3.subtract(cylinderZPos, position); var coneZPos = Vec3.sum(cylinderZPos, Vec3.multiply(Vec3.normalize(cylinderZDiff), arrowCylinderDimensions.y * 0.83)); Overlays.editOverlay(grabberTranslateZCone, { position: coneZPos, - rotation: worldRotationZ, + rotation: rotationZ, dimensions: arrowConeDimensions }); - var grabberScaleCubeOffsetX = GRABBER_SCALE_CUBE_OFFSET * worldDimensionsX; - var grabberScaleCubeOffsetY = GRABBER_SCALE_CUBE_OFFSET * worldDimensionsY; - var grabberScaleCubeOffsetZ = GRABBER_SCALE_CUBE_OFFSET * worldDimensionsZ; - var scaleDimension = dimensionAverage * GRABBER_SCALE_CUBE_DIMENSION_MULTIPLE; + var grabberScaleCubeOffsetX = GRABBER_SCALE_CUBE_OFFSET * dimensions.x; + var grabberScaleCubeOffsetY = GRABBER_SCALE_CUBE_OFFSET * dimensions.y; + var grabberScaleCubeOffsetZ = GRABBER_SCALE_CUBE_OFFSET * dimensions.z; + var scaleDimension = entityToCameraDistance * GRABBER_SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE; var scaleDimensions = { x:scaleDimension, y:scaleDimension, z:scaleDimension }; - var grabberScaleLBNCubePos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:-grabberScaleCubeOffsetX, y:-grabberScaleCubeOffsetY, z:-grabberScaleCubeOffsetZ })); + var grabberScaleLBNCubePos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:-grabberScaleCubeOffsetX, y:-grabberScaleCubeOffsetY, z:-grabberScaleCubeOffsetZ })); Overlays.editOverlay(grabberScaleLBNCube, { position: grabberScaleLBNCubePos, - rotation: worldRotation, + rotation: rotation, dimensions: scaleDimensions }); - var grabberScaleRBNCubePos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:-grabberScaleCubeOffsetX, y:-grabberScaleCubeOffsetY, z:grabberScaleCubeOffsetZ })); + var grabberScaleRBNCubePos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:-grabberScaleCubeOffsetX, y:-grabberScaleCubeOffsetY, z:grabberScaleCubeOffsetZ })); Overlays.editOverlay(grabberScaleRBNCube, { position: grabberScaleRBNCubePos, - rotation: worldRotation, + rotation: rotation, dimensions: scaleDimensions }); - var grabberScaleLBFCubePos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:grabberScaleCubeOffsetX, y:-grabberScaleCubeOffsetY, z:-grabberScaleCubeOffsetZ })); + var grabberScaleLBFCubePos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:grabberScaleCubeOffsetX, y:-grabberScaleCubeOffsetY, z:-grabberScaleCubeOffsetZ })); Overlays.editOverlay(grabberScaleLBFCube, { position: grabberScaleLBFCubePos, - rotation: worldRotation, + rotation: rotation, dimensions: scaleDimensions }); - var grabberScaleRBFCubePos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:grabberScaleCubeOffsetX, y:-grabberScaleCubeOffsetY, z:grabberScaleCubeOffsetZ })); + var grabberScaleRBFCubePos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:grabberScaleCubeOffsetX, y:-grabberScaleCubeOffsetY, z:grabberScaleCubeOffsetZ })); Overlays.editOverlay(grabberScaleRBFCube, { position: grabberScaleRBFCubePos, - rotation: worldRotation, + rotation: rotation, dimensions: scaleDimensions }); - var grabberScaleLTNCubePos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:-grabberScaleCubeOffsetX, y:grabberScaleCubeOffsetY, z:-grabberScaleCubeOffsetZ })); + var grabberScaleLTNCubePos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:-grabberScaleCubeOffsetX, y:grabberScaleCubeOffsetY, z:-grabberScaleCubeOffsetZ })); Overlays.editOverlay(grabberScaleLTNCube, { position: grabberScaleLTNCubePos, - rotation: worldRotation, + rotation: rotation, dimensions: scaleDimensions }); - var grabberScaleRTNCubePos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:-grabberScaleCubeOffsetX, y:grabberScaleCubeOffsetY, z:grabberScaleCubeOffsetZ })); + var grabberScaleRTNCubePos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:-grabberScaleCubeOffsetX, y:grabberScaleCubeOffsetY, z:grabberScaleCubeOffsetZ })); Overlays.editOverlay(grabberScaleRTNCube, { position: grabberScaleRTNCubePos, - rotation: worldRotation, + rotation: rotation, dimensions: scaleDimensions }); - var grabberScaleLTFCubePos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:grabberScaleCubeOffsetX, y:grabberScaleCubeOffsetY, z:-grabberScaleCubeOffsetZ })); + var grabberScaleLTFCubePos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:grabberScaleCubeOffsetX, y:grabberScaleCubeOffsetY, z:-grabberScaleCubeOffsetZ })); Overlays.editOverlay(grabberScaleLTFCube, { position: grabberScaleLTFCubePos, - rotation: worldRotation, + rotation: rotation, dimensions: scaleDimensions }); - var grabberScaleRTFCubePos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:grabberScaleCubeOffsetX, y:grabberScaleCubeOffsetY, z:grabberScaleCubeOffsetZ })); + var grabberScaleRTFCubePos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:grabberScaleCubeOffsetX, y:grabberScaleCubeOffsetY, z:grabberScaleCubeOffsetZ })); Overlays.editOverlay(grabberScaleRTFCube, { position: grabberScaleRTFCubePos, - rotation: worldRotation, + rotation: rotation, dimensions: scaleDimensions }); @@ -1498,82 +1525,85 @@ SelectionDisplay = (function() { Overlays.editOverlay(grabberScaleFREdge, { start: grabberScaleRTFCubePos, end: grabberScaleRBFCubePos }); Overlays.editOverlay(grabberScaleFLEdge, { start: grabberScaleLTFCubePos, end: grabberScaleLBFCubePos }); - var stretchSphereDimension = dimensionAverage * GRABBER_STRETCH_SPHERE_DIMENSION_MULTIPLE; + var stretchSphereDimension = entityToCameraDistance * GRABBER_STRETCH_SPHERE_CAMERA_DISTANCE_MULTIPLE; var stretchSphereDimensions = { x:stretchSphereDimension, y:stretchSphereDimension, z:stretchSphereDimension }; - var stretchXPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:GRABBER_STRETCH_SPHERE_OFFSET * worldDimensionsX, y:0, z:0 })); + var stretchXPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:GRABBER_STRETCH_SPHERE_OFFSET * entityToCameraDistance, y:0, z:0 })); Overlays.editOverlay(grabberStretchXSphere, { position: stretchXPos, dimensions: stretchSphereDimensions }); - var grabberScaleLTFCubePosRot = Vec3.multiplyQbyV(worldRotationInverse, grabberScaleLTFCubePos); - var grabberScaleRBFCubePosRot = Vec3.multiplyQbyV(worldRotationInverse, grabberScaleRBFCubePos); + var grabberScaleLTFCubePosRot = Vec3.multiplyQbyV(rotationInverse, grabberScaleLTFCubePos); + var grabberScaleRBFCubePosRot = Vec3.multiplyQbyV(rotationInverse, grabberScaleRBFCubePos); var stretchPanelXDimensions = Vec3.subtract(grabberScaleLTFCubePosRot, grabberScaleRBFCubePosRot); var tempY = Math.abs(stretchPanelXDimensions.y); stretchPanelXDimensions.x = 0.01; stretchPanelXDimensions.y = Math.abs(stretchPanelXDimensions.z); stretchPanelXDimensions.z = tempY; + var stretchPanelXPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:dimensions.x / 2, y:0, z:0 })); Overlays.editOverlay(grabberStretchXPanel, { - position: stretchXPos, - rotation: worldRotationZ, + position: stretchPanelXPos, + rotation: rotationZ, dimensions: stretchPanelXDimensions }); - var stretchYPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:0, y:GRABBER_STRETCH_SPHERE_OFFSET * worldDimensionsY, z:0 })); + var stretchYPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:GRABBER_STRETCH_SPHERE_OFFSET * entityToCameraDistance, z:0 })); Overlays.editOverlay(grabberStretchYSphere, { position: stretchYPos, dimensions: stretchSphereDimensions }); - var grabberScaleLTFCubePosRot = Vec3.multiplyQbyV(worldRotationInverse, grabberScaleLTNCubePos); - var grabberScaleRTNCubePosRot = Vec3.multiplyQbyV(worldRotationInverse, grabberScaleRTFCubePos); + var grabberScaleLTFCubePosRot = Vec3.multiplyQbyV(rotationInverse, grabberScaleLTNCubePos); + var grabberScaleRTNCubePosRot = Vec3.multiplyQbyV(rotationInverse, grabberScaleRTFCubePos); var stretchPanelYDimensions = Vec3.subtract(grabberScaleLTFCubePosRot, grabberScaleRTNCubePosRot); var tempX = Math.abs(stretchPanelYDimensions.x); stretchPanelYDimensions.x = Math.abs(stretchPanelYDimensions.z); stretchPanelYDimensions.y = 0.01; stretchPanelYDimensions.z = tempX; + var stretchPanelYPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:dimensions.y / 2, z:0 })); Overlays.editOverlay(grabberStretchYPanel, { - position: stretchYPos, - rotation: worldRotationY, + position: stretchPanelYPos, + rotation: rotationY, dimensions: stretchPanelYDimensions }); - var stretchZPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, { x:0, y:0, z:GRABBER_STRETCH_SPHERE_OFFSET * worldDimensionsZ })); + var stretchZPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:0, z:GRABBER_STRETCH_SPHERE_OFFSET * entityToCameraDistance })); Overlays.editOverlay(grabberStretchZSphere, { position: stretchZPos, dimensions: stretchSphereDimensions }); - var grabberScaleRTFCubePosRot = Vec3.multiplyQbyV(worldRotationInverse, grabberScaleRTFCubePos); - var grabberScaleRBNCubePosRot = Vec3.multiplyQbyV(worldRotationInverse, grabberScaleRBNCubePos); + var grabberScaleRTFCubePosRot = Vec3.multiplyQbyV(rotationInverse, grabberScaleRTFCubePos); + var grabberScaleRBNCubePosRot = Vec3.multiplyQbyV(rotationInverse, grabberScaleRBNCubePos); var stretchPanelZDimensions = Vec3.subtract(grabberScaleRTFCubePosRot, grabberScaleRBNCubePosRot); var tempX = Math.abs(stretchPanelZDimensions.x); stretchPanelZDimensions.x = Math.abs(stretchPanelZDimensions.y); stretchPanelZDimensions.y = tempX; stretchPanelZDimensions.z = 0.01; + var stretchPanelZPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:0, z:dimensions.z / 2 })); Overlays.editOverlay(grabberStretchZPanel, { - position: stretchZPos, - rotation: worldRotationX, + position: stretchPanelZPos, + rotation: rotationX, dimensions: stretchPanelZDimensions }); - var rotateDimension = dimensionAverage * GRABBER_ROTATE_RINGS_DIMENSION_MULTIPLE; + var rotateDimension = entityToCameraDistance * GRABBER_ROTATE_RINGS_CAMERA_DISTANCE_MULTIPLE; var rotateDimensions = { x:rotateDimension, y:rotateDimension, z:rotateDimension }; if (!isActiveTool(grabberRotatePitchRing)) { Overlays.editOverlay(grabberRotatePitchRing, { - position: SelectionManager.worldPosition, - rotation: worldRotationY, + position: position, + rotation: rotationY, dimensions: rotateDimensions, majorTickMarksAngle: ROTATION_DEFAULT_TICK_MARKS_ANGLE }); } if (!isActiveTool(grabberRotateYawRing)) { Overlays.editOverlay(grabberRotateYawRing, { - position: SelectionManager.worldPosition, - rotation: worldRotationZ, + position: position, + rotation: rotationZ, dimensions: rotateDimensions, majorTickMarksAngle: ROTATION_DEFAULT_TICK_MARKS_ANGLE }); } if (!isActiveTool(grabberRotateRollRing)) { Overlays.editOverlay(grabberRotateRollRing, { - position: SelectionManager.worldPosition, - rotation: worldRotationX, + position: position, + rotation: rotationX, dimensions: rotateDimensions, majorTickMarksAngle: ROTATION_DEFAULT_TICK_MARKS_ANGLE }); @@ -1588,18 +1618,18 @@ SelectionDisplay = (function() { isActiveTool(grabberCloner) || isActiveTool(selectionBox); Overlays.editOverlay(selectionBox, { - position: worldPosition, - rotation: worldRotation, - dimensions: worldDimensions, + position: position, + rotation: rotation, + dimensions: dimensions, visible: !inModeRotate }); - var grabberClonerOffset = { x:GRABBER_CLONER_OFFSET.x * worldDimensionsX, y:GRABBER_CLONER_OFFSET.y * worldDimensionsY, z:GRABBER_CLONER_OFFSET.z * worldDimensionsZ }; - var grabberClonerPos = Vec3.sum(worldPosition, Vec3.multiplyQbyV(worldRotation, grabberClonerOffset)); + var grabberClonerOffset = { x:GRABBER_CLONER_OFFSET.x * dimensions.x, y:GRABBER_CLONER_OFFSET.y * dimensions.y, z:GRABBER_CLONER_OFFSET.z * dimensions.z }; + var grabberClonerPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, grabberClonerOffset)); Overlays.editOverlay(grabberCloner, { position: grabberClonerPos, - rotation: worldRotation, - dimensions: scaleDimensions, + rotation: rotation, + dimensions: scaleDimensions }); } @@ -1615,12 +1645,14 @@ SelectionDisplay = (function() { that.setGrabberScaleVisible(!activeTool || isActiveTool(grabberScaleLBNCube) || isActiveTool(grabberScaleRBNCube) || isActiveTool(grabberScaleLBFCube) || isActiveTool(grabberScaleRBFCube) || isActiveTool(grabberScaleLTNCube) || isActiveTool(grabberScaleRTNCube) || isActiveTool(grabberScaleLTFCube) || isActiveTool(grabberScaleRTFCube) || isActiveTool(grabberStretchXSphere) || isActiveTool(grabberStretchYSphere) || isActiveTool(grabberStretchZSphere)); - that.setGrabberClonerVisible(!activeTool || isActiveTool(grabberCloner)); + //keep cloner always hidden for now since you can hold Alt to clone while dragging to translate - we may bring cloner back for HMD only later + //that.setGrabberClonerVisible(!activeTool || isActiveTool(grabberCloner)); if (wantDebug) { print("====== Update Grabbers <======="); } }; + Script.update.connect(that.updateGrabbers); that.updateActiveRotateRing = function() { var activeRotateRing = null; @@ -1756,6 +1788,7 @@ SelectionDisplay = (function() { } SelectionManager.saveProperties(); + that.resetPreviousHandleColor(); that.setGrabberTranslateVisible(false); that.setGrabberRotateVisible(false); @@ -2059,6 +2092,33 @@ SelectionDisplay = (function() { return activeTool; }; + that.resetPreviousHandleColor = function() { + if (previousHandle != null) { + Overlays.editOverlay(previousHandle, { color: previousHandleColor }); + previousHandle = null; + } + if (previousHandleHelper != null) { + Overlays.editOverlay(previousHandleHelper, { color: previousHandleColor }); + previousHandleHelper = null; + } + }; + + that.getHandleHelper = function(overlay) { + if (overlay === grabberTranslateXCone) { + return grabberTranslateXCylinder; + } else if (overlay === grabberTranslateXCylinder) { + return grabberTranslateXCone; + } else if (overlay === grabberTranslateYCone) { + return grabberTranslateYCylinder; + } else if (overlay === grabberTranslateYCylinder) { + return grabberTranslateYCone; + } else if (overlay === grabberTranslateZCone) { + return grabberTranslateZCylinder; + } else if (overlay === grabberTranslateZCylinder) { + return grabberTranslateZCone; + } + }; + // FUNCTION: MOUSE MOVE EVENT that.mouseMoveEvent = function(event) { var wantDebug = false; @@ -2083,6 +2143,66 @@ SelectionDisplay = (function() { return true; } + // if no tool is active, then just look for handles to highlight... + var pickRay = generalComputePickRay(event.x, event.y); + var result = Overlays.findRayIntersection(pickRay); + var pickedColor; + var highlightNeeded = false; + + if (result.intersects) { + switch (result.overlayID) { + case grabberTranslateXCone: + case grabberTranslateXCylinder: + case grabberRotatePitchRing: + case grabberStretchXSphere: + pickedColor = COLOR_RED; + highlightNeeded = true; + break; + case grabberTranslateYCone: + case grabberTranslateYCylinder: + case grabberRotateYawRing: + case grabberStretchYSphere: + pickedColor = COLOR_GREEN; + highlightNeeded = true; + break; + case grabberTranslateZCone: + case grabberTranslateZCylinder: + case grabberRotateRollRing: + case grabberStretchZSphere: + pickedColor = COLOR_BLUE; + highlightNeeded = true; + break; + case grabberScaleLBNCube: + case grabberScaleRBNCube: + case grabberScaleLBFCube: + case grabberScaleRBFCube: + case grabberScaleLTNCube: + case grabberScaleRTNCube: + case grabberScaleLTFCube: + case grabberScaleRTFCube: + pickedColor = GRABBER_SCALE_CUBE_IDLE_COLOR; + highlightNeeded = true; + break; + default: + that.resetPreviousHandleColor(); + break; + } + + if (highlightNeeded) { + that.resetPreviousHandleColor(); + Overlays.editOverlay(result.overlayID, { color: GRABBER_HOVER_COLOR }); + previousHandle = result.overlayID; + previousHandleHelper = that.getHandleHelper(result.overlayID); + if (previousHandleHelper != null) { + Overlays.editOverlay(previousHandleHelper, { color: GRABBER_HOVER_COLOR }); + } + previousHandleColor = pickedColor; + } + + } else { + that.resetPreviousHandleColor(); + } + if (wantDebug) { print("=============== eST::MouseMoveEvent END ======================="); } From 06afaa7470f90a6b152c474958315d4c7e80ddd5 Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 21 Dec 2017 15:13:57 -0500 Subject: [PATCH 060/569] BufferView <-> QVariant/QScriptValue conversion update MeshProxy/SimpleMeshProxy and ScriptableModel ModelScriptingInterface / scriptable::ModelProvider integration update to RC-63 initial graphics-scripting refactoring graphics-scripting baseline commit wip commit Geometry -> MeshPart remove SimpleMeshProxy collapse graphics-utils -> graphics-scripting scriptable::Model => scriptable::ScriptableModel --- interface/CMakeLists.txt | 2 +- interface/src/Application.cpp | 68 +- interface/src/Application.h | 1 - interface/src/ui/overlays/Base3DOverlay.h | 6 +- interface/src/ui/overlays/ModelOverlay.cpp | 7 + interface/src/ui/overlays/ModelOverlay.h | 1 + interface/src/ui/overlays/Shape3DOverlay.cpp | 15 + interface/src/ui/overlays/Shape3DOverlay.h | 1 + libraries/avatars-renderer/CMakeLists.txt | 1 + .../src/avatars-renderer/Avatar.cpp | 28 + .../src/avatars-renderer/Avatar.h | 5 +- libraries/entities-renderer/CMakeLists.txt | 1 + .../src/RenderableEntityItem.h | 6 +- .../src/RenderableModelEntityItem.cpp | 15 +- .../src/RenderableModelEntityItem.h | 2 +- .../src/RenderablePolyLineEntityItem.cpp | 6 +- .../src/RenderablePolyLineEntityItem.h | 1 + .../src/RenderablePolyVoxEntityItem.cpp | 54 +- .../src/RenderablePolyVoxEntityItem.h | 7 +- .../src/RenderableShapeEntityItem.cpp | 21 + .../src/RenderableShapeEntityItem.h | 2 + libraries/entities/src/EntityItem.h | 4 - .../entities/src/EntityScriptingInterface.cpp | 24 - .../entities/src/EntityScriptingInterface.h | 4 - libraries/fbx/src/FBXReader.cpp | 16 +- libraries/fbx/src/OBJWriter.cpp | 78 +- libraries/graphics-scripting/CMakeLists.txt | 5 + .../graphics-scripting/BufferViewHelpers.cpp | 195 ++++ .../graphics-scripting/BufferViewHelpers.h | 18 + .../BufferViewScripting.cpp | 83 ++ .../graphics-scripting/BufferViewScripting.h | 11 + .../src/graphics-scripting/DebugNames.h | 72 ++ .../ModelScriptingInterface.cpp | 836 ++++++++++++++++++ .../ModelScriptingInterface.h | 68 ++ .../src/graphics-scripting/ScriptableMesh.cpp | 359 ++++++++ .../src/graphics-scripting/ScriptableMesh.h | 127 +++ .../src/graphics-scripting/ScriptableModel.h | 80 ++ libraries/graphics/src/graphics/Geometry.cpp | 6 + libraries/graphics/src/graphics/Geometry.h | 1 + .../src/model-networking/SimpleMeshProxy.cpp | 27 - .../src/model-networking/SimpleMeshProxy.h | 36 - libraries/render-utils/CMakeLists.txt | 1 + libraries/render-utils/src/GeometryCache.cpp | 45 + libraries/render-utils/src/GeometryCache.h | 1 + libraries/render-utils/src/Model.cpp | 87 +- libraries/render-utils/src/Model.h | 5 +- .../src/ModelScriptingInterface.cpp | 251 ------ .../src/ModelScriptingInterface.h | 39 - libraries/script-engine/src/ScriptEngine.cpp | 6 - libraries/shared/src/RegisteredMetaTypes.cpp | 65 -- libraries/shared/src/RegisteredMetaTypes.h | 42 - 51 files changed, 2242 insertions(+), 600 deletions(-) create mode 100644 libraries/graphics-scripting/CMakeLists.txt create mode 100644 libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp create mode 100644 libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h create mode 100644 libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp create mode 100644 libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.h create mode 100644 libraries/graphics-scripting/src/graphics-scripting/DebugNames.h create mode 100644 libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp create mode 100644 libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h create mode 100644 libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp create mode 100644 libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h create mode 100644 libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h delete mode 100644 libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp delete mode 100644 libraries/model-networking/src/model-networking/SimpleMeshProxy.h delete mode 100644 libraries/script-engine/src/ModelScriptingInterface.cpp delete mode 100644 libraries/script-engine/src/ModelScriptingInterface.h diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index ee2997e216..081eeae02e 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -191,7 +191,7 @@ endif() # link required hifi libraries link_hifi_libraries( - shared octree ktx gpu gl procedural graphics render + shared octree ktx gpu gl procedural graphics graphics-scripting render pointers recording fbx networking model-networking entities avatars trackers audio audio-client animation script-engine physics diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 99bd4d5758..f24969ce60 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -166,6 +166,7 @@ #include "scripting/AccountServicesScriptingInterface.h" #include "scripting/HMDScriptingInterface.h" #include "scripting/MenuScriptingInterface.h" +#include "graphics-scripting/ModelScriptingInterface.h" #include "scripting/SettingsScriptingInterface.h" #include "scripting/WindowScriptingInterface.h" #include "scripting/ControllerScriptingInterface.h" @@ -198,7 +199,6 @@ #include #include #include -#include #include #include @@ -593,6 +593,66 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt } } + +class ApplicationMeshProvider : public scriptable::ModelProviderFactory { +public: + virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) { + QString error; + + scriptable::ModelProviderPointer provider; + if (auto entityInterface = getEntityModelProvider(static_cast(uuid))) { + provider = entityInterface; + } else if (auto overlayInterface = getOverlayModelProvider(static_cast(uuid))) { + provider = overlayInterface; + } else if (auto avatarInterface = getAvatarModelProvider(uuid)) { + provider = avatarInterface; + } + + return provider; + } + + scriptable::ModelProviderPointer getEntityModelProvider(EntityItemID entityID) { + scriptable::ModelProviderPointer provider; + auto entityTreeRenderer = qApp->getEntities(); + auto entityTree = entityTreeRenderer->getTree(); + if (auto entity = entityTree->findEntityByID(entityID)) { + if (auto renderer = entityTreeRenderer->renderableForEntityId(entityID)) { + provider = std::dynamic_pointer_cast(renderer); + provider->metadata["providerType"] = "entity"; + } else { + qCWarning(interfaceapp) << "no renderer for entity ID" << entityID.toString(); + } + } + return provider; + } + + scriptable::ModelProviderPointer getOverlayModelProvider(OverlayID overlayID) { + scriptable::ModelProviderPointer provider; + auto &overlays = qApp->getOverlays(); + if (auto overlay = overlays.getOverlay(overlayID)) { + if (auto base3d = std::dynamic_pointer_cast(overlay)) { + provider = std::dynamic_pointer_cast(base3d); + provider->metadata["providerType"] = "overlay"; + } else { + qCWarning(interfaceapp) << "no renderer for overlay ID" << overlayID.toString(); + } + } + return provider; + } + + scriptable::ModelProviderPointer getAvatarModelProvider(QUuid sessionUUID) { + scriptable::ModelProviderPointer provider; + auto avatarManager = DependencyManager::get(); + if (auto avatar = avatarManager->getAvatarBySessionID(sessionUUID)) { + if (avatar->getSessionUUID() == sessionUUID) { + provider = std::dynamic_pointer_cast(avatar); + provider->metadata["providerType"] = "avatar"; + } + } + return provider; + } +}; + static const QString STATE_IN_HMD = "InHMD"; static const QString STATE_CAMERA_FULL_SCREEN_MIRROR = "CameraFSM"; static const QString STATE_CAMERA_FIRST_PERSON = "CameraFirstPerson"; @@ -737,6 +797,9 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(true); + DependencyManager::set(); + DependencyManager::registerInheritance(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -5915,6 +5978,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGlobalObject("Scene", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Render", _renderEngine->getConfiguration().get()); + ModelScriptingInterface::registerMetaTypes(scriptEngine.data()); + scriptEngine->registerGlobalObject("Model", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("ScriptDiscoveryService", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Reticle", getApplicationCompositor().getReticleInterface()); diff --git a/interface/src/Application.h b/interface/src/Application.h index ddb8ce11e5..ae07ebd9dd 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -72,7 +72,6 @@ #include #include -#include #include "FrameTimingsScriptingInterface.h" #include "Sound.h" diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index df0f3c4728..0c8bc5aacb 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -13,10 +13,11 @@ #include #include - +#include #include "Overlay.h" -class Base3DOverlay : public Overlay, public SpatiallyNestable { +namespace model { class Mesh; } +class Base3DOverlay : public Overlay, public SpatiallyNestable, public scriptable::ModelProvider { Q_OBJECT using Parent = Overlay; @@ -36,6 +37,7 @@ public: virtual bool is3D() const override { return true; } virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override { subItems.push_back(getRenderItemID()); return (uint32_t) subItems.size(); } + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); } // TODO: consider implementing registration points in this class glm::vec3 getCenter() const { return getWorldPosition(); } diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 310dbf78d8..5a80ca1abf 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -629,3 +629,10 @@ uint32_t ModelOverlay::fetchMetaSubItems(render::ItemIDs& subItems) const { } return 0; } + +scriptable::ScriptableModel ModelOverlay::getScriptableModel(bool* ok) { + if (!_model || !_model->isLoaded()) { + return Base3DOverlay::getScriptableModel(ok); + } + return _model->getScriptableModel(ok); +} diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 60ba90e568..32d9a08c70 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -59,6 +59,7 @@ public: void setDrawInFront(bool drawInFront) override; void setDrawHUDLayer(bool drawHUDLayer) override; + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; protected: Transform evalRenderTransform() override; diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index 97342a80ab..8bb3d16888 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -179,3 +179,18 @@ Transform Shape3DOverlay::evalRenderTransform() { transform.setRotation(rotation); return transform; } + +scriptable::ScriptableModel Shape3DOverlay::getScriptableModel(bool* ok) { + auto geometryCache = DependencyManager::get(); + auto vertexColor = ColorUtils::toVec3(_color); + scriptable::ScriptableModel result; + result.metadata = { + { "origin", "Shape3DOverlay::"+shapeStrings[_shape] }, + { "overlayID", getID() }, + }; + result.meshes << geometryCache->meshFromShape(_shape, vertexColor); + if (ok) { + *ok = true; + } + return result; +} diff --git a/interface/src/ui/overlays/Shape3DOverlay.h b/interface/src/ui/overlays/Shape3DOverlay.h index 7fc95ec981..34f82af278 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.h +++ b/interface/src/ui/overlays/Shape3DOverlay.h @@ -37,6 +37,7 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; protected: Transform evalRenderTransform() override; diff --git a/libraries/avatars-renderer/CMakeLists.txt b/libraries/avatars-renderer/CMakeLists.txt index 53edc692f2..f7e951500b 100644 --- a/libraries/avatars-renderer/CMakeLists.txt +++ b/libraries/avatars-renderer/CMakeLists.txt @@ -13,5 +13,6 @@ include_hifi_library_headers(entities-renderer) include_hifi_library_headers(audio) include_hifi_library_headers(entities) include_hifi_library_headers(octree) +include_hifi_library_headers(graphics-scripting) # for ScriptableModel.h target_bullet() diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 500a24763d..8e22f355e4 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -35,6 +35,8 @@ #include "ModelEntityItem.h" #include "RenderableModelEntityItem.h" +#include + #include "Logging.h" using namespace std; @@ -1760,3 +1762,29 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const { return DEFAULT_AVATAR_EYE_HEIGHT; } } + +scriptable::ScriptableModel Avatar::getScriptableModel(bool* ok) { + qDebug() << "Avatar::getScriptableModel" ; + if (!_skeletonModel || !_skeletonModel->isLoaded()) { + return scriptable::ModelProvider::modelUnavailableError(ok); + } + scriptable::ScriptableModel result; + result.metadata = { + { "avatarID", getSessionUUID().toString() }, + { "url", _skeletonModelURL.toString() }, + { "origin", "Avatar/avatar::" + _displayName }, + { "textures", _skeletonModel->getTextures() }, + }; + result.mixin(_skeletonModel->getScriptableModel(ok)); + + // FIXME: for now access to attachment models are merged with the main avatar model + for (auto& attachmentModel : _attachmentModels) { + if (attachmentModel->isLoaded()) { + result.mixin(attachmentModel->getScriptableModel(ok)); + } + } + if (ok) { + *ok = true; + } + return result; +} diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index c2b404a925..5cfc399b65 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -20,6 +20,7 @@ #include #include #include +#include #include @@ -53,7 +54,7 @@ class Texture; using AvatarPhysicsCallback = std::function; -class Avatar : public AvatarData { +class Avatar : public AvatarData, public scriptable::ModelProvider { Q_OBJECT /**jsdoc @@ -272,6 +273,8 @@ public: virtual void setAvatarEntityDataChanged(bool value) override; + + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; public slots: // FIXME - these should be migrated to use Pose data instead diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index ea75367e1e..27ea04f642 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -13,6 +13,7 @@ include_hifi_library_headers(fbx) include_hifi_library_headers(entities) include_hifi_library_headers(avatars) include_hifi_library_headers(controllers) +include_hifi_library_headers(graphics-scripting) # for ScriptableModel.h target_bullet() target_polyvox() diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 8eb82e2c6e..f07b67fbd0 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -17,13 +17,14 @@ #include #include "AbstractViewStateInterface.h" #include "EntitiesRendererLogging.h" +#include class EntityTreeRenderer; namespace render { namespace entities { // Base class for all renderable entities -class EntityRenderer : public QObject, public std::enable_shared_from_this, public PayloadProxyInterface, protected ReadWriteLockable { +class EntityRenderer : public QObject, public std::enable_shared_from_this, public PayloadProxyInterface, protected ReadWriteLockable, public scriptable::ModelProvider { Q_OBJECT using Pointer = std::shared_ptr; @@ -37,7 +38,7 @@ public: virtual bool wantsKeyboardFocus() const { return false; } virtual void setProxyWindow(QWindow* proxyWindow) {} virtual QObject* getEventHandler() { return nullptr; } - const EntityItemPointer& getEntity() { return _entity; } + const EntityItemPointer& getEntity() const { return _entity; } const ItemID& getRenderItemID() const { return _renderItemID; } const SharedSoundPointer& getCollisionSound() { return _collisionSound; } @@ -54,6 +55,7 @@ public: const uint64_t& getUpdateTime() const { return _updateTime; } + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); } protected: virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); } virtual void onAddToScene(const EntityItemPointer& entity); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 9fcb7640ef..7b022fefac 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -950,13 +950,18 @@ QStringList RenderableModelEntityItem::getJointNames() const { return result; } -bool RenderableModelEntityItem::getMeshes(MeshProxyList& result) { - auto model = getModel(); + +scriptable::ScriptableModel render::entities::ModelEntityRenderer::getScriptableModel(bool* ok) { + ModelPointer model; + withReadLock([&] { + model = _model; + }); + if (!model || !model->isLoaded()) { - return false; + return scriptable::ModelProvider::modelUnavailableError(ok); } - BLOCKING_INVOKE_METHOD(model.get(), "getMeshes", Q_RETURN_ARG(MeshProxyList, result)); - return !result.isEmpty(); + + return _model->getScriptableModel(ok); } void RenderableModelEntityItem::simulateRelayedJoints() { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 33fc9910a0..3e952cb9a7 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -111,7 +111,6 @@ public: virtual int getJointIndex(const QString& name) const override; virtual QStringList getJointNames() const override; - bool getMeshes(MeshProxyList& result) override; const void* getCollisionMeshKey() const { return _collisionMeshKey; } signals: @@ -141,6 +140,7 @@ class ModelEntityRenderer : public TypedEntityRenderer PolyLineEntityRenderer::updateVertic return vertices; } +scriptable::ScriptableModel PolyLineEntityRenderer::getScriptableModel(bool *ok) { + // TODO: adapt polyline into a triangles mesh... + return EntityRenderer::getScriptableModel(ok); +} void PolyLineEntityRenderer::doRender(RenderArgs* args) { if (_empty) { @@ -319,4 +323,4 @@ void PolyLineEntityRenderer::doRender(RenderArgs* args) { #endif batch.draw(gpu::TRIANGLE_STRIP, _numVertices, 0); -} \ No newline at end of file +} diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h index 1e27ac9ae7..3bb8901178 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h @@ -25,6 +25,7 @@ class PolyLineEntityRenderer : public TypedEntityRenderer { public: PolyLineEntityRenderer(const EntityItemPointer& entity); + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; protected: virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index ade3790df6..fd923c40b0 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -20,8 +20,6 @@ #include #include -#include -#include #include #include #include @@ -1416,36 +1414,36 @@ void RenderablePolyVoxEntityItem::bonkNeighbors() { } } -bool RenderablePolyVoxEntityItem::getMeshes(MeshProxyList& result) { - if (!updateDependents()) { - return false; +scriptable::ScriptableModel RenderablePolyVoxEntityItem::getScriptableModel(bool * ok) { + if (!updateDependents() || !_mesh) { + return scriptable::ModelProvider::modelUnavailableError(ok); } bool success = false; - if (_mesh) { - MeshProxy* meshProxy = nullptr; - glm::mat4 transform = voxelToLocalMatrix(); - withReadLock([&] { - gpu::BufferView::Index numVertices = (gpu::BufferView::Index)_mesh->getNumVertices(); - if (!_meshReady) { - // we aren't ready to return a mesh. the caller will have to try again later. - success = false; - } else if (numVertices == 0) { - // we are ready, but there are no triangles in the mesh. - success = true; - } else { - success = true; - // the mesh will be in voxel-space. transform it into object-space - meshProxy = new SimpleMeshProxy( - _mesh->map([=](glm::vec3 position) { return glm::vec3(transform * glm::vec4(position, 1.0f)); }, - [=](glm::vec3 color) { return color; }, - [=](glm::vec3 normal) { return glm::normalize(glm::vec3(transform * glm::vec4(normal, 0.0f))); }, - [&](uint32_t index) { return index; })); - result << meshProxy; - } - }); + glm::mat4 transform = voxelToLocalMatrix(); + scriptable::ScriptableModel result; + withReadLock([&] { + gpu::BufferView::Index numVertices = (gpu::BufferView::Index)_mesh->getNumVertices(); + if (!_meshReady) { + // we aren't ready to return a mesh. the caller will have to try again later. + success = false; + } else if (numVertices == 0) { + // we are ready, but there are no triangles in the mesh. + success = true; + } else { + success = true; + // the mesh will be in voxel-space. transform it into object-space + result.meshes << + _mesh->map([=](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); }, + [=](glm::vec3 color){ return color; }, + [=](glm::vec3 normal){ return glm::normalize(glm::vec3(transform * glm::vec4(normal, 0.0f))); }, + [&](uint32_t index){ return index; }); + } + }); + if (ok) { + *ok = success; } - return success; + return result; } using namespace render; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index db0f0b729a..55b9be23d8 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -32,7 +32,7 @@ namespace render { namespace entities { class PolyVoxEntityRenderer; } } -class RenderablePolyVoxEntityItem : public PolyVoxEntityItem { +class RenderablePolyVoxEntityItem : public PolyVoxEntityItem, public scriptable::ModelProvider { friend class render::entities::PolyVoxEntityRenderer; public: @@ -113,7 +113,7 @@ public: void setVolDataDirty() { withWriteLock([&] { _volDataDirty = true; _meshReady = false; }); } - bool getMeshes(MeshProxyList& result) override; + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; private: bool updateOnCount(const ivec3& v, uint8_t toValue); @@ -163,6 +163,9 @@ class PolyVoxEntityRenderer : public TypedEntityRenderer()->getScriptableModel(ok); + } protected: virtual ItemKey getKey() override { return ItemKey::Builder::opaqueShape(); } diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index cdee2c5ec9..746102681c 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -156,3 +156,24 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { const auto triCount = geometryCache->getShapeTriangleCount(geometryShape); args->_details._trianglesRendered += (int)triCount; } + +scriptable::ScriptableModel ShapeEntityRenderer::getScriptableModel(bool* ok) { + scriptable::ScriptableModel result; + result.metadata = { + { "entityID", getEntity()->getID().toString() }, + { "shape", entity::stringFromShape(_shape) }, + { "userData", getEntity()->getUserData() }, + }; + auto geometryCache = DependencyManager::get(); + auto geometryShape = geometryCache->getShapeForEntityShape(_shape); + auto vertexColor = glm::vec3(_color); + auto success = false; + if (auto mesh = geometryCache->meshFromShape(geometryShape, vertexColor)) { + result.meshes << mesh; + success = true; + } + if (ok) { + *ok = success; + } + return result; +} diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.h b/libraries/entities-renderer/src/RenderableShapeEntityItem.h index 433cb41ad2..6ada7e7317 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.h +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.h @@ -22,6 +22,8 @@ class ShapeEntityRenderer : public TypedEntityRenderer { public: ShapeEntityRenderer(const EntityItemPointer& entity); + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; + private: virtual bool needsRenderUpdate() const override; virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 4c398b8a29..5c9324fc8a 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -57,8 +57,6 @@ using EntityTreeElementExtraEncodeDataPointer = std::shared_ptr(_entityTree->findEntityByEntityItemID(entityID)); - if (!entity) { - qCDebug(entities) << "EntityScriptingInterface::getMeshes no entity with ID" << entityID; - QScriptValueList args { callback.engine()->undefinedValue(), false }; - callback.call(QScriptValue(), args); - return; - } - - MeshProxyList result; - bool success = entity->getMeshes(result); - - if (success) { - QScriptValue resultAsScriptValue = meshesToScriptValue(callback.engine(), result); - QScriptValueList args { resultAsScriptValue, true }; - callback.call(QScriptValue(), args); - } else { - QScriptValueList args { callback.engine()->undefinedValue(), false }; - callback.call(QScriptValue(), args); - } -} - glm::mat4 EntityScriptingInterface::getEntityTransform(const QUuid& entityID) { glm::mat4 result; if (_entityTree) { diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 4c4e2ffbfd..da201f93eb 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -37,7 +37,6 @@ #include "BaseScriptEngine.h" class EntityTree; -class MeshProxy; // helper factory to compose standardized, async metadata queries for "magic" Entity properties // like .script and .serverScripts. This is used for automated testing of core scripting features @@ -401,9 +400,6 @@ public slots: Q_INVOKABLE bool AABoxIntersectsCapsule(const glm::vec3& low, const glm::vec3& dimensions, const glm::vec3& start, const glm::vec3& end, float radius); - // FIXME move to a renderable entity interface - Q_INVOKABLE void getMeshes(QUuid entityID, QScriptValue callback); - /**jsdoc * Returns object to world transform, excluding scale * diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 14462e0558..50abe7928f 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1967,19 +1967,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } } } - { - int i = 0; - for (const auto& mesh : geometry.meshes) { - auto name = geometry.getModelNameOfMesh(i++); - if (!name.isEmpty()) { - if (mesh._mesh) { - mesh._mesh->displayName += "#" + name; - } else { - qDebug() << "modelName but no mesh._mesh" << name; - } - } - } - } + return geometryPtr; } @@ -1995,7 +1983,7 @@ FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QStri reader._loadLightmaps = loadLightmaps; reader._lightmapLevel = lightmapLevel; - qCDebug(modelformat) << "Reading FBX: " << url; + qDebug() << "Reading FBX: " << url; return reader.extractFBXGeometry(mapping, url); } diff --git a/libraries/fbx/src/OBJWriter.cpp b/libraries/fbx/src/OBJWriter.cpp index 4441ae6649..37bced8458 100644 --- a/libraries/fbx/src/OBJWriter.cpp +++ b/libraries/fbx/src/OBJWriter.cpp @@ -46,9 +46,12 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { QList meshNormalStartOffset; int currentVertexStartOffset = 0; int currentNormalStartOffset = 0; + int subMeshIndex = 0; + out << "# OBJWriter::writeOBJToTextStream\n"; // write out vertices (and maybe colors) foreach (const MeshPointer& mesh, meshes) { + out << "# vertices::subMeshIndex " << subMeshIndex++ << "\n"; meshVertexStartOffset.append(currentVertexStartOffset); const gpu::BufferView& vertexBuffer = mesh->getVertexBuffer(); @@ -81,7 +84,9 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { // write out normals bool haveNormals = true; + subMeshIndex = 0; foreach (const MeshPointer& mesh, meshes) { + out << "# normals::subMeshIndex " << subMeshIndex++ << "\n"; meshNormalStartOffset.append(currentNormalStartOffset); const gpu::BufferView& normalsBufferView = mesh->getAttributeBuffer(gpu::Stream::InputSlot::NORMAL); gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements(); @@ -98,7 +103,9 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { // write out faces int nth = 0; + subMeshIndex = 0; foreach (const MeshPointer& mesh, meshes) { + out << "# faces::subMeshIndex " << subMeshIndex++ << "\n"; currentVertexStartOffset = meshVertexStartOffset.takeFirst(); currentNormalStartOffset = meshNormalStartOffset.takeFirst(); @@ -106,35 +113,25 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { const gpu::BufferView& indexBuffer = mesh->getIndexBuffer(); graphics::Index partCount = (graphics::Index)mesh->getNumParts(); + QString name = (!mesh->displayName.size() ? QString("mesh-%1-part").arg(nth) : QString(mesh->displayName)) + .replace(QRegExp("[^-_a-zA-Z0-9]"), "_"); for (int partIndex = 0; partIndex < partCount; partIndex++) { const graphics::Mesh::Part& part = partBuffer.get(partIndex); - out << "g part-" << nth++ << "\n"; + out << QString("g %1-%2-%3\n").arg(subMeshIndex, 3, 10, QChar('0')).arg(name).arg(partIndex); - // graphics::Mesh::TRIANGLES - // TODO -- handle other formats - gpu::BufferView::Iterator indexItr = indexBuffer.cbegin(); - indexItr += part._startIndex; - - int indexCount = 0; - while (indexItr != indexBuffer.cend() && indexCount < part._numIndices) { - uint32_t index0 = *indexItr; - indexItr++; - indexCount++; - if (indexItr == indexBuffer.cend() || indexCount >= part._numIndices) { - qCDebug(modelformat) << "OBJWriter -- index buffer length isn't multiple of 3"; - break; + const bool shorts = indexBuffer._element == gpu::Element::INDEX_UINT16; + auto face = [&](uint32_t i0, uint32_t i1, uint32_t i2) { + uint32_t index0, index1, index2; + if (shorts) { + index0 = indexBuffer.get(i0); + index1 = indexBuffer.get(i1); + index2 = indexBuffer.get(i2); + } else { + index0 = indexBuffer.get(i0); + index1 = indexBuffer.get(i1); + index2 = indexBuffer.get(i2); } - uint32_t index1 = *indexItr; - indexItr++; - indexCount++; - if (indexItr == indexBuffer.cend() || indexCount >= part._numIndices) { - qCDebug(modelformat) << "OBJWriter -- index buffer length isn't multiple of 3"; - break; - } - uint32_t index2 = *indexItr; - indexItr++; - indexCount++; out << "f "; if (haveNormals) { @@ -146,6 +143,39 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { out << currentVertexStartOffset + index1 + 1 << " "; out << currentVertexStartOffset + index2 + 1 << "\n"; } + }; + + // graphics::Mesh::TRIANGLES / graphics::Mesh::QUADS + // TODO -- handle other formats + uint32_t len = part._startIndex + part._numIndices; + auto stringFromTopology = [&](graphics::Mesh::Topology topo) -> QString { + return topo == graphics::Mesh::Topology::QUADS ? "QUADS" : + topo == graphics::Mesh::Topology::QUAD_STRIP ? "QUAD_STRIP" : + topo == graphics::Mesh::Topology::TRIANGLES ? "TRIANGLES" : + topo == graphics::Mesh::Topology::TRIANGLE_STRIP ? "TRIANGLE_STRIP" : + topo == graphics::Mesh::Topology::QUAD_STRIP ? "QUAD_STRIP" : + QString("topo:%1").arg((int)topo); + }; + + qCDebug(modelformat) << "OBJWriter -- part" << partIndex << "topo" << stringFromTopology(part._topology) << "index elements" << (shorts ? "uint16_t" : "uint32_t"); + if (part._topology == graphics::Mesh::TRIANGLES && len % 3 != 0) { + qCDebug(modelformat) << "OBJWriter -- index buffer length isn't a multiple of 3" << len; + } + if (part._topology == graphics::Mesh::QUADS && len % 4 != 0) { + qCDebug(modelformat) << "OBJWriter -- index buffer length isn't a multiple of 4" << len; + } + if (len > indexBuffer.getNumElements()) { + qCDebug(modelformat) << "OBJWriter -- len > index size" << len << indexBuffer.getNumElements(); + } + if (part._topology == graphics::Mesh::QUADS) { + for (uint32_t idx = part._startIndex; idx+3 < len; idx += 4) { + face(idx+0, idx+1, idx+3); + face(idx+1, idx+2, idx+3); + } + } else if (part._topology == graphics::Mesh::TRIANGLES) { + for (uint32_t idx = part._startIndex; idx+2 < len; idx += 3) { + face(idx+0, idx+1, idx+2); + } } out << "\n"; } diff --git a/libraries/graphics-scripting/CMakeLists.txt b/libraries/graphics-scripting/CMakeLists.txt new file mode 100644 index 0000000000..e7fa3de155 --- /dev/null +++ b/libraries/graphics-scripting/CMakeLists.txt @@ -0,0 +1,5 @@ +set(TARGET_NAME graphics-scripting) +setup_hifi_library() +link_hifi_libraries(shared networking graphics fbx model-networking script-engine) +include_hifi_library_headers(gpu) +include_hifi_library_headers(graphics-scripting) diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp new file mode 100644 index 0000000000..e865ed0e5a --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp @@ -0,0 +1,195 @@ +#include "./graphics-scripting/BufferViewHelpers.h" + +#include +#include + +#include +#include +#include + +#include + +#ifdef DEBUG_BUFFERVIEW_SCRIPTING + #include "DebugNames.h" +#endif + +namespace { + const std::array XYZW = {{ "x", "y", "z", "w" }}; + const std::array ZERO123 = {{ "0", "1", "2", "3" }}; +} + +template +QVariant getBufferViewElement(const gpu::BufferView& view, quint32 index, bool asArray = false) { + return glmVecToVariant(view.get(index), asArray); +} + +template +void setBufferViewElement(const gpu::BufferView& view, quint32 index, const QVariant& v) { + view.edit(index) = glmVecFromVariant(v); +} + +//FIXME copied from Model.cpp +static void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent) { + auto absNormal = glm::abs(normal); + auto absTangent = glm::abs(tangent); + normal /= glm::max(1e-6f, glm::max(glm::max(absNormal.x, absNormal.y), absNormal.z)); + tangent /= glm::max(1e-6f, glm::max(glm::max(absTangent.x, absTangent.y), absTangent.z)); + normal = glm::clamp(normal, -1.0f, 1.0f); + tangent = glm::clamp(tangent, -1.0f, 1.0f); + normal *= 511.0f; + tangent *= 511.0f; + normal = glm::round(normal); + tangent = glm::round(tangent); + + glm::detail::i10i10i10i2 normalStruct; + glm::detail::i10i10i10i2 tangentStruct; + normalStruct.data.x = int(normal.x); + normalStruct.data.y = int(normal.y); + normalStruct.data.z = int(normal.z); + normalStruct.data.w = 0; + tangentStruct.data.x = int(tangent.x); + tangentStruct.data.y = int(tangent.y); + tangentStruct.data.z = int(tangent.z); + tangentStruct.data.w = 0; + packedNormal = normalStruct.pack; + packedTangent = tangentStruct.pack; +} + +bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v) { + const auto& element = view._element; + const auto vecN = element.getScalarCount(); + const auto dataType = element.getType(); + const auto byteLength = element.getSize(); + const auto BYTES_PER_ELEMENT = byteLength / vecN; + if (BYTES_PER_ELEMENT == 1) { + switch(vecN) { + case 2: setBufferViewElement(view, index, v); return true; + case 3: setBufferViewElement(view, index, v); return true; + case 4: { + if (element == gpu::Element::COLOR_RGBA_32) { + glm::uint32 rawColor;// = glm::packUnorm4x8(glm::vec4(glmVecFromVariant(v), 0.0f)); + glm::uint32 unused; + packNormalAndTangent(glmVecFromVariant(v), glm::vec3(), rawColor, unused); + view.edit(index) = rawColor; + } else if (element == gpu::Element::VEC4F_NORMALIZED_XYZ10W2) { + glm::uint32 packedNormal;// = glm::packSnorm3x10_1x2(glm::vec4(glmVecFromVariant(v), 0.0f)); + glm::uint32 unused; + packNormalAndTangent(glm::vec3(), glmVecFromVariant(v), unused, packedNormal); + view.edit(index) = packedNormal; + } + setBufferViewElement(view, index, v); return true; + } + } + } else if (BYTES_PER_ELEMENT == 2) { + switch(vecN) { + case 2: setBufferViewElement(view, index, v); return true; + case 3: setBufferViewElement(view, index, v); return true; + case 4: setBufferViewElement(view, index, v); return true; + } + } else if (BYTES_PER_ELEMENT == 4) { + if (dataType == gpu::FLOAT) { + switch(vecN) { + case 2: setBufferViewElement(view, index, v); return true; + case 3: setBufferViewElement(view, index, v); return true; + case 4: setBufferViewElement(view, index, v); return true; + } + } else { + switch(vecN) { + case 2: setBufferViewElement(view, index, v); return true; + case 3: setBufferViewElement(view, index, v); return true; + case 4: setBufferViewElement(view, index, v); return true; + } + } + } + return false; +} + +QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) { + const auto& element = view._element; + const auto vecN = element.getScalarCount(); + const auto dataType = element.getType(); + const auto byteLength = element.getSize(); + const auto BYTES_PER_ELEMENT = byteLength / vecN; + Q_ASSERT(index < view.getNumElements()); + Q_ASSERT(index * vecN * BYTES_PER_ELEMENT < (view._size - vecN * BYTES_PER_ELEMENT)); + if (BYTES_PER_ELEMENT == 1) { + switch(vecN) { + case 2: return getBufferViewElement(view, index, asArray); + case 3: return getBufferViewElement(view, index, asArray); + case 4: { + if (element == gpu::Element::COLOR_RGBA_32) { + auto rawColor = view.get(index); + return glmVecToVariant(glm::vec3(glm::unpackUnorm4x8(rawColor))); + } else if (element == gpu::Element::VEC4F_NORMALIZED_XYZ10W2) { + auto packedNormal = view.get(index); + return glmVecToVariant(glm::vec3(glm::unpackSnorm3x10_1x2(packedNormal))); + } + + return getBufferViewElement(view, index, asArray); + } + } + } else if (BYTES_PER_ELEMENT == 2) { + switch(vecN) { + case 2: return getBufferViewElement(view, index, asArray); + case 3: return getBufferViewElement(view, index, asArray); + case 4: return getBufferViewElement(view, index, asArray); + } + } else if (BYTES_PER_ELEMENT == 4) { + if (dataType == gpu::FLOAT) { + switch(vecN) { + case 2: return getBufferViewElement(view, index, asArray); + case 3: return getBufferViewElement(view, index, asArray); + case 4: return getBufferViewElement(view, index, asArray); + } + } else { + switch(vecN) { + case 2: return getBufferViewElement(view, index, asArray); + case 3: return getBufferViewElement(view, index, asArray); + case 4: return getBufferViewElement(view, index, asArray); + } + } + } + return QVariant(); +} + +template +QVariant glmVecToVariant(const T& v, bool asArray /*= false*/) { + static const auto len = T().length(); + if (asArray) { + QVariantList list; + for (int i = 0; i < len ; i++) { + list << v[i]; + } + return list; + } else { + QVariantMap obj; + for (int i = 0; i < len ; i++) { + obj[XYZW[i]] = v[i]; + } + return obj; + } +} +template +const T glmVecFromVariant(const QVariant& v) { + auto isMap = v.type() == (QVariant::Type)QMetaType::QVariantMap; + static const auto len = T().length(); + const auto& components = isMap ? XYZW : ZERO123; + T result; + QVariantMap map; + QVariantList list; + if (isMap) map = v.toMap(); else list = v.toList(); + for (int i = 0; i < len ; i++) { + float value; + if (isMap) { + value = map.value(components[i]).toFloat(); + } else { + value = list.value(i).toFloat(); + } + if (value != value) { // NAN + qWarning().nospace()<< "vec" << len << "." << components[i] << " NAN received from script.... " << v.toString(); + } + result[i] = value; + } + return result; +} + diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h new file mode 100644 index 0000000000..0fe2602f6c --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h @@ -0,0 +1,18 @@ +// +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include + +namespace gpu { class BufferView; } + +template QVariant glmVecToVariant(const T& v, bool asArray = false); +template const T glmVecFromVariant(const QVariant& v); +QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, bool asArray = false, const char* hint = ""); +bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v); + + diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp new file mode 100644 index 0000000000..367c0589e9 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp @@ -0,0 +1,83 @@ +#include "BufferViewScripting.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#ifdef DEBUG_BUFFERVIEW_SCRIPTING + #include +#endif + +namespace { + const std::array XYZW = {{ "x", "y", "z", "w" }}; + const std::array ZERO123 = {{ "0", "1", "2", "3" }}; +} + +template +QScriptValue getBufferViewElement(QScriptEngine* js, const gpu::BufferView& view, quint32 index, bool asArray = false) { + return glmVecToScriptValue(js, view.get(index), asArray); +} + +QScriptValue bufferViewElementToScriptValue(QScriptEngine* engine, const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) { + QVariant result = bufferViewElementToVariant(view, index, asArray, hint); + if (!result.isValid()) { + return QScriptValue::NullValue; + } + return engine->toScriptValue(result); +} + +template +void setBufferViewElement(const gpu::BufferView& view, quint32 index, const QScriptValue& v) { + view.edit(index) = glmVecFromScriptValue(v); +} + +bool bufferViewElementFromScriptValue(const QScriptValue& v, const gpu::BufferView& view, quint32 index) { + return bufferViewElementFromVariant(view, index, v.toVariant()); +} + +// + +template +QScriptValue glmVecToScriptValue(QScriptEngine *js, const T& v, bool asArray) { + static const auto len = T().length(); + const auto& components = asArray ? ZERO123 : XYZW; + auto obj = asArray ? js->newArray() : js->newObject(); + for (int i = 0; i < len ; i++) { + const auto key = components[i]; + const auto value = v[i]; + if (value != value) { // NAN +#ifdef DEV_BUILD + qWarning().nospace()<< "vec" << len << "." << key << " converting NAN to javascript NaN.... " << value; +#endif + obj.setProperty(key, js->globalObject().property("NaN")); + } else { + obj.setProperty(key, value); + } + } + return obj; +} + +template +const T glmVecFromScriptValue(const QScriptValue& v) { + static const auto len = T().length(); + const auto& components = v.property("x").isValid() ? XYZW : ZERO123; + T result; + for (int i = 0; i < len ; i++) { + const auto key = components[i]; + const auto value = v.property(key).toNumber(); +#ifdef DEV_BUILD + if (value != value) { // NAN + qWarning().nospace()<< "vec" << len << "." << key << " NAN received from script.... " << v.toVariant().toString(); + } +#endif + result[i] = value; + } + return result; +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.h b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.h new file mode 100644 index 0000000000..f2e3fe734e --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace gpu { class BufferView; } +class QScriptValue; +class QScriptEngine; +template QScriptValue glmVecToScriptValue(QScriptEngine *js, const T& v, bool asArray = false); +template const T glmVecFromScriptValue(const QScriptValue& v); +QScriptValue bufferViewElementToScriptValue(QScriptEngine* engine, const gpu::BufferView& view, quint32 index, bool asArray = false, const char* hint = ""); +bool bufferViewElementFromScriptValue(const QScriptValue& v, const gpu::BufferView& view, quint32 index); diff --git a/libraries/graphics-scripting/src/graphics-scripting/DebugNames.h b/libraries/graphics-scripting/src/graphics-scripting/DebugNames.h new file mode 100644 index 0000000000..e5edf1c9d8 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/DebugNames.h @@ -0,0 +1,72 @@ +#pragma once +#include +#include +#include +#include +#include +//#include + +Q_DECLARE_METATYPE(gpu::Type); +#ifdef QT_MOC_RUN +class DebugNames { + Q_OBJECT +public: +#else + namespace DebugNames { + Q_NAMESPACE + #endif + +enum Type : uint8_t { + + FLOAT = 0, + INT32, + UINT32, + HALF, + INT16, + UINT16, + INT8, + UINT8, + + NINT32, + NUINT32, + NINT16, + NUINT16, + NINT8, + NUINT8, + + COMPRESSED, + + NUM_TYPES, + + BOOL = UINT8, + NORMALIZED_START = NINT32, +}; + + Q_ENUM_NS(Type) + enum InputSlot { + POSITION = 0, + NORMAL = 1, + COLOR = 2, + TEXCOORD0 = 3, + TEXCOORD = TEXCOORD0, + TANGENT = 4, + SKIN_CLUSTER_INDEX = 5, + SKIN_CLUSTER_WEIGHT = 6, + TEXCOORD1 = 7, + TEXCOORD2 = 8, + TEXCOORD3 = 9, + TEXCOORD4 = 10, + + NUM_INPUT_SLOTS, + + DRAW_CALL_INFO = 15, // Reserve last input slot for draw call infos + }; + + Q_ENUM_NS(InputSlot) + inline QString stringFrom(Type t) { return QVariant::fromValue(t).toString(); } + inline QString stringFrom(InputSlot t) { return QVariant::fromValue(t).toString(); } + inline QString stringFrom(gpu::Type t) { return stringFrom((Type)t); } + inline QString stringFrom(gpu::Stream::Slot t) { return stringFrom((InputSlot)t); } + + extern const QMetaObject staticMetaObject; + }; diff --git a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp new file mode 100644 index 0000000000..68a00bc02c --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp @@ -0,0 +1,836 @@ +// +// ModelScriptingInterface.cpp +// libraries/script-engine/src +// +// Created by Seth Alves on 2017-1-27. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ModelScriptingInterface.h" +#include +#include +#include +#include +#include "BaseScriptEngine.h" +#include "ScriptEngineLogging.h" +#include "OBJWriter.h" +#include "OBJReader.h" +//#include "ui/overlays/Base3DOverlay.h" +//#include "EntityTreeRenderer.h" +//#include "avatar/AvatarManager.h" +//#include "RenderableEntityItem.h" + +#include +#include + +#include + + +#include + +#include +#include "BufferViewScripting.h" + +#include "ScriptableMesh.h" + +using ScriptableMesh = scriptable::ScriptableMesh; + +#include "ModelScriptingInterface.moc" + +namespace { + QLoggingCategory model_scripting { "hifi.model.scripting" }; +} + +ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(parent) { + if (auto scriptEngine = qobject_cast(parent)) { + this->registerMetaTypes(scriptEngine); + } +} + +QString ModelScriptingInterface::meshToOBJ(const scriptable::ScriptableModel& _in) { + const auto& in = _in.getMeshes(); + qCDebug(model_scripting) << "meshToOBJ" << in.size(); + if (in.size()) { + QList meshes; + foreach (const auto meshProxy, in) { + qCDebug(model_scripting) << "meshToOBJ" << meshProxy.get(); + if (meshProxy) { + meshes.append(getMeshPointer(meshProxy)); + } + } + if (meshes.size()) { + return writeOBJToString(meshes); + } + } + context()->throwError(QString("null mesh")); + return QString(); +} + +QScriptValue ModelScriptingInterface::appendMeshes(scriptable::ScriptableModel _in) { + const auto& in = _in.getMeshes(); + + // figure out the size of the resulting mesh + size_t totalVertexCount { 0 }; + size_t totalColorCount { 0 }; + size_t totalNormalCount { 0 }; + size_t totalIndexCount { 0 }; + foreach (const scriptable::ScriptableMeshPointer meshProxy, in) { + scriptable::MeshPointer mesh = getMeshPointer(meshProxy); + totalVertexCount += mesh->getNumVertices(); + + int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h + const gpu::BufferView& colorsBufferView = mesh->getAttributeBuffer(attributeTypeColor); + gpu::BufferView::Index numColors = (gpu::BufferView::Index)colorsBufferView.getNumElements(); + totalColorCount += numColors; + + int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h + const gpu::BufferView& normalsBufferView = mesh->getAttributeBuffer(attributeTypeNormal); + gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements(); + totalNormalCount += numNormals; + + totalIndexCount += mesh->getNumIndices(); + } + + // alloc the resulting mesh + gpu::Resource::Size combinedVertexSize = totalVertexCount * sizeof(glm::vec3); + unsigned char* combinedVertexData = new unsigned char[combinedVertexSize]; + unsigned char* combinedVertexDataCursor = combinedVertexData; + + gpu::Resource::Size combinedColorSize = totalColorCount * sizeof(glm::vec3); + unsigned char* combinedColorData = new unsigned char[combinedColorSize]; + unsigned char* combinedColorDataCursor = combinedColorData; + + gpu::Resource::Size combinedNormalSize = totalNormalCount * sizeof(glm::vec3); + unsigned char* combinedNormalData = new unsigned char[combinedNormalSize]; + unsigned char* combinedNormalDataCursor = combinedNormalData; + + gpu::Resource::Size combinedIndexSize = totalIndexCount * sizeof(uint32_t); + unsigned char* combinedIndexData = new unsigned char[combinedIndexSize]; + unsigned char* combinedIndexDataCursor = combinedIndexData; + + uint32_t indexStartOffset { 0 }; + + foreach (const scriptable::ScriptableMeshPointer meshProxy, in) { + scriptable::MeshPointer mesh = getMeshPointer(meshProxy); + mesh->forEach( + [&](glm::vec3 position){ + memcpy(combinedVertexDataCursor, &position, sizeof(position)); + combinedVertexDataCursor += sizeof(position); + }, + [&](glm::vec3 color){ + memcpy(combinedColorDataCursor, &color, sizeof(color)); + combinedColorDataCursor += sizeof(color); + }, + [&](glm::vec3 normal){ + memcpy(combinedNormalDataCursor, &normal, sizeof(normal)); + combinedNormalDataCursor += sizeof(normal); + }, + [&](uint32_t index){ + index += indexStartOffset; + memcpy(combinedIndexDataCursor, &index, sizeof(index)); + combinedIndexDataCursor += sizeof(index); + }); + + gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices(); + indexStartOffset += numVertices; + } + + graphics::MeshPointer result(new graphics::Mesh()); + + gpu::Element vertexElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); + gpu::Buffer* combinedVertexBuffer = new gpu::Buffer(combinedVertexSize, combinedVertexData); + gpu::BufferPointer combinedVertexBufferPointer(combinedVertexBuffer); + gpu::BufferView combinedVertexBufferView(combinedVertexBufferPointer, vertexElement); + result->setVertexBuffer(combinedVertexBufferView); + + int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h + gpu::Element colorElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); + gpu::Buffer* combinedColorsBuffer = new gpu::Buffer(combinedColorSize, combinedColorData); + gpu::BufferPointer combinedColorsBufferPointer(combinedColorsBuffer); + gpu::BufferView combinedColorsBufferView(combinedColorsBufferPointer, colorElement); + result->addAttribute(attributeTypeColor, combinedColorsBufferView); + + int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h + gpu::Element normalElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); + gpu::Buffer* combinedNormalsBuffer = new gpu::Buffer(combinedNormalSize, combinedNormalData); + gpu::BufferPointer combinedNormalsBufferPointer(combinedNormalsBuffer); + gpu::BufferView combinedNormalsBufferView(combinedNormalsBufferPointer, normalElement); + result->addAttribute(attributeTypeNormal, combinedNormalsBufferView); + + gpu::Element indexElement = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW); + gpu::Buffer* combinedIndexesBuffer = new gpu::Buffer(combinedIndexSize, combinedIndexData); + gpu::BufferPointer combinedIndexesBufferPointer(combinedIndexesBuffer); + gpu::BufferView combinedIndexesBufferView(combinedIndexesBufferPointer, indexElement); + result->setIndexBuffer(combinedIndexesBufferView); + + std::vector parts; + parts.emplace_back(graphics::Mesh::Part((graphics::Index)0, // startIndex + (graphics::Index)result->getNumIndices(), // numIndices + (graphics::Index)0, // baseVertex + graphics::Mesh::TRIANGLES)); // topology + result->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), + (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); + + + scriptable::ScriptableMeshPointer resultProxy = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, result)); + return engine()->toScriptValue(result); +} + +QScriptValue ModelScriptingInterface::transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform) { + auto mesh = getMeshPointer(meshProxy); + if (!mesh) { + return false; + } + + graphics::MeshPointer result = mesh->map([&](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); }, + [&](glm::vec3 color){ return color; }, + [&](glm::vec3 normal){ return glm::vec3(transform * glm::vec4(normal, 0.0f)); }, + [&](uint32_t index){ return index; }); + scriptable::ScriptableMeshPointer resultProxy = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, result)); + return engine()->toScriptValue(resultProxy); +} + +QScriptValue ModelScriptingInterface::getVertexCount(scriptable::ScriptableMeshPointer meshProxy) { + auto mesh = getMeshPointer(meshProxy); + if (!mesh) { + return -1; + } + return (uint32_t)mesh->getNumVertices(); +} + +QScriptValue ModelScriptingInterface::getVertex(scriptable::ScriptableMeshPointer meshProxy, mesh::uint32 vertexIndex) { + auto mesh = getMeshPointer(meshProxy); + + const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer(); + auto numVertices = mesh->getNumVertices(); + + if (vertexIndex >= numVertices) { + context()->throwError(QString("invalid index: %1 [0,%2)").arg(vertexIndex).arg(numVertices)); + return QScriptValue::NullValue; + } + + glm::vec3 pos = vertexBufferView.get(vertexIndex); + return engine()->toScriptValue(pos); +} + +QScriptValue ModelScriptingInterface::newMesh(const QVector& vertices, + const QVector& normals, + const QVector& faces) { + graphics::MeshPointer mesh(new graphics::Mesh()); + + // vertices + auto vertexBuffer = std::make_shared(vertices.size() * sizeof(glm::vec3), (gpu::Byte*)vertices.data()); + auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); + gpu::BufferView vertexBufferView(vertexBufferPtr, 0, vertexBufferPtr->getSize(), + sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + mesh->setVertexBuffer(vertexBufferView); + + if (vertices.size() == normals.size()) { + // normals + auto normalBuffer = std::make_shared(normals.size() * sizeof(glm::vec3), (gpu::Byte*)normals.data()); + auto normalBufferPtr = gpu::BufferPointer(normalBuffer); + gpu::BufferView normalBufferView(normalBufferPtr, 0, normalBufferPtr->getSize(), + sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + mesh->addAttribute(gpu::Stream::NORMAL, normalBufferView); + } else { + qCWarning(model_scripting, "ModelScriptingInterface::newMesh normals must be same length as vertices"); + } + + // indices (faces) + int VERTICES_PER_TRIANGLE = 3; + int indexBufferSize = faces.size() * sizeof(uint32_t) * VERTICES_PER_TRIANGLE; + unsigned char* indexData = new unsigned char[indexBufferSize]; + unsigned char* indexDataCursor = indexData; + foreach(const mesh::MeshFace& meshFace, faces) { + for (int i = 0; i < VERTICES_PER_TRIANGLE; i++) { + memcpy(indexDataCursor, &meshFace.vertexIndices[i], sizeof(uint32_t)); + indexDataCursor += sizeof(uint32_t); + } + } + auto indexBuffer = std::make_shared(indexBufferSize, (gpu::Byte*)indexData); + auto indexBufferPtr = gpu::BufferPointer(indexBuffer); + gpu::BufferView indexBufferView(indexBufferPtr, gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW)); + mesh->setIndexBuffer(indexBufferView); + + // parts + std::vector parts; + parts.emplace_back(graphics::Mesh::Part((graphics::Index)0, // startIndex + (graphics::Index)faces.size() * 3, // numIndices + (graphics::Index)0, // baseVertex + graphics::Mesh::TRIANGLES)); // topology + mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), + (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); + + + + scriptable::ScriptableMeshPointer meshProxy = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, mesh)); + return engine()->toScriptValue(meshProxy); +} + +QScriptValue ModelScriptingInterface::mapAttributeValues( + QScriptValue _in, + QScriptValue scopeOrCallback, + QScriptValue methodOrName + ) { + qCInfo(model_scripting) << "mapAttributeValues" << _in.toVariant().typeName() << _in.toVariant().toString() << _in.toQObject(); + auto in = qscriptvalue_cast(_in).getMeshes(); + if (in.size()) { + foreach (scriptable::ScriptableMeshPointer meshProxy, in) { + mapMeshAttributeValues(meshProxy, scopeOrCallback, methodOrName); + } + return thisObject(); + } else if (auto meshProxy = qobject_cast(_in.toQObject())) { + return mapMeshAttributeValues(meshProxy->shared_from_this(), scopeOrCallback, methodOrName); + } else { + context()->throwError("invalid ModelProxy || MeshProxyPointer"); + } + return false; +} + + +QScriptValue ModelScriptingInterface::unrollVertices(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals) { + auto mesh = getMeshPointer(meshProxy); + qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices" << !!mesh<< !!meshProxy; + if (!mesh) { + return QScriptValue(); + } + + auto positions = mesh->getVertexBuffer(); + auto indices = mesh->getIndexBuffer(); + quint32 numPoints = (quint32)indices.getNumElements(); + auto buffer = new gpu::Buffer(); + buffer->resize(numPoints * sizeof(uint32_t)); + auto newindices = gpu::BufferView(buffer, { gpu::SCALAR, gpu::UINT32, gpu::INDEX }); + qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices numPoints" << numPoints; + auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); + for (const auto& a : attributeViews) { + auto& view = a.second; + auto sz = view._element.getSize(); + auto buffer = new gpu::Buffer(); + buffer->resize(numPoints * sz); + auto points = gpu::BufferView(buffer, view._element); + auto src = (uint8_t*)view._buffer->getData(); + auto dest = (uint8_t*)points._buffer->getData(); + auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices buffer" << a.first; + qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices source" << view.getNumElements(); + qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices dest" << points.getNumElements(); + qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices sz" << sz << src << dest << slot; + auto esize = indices._element.getSize(); + const char* hint= a.first.toStdString().c_str(); + for(quint32 i = 0; i < numPoints; i++) { + quint32 index = esize == 4 ? indices.get(i) : indices.get(i); + newindices.edit(i) = i; + bufferViewElementFromVariant( + points, i, + bufferViewElementToVariant(view, index, false, hint) + ); + } + if (slot == gpu::Stream::POSITION) { + mesh->setVertexBuffer(points); + } else { + mesh->addAttribute(slot, points); + } + } + mesh->setIndexBuffer(newindices); + if (recalcNormals) { + recalculateNormals(meshProxy); + } + return true; +} + +namespace { + template + gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType) { + auto vertexBuffer = std::make_shared( + elements.size() * sizeof(T), + (gpu::Byte*)elements.data() + ); + return { vertexBuffer, 0, vertexBuffer->getSize(),sizeof(T), elementType }; + } + + gpu::BufferView cloneBufferView(const gpu::BufferView& input) { + //qCInfo(model_scripting) << "input" << input.getNumElements() << input._buffer->getSize(); + auto output = gpu::BufferView( + std::make_shared(input._buffer->getSize(), input._buffer->getData()), + input._offset, + input._size, + input._stride, + input._element + ); + //qCInfo(model_scripting) << "after" << output.getNumElements() << output._buffer->getSize(); + return output; + } + + gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numElements) { + auto effectiveSize = input._buffer->getSize() / input.getNumElements(); + qCInfo(model_scripting) << "resize input" << input.getNumElements() << input._buffer->getSize() << "effectiveSize" << effectiveSize; + auto vsize = input._element.getSize() * numElements; + gpu::Byte *data = new gpu::Byte[vsize]; + memset(data, 0, vsize); + auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data); + delete[] data; + auto output = gpu::BufferView(buffer, input._element); + qCInfo(model_scripting) << "resized output" << output.getNumElements() << output._buffer->getSize(); + return output; + } +} + +bool ModelScriptingInterface::replaceMeshData(scriptable::ScriptableMeshPointer dest, scriptable::ScriptableMeshPointer src, const QVector& attributeNames) { + auto target = getMeshPointer(dest); + auto source = getMeshPointer(src); + if (!target || !source) { + context()->throwError("ModelScriptingInterface::replaceMeshData -- expected dest and src to be valid mesh proxy pointers"); + return false; + } + + QVector attributes = attributeNames.isEmpty() ? src->getAttributeNames() : attributeNames; + + //qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData -- source:" << source->displayName << "target:" << target->displayName << "attributes:" << attributes; + + // remove attributes only found on target mesh, unless user has explicitly specified the relevant attribute names + if (attributeNames.isEmpty()) { + auto attributeViews = ScriptableMesh::gatherBufferViews(target); + for (const auto& a : attributeViews) { + auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + if (!attributes.contains(a.first)) { + //qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData -- pruning target attribute" << a.first << slot; + target->removeAttribute(slot); + } + } + } + + target->setVertexBuffer(cloneBufferView(source->getVertexBuffer())); + target->setIndexBuffer(cloneBufferView(source->getIndexBuffer())); + target->setPartBuffer(cloneBufferView(source->getPartBuffer())); + + for (const auto& a : attributes) { + auto slot = ScriptableMesh::ATTRIBUTES[a]; + if (slot == gpu::Stream::POSITION) { + continue; + } + // auto& before = target->getAttributeBuffer(slot); + auto& input = source->getAttributeBuffer(slot); + if (input.getNumElements() == 0) { + //qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData buffer is empty -- pruning" << a << slot; + target->removeAttribute(slot); + } else { + // if (before.getNumElements() == 0) { + // qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData target buffer is empty -- adding" << a << slot; + // } else { + // qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData target buffer exists -- updating" << a << slot; + // } + target->addAttribute(slot, cloneBufferView(input)); + } + // auto& after = target->getAttributeBuffer(slot); + // qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements(); + } + + + return true; +} + +bool ModelScriptingInterface::dedupeVertices(scriptable::ScriptableMeshPointer meshProxy, float epsilon) { + auto mesh = getMeshPointer(meshProxy); + if (!mesh) { + return false; + } + auto positions = mesh->getVertexBuffer(); + auto numPositions = positions.getNumElements(); + const auto epsilon2 = epsilon*epsilon; + + QVector uniqueVerts; + uniqueVerts.reserve((int)numPositions); + QMap remapIndices; + + for (quint32 i = 0; i < numPositions; i++) { + const quint32 numUnique = uniqueVerts.size(); + const auto& position = positions.get(i); + bool unique = true; + for (quint32 j = 0; j < numUnique; j++) { + if (glm::length2(uniqueVerts[j] - position) <= epsilon2) { + remapIndices[i] = j; + unique = false; + break; + } + } + if (unique) { + uniqueVerts << position; + remapIndices[i] = numUnique; + } + } + + qCInfo(model_scripting) << "//VERTS before" << numPositions << "after" << uniqueVerts.size(); + + auto indices = mesh->getIndexBuffer(); + auto numIndices = indices.getNumElements(); + auto esize = indices._element.getSize(); + QVector newIndices; + newIndices.reserve((int)numIndices); + for (quint32 i = 0; i < numIndices; i++) { + quint32 index = esize == 4 ? indices.get(i) : indices.get(i); + if (remapIndices.contains(index)) { + //qCInfo(model_scripting) << i << index << "->" << remapIndices[index]; + newIndices << remapIndices[index]; + } else { + qCInfo(model_scripting) << i << index << "!remapIndices[index]"; + } + } + + mesh->setIndexBuffer(bufferViewFromVector(newIndices, { gpu::SCALAR, gpu::UINT32, gpu::INDEX })); + mesh->setVertexBuffer(bufferViewFromVector(uniqueVerts, { gpu::VEC3, gpu::FLOAT, gpu::XYZ })); + + auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); + quint32 numUniqueVerts = uniqueVerts.size(); + for (const auto& a : attributeViews) { + auto& view = a.second; + auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + if (slot == gpu::Stream::POSITION) { + continue; + } + qCInfo(model_scripting) << "ModelScriptingInterface::dedupeVertices" << a.first << slot << view.getNumElements(); + auto newView = resizedBufferView(view, numUniqueVerts); + qCInfo(model_scripting) << a.first << "before: #" << view.getNumElements() << "after: #" << newView.getNumElements(); + quint32 numElements = (quint32)view.getNumElements(); + for (quint32 i = 0; i < numElements; i++) { + quint32 fromVertexIndex = i; + quint32 toVertexIndex = remapIndices.contains(fromVertexIndex) ? remapIndices[fromVertexIndex] : fromVertexIndex; + bufferViewElementFromVariant( + newView, toVertexIndex, + bufferViewElementToVariant(view, fromVertexIndex, false, "dedupe") + ); + } + mesh->addAttribute(slot, newView); + } + return true; +} + +QScriptValue ModelScriptingInterface::cloneMesh(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals) { + auto mesh = getMeshPointer(meshProxy); + if (!mesh) { + return QScriptValue::NullValue; + } + graphics::MeshPointer clone(new graphics::Mesh()); + clone->displayName = mesh->displayName + "-clone"; + qCInfo(model_scripting) << "ModelScriptingInterface::cloneMesh" << !!mesh<< !!meshProxy; + if (!mesh) { + return QScriptValue::NullValue; + } + + clone->setIndexBuffer(cloneBufferView(mesh->getIndexBuffer())); + clone->setPartBuffer(cloneBufferView(mesh->getPartBuffer())); + auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); + for (const auto& a : attributeViews) { + auto& view = a.second; + auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + qCInfo(model_scripting) << "ModelScriptingInterface::cloneVertices buffer" << a.first << slot; + auto points = cloneBufferView(view); + qCInfo(model_scripting) << "ModelScriptingInterface::cloneVertices source" << view.getNumElements(); + qCInfo(model_scripting) << "ModelScriptingInterface::cloneVertices dest" << points.getNumElements(); + if (slot == gpu::Stream::POSITION) { + clone->setVertexBuffer(points); + } else { + clone->addAttribute(slot, points); + } + } + + auto result = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, clone)); + if (recalcNormals) { + recalculateNormals(result); + } + return engine()->toScriptValue(result); +} + +bool ModelScriptingInterface::recalculateNormals(scriptable::ScriptableMeshPointer meshProxy) { + qCInfo(model_scripting) << "Recalculating normals" << !!meshProxy; + auto mesh = getMeshPointer(meshProxy); + if (!mesh) { + return false; + } + ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); // ensures #normals >= #positions + auto normals = mesh->getAttributeBuffer(gpu::Stream::NORMAL); + auto verts = mesh->getVertexBuffer(); + auto indices = mesh->getIndexBuffer(); + auto esize = indices._element.getSize(); + auto numPoints = indices.getNumElements(); + const auto TRIANGLE = 3; + quint32 numFaces = (quint32)numPoints / TRIANGLE; + //QVector faces; + QVector faceNormals; + QMap> vertexToFaces; + //faces.resize(numFaces); + faceNormals.resize(numFaces); + auto numNormals = normals.getNumElements(); + qCInfo(model_scripting) << QString("numFaces: %1, numNormals: %2, numPoints: %3").arg(numFaces).arg(numNormals).arg(numPoints); + if (normals.getNumElements() != verts.getNumElements()) { + return false; + } + for (quint32 i = 0; i < numFaces; i++) { + quint32 I = TRIANGLE * i; + quint32 i0 = esize == 4 ? indices.get(I+0) : indices.get(I+0); + quint32 i1 = esize == 4 ? indices.get(I+1) : indices.get(I+1); + quint32 i2 = esize == 4 ? indices.get(I+2) : indices.get(I+2); + + Triangle face = { + verts.get(i1), + verts.get(i2), + verts.get(i0) + }; + faceNormals[i] = face.getNormal(); + if (glm::isnan(faceNormals[i].x)) { + qCInfo(model_scripting) << i << i0 << i1 << i2 << vec3toVariant(face.v0) << vec3toVariant(face.v1) << vec3toVariant(face.v2); + break; + } + vertexToFaces[glm::to_string(face.v0).c_str()] << i; + vertexToFaces[glm::to_string(face.v1).c_str()] << i; + vertexToFaces[glm::to_string(face.v2).c_str()] << i; + } + for (quint32 j = 0; j < numNormals; j++) { + //auto v = verts.get(j); + glm::vec3 normal { 0.0f, 0.0f, 0.0f }; + QString key { glm::to_string(verts.get(j)).c_str() }; + const auto& faces = vertexToFaces.value(key); + if (faces.size()) { + for (const auto i : faces) { + normal += faceNormals[i]; + } + normal *= 1.0f / (float)faces.size(); + } else { + static int logged = 0; + if (logged++ < 10) { + qCInfo(model_scripting) << "no faces for key!?" << key; + } + normal = verts.get(j); + } + if (glm::isnan(normal.x)) { + static int logged = 0; + if (logged++ < 10) { + qCInfo(model_scripting) << "isnan(normal.x)" << j << vec3toVariant(normal); + } + break; + } + normals.edit(j) = glm::normalize(normal); + } + return true; +} + +QScriptValue ModelScriptingInterface::mapMeshAttributeValues( + scriptable::ScriptableMeshPointer meshProxy, QScriptValue scopeOrCallback, QScriptValue methodOrName +) { + auto mesh = getMeshPointer(meshProxy); + if (!mesh) { + return false; + } + auto scopedHandler = makeScopedHandlerObject(scopeOrCallback, methodOrName); + + // input buffers + gpu::BufferView positions = mesh->getVertexBuffer(); + + const auto nPositions = positions.getNumElements(); + + // destructure so we can still invoke callback scoped, but with a custom signature (obj, i, jsMesh) + auto scope = scopedHandler.property("scope"); + auto callback = scopedHandler.property("callback"); + auto js = engine(); // cache value to avoid resolving each iteration + auto meshPart = js->toScriptValue(meshProxy); + + auto obj = js->newObject(); + auto attributeViews = ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); + for (uint32_t i=0; i < nPositions; i++) { + for (const auto& a : attributeViews) { + bool asArray = a.second._element.getType() != gpu::FLOAT; + obj.setProperty(a.first, bufferViewElementToScriptValue(js, a.second, i, asArray, a.first.toStdString().c_str())); + } + auto result = callback.call(scope, { obj, i, meshPart }); + if (js->hasUncaughtException()) { + context()->throwValue(js->uncaughtException()); + return false; + } + + if (result.isBool() && !result.toBool()) { + // bail without modifying data if user explicitly returns false + continue; + } + if (result.isObject() && !result.strictlyEquals(obj)) { + // user returned a new object (ie: instead of modifying input properties) + obj = result; + } + + for (const auto& a : attributeViews) { + const auto& attribute = obj.property(a.first); + auto& view = a.second; + if (attribute.isValid()) { + bufferViewElementFromScriptValue(attribute, view, i); + } + } + } + return thisObject(); +} + +void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName) { + auto handler = makeScopedHandlerObject(scopeOrCallback, methodOrName); + Q_ASSERT(handler.engine() == this->engine()); + QPointer engine = dynamic_cast(handler.engine()); + + scriptable::ScriptableModel meshes; + bool success = false; + QString error; + + auto appProvider = DependencyManager::get(); + qDebug() << "appProvider" << appProvider.data(); + scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr; + QString providerType = provider ? provider->metadata.value("providerType").toString() : QString(); + if (providerType.isEmpty()) { + providerType = "unknown"; + } + if (provider) { + qCDebug(model_scripting) << "fetching meshes from " << providerType << "..."; + auto scriptableMeshes = provider->getScriptableModel(&success); + qCDebug(model_scripting) << "//fetched meshes from " << providerType << "success:" <makeError(error), QScriptValue::NullValue); + } else { + callScopedHandlerObject(handler, QScriptValue::NullValue, engine->toScriptValue(meshes)); + } +} + +namespace { + QScriptValue meshToScriptValue(QScriptEngine* engine, scriptable::ScriptableMeshPointer const &in) { + return engine->newQObject(in.get(), QScriptEngine::QtOwnership, + QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects + ); + } + + void meshFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPointer &out) { + auto obj = value.toQObject(); + //qDebug() << "meshFromScriptValue" << obj; + if (auto tmp = qobject_cast(obj)) { + out = tmp->shared_from_this(); + } + // FIXME: Why does above cast not work on Win32!? + if (!out) { + auto smp = static_cast(obj); + //qDebug() << "meshFromScriptValue2" << smp; + out = smp->shared_from_this(); + } + } + + QScriptValue meshesToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModelPointer &in) { + // QScriptValueList result; + QScriptValue result = engine->newArray(); + int i = 0; + foreach(scriptable::ScriptableMeshPointer const meshProxy, in->getMeshes()) { + result.setProperty(i++, meshToScriptValue(engine, meshProxy)); + } + return result; + } + + void meshesFromScriptValue(const QScriptValue& value, scriptable::ScriptableModelPointer &out) { + const auto length = value.property("length").toInt32(); + qCDebug(model_scripting) << "in meshesFromScriptValue, length =" << length; + for (int i = 0; i < length; i++) { + if (const auto meshProxy = qobject_cast(value.property(i).toQObject())) { + out->meshes.append(meshProxy->getMeshPointer()); + } else { + qCDebug(model_scripting) << "null meshProxy" << i; + } + } + } + + void modelProxyFromScriptValue(const QScriptValue& object, scriptable::ScriptableModel &meshes) { + auto meshesProperty = object.property("meshes"); + if (meshesProperty.property("length").toInt32() > 0) { + //meshes._meshes = qobject_cast(meshesProperty.toQObject()); + // qDebug() << "modelProxyFromScriptValue" << meshesProperty.property("length").toInt32() << meshesProperty.toVariant().typeName(); + qScriptValueToSequence(meshesProperty, meshes.meshes); + } else if (auto mesh = qobject_cast(object.toQObject())) { + meshes.meshes << mesh->getMeshPointer(); + } else { + qDebug() << "modelProxyFromScriptValue -- unrecognized input" << object.toVariant().toString(); + } + + meshes.metadata = object.property("metadata").toVariant().toMap(); + } + + QScriptValue modelProxyToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModel &in) { + QScriptValue obj = engine->newObject(); + obj.setProperty("meshes", qScriptValueFromSequence(engine, in.meshes)); + obj.setProperty("metadata", engine->toScriptValue(in.metadata)); + return obj; + } + + QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const mesh::MeshFace &meshFace) { + QScriptValue obj = engine->newObject(); + obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices)); + return obj; + } + + void meshFaceFromScriptValue(const QScriptValue &object, mesh::MeshFace& meshFaceResult) { + qScriptValueToSequence(object.property("vertices"), meshFaceResult.vertexIndices); + } + + QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector) { + return qScriptValueFromSequence(engine, vector); + } + + void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result) { + qScriptValueToSequence(array, result); + } + + QScriptValue qVectorUInt32ToScriptValue(QScriptEngine* engine, const QVector& vector) { + return qScriptValueFromSequence(engine, vector); + } + + void qVectorUInt32FromScriptValue(const QScriptValue& array, QVector& result) { + qScriptValueToSequence(array, result); + } +} + +int meshUint32 = qRegisterMetaType(); +namespace mesh { + int meshUint32 = qRegisterMetaType(); +} +int qVectorMeshUint32 = qRegisterMetaType>(); + +void ModelScriptingInterface::registerMetaTypes(QScriptEngine* engine) { + qScriptRegisterSequenceMetaType>(engine); + qScriptRegisterSequenceMetaType(engine); + qScriptRegisterSequenceMetaType>(engine); + qScriptRegisterMetaType(engine, modelProxyToScriptValue, modelProxyFromScriptValue); + + qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue); + qScriptRegisterMetaType(engine, meshToScriptValue, meshFromScriptValue); + qScriptRegisterMetaType(engine, meshesToScriptValue, meshesFromScriptValue); + qScriptRegisterMetaType(engine, meshFaceToScriptValue, meshFaceFromScriptValue); + qScriptRegisterMetaType(engine, qVectorMeshFaceToScriptValue, qVectorMeshFaceFromScriptValue); +} + +MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPointer meshProxy) { + MeshPointer result; + if (!meshProxy) { + if (context()){ + context()->throwError("expected meshProxy as first parameter"); + } + return result; + } + auto mesh = meshProxy->getMeshPointer(); + if (!mesh) { + if (context()) { + context()->throwError("expected valid meshProxy as first parameter"); + } + return result; + } + return mesh; +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h new file mode 100644 index 0000000000..d10fd28170 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h @@ -0,0 +1,68 @@ +// +// ModelScriptingInterface.h +// libraries/script-engine/src +// +// Created by Seth Alves on 2017-1-27. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ModelScriptingInterface_h +#define hifi_ModelScriptingInterface_h + +#include +#include + +#include + +#include +#include + +#include "ScriptableMesh.h" +#include +class ModelScriptingInterface : public QObject, public QScriptable, public Dependency { + Q_OBJECT + +public: + ModelScriptingInterface(QObject* parent = nullptr); + static void registerMetaTypes(QScriptEngine* engine); + +public slots: + /**jsdoc + * Returns the meshes associated with a UUID (entityID, overlayID, or avatarID) + * + * @function ModelScriptingInterface.getMeshes + * @param {EntityID} entityID The ID of the entity whose meshes are to be retrieve + */ + void getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue()); + + bool dedupeVertices(scriptable::ScriptableMeshPointer meshProxy, float epsilon = 1e-6); + bool recalculateNormals(scriptable::ScriptableMeshPointer meshProxy); + QScriptValue cloneMesh(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals = true); + QScriptValue unrollVertices(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals = true); + QScriptValue mapAttributeValues(QScriptValue in, + QScriptValue scopeOrCallback, + QScriptValue methodOrName = QScriptValue()); + QScriptValue mapMeshAttributeValues(scriptable::ScriptableMeshPointer meshProxy, + QScriptValue scopeOrCallback, + QScriptValue methodOrName = QScriptValue()); + + QString meshToOBJ(const scriptable::ScriptableModel& in); + + bool replaceMeshData(scriptable::ScriptableMeshPointer dest, scriptable::ScriptableMeshPointer source, const QVector& attributeNames = QVector()); + QScriptValue appendMeshes(scriptable::ScriptableModel in); + QScriptValue transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform); + QScriptValue newMesh(const QVector& vertices, + const QVector& normals, + const QVector& faces); + QScriptValue getVertexCount(scriptable::ScriptableMeshPointer meshProxy); + QScriptValue getVertex(scriptable::ScriptableMeshPointer meshProxy, mesh::uint32 vertexIndex); + +private: + scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMeshPointer meshProxy); + +}; + +#endif // hifi_ModelScriptingInterface_h diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp new file mode 100644 index 0000000000..47d91e9e59 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -0,0 +1,359 @@ +// +// SimpleMeshProxy.cpp +// libraries/model-networking/src/model-networking/ +// +// Created by Seth Alves on 2017-3-22. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptableMesh.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "ScriptableMesh.moc" + +#include + +QLoggingCategory mesh_logging { "hifi.scripting.mesh" }; + +// FIXME: unroll/resolve before PR +using namespace scriptable; +QMap ScriptableMesh::ATTRIBUTES{ + {"position", gpu::Stream::POSITION }, + {"normal", gpu::Stream::NORMAL }, + {"color", gpu::Stream::COLOR }, + {"tangent", gpu::Stream::TEXCOORD0 }, + {"skin_cluster_index", gpu::Stream::SKIN_CLUSTER_INDEX }, + {"skin_cluster_weight", gpu::Stream::SKIN_CLUSTER_WEIGHT }, + {"texcoord0", gpu::Stream::TEXCOORD0 }, + {"texcoord1", gpu::Stream::TEXCOORD1 }, + {"texcoord2", gpu::Stream::TEXCOORD2 }, + {"texcoord3", gpu::Stream::TEXCOORD3 }, + {"texcoord4", gpu::Stream::TEXCOORD4 }, +}; + +QVector scriptable::ScriptableModel::getMeshes() const { + QVector out; + for(auto& mesh : meshes) { + out << scriptable::ScriptableMeshPointer(new ScriptableMesh(std::const_pointer_cast(this->shared_from_this()), mesh)); + } + return out; +} + +quint32 ScriptableMesh::getNumVertices() const { + if (auto mesh = getMeshPointer()) { + return (quint32)mesh->getNumVertices(); + } + return 0; +} + +// glm::vec3 ScriptableMesh::getPos3(quint32 index) const { +// if (auto mesh = getMeshPointer()) { +// if (index < getNumVertices()) { +// return mesh->getPos3(index); +// } +// } +// return glm::vec3(NAN); +// } + +namespace { + gpu::BufferView getBufferView(scriptable::MeshPointer mesh, gpu::Stream::Slot slot) { + return slot == gpu::Stream::POSITION ? mesh->getVertexBuffer() : mesh->getAttributeBuffer(slot); + } +} + +QVector ScriptableMesh::findNearbyIndices(const glm::vec3& origin, float epsilon) const { + QVector result; + if (auto mesh = getMeshPointer()) { + const auto& pos = getBufferView(mesh, gpu::Stream::POSITION); + const uint32_t num = (uint32_t)pos.getNumElements(); + for (uint32_t i = 0; i < num; i++) { + const auto& position = pos.get(i); + if (glm::distance(position, origin) <= epsilon) { + result << i; + } + } + } + return result; +} + +QVector ScriptableMesh::getIndices() const { + QVector result; + if (auto mesh = getMeshPointer()) { + qCDebug(mesh_logging, "getTriangleIndices mesh %p", mesh.get()); + gpu::BufferView indexBufferView = mesh->getIndexBuffer(); + if (quint32 count = (quint32)indexBufferView.getNumElements()) { + result.resize(count); + auto buffer = indexBufferView._buffer; + if (indexBufferView._element.getSize() == 4) { + // memcpy(result.data(), buffer->getData(), result.size()*sizeof(quint32)); + for (quint32 i = 0; i < count; i++) { + result[i] = indexBufferView.get(i); + } + } else { + for (quint32 i = 0; i < count; i++) { + result[i] = indexBufferView.get(i); + } + } + } + } + return result; +} + +quint32 ScriptableMesh::getNumAttributes() const { + if (auto mesh = getMeshPointer()) { + return (quint32)mesh->getNumAttributes(); + } + return 0; +} +QVector ScriptableMesh::getAttributeNames() const { + QVector result; + if (auto mesh = getMeshPointer()) { + for (const auto& a : ATTRIBUTES.toStdMap()) { + auto bufferView = getBufferView(mesh, a.second); + if (bufferView.getNumElements() > 0) { + result << a.first; + } + } + } + return result; +} + +// override +QVariantMap ScriptableMesh::getVertexAttributes(quint32 vertexIndex) const { + return getVertexAttributes(vertexIndex, getAttributeNames()); +} + +bool ScriptableMesh::setVertexAttributes(quint32 vertexIndex, QVariantMap attributes) { + qDebug() << "setVertexAttributes" << vertexIndex << attributes; + for (auto& a : gatherBufferViews(getMeshPointer())) { + const auto& name = a.first; + const auto& value = attributes.value(name); + if (value.isValid()) { + auto& view = a.second; + bufferViewElementFromVariant(view, vertexIndex, value); + } else { + qCDebug(mesh_logging) << "setVertexAttributes" << vertexIndex << name; + } + } + return true; +} + +int ScriptableMesh::_getSlotNumber(const QString& attributeName) const { + if (auto mesh = getMeshPointer()) { + return ATTRIBUTES.value(attributeName, -1); + } + return -1; +} + + +QVariantMap ScriptableMesh::getMeshExtents() const { + auto mesh = getMeshPointer(); + auto box = mesh ? mesh->evalPartsBound(0, (int)mesh->getNumParts()) : AABox(); + return { + { "brn", glmVecToVariant(box.getCorner()) }, + { "tfl", glmVecToVariant(box.calcTopFarLeft()) }, + { "center", glmVecToVariant(box.calcCenter()) }, + { "min", glmVecToVariant(box.getMinimumPoint()) }, + { "max", glmVecToVariant(box.getMaximumPoint()) }, + { "dimensions", glmVecToVariant(box.getDimensions()) }, + }; +} + +quint32 ScriptableMesh::getNumParts() const { + if (auto mesh = getMeshPointer()) { + return (quint32)mesh->getNumParts(); + } + return 0; +} + +QVariantMap ScriptableMesh::scaleToFit(float unitScale) { + if (auto mesh = getMeshPointer()) { + auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts()); + auto center = box.calcCenter(); + float maxDimension = glm::distance(box.getMaximumPoint(), box.getMinimumPoint()); + return scale(glm::vec3(unitScale / maxDimension), center); + } + return {}; +} +QVariantMap ScriptableMesh::translate(const glm::vec3& translation) { + return transform(glm::translate(translation)); +} +QVariantMap ScriptableMesh::scale(const glm::vec3& scale, const glm::vec3& origin) { + if (auto mesh = getMeshPointer()) { + auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts()); + glm::vec3 center = glm::isnan(origin.x) ? box.calcCenter() : origin; + return transform(glm::translate(center) * glm::scale(scale)); + } + return {}; +} +QVariantMap ScriptableMesh::rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin) { + return rotate(glm::quat(glm::radians(eulerAngles)), origin); +} +QVariantMap ScriptableMesh::rotate(const glm::quat& rotation, const glm::vec3& origin) { + if (auto mesh = getMeshPointer()) { + auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts()); + glm::vec3 center = glm::isnan(origin.x) ? box.calcCenter() : origin; + return transform(glm::translate(center) * glm::toMat4(rotation)); + } + return {}; +} +QVariantMap ScriptableMesh::transform(const glm::mat4& transform) { + if (auto mesh = getMeshPointer()) { + const auto& pos = getBufferView(mesh, gpu::Stream::POSITION); + const uint32_t num = (uint32_t)pos.getNumElements(); + for (uint32_t i = 0; i < num; i++) { + auto& position = pos.edit(i); + position = transform * glm::vec4(position, 1.0f); + } + } + return getMeshExtents(); +} + +QVariantList ScriptableMesh::getAttributeValues(const QString& attributeName) const { + QVariantList result; + auto slotNum = _getSlotNumber(attributeName); + if (slotNum >= 0) { + auto slot = (gpu::Stream::Slot)slotNum; + const auto& bufferView = getBufferView(getMeshPointer(), slot); + if (auto len = bufferView.getNumElements()) { + bool asArray = bufferView._element.getType() != gpu::FLOAT; + for (quint32 i = 0; i < len; i++) { + result << bufferViewElementToVariant(bufferView, i, asArray, attributeName.toStdString().c_str()); + } + } + } + return result; +} +QVariantMap ScriptableMesh::getVertexAttributes(quint32 vertexIndex, QVector names) const { + QVariantMap result; + auto mesh = getMeshPointer(); + if (!mesh || vertexIndex >= getNumVertices()) { + return result; + } + for (const auto& a : ATTRIBUTES.toStdMap()) { + auto name = a.first; + if (!names.contains(name)) { + continue; + } + auto slot = a.second; + const gpu::BufferView& bufferView = getBufferView(mesh, slot); + if (vertexIndex < bufferView.getNumElements()) { + bool asArray = bufferView._element.getType() != gpu::FLOAT; + result[name] = bufferViewElementToVariant(bufferView, vertexIndex, asArray, name.toStdString().c_str()); + } + } + return result; +} + +/// --- buffer view <-> variant helpers + +namespace { + // expand the corresponding attribute buffer (creating it if needed) so that it matches POSITIONS size and specified element type + gpu::BufferView _expandedAttributeBuffer(const scriptable::MeshPointer mesh, gpu::Stream::Slot slot, const gpu::Element& elementType) { + gpu::Size elementSize = elementType.getSize(); + gpu::BufferView bufferView = getBufferView(mesh, slot); + auto nPositions = mesh->getNumVertices(); + auto vsize = nPositions * elementSize; + auto diffTypes = (elementType.getType() != bufferView._element.getType() || + elementType.getSize() > bufferView._element.getSize() || + elementType.getScalarCount() > bufferView._element.getScalarCount() || + vsize > bufferView._size + ); + auto hint = DebugNames::stringFrom(slot); + +#ifdef DEV_BUILD + auto beforeCount = bufferView.getNumElements(); + auto beforeTotal = bufferView._size; +#endif + if (bufferView.getNumElements() < nPositions || diffTypes) { + if (!bufferView._buffer || bufferView.getNumElements() == 0) { + qCInfo(mesh_logging).nospace() << "ScriptableMesh -- adding missing mesh attribute '" << hint << "' for BufferView"; + gpu::Byte *data = new gpu::Byte[vsize]; + memset(data, 0, vsize); + auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data); + delete[] data; + bufferView = gpu::BufferView(buffer, elementType); + mesh->addAttribute(slot, bufferView); + } else { + qCInfo(mesh_logging) << "ScriptableMesh -- resizing Buffer current:" << hint << bufferView._buffer->getSize() << "wanted:" << vsize; + bufferView._element = elementType; + bufferView._buffer->resize(vsize); + bufferView._size = bufferView._buffer->getSize(); + } + } +#ifdef DEV_BUILD + auto afterCount = bufferView.getNumElements(); + auto afterTotal = bufferView._size; + if (beforeTotal != afterTotal || beforeCount != afterCount) { + auto typeName = DebugNames::stringFrom(bufferView._element.getType()); + qCDebug(mesh_logging, "NOTE:: _expandedAttributeBuffer.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)", + hint.toStdString().c_str(), bufferView._element.getScalarCount(), + typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); + } +#endif + return bufferView; + } + const gpu::Element UNUSED{ gpu::SCALAR, gpu::UINT8, gpu::RAW }; + + gpu::Element getVecNElement(gpu::Type T, int N) { + switch(N) { + case 2: return { gpu::VEC2, T, gpu::XY }; + case 3: return { gpu::VEC3, T, gpu::XYZ }; + case 4: return { gpu::VEC4, T, gpu::XYZW }; + } + Q_ASSERT(false); + return UNUSED; + } + + gpu::BufferView expandAttributeToMatchPositions(scriptable::MeshPointer mesh, gpu::Stream::Slot slot) { + if (slot == gpu::Stream::POSITION) { + return getBufferView(mesh, slot); + } + return _expandedAttributeBuffer(mesh, slot, getVecNElement(gpu::FLOAT, 3)); + } +} + +std::map ScriptableMesh::gatherBufferViews(scriptable::MeshPointer mesh, const QStringList& expandToMatchPositions) { + std::map attributeViews; + if (!mesh) { + return attributeViews; + } + for (const auto& a : ScriptableMesh::ATTRIBUTES.toStdMap()) { + auto name = a.first; + auto slot = a.second; + if (expandToMatchPositions.contains(name)) { + expandAttributeToMatchPositions(mesh, slot); + } + auto view = getBufferView(mesh, slot); + auto beforeCount = view.getNumElements(); + if (beforeCount > 0) { + auto element = view._element; + auto vecN = element.getScalarCount(); + auto type = element.getType(); + QString typeName = DebugNames::stringFrom(element.getType()); + auto beforeTotal = view._size; + + attributeViews[name] = _expandedAttributeBuffer(mesh, slot, getVecNElement(type, vecN)); + +#if DEV_BUILD + auto afterTotal = attributeViews[name]._size; + auto afterCount = attributeViews[name].getNumElements(); + if (beforeTotal != afterTotal || beforeCount != afterCount) { + qCDebug(mesh_logging, "NOTE:: gatherBufferViews.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)", + name.toStdString().c_str(), vecN, typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); + } +#endif + } + } + return attributeViews; +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h new file mode 100644 index 0000000000..da11002906 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -0,0 +1,127 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace graphics { + class Mesh; +} +namespace gpu { + class BufferView; +} +namespace scriptable { + class ScriptableMesh : public QObject, public std::enable_shared_from_this { + Q_OBJECT + public: + ScriptableModelPointer _model; + scriptable::MeshPointer _mesh; + QVariantMap _metadata; + ScriptableMesh() : QObject() {} + ScriptableMesh(ScriptableModelPointer parent, scriptable::MeshPointer mesh) : QObject(), _model(parent), _mesh(mesh) {} + ScriptableMesh(const ScriptableMesh& other) : QObject(), _model(other._model), _mesh(other._mesh), _metadata(other._metadata) {} + ~ScriptableMesh() { qDebug() << "~ScriptableMesh" << this; } + Q_PROPERTY(quint32 numParts READ getNumParts) + Q_PROPERTY(quint32 numAttributes READ getNumAttributes) + Q_PROPERTY(quint32 numVertices READ getNumVertices) + Q_PROPERTY(quint32 numIndices READ getNumIndices) + Q_PROPERTY(QVector attributeNames READ getAttributeNames) + + virtual scriptable::MeshPointer getMeshPointer() const { return _mesh; } + Q_INVOKABLE virtual quint32 getNumParts() const; + Q_INVOKABLE virtual quint32 getNumVertices() const; + Q_INVOKABLE virtual quint32 getNumAttributes() const; + Q_INVOKABLE virtual quint32 getNumIndices() const { return 0; } + Q_INVOKABLE virtual QVector getAttributeNames() const; + Q_INVOKABLE virtual QVariantMap getVertexAttributes(quint32 vertexIndex) const; + Q_INVOKABLE virtual QVariantMap getVertexAttributes(quint32 vertexIndex, QVector attributes) const; + + Q_INVOKABLE virtual QVector getIndices() const; + Q_INVOKABLE virtual QVector findNearbyIndices(const glm::vec3& origin, float epsilon = 1e-6) const; + Q_INVOKABLE virtual QVariantMap getMeshExtents() const; + Q_INVOKABLE virtual bool setVertexAttributes(quint32 vertexIndex, QVariantMap attributes); + Q_INVOKABLE virtual QVariantMap scaleToFit(float unitScale); + + static QMap ATTRIBUTES; + static std::map gatherBufferViews(MeshPointer mesh, const QStringList& expandToMatchPositions = QStringList()); + + Q_INVOKABLE QVariantList getAttributeValues(const QString& attributeName) const; + + Q_INVOKABLE int _getSlotNumber(const QString& attributeName) const; + + QVariantMap translate(const glm::vec3& translation); + QVariantMap scale(const glm::vec3& scale, const glm::vec3& origin = glm::vec3(NAN)); + QVariantMap rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin = glm::vec3(NAN)); + QVariantMap rotate(const glm::quat& rotation, const glm::vec3& origin = glm::vec3(NAN)); + Q_INVOKABLE QVariantMap transform(const glm::mat4& transform); + }; + + // TODO: for now this is a part-specific wrapper around ScriptableMesh + class ScriptableMeshPart : public ScriptableMesh { + Q_OBJECT + public: + ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { _model=view._model; _mesh=view._mesh; return *this; }; + ScriptableMeshPart(const ScriptableMeshPart& other) : ScriptableMesh(other._model, other._mesh) {} + ScriptableMeshPart() : ScriptableMesh(nullptr, nullptr) {} + ~ScriptableMeshPart() { qDebug() << "~ScriptableMeshPart" << this; } + ScriptableMeshPart(ScriptableMeshPointer mesh) : ScriptableMesh(mesh->_model, mesh->_mesh) {} + Q_PROPERTY(QString topology READ getTopology) + Q_PROPERTY(quint32 numFaces READ getNumFaces) + + scriptable::MeshPointer parentMesh; + int partIndex; + QString getTopology() const { return "triangles"; } + Q_INVOKABLE virtual quint32 getNumFaces() const { return getIndices().size() / 3; } + Q_INVOKABLE virtual QVector getFace(quint32 faceIndex) const { + auto inds = getIndices(); + return faceIndex+2 < (quint32)inds.size() ? inds.mid(faceIndex*3, 3) : QVector(); + } + }; + + class GraphicsScriptingInterface : public QObject { + Q_OBJECT + public: + GraphicsScriptingInterface(QObject* parent = nullptr) : QObject(parent) {} + GraphicsScriptingInterface(const GraphicsScriptingInterface& other) {} + public slots: + ScriptableMeshPart exportMeshPart(ScriptableMesh mesh, int part) { return {}; } + + }; +} + +Q_DECLARE_METATYPE(scriptable::ScriptableMesh) +Q_DECLARE_METATYPE(scriptable::ScriptableMeshPointer) +Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(scriptable::ScriptableMeshPart) +Q_DECLARE_METATYPE(scriptable::GraphicsScriptingInterface) + +// FIXME: faces were supported in the original Model.* API -- are they still needed/used/useful for anything yet? +#include + +namespace mesh { + using uint32 = quint32; + class MeshFace; + using MeshFaces = QVector; + class MeshFace { + public: + MeshFace() {} + MeshFace(QVector vertexIndices) : vertexIndices(vertexIndices) {} + ~MeshFace() {} + + QVector vertexIndices; + // TODO -- material... + }; +}; + +Q_DECLARE_METATYPE(mesh::MeshFace) +Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(mesh::uint32) +Q_DECLARE_METATYPE(QVector) diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h new file mode 100644 index 0000000000..e8cf6f1656 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace graphics { + class Mesh; +} +namespace gpu { + class BufferView; +} +namespace scriptable { + using Mesh = graphics::Mesh; + using MeshPointer = std::shared_ptr; + + class ScriptableModel; + class ScriptableMesh; + class ScriptableMeshPart; + using ScriptableModelPointer = std::shared_ptr; + using ScriptableMeshPointer = std::shared_ptr; + using ScriptableMeshPartPointer = std::shared_ptr; + class ScriptableModel : public QObject, public std::enable_shared_from_this { + Q_OBJECT + public: + Q_PROPERTY(QVector meshes READ getMeshes) + + Q_INVOKABLE QString toString() { return "[ScriptableModel " + objectName()+"]"; } + ScriptableModel(QObject* parent = nullptr) : QObject(parent) {} + ScriptableModel(const ScriptableModel& other) : objectID(other.objectID), metadata(other.metadata), meshes(other.meshes) {} + ScriptableModel& operator=(const ScriptableModel& view) { + objectID = view.objectID; + metadata = view.metadata; + meshes = view.meshes; + return *this; + } + ~ScriptableModel() { qDebug() << "~ScriptableModel" << this; } + void mixin(const ScriptableModel& other) { + for (const auto& key : other.metadata.keys()) { + metadata[key] = other.metadata[key]; + } + for(const auto&mesh : other.meshes) { + meshes << mesh; + } + } + QUuid objectID; + QVariantMap metadata; + QVector meshes; + // TODO: in future accessors for these could go here + QVariantMap shapes; + QVariantMap materials; + QVariantMap armature; + + QVector getMeshes() const; + }; + + class ModelProvider { + public: + QVariantMap metadata; + static scriptable::ScriptableModel modelUnavailableError(bool* ok) { if (ok) { *ok = false; } return {}; } + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) = 0; + }; + using ModelProviderPointer = std::shared_ptr; + class ModelProviderFactory : public Dependency { + public: + virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) = 0; + }; + +} + +Q_DECLARE_METATYPE(scriptable::MeshPointer) +Q_DECLARE_METATYPE(scriptable::ScriptableModel) +Q_DECLARE_METATYPE(scriptable::ScriptableModelPointer) + diff --git a/libraries/graphics/src/graphics/Geometry.cpp b/libraries/graphics/src/graphics/Geometry.cpp index ba5afcbc62..d43c773249 100755 --- a/libraries/graphics/src/graphics/Geometry.cpp +++ b/libraries/graphics/src/graphics/Geometry.cpp @@ -42,6 +42,11 @@ void Mesh::addAttribute(Slot slot, const BufferView& buffer) { evalVertexFormat(); } +void Mesh::removeAttribute(Slot slot) { + _attributeBuffers.erase(slot); + evalVertexFormat(); +} + const BufferView Mesh::getAttributeBuffer(int attrib) const { auto attribBuffer = _attributeBuffers.find(attrib); if (attribBuffer != _attributeBuffers.end()) { @@ -224,6 +229,7 @@ graphics::MeshPointer Mesh::map(std::function vertexFunc, } graphics::MeshPointer result(new graphics::Mesh()); + result->displayName = displayName; gpu::Element vertexElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); gpu::Buffer* resultVertexBuffer = new gpu::Buffer(vertexSize, resultVertexData.get()); diff --git a/libraries/graphics/src/graphics/Geometry.h b/libraries/graphics/src/graphics/Geometry.h index 642aa9e38d..23ebec2965 100755 --- a/libraries/graphics/src/graphics/Geometry.h +++ b/libraries/graphics/src/graphics/Geometry.h @@ -56,6 +56,7 @@ public: // Attribute Buffers size_t getNumAttributes() const { return _attributeBuffers.size(); } void addAttribute(Slot slot, const BufferView& buffer); + void removeAttribute(Slot slot); const BufferView getAttributeBuffer(int attrib) const; // Stream format diff --git a/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp b/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp deleted file mode 100644 index 741478789e..0000000000 --- a/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// -// SimpleMeshProxy.cpp -// libraries/model-networking/src/model-networking/ -// -// Created by Seth Alves on 2017-3-22. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "SimpleMeshProxy.h" - -#include - -MeshPointer SimpleMeshProxy::getMeshPointer() const { - return _mesh; -} - -int SimpleMeshProxy::getNumVertices() const { - return (int)_mesh->getNumVertices(); -} - -glm::vec3 SimpleMeshProxy::getPos3(int index) const { - return _mesh->getPos3(index); -} - diff --git a/libraries/model-networking/src/model-networking/SimpleMeshProxy.h b/libraries/model-networking/src/model-networking/SimpleMeshProxy.h deleted file mode 100644 index 24c3fca27e..0000000000 --- a/libraries/model-networking/src/model-networking/SimpleMeshProxy.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// SimpleMeshProxy.h -// libraries/model-networking/src/model-networking/ -// -// Created by Seth Alves on 2017-1-27. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_SimpleMeshProxy_h -#define hifi_SimpleMeshProxy_h - -#include -#include -#include - -#include - -class SimpleMeshProxy : public MeshProxy { -public: - SimpleMeshProxy(const MeshPointer& mesh) : _mesh(mesh) { } - - MeshPointer getMeshPointer() const override; - - int getNumVertices() const override; - - glm::vec3 getPos3(int index) const override; - - -protected: - const MeshPointer _mesh; -}; - -#endif // hifi_SimpleMeshProxy_h diff --git a/libraries/render-utils/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index 6be3057c93..55762e38fd 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -7,6 +7,7 @@ link_hifi_libraries(shared ktx gpu graphics model-networking render animation fb include_hifi_library_headers(networking) include_hifi_library_headers(octree) include_hifi_library_headers(audio) +include_hifi_library_headers(graphics-scripting) # for ScriptableModel.h if (NOT ANDROID) target_nsight() diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 23473e74f2..6aa42cf6df 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -2407,3 +2407,48 @@ void GeometryCache::renderWireCubeInstance(RenderArgs* args, gpu::Batch& batch, assert(pipeline != nullptr); renderInstances(args, batch, color, true, pipeline, GeometryCache::Cube); } + +graphics::MeshPointer GeometryCache::meshFromShape(Shape geometryShape, glm::vec3 color) { + auto shapeData = getShapeData(geometryShape); + + qDebug() << "GeometryCache::getMeshProxyListFromShape" << shapeData << stringFromShape(geometryShape); + + auto cloneBufferView = [](const gpu::BufferView& in) -> gpu::BufferView { + auto buffer = std::make_shared(*in._buffer); // copy + // FIXME: gpu::BufferView seems to have a bug where constructing a new instance from an existing one + // results in over-multiplied buffer/view sizes -- hence constructing manually here from each input prop + auto out = gpu::BufferView(buffer, in._offset, in._size, in._stride, in._element); + Q_ASSERT(out.getNumElements() == in.getNumElements()); + Q_ASSERT(out._size == in._size); + Q_ASSERT(out._buffer->getSize() == in._buffer->getSize()); + return out; + }; + + auto positionsBufferView = cloneBufferView(shapeData->_positionView); + auto normalsBufferView = cloneBufferView(shapeData->_normalView); + auto indexBufferView = cloneBufferView(shapeData->_indicesView); + + gpu::BufferView::Size numVertices = positionsBufferView.getNumElements(); + Q_ASSERT(numVertices == normalsBufferView.getNumElements()); + + // apply input color across all vertices + auto colorsBufferView = cloneBufferView(shapeData->_normalView); + for (gpu::BufferView::Size i = 0; i < numVertices; i++) { + colorsBufferView.edit((gpu::BufferView::Index)i) = color; + } + + graphics::MeshPointer mesh(new graphics::Mesh()); + mesh->setVertexBuffer(positionsBufferView); + mesh->setIndexBuffer(indexBufferView); + mesh->addAttribute(gpu::Stream::NORMAL, normalsBufferView); + mesh->addAttribute(gpu::Stream::COLOR, colorsBufferView); + + const auto startIndex = 0, baseVertex = 0; + graphics::Mesh::Part part(startIndex, (graphics::Index)indexBufferView.getNumElements(), baseVertex, graphics::Mesh::TRIANGLES); + auto partBuffer = new gpu::Buffer(sizeof(graphics::Mesh::Part), (gpu::Byte*)&part); + mesh->setPartBuffer(gpu::BufferView(partBuffer, gpu::Element::PART_DRAWCALL)); + + mesh->displayName = QString("GeometryCache/shape::%1").arg(GeometryCache::stringFromShape(geometryShape)); + + return mesh; +} diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 63af30bb79..998043b80e 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -375,6 +375,7 @@ public: /// otherwise nullptr in the event of an error. const ShapeData * getShapeData(Shape shape) const; + graphics::MeshPointer meshFromShape(Shape geometryShape, glm::vec3 color); private: GeometryCache(); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index b0763c0fb3..d595136c56 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -26,7 +26,7 @@ #include #include -#include +#include #include #include @@ -573,15 +573,21 @@ bool Model::convexHullContains(glm::vec3 point) { return false; } -MeshProxyList Model::getMeshes() const { - MeshProxyList result; +scriptable::ScriptableModel Model::getScriptableModel(bool* ok) { + scriptable::ScriptableModel result; const Geometry::Pointer& renderGeometry = getGeometry(); - const Geometry::GeometryMeshes& meshes = renderGeometry->getMeshes(); if (!isLoaded()) { + qDebug() << "Model::getScriptableModel -- !isLoaded"; + if (ok) { + *ok = false; + } return result; } +// TODO: remove -- this was an earlier approach using renderGeometry instead of FBXGeometry +#if 0 // renderGeometry approach + const Geometry::GeometryMeshes& meshes = renderGeometry->getMeshes(); Transform offset; offset.setScale(_scale); offset.postTranslate(_offset); @@ -591,20 +597,67 @@ MeshProxyList Model::getMeshes() const { if (!mesh) { continue; } - - MeshProxy* meshProxy = new SimpleMeshProxy( - mesh->map( - [=](glm::vec3 position) { - return glm::vec3(offsetMat * glm::vec4(position, 1.0f)); - }, - [=](glm::vec3 color) { return color; }, - [=](glm::vec3 normal) { - return glm::normalize(glm::vec3(offsetMat * glm::vec4(normal, 0.0f))); - }, - [&](uint32_t index) { return index; })); - result << meshProxy; + qDebug() << "Model::getScriptableModel #" << i++ << mesh->displayName; + auto newmesh = mesh->map( + [=](glm::vec3 position) { + return glm::vec3(offsetMat * glm::vec4(position, 1.0f)); + }, + [=](glm::vec3 color) { return color; }, + [=](glm::vec3 normal) { + return glm::normalize(glm::vec3(offsetMat * glm::vec4(normal, 0.0f))); + }, + [&](uint32_t index) { return index; }); + newmesh->displayName = mesh->displayName; + result << newmesh; } - +#endif + const FBXGeometry& geometry = getFBXGeometry(); + auto mat4toVariant = [](const glm::mat4& mat4) -> QVariant { + QVector floats; + floats.resize(16); + memcpy(floats.data(), &mat4, sizeof(glm::mat4)); + QVariant v; + v.setValue>(floats); + return v; + }; + result.metadata = { + { "url", _url.toString() }, + { "textures", renderGeometry->getTextures() }, + { "offset", vec3toVariant(_offset) }, + { "scale", vec3toVariant(_scale) }, + { "rotation", quatToVariant(_rotation) }, + { "translation", vec3toVariant(_translation) }, + { "meshToModel", mat4toVariant(glm::scale(_scale) * glm::translate(_offset)) }, + { "meshToWorld", mat4toVariant(createMatFromQuatAndPos(_rotation, _translation) * (glm::scale(_scale) * glm::translate(_offset))) }, + { "geometryOffset", mat4toVariant(geometry.offset) }, + }; + QVariantList submeshes; + int numberOfMeshes = geometry.meshes.size(); + for (int i = 0; i < numberOfMeshes; i++) { + const FBXMesh& fbxMesh = geometry.meshes.at(i); + auto mesh = fbxMesh._mesh; + if (!mesh) { + continue; + } + result.meshes << std::const_pointer_cast(mesh); + auto extraInfo = geometry.getModelNameOfMesh(i); + qDebug() << "Model::getScriptableModel #" << i << QString(mesh->displayName) << extraInfo; + submeshes << QVariantMap{ + { "index", i }, + { "meshIndex", fbxMesh.meshIndex }, + { "modelName", extraInfo }, + { "transform", mat4toVariant(fbxMesh.modelTransform) }, + { "extents", QVariantMap({ + { "minimum", vec3toVariant(fbxMesh.meshExtents.minimum) }, + { "maximum", vec3toVariant(fbxMesh.meshExtents.maximum) }, + })}, + }; + } + if (ok) { + *ok = true; + } + qDebug() << "//Model::getScriptableModel -- #" << result.meshes.size(); + result.metadata["submeshes"] = submeshes; return result; } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 027d52ecfd..4fd00c9f9a 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -64,7 +65,7 @@ using ModelWeakPointer = std::weak_ptr; /// A generic 3D model displaying geometry loaded from a URL. -class Model : public QObject, public std::enable_shared_from_this { +class Model : public QObject, public std::enable_shared_from_this, public scriptable::ModelProvider { Q_OBJECT public: @@ -313,7 +314,7 @@ public: int getResourceDownloadAttempts() { return _renderWatcher.getResourceDownloadAttempts(); } int getResourceDownloadAttemptsRemaining() { return _renderWatcher.getResourceDownloadAttemptsRemaining(); } - Q_INVOKABLE MeshProxyList getMeshes() const; + Q_INVOKABLE virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; void scaleToFit(); diff --git a/libraries/script-engine/src/ModelScriptingInterface.cpp b/libraries/script-engine/src/ModelScriptingInterface.cpp deleted file mode 100644 index c693083ebf..0000000000 --- a/libraries/script-engine/src/ModelScriptingInterface.cpp +++ /dev/null @@ -1,251 +0,0 @@ -// -// ModelScriptingInterface.cpp -// libraries/script-engine/src -// -// Created by Seth Alves on 2017-1-27. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "ModelScriptingInterface.h" -#include -#include -#include -#include -#include "ScriptEngine.h" -#include "ScriptEngineLogging.h" -#include "OBJWriter.h" - -ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(parent) { - _modelScriptEngine = qobject_cast(parent); - - qScriptRegisterSequenceMetaType>(_modelScriptEngine); - qScriptRegisterMetaType(_modelScriptEngine, meshFaceToScriptValue, meshFaceFromScriptValue); - qScriptRegisterMetaType(_modelScriptEngine, qVectorMeshFaceToScriptValue, qVectorMeshFaceFromScriptValue); -} - -QString ModelScriptingInterface::meshToOBJ(MeshProxyList in) { - QList meshes; - foreach (const MeshProxy* meshProxy, in) { - meshes.append(meshProxy->getMeshPointer()); - } - - return writeOBJToString(meshes); -} - -QScriptValue ModelScriptingInterface::appendMeshes(MeshProxyList in) { - // figure out the size of the resulting mesh - size_t totalVertexCount { 0 }; - size_t totalColorCount { 0 }; - size_t totalNormalCount { 0 }; - size_t totalIndexCount { 0 }; - foreach (const MeshProxy* meshProxy, in) { - MeshPointer mesh = meshProxy->getMeshPointer(); - totalVertexCount += mesh->getNumVertices(); - - int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h - const gpu::BufferView& colorsBufferView = mesh->getAttributeBuffer(attributeTypeColor); - gpu::BufferView::Index numColors = (gpu::BufferView::Index)colorsBufferView.getNumElements(); - totalColorCount += numColors; - - int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h - const gpu::BufferView& normalsBufferView = mesh->getAttributeBuffer(attributeTypeNormal); - gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements(); - totalNormalCount += numNormals; - - totalIndexCount += mesh->getNumIndices(); - } - - // alloc the resulting mesh - gpu::Resource::Size combinedVertexSize = totalVertexCount * sizeof(glm::vec3); - std::unique_ptr combinedVertexData{ new unsigned char[combinedVertexSize] }; - unsigned char* combinedVertexDataCursor = combinedVertexData.get(); - - gpu::Resource::Size combinedColorSize = totalColorCount * sizeof(glm::vec3); - std::unique_ptr combinedColorData{ new unsigned char[combinedColorSize] }; - unsigned char* combinedColorDataCursor = combinedColorData.get(); - - gpu::Resource::Size combinedNormalSize = totalNormalCount * sizeof(glm::vec3); - std::unique_ptr combinedNormalData{ new unsigned char[combinedNormalSize] }; - unsigned char* combinedNormalDataCursor = combinedNormalData.get(); - - gpu::Resource::Size combinedIndexSize = totalIndexCount * sizeof(uint32_t); - std::unique_ptr combinedIndexData{ new unsigned char[combinedIndexSize] }; - unsigned char* combinedIndexDataCursor = combinedIndexData.get(); - - uint32_t indexStartOffset { 0 }; - - foreach (const MeshProxy* meshProxy, in) { - MeshPointer mesh = meshProxy->getMeshPointer(); - mesh->forEach( - [&](glm::vec3 position){ - memcpy(combinedVertexDataCursor, &position, sizeof(position)); - combinedVertexDataCursor += sizeof(position); - }, - [&](glm::vec3 color){ - memcpy(combinedColorDataCursor, &color, sizeof(color)); - combinedColorDataCursor += sizeof(color); - }, - [&](glm::vec3 normal){ - memcpy(combinedNormalDataCursor, &normal, sizeof(normal)); - combinedNormalDataCursor += sizeof(normal); - }, - [&](uint32_t index){ - index += indexStartOffset; - memcpy(combinedIndexDataCursor, &index, sizeof(index)); - combinedIndexDataCursor += sizeof(index); - }); - - gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices(); - indexStartOffset += numVertices; - } - - graphics::MeshPointer result(new graphics::Mesh()); - - gpu::Element vertexElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* combinedVertexBuffer = new gpu::Buffer(combinedVertexSize, combinedVertexData.get()); - gpu::BufferPointer combinedVertexBufferPointer(combinedVertexBuffer); - gpu::BufferView combinedVertexBufferView(combinedVertexBufferPointer, vertexElement); - result->setVertexBuffer(combinedVertexBufferView); - - int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h - gpu::Element colorElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* combinedColorsBuffer = new gpu::Buffer(combinedColorSize, combinedColorData.get()); - gpu::BufferPointer combinedColorsBufferPointer(combinedColorsBuffer); - gpu::BufferView combinedColorsBufferView(combinedColorsBufferPointer, colorElement); - result->addAttribute(attributeTypeColor, combinedColorsBufferView); - - int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h - gpu::Element normalElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* combinedNormalsBuffer = new gpu::Buffer(combinedNormalSize, combinedNormalData.get()); - gpu::BufferPointer combinedNormalsBufferPointer(combinedNormalsBuffer); - gpu::BufferView combinedNormalsBufferView(combinedNormalsBufferPointer, normalElement); - result->addAttribute(attributeTypeNormal, combinedNormalsBufferView); - - gpu::Element indexElement = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW); - gpu::Buffer* combinedIndexesBuffer = new gpu::Buffer(combinedIndexSize, combinedIndexData.get()); - gpu::BufferPointer combinedIndexesBufferPointer(combinedIndexesBuffer); - gpu::BufferView combinedIndexesBufferView(combinedIndexesBufferPointer, indexElement); - result->setIndexBuffer(combinedIndexesBufferView); - - std::vector parts; - parts.emplace_back(graphics::Mesh::Part((graphics::Index)0, // startIndex - (graphics::Index)result->getNumIndices(), // numIndices - (graphics::Index)0, // baseVertex - graphics::Mesh::TRIANGLES)); // topology - result->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), - (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); - - - MeshProxy* resultProxy = new SimpleMeshProxy(result); - return meshToScriptValue(_modelScriptEngine, resultProxy); -} - -QScriptValue ModelScriptingInterface::transformMesh(glm::mat4 transform, MeshProxy* meshProxy) { - if (!meshProxy) { - return QScriptValue(false); - } - MeshPointer mesh = meshProxy->getMeshPointer(); - if (!mesh) { - return QScriptValue(false); - } - - const auto inverseTransposeTransform = glm::inverse(glm::transpose(transform)); - graphics::MeshPointer result = mesh->map([&](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); }, - [&](glm::vec3 color){ return color; }, - [&](glm::vec3 normal){ return glm::vec3(inverseTransposeTransform * glm::vec4(normal, 0.0f)); }, - [&](uint32_t index){ return index; }); - MeshProxy* resultProxy = new SimpleMeshProxy(result); - return meshToScriptValue(_modelScriptEngine, resultProxy); -} - -QScriptValue ModelScriptingInterface::getVertexCount(MeshProxy* meshProxy) { - if (!meshProxy) { - return QScriptValue(false); - } - MeshPointer mesh = meshProxy->getMeshPointer(); - if (!mesh) { - return QScriptValue(false); - } - - gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices(); - - return numVertices; -} - -QScriptValue ModelScriptingInterface::getVertex(MeshProxy* meshProxy, int vertexIndex) { - if (!meshProxy) { - return QScriptValue(false); - } - MeshPointer mesh = meshProxy->getMeshPointer(); - if (!mesh) { - return QScriptValue(false); - } - - const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer(); - gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices(); - - if (vertexIndex < 0 || vertexIndex >= numVertices) { - return QScriptValue(false); - } - - glm::vec3 pos = vertexBufferView.get(vertexIndex); - return vec3toScriptValue(_modelScriptEngine, pos); -} - - -QScriptValue ModelScriptingInterface::newMesh(const QVector& vertices, - const QVector& normals, - const QVector& faces) { - graphics::MeshPointer mesh(new graphics::Mesh()); - - // vertices - auto vertexBuffer = std::make_shared(vertices.size() * sizeof(glm::vec3), (gpu::Byte*)vertices.data()); - auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); - gpu::BufferView vertexBufferView(vertexBufferPtr, 0, vertexBufferPtr->getSize(), - sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - mesh->setVertexBuffer(vertexBufferView); - - if (vertices.size() == normals.size()) { - // normals - auto normalBuffer = std::make_shared(normals.size() * sizeof(glm::vec3), (gpu::Byte*)normals.data()); - auto normalBufferPtr = gpu::BufferPointer(normalBuffer); - gpu::BufferView normalBufferView(normalBufferPtr, 0, normalBufferPtr->getSize(), - sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - mesh->addAttribute(gpu::Stream::NORMAL, normalBufferView); - } else { - qCDebug(scriptengine) << "ModelScriptingInterface::newMesh normals must be same length as vertices"; - } - - // indices (faces) - int VERTICES_PER_TRIANGLE = 3; - int indexBufferSize = faces.size() * sizeof(uint32_t) * VERTICES_PER_TRIANGLE; - unsigned char* indexData = new unsigned char[indexBufferSize]; - unsigned char* indexDataCursor = indexData; - foreach(const MeshFace& meshFace, faces) { - for (int i = 0; i < VERTICES_PER_TRIANGLE; i++) { - memcpy(indexDataCursor, &meshFace.vertexIndices[i], sizeof(uint32_t)); - indexDataCursor += sizeof(uint32_t); - } - } - auto indexBuffer = std::make_shared(indexBufferSize, (gpu::Byte*)indexData); - auto indexBufferPtr = gpu::BufferPointer(indexBuffer); - gpu::BufferView indexBufferView(indexBufferPtr, gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW)); - mesh->setIndexBuffer(indexBufferView); - - // parts - std::vector parts; - parts.emplace_back(graphics::Mesh::Part((graphics::Index)0, // startIndex - (graphics::Index)faces.size() * 3, // numIndices - (graphics::Index)0, // baseVertex - graphics::Mesh::TRIANGLES)); // topology - mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), - (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); - - - - MeshProxy* meshProxy = new SimpleMeshProxy(mesh); - return meshToScriptValue(_modelScriptEngine, meshProxy); -} diff --git a/libraries/script-engine/src/ModelScriptingInterface.h b/libraries/script-engine/src/ModelScriptingInterface.h deleted file mode 100644 index 3c239f006f..0000000000 --- a/libraries/script-engine/src/ModelScriptingInterface.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// ModelScriptingInterface.h -// libraries/script-engine/src -// -// Created by Seth Alves on 2017-1-27. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_ModelScriptingInterface_h -#define hifi_ModelScriptingInterface_h - -#include - -#include -class QScriptEngine; - -class ModelScriptingInterface : public QObject { - Q_OBJECT - -public: - ModelScriptingInterface(QObject* parent); - - Q_INVOKABLE QString meshToOBJ(MeshProxyList in); - Q_INVOKABLE QScriptValue appendMeshes(MeshProxyList in); - Q_INVOKABLE QScriptValue transformMesh(glm::mat4 transform, MeshProxy* meshProxy); - Q_INVOKABLE QScriptValue newMesh(const QVector& vertices, - const QVector& normals, - const QVector& faces); - Q_INVOKABLE QScriptValue getVertexCount(MeshProxy* meshProxy); - Q_INVOKABLE QScriptValue getVertex(MeshProxy* meshProxy, int vertexIndex); - -private: - QScriptEngine* _modelScriptEngine { nullptr }; -}; - -#endif // hifi_ModelScriptingInterface_h diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index c79ffffec7..ffb1b7bc74 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -73,8 +73,6 @@ #include "WebSocketClass.h" #include "RecordingScriptingInterface.h" #include "ScriptEngines.h" -#include "ModelScriptingInterface.h" - #include @@ -711,10 +709,6 @@ void ScriptEngine::init() { registerGlobalObject("DebugDraw", &DebugDraw::getInstance()); - registerGlobalObject("Model", new ModelScriptingInterface(this)); - qScriptRegisterMetaType(this, meshToScriptValue, meshFromScriptValue); - qScriptRegisterMetaType(this, meshesToScriptValue, meshesFromScriptValue); - registerGlobalObject("UserActivityLogger", DependencyManager::get().data()); } diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 7b455beae5..9ad5c27072 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -855,68 +855,3 @@ QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const Animatio void animationDetailsFromScriptValue(const QScriptValue& object, AnimationDetails& details) { // nothing for now... } - -QScriptValue meshToScriptValue(QScriptEngine* engine, MeshProxy* const &in) { - return engine->newQObject(in, QScriptEngine::QtOwnership, - QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); -} - -void meshFromScriptValue(const QScriptValue& value, MeshProxy* &out) { - out = qobject_cast(value.toQObject()); -} - -QScriptValue meshesToScriptValue(QScriptEngine* engine, const MeshProxyList &in) { - // QScriptValueList result; - QScriptValue result = engine->newArray(); - int i = 0; - foreach(MeshProxy* const meshProxy, in) { - result.setProperty(i++, meshToScriptValue(engine, meshProxy)); - } - return result; -} - -void meshesFromScriptValue(const QScriptValue& value, MeshProxyList &out) { - QScriptValueIterator itr(value); - - qDebug() << "in meshesFromScriptValue, value.length =" << value.property("length").toInt32(); - - while (itr.hasNext()) { - itr.next(); - MeshProxy* meshProxy = qscriptvalue_cast(itr.value()); - if (meshProxy) { - out.append(meshProxy); - } else { - qDebug() << "null meshProxy"; - } - } -} - - -QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const MeshFace &meshFace) { - QScriptValue obj = engine->newObject(); - obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices)); - return obj; -} - -void meshFaceFromScriptValue(const QScriptValue &object, MeshFace& meshFaceResult) { - qVectorIntFromScriptValue(object.property("vertices"), meshFaceResult.vertexIndices); -} - -QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector) { - QScriptValue array = engine->newArray(); - for (int i = 0; i < vector.size(); i++) { - array.setProperty(i, meshFaceToScriptValue(engine, vector.at(i))); - } - return array; -} - -void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result) { - int length = array.property("length").toInteger(); - result.clear(); - - for (int i = 0; i < length; i++) { - MeshFace meshFace = MeshFace(); - meshFaceFromScriptValue(array.property(i), meshFace); - result << meshFace; - } -} diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 25b2cec331..e8390c3a86 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -315,51 +315,9 @@ Q_DECLARE_METATYPE(AnimationDetails); QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const AnimationDetails& event); void animationDetailsFromScriptValue(const QScriptValue& object, AnimationDetails& event); -namespace graphics { - class Mesh; -} - -using MeshPointer = std::shared_ptr; -class MeshProxy : public QObject { - Q_OBJECT -public: - virtual MeshPointer getMeshPointer() const = 0; - Q_INVOKABLE virtual int getNumVertices() const = 0; - Q_INVOKABLE virtual glm::vec3 getPos3(int index) const = 0; -}; - -Q_DECLARE_METATYPE(MeshProxy*); - -class MeshProxyList : public QList {}; // typedef and using fight with the Qt macros/templates, do this instead -Q_DECLARE_METATYPE(MeshProxyList); - - -QScriptValue meshToScriptValue(QScriptEngine* engine, MeshProxy* const &in); -void meshFromScriptValue(const QScriptValue& value, MeshProxy* &out); - -QScriptValue meshesToScriptValue(QScriptEngine* engine, const MeshProxyList &in); -void meshesFromScriptValue(const QScriptValue& value, MeshProxyList &out); - -class MeshFace { - -public: - MeshFace() {} - ~MeshFace() {} - - QVector vertexIndices; - // TODO -- material... -}; - -Q_DECLARE_METATYPE(MeshFace) -Q_DECLARE_METATYPE(QVector) - -QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const MeshFace &meshFace); -void meshFaceFromScriptValue(const QScriptValue &object, MeshFace& meshFaceResult); -QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector); -void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result); #endif // hifi_RegisteredMetaTypes_h From db56246cd6b1e88bd438ee5d8e4714b0948cc285 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 1 Feb 2018 17:30:56 -0800 Subject: [PATCH 061/569] more fixes, clean up, updates --- scripts/system/edit.js | 3 +- .../system/libraries/entitySelectionTool.js | 2525 +++++++++-------- 2 files changed, 1313 insertions(+), 1215 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 8dd2980ee6..4ef88fcd21 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -65,7 +65,7 @@ gridTool.setVisible(false); var entityListTool = new EntityListTool(); selectionManager.addEventListener(function () { - selectionDisplay.updateGrabbers(); + selectionDisplay.updateHandles(); entityIconOverlayManager.updatePositions(); // Update particle explorer @@ -1250,7 +1250,6 @@ var lastPosition = null; // Do some stuff regularly, like check for placement of various overlays Script.update.connect(function (deltaTime) { progressDialog.move(); - selectionDisplay.checkMove(); selectionDisplay.checkControllerMove(); var dOrientation = Math.abs(Quat.dot(Camera.orientation, lastOrientation) - 1); var dPosition = Vec3.distance(Camera.position, lastPosition); diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 87dc089c1a..c68fb6e71c 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -19,6 +19,7 @@ HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; SPACE_LOCAL = "local"; SPACE_WORLD = "world"; +HIGHLIGHT_LIST_NAME = "editHandleHighlightList"; Script.include("./controllers.js"); @@ -58,6 +59,22 @@ SelectionManager = (function() { subscribeToUpdateMessages(); + var COLOR_ORANGE_HIGHLIGHT = { red: 255, green: 99, blue: 9 } + var editHandleOutlineStyle = { + outlineUnoccludedColor: COLOR_ORANGE_HIGHLIGHT, + outlineOccludedColor: COLOR_ORANGE_HIGHLIGHT, + fillUnoccludedColor: COLOR_ORANGE_HIGHLIGHT, + fillOccludedColor: COLOR_ORANGE_HIGHLIGHT, + outlineUnoccludedAlpha: 1, + outlineOccludedAlpha: 0, + fillUnoccludedAlpha: 0, + fillOccludedAlpha: 0, + outlineWidth: 3, + isOutlineSmooth: true + }; + //disabling this for now as it is causing rendering issues with the other handle overlays + //Selection.enableListHighlight(HIGHLIGHT_LIST_NAME, editHandleOutlineStyle); + that.savedProperties = {}; that.selections = []; var listeners = []; @@ -94,7 +111,7 @@ SelectionManager = (function() { for (var i = 0; i < entityIDs.length; i++) { var entityID = entityIDs[i]; that.selections.push(entityID); - //Selection.addToSelectedItemsList("contextOverlayHighlightList", "entity", entityID); + Selection.addToSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID); } that._update(true); @@ -111,10 +128,10 @@ SelectionManager = (function() { } if (idx === -1) { that.selections.push(entityID); - //Selection.addToSelectedItemsList("contextOverlayHighlightList", "entity", entityID); + Selection.addToSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID); } else if (toggleSelection) { that.selections.splice(idx, 1); - //Selection.removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID); + Selection.removeFromSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID); } } @@ -125,7 +142,7 @@ SelectionManager = (function() { var idx = that.selections.indexOf(entityID); if (idx >= 0) { that.selections.splice(idx, 1); - //Selection.removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID); + Selection.removeFromSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID); } that._update(true); }; @@ -149,9 +166,11 @@ SelectionManager = (function() { that.localPosition = properties.position; that.localRotation = properties.rotation; that.localRegistrationPoint = properties.registrationPoint; + that.worldDimensions = properties.boundingBox.dimensions; that.worldPosition = properties.boundingBox.center; that.worldRotation = properties.boundingBox.rotation; + SelectionDisplay.setSpaceMode(SPACE_LOCAL); } else { that.localRotation = null; @@ -163,7 +182,7 @@ SelectionManager = (function() { var brn = properties.boundingBox.brn; var tfl = properties.boundingBox.tfl; - for (var i = 1; i < that.selections.length; i++) { + for (var i = 0; i < that.selections.length; i++) { properties = Entities.getEntityProperties(that.selections[i]); var bb = properties.boundingBox; brn.x = Math.min(bb.brn.x, brn.x); @@ -213,18 +232,6 @@ function normalizeDegrees(degrees) { return degrees; } -// FUNCTION: getRelativeCenterPosition -// Return the enter position of an entity relative to it's registrationPoint -// A registration point of (0.5, 0.5, 0.5) will have an offset of (0, 0, 0) -// A registration point of (1.0, 1.0, 1.0) will have an offset of (-dimensions.x / 2, -dimensions.y / 2, -dimensions.z / 2) -function getRelativeCenterPosition(dimensions, registrationPoint) { - return { - x: -dimensions.x * (registrationPoint.x - 0.5), - y: -dimensions.y * (registrationPoint.y - 0.5), - z: -dimensions.z * (registrationPoint.z - 0.5) - }; -} - // SELECTION DISPLAY DEFINITION SelectionDisplay = (function() { var that = {}; @@ -232,39 +239,39 @@ SelectionDisplay = (function() { var COLOR_GREEN = { red:44, green:142, blue:14 }; var COLOR_BLUE = { red:0, green:147, blue:197 }; var COLOR_RED = { red:183, green:10, blue:55 }; + var COLOR_HOVER = { red:227, green:227, blue:227 }; + var COLOR_SCALE_EDGE = { red:87, green:87, blue:87 }; + var COLOR_SCALE_CUBE = { red:106, green:106, blue:106 }; + var COLOR_SCALE_CUBE_SELECTED = { red:18, green:18, blue:18 }; - var GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET = 0.1; - var GRABBER_TRANSLATE_ARROW_CYLINDER_CAMERA_DISTANCE_MULTIPLE = 0.005; - var GRABBER_TRANSLATE_ARROW_CYLINDER_Y_MULTIPLE = 7.5; - var GRABBER_TRANSLATE_ARROW_CONE_CAMERA_DISTANCE_MULTIPLE = 0.025; - var GRABBER_ROTATE_RINGS_CAMERA_DISTANCE_MULTIPLE = 0.15; - var GRABBER_STRETCH_SPHERE_OFFSET = 0.06; - var GRABBER_STRETCH_SPHERE_CAMERA_DISTANCE_MULTIPLE = 0.01; - var GRABBER_SCALE_CUBE_OFFSET = 0.5; - var GRABBER_SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.015; - var GRABBER_CLONER_OFFSET = { x:0.9, y:-0.9, z:0.9 }; + var TRANSLATE_ARROW_CYLINDER_OFFSET = 0.1; + var TRANSLATE_ARROW_CYLINDER_CAMERA_DISTANCE_MULTIPLE = 0.005; + var TRANSLATE_ARROW_CYLINDER_Y_MULTIPLE = 7.5; + var TRANSLATE_ARROW_CONE_CAMERA_DISTANCE_MULTIPLE = 0.025; - var GRABBER_SCALE_CUBE_IDLE_COLOR = { red:106, green:106, blue:106 }; - var GRABBER_SCALE_CUBE_SELECTED_COLOR = { red:18, green:18, blue:18 }; - var GRABBER_SCALE_EDGE_COLOR = { red:87, green:87, blue:87 }; - var GRABBER_HOVER_COLOR = { red:227, green:227, blue:227 }; - - var SCALE_MINIMUM_DIMENSION = 0.02; - var STRETCH_MINIMUM_DIMENSION = 0.001; - var STRETCH_DIRECTION_ALL_FACTOR = 25; + var ROTATE_RING_CAMERA_DISTANCE_MULTIPLE = 0.15; + var ROTATE_CTRL_SNAP_ANGLE = 22.5; + var ROTATE_DEFAULT_SNAP_ANGLE = 1; + var ROTATE_DEFAULT_TICK_MARKS_ANGLE = 5; + var ROTATE_RING_IDLE_INNER_RADIUS = 0.97; + var ROTATE_RING_SELECTED_INNER_RADIUS = 0.9; // These are multipliers for sizing the rotation degrees display while rotating an entity - var ROTATION_DISPLAY_DISTANCE_MULTIPLIER = 1.0; - var ROTATION_DISPLAY_SIZE_X_MULTIPLIER = 0.3; - var ROTATION_DISPLAY_SIZE_Y_MULTIPLIER = 0.09; - var ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER = 0.07; + var ROTATE_DISPLAY_DISTANCE_MULTIPLIER = 1.0; + var ROTATE_DISPLAY_SIZE_X_MULTIPLIER = 0.3; + var ROTATE_DISPLAY_SIZE_Y_MULTIPLIER = 0.09; + var ROTATE_DISPLAY_LINE_HEIGHT_MULTIPLIER = 0.07; - var ROTATION_CTRL_SNAP_ANGLE = 22.5; - var ROTATION_DEFAULT_SNAP_ANGLE = 1; - var ROTATION_DEFAULT_TICK_MARKS_ANGLE = 5; + var STRETCH_SPHERE_OFFSET = 0.06; + var STRETCH_SPHERE_CAMERA_DISTANCE_MULTIPLE = 0.01; + var STRETCH_MINIMUM_DIMENSION = 0.001; + var STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE = 2; - var ROTATION_RING_IDLE_INNER_RADIUS = 0.97; - var ROTATION_RING_SELECTED_INNER_RADIUS = 0.9; + var SCALE_CUBE_OFFSET = 0.5; + var SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.015; + var SCALE_MINIMUM_DIMENSION = 0.02; + + var CLONER_OFFSET = { x:0.9, y:-0.9, z:0.9 }; var TRANSLATE_DIRECTION = { X : 0, @@ -305,79 +312,76 @@ SelectionDisplay = (function() { getControllerWorldLocation(Controller.Standard.RightHand, true) ]; - var rotZero; + var rotationZero; var rotationNormal; - var rotDegreePos; + var rotationDegreesPosition; var worldRotationX; var worldRotationY; var worldRotationZ; - var ctrlPressed = false; - var previousHandle = null; var previousHandleHelper = null; var previousHandleColor; - var activeTool = null; - var grabberTools = {}; + var ctrlPressed = false; - var grabberPropertiesTranslateArrowCones = { + var handlePropertiesTranslateArrowCones = { shape: "Cone", solid: true, visible: false, ignoreRayIntersection: false, drawInFront: true }; - var grabberPropertiesTranslateArrowCylinders = { + var handlePropertiesTranslateArrowCylinders = { shape: "Cylinder", solid: true, visible: false, ignoreRayIntersection: false, drawInFront: true }; - var grabberTranslateXCone = Overlays.addOverlay("shape", grabberPropertiesTranslateArrowCones); - var grabberTranslateXCylinder = Overlays.addOverlay("shape", grabberPropertiesTranslateArrowCylinders); - Overlays.editOverlay(grabberTranslateXCone, { color : COLOR_RED }); - Overlays.editOverlay(grabberTranslateXCylinder, { color : COLOR_RED }); - var grabberTranslateYCone = Overlays.addOverlay("shape", grabberPropertiesTranslateArrowCones); - var grabberTranslateYCylinder = Overlays.addOverlay("shape", grabberPropertiesTranslateArrowCylinders); - Overlays.editOverlay(grabberTranslateYCone, { color : COLOR_GREEN }); - Overlays.editOverlay(grabberTranslateYCylinder, { color : COLOR_GREEN }); - var grabberTranslateZCone = Overlays.addOverlay("shape", grabberPropertiesTranslateArrowCones); - var grabberTranslateZCylinder = Overlays.addOverlay("shape", grabberPropertiesTranslateArrowCylinders); - Overlays.editOverlay(grabberTranslateZCone, { color : COLOR_BLUE }); - Overlays.editOverlay(grabberTranslateZCylinder, { color : COLOR_BLUE }); + var handleTranslateXCone = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCones); + var handleTranslateXCylinder = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCylinders); + Overlays.editOverlay(handleTranslateXCone, { color : COLOR_RED }); + Overlays.editOverlay(handleTranslateXCylinder, { color : COLOR_RED }); + var handleTranslateYCone = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCones); + var handleTranslateYCylinder = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCylinders); + Overlays.editOverlay(handleTranslateYCone, { color : COLOR_GREEN }); + Overlays.editOverlay(handleTranslateYCylinder, { color : COLOR_GREEN }); + var handleTranslateZCone = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCones); + var handleTranslateZCylinder = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCylinders); + Overlays.editOverlay(handleTranslateZCone, { color : COLOR_BLUE }); + Overlays.editOverlay(handleTranslateZCylinder, { color : COLOR_BLUE }); - var grabberPropertiesRotateRings = { + var handlePropertiesRotateRings = { alpha: 1, solid: true, startAt: 0, endAt: 360, - innerRadius: ROTATION_RING_IDLE_INNER_RADIUS, - majorTickMarksAngle: ROTATION_DEFAULT_TICK_MARKS_ANGLE, + innerRadius: ROTATE_RING_IDLE_INNER_RADIUS, + majorTickMarksAngle: ROTATE_DEFAULT_TICK_MARKS_ANGLE, majorTickMarksLength: 0.1, visible: false, ignoreRayIntersection: false, drawInFront: true }; - var grabberRotatePitchRing = Overlays.addOverlay("circle3d", grabberPropertiesRotateRings); - Overlays.editOverlay(grabberRotatePitchRing, { + var handleRotatePitchRing = Overlays.addOverlay("circle3d", handlePropertiesRotateRings); + Overlays.editOverlay(handleRotatePitchRing, { color : COLOR_RED, majorTickMarksColor: COLOR_RED, }); - var grabberRotateYawRing = Overlays.addOverlay("circle3d", grabberPropertiesRotateRings); - Overlays.editOverlay(grabberRotateYawRing, { + var handleRotateYawRing = Overlays.addOverlay("circle3d", handlePropertiesRotateRings); + Overlays.editOverlay(handleRotateYawRing, { color : COLOR_GREEN, majorTickMarksColor: COLOR_GREEN, }); - var grabberRotateRollRing = Overlays.addOverlay("circle3d", grabberPropertiesRotateRings); - Overlays.editOverlay(grabberRotateRollRing, { + var handleRotateRollRing = Overlays.addOverlay("circle3d", handlePropertiesRotateRings); + Overlays.editOverlay(handleRotateRollRing, { color : COLOR_BLUE, majorTickMarksColor: COLOR_BLUE, }); - var grabberRotateCurrentRing = Overlays.addOverlay("circle3d", { + var handleRotateCurrentRing = Overlays.addOverlay("circle3d", { alpha: 1, color: { red: 255, green: 99, blue: 9 }, solid: true, @@ -405,21 +409,21 @@ SelectionDisplay = (function() { leftMargin: 0 }); - var grabberPropertiesStretchSpheres = { + var handlePropertiesStretchSpheres = { shape: "Sphere", solid: true, visible: false, ignoreRayIntersection: false, drawInFront: true }; - var grabberStretchXSphere = Overlays.addOverlay("shape", grabberPropertiesStretchSpheres); - Overlays.editOverlay(grabberStretchXSphere, { color : COLOR_RED }); - var grabberStretchYSphere = Overlays.addOverlay("shape", grabberPropertiesStretchSpheres); - Overlays.editOverlay(grabberStretchYSphere, { color : COLOR_GREEN }); - var grabberStretchZSphere = Overlays.addOverlay("shape", grabberPropertiesStretchSpheres); - Overlays.editOverlay(grabberStretchZSphere, { color : COLOR_BLUE }); + var handleStretchXSphere = Overlays.addOverlay("shape", handlePropertiesStretchSpheres); + Overlays.editOverlay(handleStretchXSphere, { color : COLOR_RED }); + var handleStretchYSphere = Overlays.addOverlay("shape", handlePropertiesStretchSpheres); + Overlays.editOverlay(handleStretchYSphere, { color : COLOR_GREEN }); + var handleStretchZSphere = Overlays.addOverlay("shape", handlePropertiesStretchSpheres); + Overlays.editOverlay(handleStretchZSphere, { color : COLOR_BLUE }); - var grabberPropertiesStretchPanel = { + var handlePropertiesStretchPanel = { shape: "Quad", alpha: 0.5, solid: true, @@ -427,52 +431,52 @@ SelectionDisplay = (function() { ignoreRayIntersection: true, drawInFront: true, } - var grabberStretchXPanel = Overlays.addOverlay("shape", grabberPropertiesStretchPanel); - Overlays.editOverlay(grabberStretchXPanel, { color : COLOR_RED }); - var grabberStretchYPanel = Overlays.addOverlay("shape", grabberPropertiesStretchPanel); - Overlays.editOverlay(grabberStretchYPanel, { color : COLOR_GREEN }); - var grabberStretchZPanel = Overlays.addOverlay("shape", grabberPropertiesStretchPanel); - Overlays.editOverlay(grabberStretchZPanel, { color : COLOR_BLUE }); + var handleStretchXPanel = Overlays.addOverlay("shape", handlePropertiesStretchPanel); + Overlays.editOverlay(handleStretchXPanel, { color : COLOR_RED }); + var handleStretchYPanel = Overlays.addOverlay("shape", handlePropertiesStretchPanel); + Overlays.editOverlay(handleStretchYPanel, { color : COLOR_GREEN }); + var handleStretchZPanel = Overlays.addOverlay("shape", handlePropertiesStretchPanel); + Overlays.editOverlay(handleStretchZPanel, { color : COLOR_BLUE }); - var grabberPropertiesScaleCubes = { + var handlePropertiesScaleCubes = { size: 0.025, - color: GRABBER_SCALE_CUBE_IDLE_COLOR, + color: COLOR_SCALE_CUBE, solid: true, visible: false, ignoreRayIntersection: false, drawInFront: true, borderSize: 1.4 }; - var grabberScaleLBNCube = Overlays.addOverlay("cube", grabberPropertiesScaleCubes); // (-x, -y, -z) - var grabberScaleRBNCube = Overlays.addOverlay("cube", grabberPropertiesScaleCubes); // (-x, -y, z) - var grabberScaleLBFCube = Overlays.addOverlay("cube", grabberPropertiesScaleCubes); // ( x, -y, -z) - var grabberScaleRBFCube = Overlays.addOverlay("cube", grabberPropertiesScaleCubes); // ( x, -y, z) - var grabberScaleLTNCube = Overlays.addOverlay("cube", grabberPropertiesScaleCubes); // (-x, y, -z) - var grabberScaleRTNCube = Overlays.addOverlay("cube", grabberPropertiesScaleCubes); // (-x, y, z) - var grabberScaleLTFCube = Overlays.addOverlay("cube", grabberPropertiesScaleCubes); // ( x, y, -z) - var grabberScaleRTFCube = Overlays.addOverlay("cube", grabberPropertiesScaleCubes); // ( x, y, z) + var handleScaleLBNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, -y, -z) + var handleScaleRBNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, -y, z) + var handleScaleLBFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, -y, -z) + var handleScaleRBFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, -y, z) + var handleScaleLTNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, y, -z) + var handleScaleRTNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, y, z) + var handleScaleLTFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, y, -z) + var handleScaleRTFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, y, z) - var grabberPropertiesScaleEdge = { - color: GRABBER_SCALE_EDGE_COLOR, + var handlePropertiesScaleEdge = { + color: COLOR_SCALE_EDGE, visible: false, ignoreRayIntersection: true, drawInFront: true, lineWidth: 0.2 } - var grabberScaleTREdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); - var grabberScaleTLEdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); - var grabberScaleTFEdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); - var grabberScaleTNEdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); - var grabberScaleBREdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); - var grabberScaleBLEdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); - var grabberScaleBFEdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); - var grabberScaleBNEdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); - var grabberScaleNREdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); - var grabberScaleNLEdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); - var grabberScaleFREdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); - var grabberScaleFLEdge = Overlays.addOverlay("line3d", grabberPropertiesScaleEdge); + var handleScaleTREdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); + var handleScaleTLEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); + var handleScaleTFEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); + var handleScaleTNEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); + var handleScaleBREdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); + var handleScaleBLEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); + var handleScaleBFEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); + var handleScaleBNEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); + var handleScaleNREdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); + var handleScaleNLEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); + var handleScaleFREdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); + var handleScaleFLEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var grabberCloner = Overlays.addOverlay("cube", { + var handleCloner = Overlays.addOverlay("cube", { size: 0.05, color: COLOR_GREEN, solid: true, @@ -482,97 +486,107 @@ SelectionDisplay = (function() { borderSize: 1.4 }); + // setting to 0 alpha for now to keep this hidden vs using visible false + // because its used as the translate xz tool handle overlay var selectionBox = Overlays.addOverlay("cube", { size: 1, color: COLOR_RED, - alpha: 0, // setting to 0 alpha for now to keep this hidden vs using visible false because its used as the translate xz tool overlay + alpha: 0, solid: false, visible: false, dashed: false }); var allOverlays = [ - grabberTranslateXCone, - grabberTranslateXCylinder, - grabberTranslateYCone, - grabberTranslateYCylinder, - grabberTranslateZCone, - grabberTranslateZCylinder, - grabberRotatePitchRing, - grabberRotateYawRing, - grabberRotateRollRing, - grabberRotateCurrentRing, + handleTranslateXCone, + handleTranslateXCylinder, + handleTranslateYCone, + handleTranslateYCylinder, + handleTranslateZCone, + handleTranslateZCylinder, + handleRotatePitchRing, + handleRotateYawRing, + handleRotateRollRing, + handleRotateCurrentRing, rotationDegreesDisplay, - grabberStretchXSphere, - grabberStretchYSphere, - grabberStretchZSphere, - grabberStretchXPanel, - grabberStretchYPanel, - grabberStretchZPanel, - grabberScaleLBNCube, - grabberScaleRBNCube, - grabberScaleLBFCube, - grabberScaleRBFCube, - grabberScaleLTNCube, - grabberScaleRTNCube, - grabberScaleLTFCube, - grabberScaleRTFCube, - grabberScaleTREdge, - grabberScaleTLEdge, - grabberScaleTFEdge, - grabberScaleTNEdge, - grabberScaleBREdge, - grabberScaleBLEdge, - grabberScaleBFEdge, - grabberScaleBNEdge, - grabberScaleNREdge, - grabberScaleNLEdge, - grabberScaleFREdge, - grabberScaleFLEdge, - grabberCloner, + handleStretchXSphere, + handleStretchYSphere, + handleStretchZSphere, + handleStretchXPanel, + handleStretchYPanel, + handleStretchZPanel, + handleScaleLBNCube, + handleScaleRBNCube, + handleScaleLBFCube, + handleScaleRBFCube, + handleScaleLTNCube, + handleScaleRTNCube, + handleScaleLTFCube, + handleScaleRTFCube, + handleScaleTREdge, + handleScaleTLEdge, + handleScaleTFEdge, + handleScaleTNEdge, + handleScaleBREdge, + handleScaleBLEdge, + handleScaleBFEdge, + handleScaleBNEdge, + handleScaleNREdge, + handleScaleNLEdge, + handleScaleFREdge, + handleScaleFLEdge, + handleCloner, selectionBox ]; - overlayNames[grabberTranslateXCone] = "grabberTranslateXCone"; - overlayNames[grabberTranslateXCylinder] = "grabberTranslateXCylinder"; - overlayNames[grabberTranslateYCone] = "grabberTranslateYCone"; - overlayNames[grabberTranslateYCylinder] = "grabberTranslateYCylinder"; - overlayNames[grabberTranslateZCone] = "grabberTranslateZCone"; - overlayNames[grabberTranslateZCylinder] = "grabberTranslateZCylinder"; - overlayNames[grabberRotatePitchRing] = "grabberRotatePitchRing"; - overlayNames[grabberRotateYawRing] = "grabberRotateYawRing"; - overlayNames[grabberRotateRollRing] = "grabberRotateRollRing"; - overlayNames[grabberRotateCurrentRing] = "grabberRotateCurrentRing"; + overlayNames[handleTranslateXCone] = "handleTranslateXCone"; + overlayNames[handleTranslateXCylinder] = "handleTranslateXCylinder"; + overlayNames[handleTranslateYCone] = "handleTranslateYCone"; + overlayNames[handleTranslateYCylinder] = "handleTranslateYCylinder"; + overlayNames[handleTranslateZCone] = "handleTranslateZCone"; + overlayNames[handleTranslateZCylinder] = "handleTranslateZCylinder"; + + overlayNames[handleRotatePitchRing] = "handleRotatePitchRing"; + overlayNames[handleRotateYawRing] = "handleRotateYawRing"; + overlayNames[handleRotateRollRing] = "handleRotateRollRing"; + overlayNames[handleRotateCurrentRing] = "handleRotateCurrentRing"; overlayNames[rotationDegreesDisplay] = "rotationDegreesDisplay"; - overlayNames[grabberStretchXSphere] = "grabberStretchXSphere"; - overlayNames[grabberStretchYSphere] = "grabberStretchYSphere"; - overlayNames[grabberStretchZSphere] = "grabberStretchZSphere"; - overlayNames[grabberStretchXPanel] = "grabberStretchXPanel"; - overlayNames[grabberStretchYPanel] = "grabberStretchYPanel"; - overlayNames[grabberStretchZPanel] = "grabberStretchZPanel"; - overlayNames[grabberScaleLBNCube] = "grabberScaleLBNCube"; - overlayNames[grabberScaleRBNCube] = "grabberScaleRBNCube"; - overlayNames[grabberScaleLBFCube] = "grabberScaleLBFCube"; - overlayNames[grabberScaleRBFCube] = "grabberScaleRBFCube"; - overlayNames[grabberScaleLTNCube] = "grabberScaleLTNCube"; - overlayNames[grabberScaleRTNCube] = "grabberScaleRTNCube"; - overlayNames[grabberScaleLTFCube] = "grabberScaleLTFCube"; - overlayNames[grabberScaleRTFCube] = "grabberScaleRTFCube"; - overlayNames[grabberScaleTREdge] = "grabberScaleTREdge"; - overlayNames[grabberScaleTLEdge] = "grabberScaleTLEdge"; - overlayNames[grabberScaleTFEdge] = "grabberScaleTFEdge"; - overlayNames[grabberScaleTNEdge] = "grabberScaleTNEdge"; - overlayNames[grabberScaleBREdge] = "grabberScaleBREdge"; - overlayNames[grabberScaleBLEdge] = "grabberScaleBLEdge"; - overlayNames[grabberScaleBFEdge] = "grabberScaleBFEdge"; - overlayNames[grabberScaleBNEdge] = "grabberScaleBNEdge"; - overlayNames[grabberScaleNREdge] = "grabberScaleNREdge"; - overlayNames[grabberScaleNLEdge] = "grabberScaleNLEdge"; - overlayNames[grabberScaleFREdge] = "grabberScaleFREdge"; - overlayNames[grabberScaleFLEdge] = "grabberScaleFLEdge"; - overlayNames[grabberCloner] = "grabberCloner"; + + overlayNames[handleStretchXSphere] = "handleStretchXSphere"; + overlayNames[handleStretchYSphere] = "handleStretchYSphere"; + overlayNames[handleStretchZSphere] = "handleStretchZSphere"; + overlayNames[handleStretchXPanel] = "handleStretchXPanel"; + overlayNames[handleStretchYPanel] = "handleStretchYPanel"; + overlayNames[handleStretchZPanel] = "handleStretchZPanel"; + + overlayNames[handleScaleLBNCube] = "handleScaleLBNCube"; + overlayNames[handleScaleRBNCube] = "handleScaleRBNCube"; + overlayNames[handleScaleLBFCube] = "handleScaleLBFCube"; + overlayNames[handleScaleRBFCube] = "handleScaleRBFCube"; + overlayNames[handleScaleLTNCube] = "handleScaleLTNCube"; + overlayNames[handleScaleRTNCube] = "handleScaleRTNCube"; + overlayNames[handleScaleLTFCube] = "handleScaleLTFCube"; + overlayNames[handleScaleRTFCube] = "handleScaleRTFCube"; + + overlayNames[handleScaleTREdge] = "handleScaleTREdge"; + overlayNames[handleScaleTLEdge] = "handleScaleTLEdge"; + overlayNames[handleScaleTFEdge] = "handleScaleTFEdge"; + overlayNames[handleScaleTNEdge] = "handleScaleTNEdge"; + overlayNames[handleScaleBREdge] = "handleScaleBREdge"; + overlayNames[handleScaleBLEdge] = "handleScaleBLEdge"; + overlayNames[handleScaleBFEdge] = "handleScaleBFEdge"; + overlayNames[handleScaleBNEdge] = "handleScaleBNEdge"; + overlayNames[handleScaleNREdge] = "handleScaleNREdge"; + overlayNames[handleScaleNLEdge] = "handleScaleNLEdge"; + overlayNames[handleScaleFREdge] = "handleScaleFREdge"; + overlayNames[handleScaleFLEdge] = "handleScaleFLEdge"; + + overlayNames[handleCloner] = "handleCloner"; overlayNames[selectionBox] = "selectionBox"; + var activeTool = null; + var handleTools = {}; + // We get mouseMoveEvents from the handControllers, via handControllerPointer. // But we dont' get mousePressEvents. that.triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); @@ -604,8 +618,247 @@ SelectionDisplay = (function() { that.triggerMapping.from(Controller.Standard.RT).peek().to(makeTriggerHandler(Controller.Standard.RightHand)); that.triggerMapping.from(Controller.Standard.LT).peek().to(makeTriggerHandler(Controller.Standard.LeftHand)); + // FUNCTION DEF(s): Intersection Check Helpers + function testRayIntersect(queryRay, overlayIncludes, overlayExcludes) { + var wantDebug = false; + if ((queryRay === undefined) || (queryRay === null)) { + if (wantDebug) { + print("testRayIntersect - EARLY EXIT -> queryRay is undefined OR null!"); + } + return null; + } + + var intersectObj = Overlays.findRayIntersection(queryRay, true, overlayIncludes, overlayExcludes); + + if (wantDebug) { + if (!overlayIncludes) { + print("testRayIntersect - no overlayIncludes provided."); + } + if (!overlayExcludes) { + print("testRayIntersect - no overlayExcludes provided."); + } + print("testRayIntersect - Hit: " + intersectObj.intersects); + print(" intersectObj.overlayID:" + intersectObj.overlayID + "[" + overlayNames[intersectObj.overlayID] + "]"); + print(" OverlayName: " + overlayNames[intersectObj.overlayID]); + print(" intersectObj.distance:" + intersectObj.distance); + print(" intersectObj.face:" + intersectObj.face); + Vec3.print(" intersectObj.intersection:", intersectObj.intersection); + } + + return intersectObj; + } + + // FUNCTION: MOUSE PRESS EVENT + that.mousePressEvent = function (event) { + var wantDebug = false; + if (wantDebug) { + print("=============== eST::MousePressEvent BEG ======================="); + } + if (!event.isLeftButton && !that.triggered) { + // EARLY EXIT-(if another mouse button than left is pressed ignore it) + return false; + } + + var pickRay = generalComputePickRay(event.x, event.y); + // TODO_Case6491: Move this out to setup just to make it once + var interactiveOverlays = [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]; + for (var key in handleTools) { + if (handleTools.hasOwnProperty(key)) { + interactiveOverlays.push(key); + } + } + + // Start with unknown mode, in case no tool can handle this. + activeTool = null; + + var results = testRayIntersect(pickRay, interactiveOverlays); + if (results.intersects) { + var hitOverlayID = results.overlayID; + if ((hitOverlayID === HMD.tabletID) || (hitOverlayID === HMD.tabletScreenID) || (hitOverlayID === HMD.homeButtonID)) { + // EARLY EXIT-(mouse clicks on the tablet should override the edit affordances) + return false; + } + + entityIconOverlayManager.setIconsSelectable(SelectionManager.selections, true); + + var hitTool = handleTools[ hitOverlayID ]; + if (hitTool) { + activeTool = hitTool; + if (activeTool.onBegin) { + activeTool.onBegin(event, pickRay, results); + } else { + print("ERROR: entitySelectionTool.mousePressEvent - ActiveTool(" + activeTool.mode + ") missing onBegin"); + } + } else { + print("ERROR: entitySelectionTool.mousePressEvent - Hit unexpected object, check interactiveOverlays"); + }// End_if (hitTool) + }// End_If(results.intersects) + + if (wantDebug) { + print(" DisplayMode: " + getMode()); + print("=============== eST::MousePressEvent END ======================="); + } + + // If mode is known then we successfully handled this; + // otherwise, we're missing a tool. + return activeTool; + }; + + that.resetPreviousHandleColor = function() { + if (previousHandle != null) { + Overlays.editOverlay(previousHandle, { color: previousHandleColor }); + previousHandle = null; + } + if (previousHandleHelper != null) { + Overlays.editOverlay(previousHandleHelper, { color: previousHandleColor }); + previousHandleHelper = null; + } + }; + + that.getHandleHelper = function(overlay) { + if (overlay === handleTranslateXCone) { + return handleTranslateXCylinder; + } else if (overlay === handleTranslateXCylinder) { + return handleTranslateXCone; + } else if (overlay === handleTranslateYCone) { + return handleTranslateYCylinder; + } else if (overlay === handleTranslateYCylinder) { + return handleTranslateYCone; + } else if (overlay === handleTranslateZCone) { + return handleTranslateZCylinder; + } else if (overlay === handleTranslateZCylinder) { + return handleTranslateZCone; + } + }; + + // FUNCTION: MOUSE MOVE EVENT + that.mouseMoveEvent = function(event) { + var wantDebug = false; + if (wantDebug) { + print("=============== eST::MouseMoveEvent BEG ======================="); + } + if (activeTool) { + if (wantDebug) { + print(" Trigger ActiveTool(" + activeTool.mode + ")'s onMove"); + } + activeTool.onMove(event); + + if (wantDebug) { + print(" Trigger SelectionManager::update"); + } + SelectionManager._update(); + + if (wantDebug) { + print("=============== eST::MouseMoveEvent END ======================="); + } + // EARLY EXIT--(Move handled via active tool) + return true; + } + + // if no tool is active, then just look for handles to highlight... + var pickRay = generalComputePickRay(event.x, event.y); + var result = Overlays.findRayIntersection(pickRay); + var pickedColor; + var highlightNeeded = false; + + if (result.intersects) { + switch (result.overlayID) { + case handleTranslateXCone: + case handleTranslateXCylinder: + case handleRotatePitchRing: + case handleStretchXSphere: + pickedColor = COLOR_RED; + highlightNeeded = true; + break; + case handleTranslateYCone: + case handleTranslateYCylinder: + case handleRotateYawRing: + case handleStretchYSphere: + pickedColor = COLOR_GREEN; + highlightNeeded = true; + break; + case handleTranslateZCone: + case handleTranslateZCylinder: + case handleRotateRollRing: + case handleStretchZSphere: + pickedColor = COLOR_BLUE; + highlightNeeded = true; + break; + case handleScaleLBNCube: + case handleScaleRBNCube: + case handleScaleLBFCube: + case handleScaleRBFCube: + case handleScaleLTNCube: + case handleScaleRTNCube: + case handleScaleLTFCube: + case handleScaleRTFCube: + pickedColor = COLOR_SCALE_CUBE; + highlightNeeded = true; + break; + default: + that.resetPreviousHandleColor(); + break; + } + + if (highlightNeeded) { + that.resetPreviousHandleColor(); + Overlays.editOverlay(result.overlayID, { color: COLOR_HOVER }); + previousHandle = result.overlayID; + previousHandleHelper = that.getHandleHelper(result.overlayID); + if (previousHandleHelper != null) { + Overlays.editOverlay(previousHandleHelper, { color: COLOR_HOVER }); + } + previousHandleColor = pickedColor; + } + + } else { + that.resetPreviousHandleColor(); + } + + if (wantDebug) { + print("=============== eST::MouseMoveEvent END ======================="); + } + return false; + }; + + // FUNCTION: MOUSE RELEASE EVENT + that.mouseReleaseEvent = function(event) { + var wantDebug = false; + if (wantDebug) { + print("=============== eST::MouseReleaseEvent BEG ======================="); + } + var showHandles = false; + if (activeTool) { + if (activeTool.onEnd) { + if (wantDebug) { + print(" Triggering ActiveTool(" + activeTool.mode + ")'s onEnd"); + } + activeTool.onEnd(event); + } else if (wantDebug) { + print(" ActiveTool(" + activeTool.mode + ")'s missing onEnd"); + } + } + + showHandles = activeTool; // base on prior tool value + activeTool = null; + + // if something is selected, then reset the "original" properties for any potential next click+move operation + if (SelectionManager.hasSelection()) { + if (showHandles) { + if (wantDebug) { + print(" Triggering that.select"); + } + that.select(SelectionManager.selections[0], event); + } + } + + if (wantDebug) { + print("=============== eST::MouseReleaseEvent END ======================="); + } + }; + // Control key remains active only while key is held down - function keyReleaseEvent(key) { + that.keyReleaseEvent = function(key) { if (key.key === 16777249) { ctrlPressed = false; that.updateActiveRotateRing(); @@ -613,15 +866,33 @@ SelectionDisplay = (function() { } // Triggers notification on specific key driven events - function keyPressEvent(key) { + that.keyPressEvent = function(key) { if (key.key === 16777249) { ctrlPressed = true; that.updateActiveRotateRing(); } } - Controller.keyPressEvent.connect(keyPressEvent); - Controller.keyReleaseEvent.connect(keyReleaseEvent); + // NOTE: mousePressEvent and mouseMoveEvent from the main script should call us., so we don't hook these: + // Controller.mousePressEvent.connect(that.mousePressEvent); + // Controller.mouseMoveEvent.connect(that.mouseMoveEvent); + Controller.mouseReleaseEvent.connect(that.mouseReleaseEvent); + Controller.keyPressEvent.connect(that.keyPressEvent); + Controller.keyReleaseEvent.connect(that.keyReleaseEvent); + + that.checkControllerMove = function() { + if (SelectionManager.hasSelection()) { + var controllerPose = getControllerWorldLocation(activeHand, true); + var hand = (activeHand === Controller.Standard.LeftHand) ? 0 : 1; + if (controllerPose.valid && lastControllerPoses[hand].valid) { + if (!Vec3.equal(controllerPose.position, lastControllerPoses[hand].position) || + !Vec3.equal(controllerPose.rotation, lastControllerPoses[hand].rotation)) { + that.mouseMoveEvent({}); + } + } + lastControllerPoses[hand] = controllerPose; + } + }; function controllerComputePickRay() { var controllerPose = getControllerWorldLocation(activeHand, true); @@ -636,17 +907,703 @@ SelectionDisplay = (function() { function generalComputePickRay(x, y) { return controllerComputePickRay() || Camera.computePickRay(x, y); } - - function addGrabberTool(overlay, tool) { - grabberTools[overlay] = tool; + + function getDistanceToCamera(position) { + var cameraPosition = Camera.getPosition(); + var toCameraDistance = Vec3.length(Vec3.subtract(cameraPosition, position)); + return toCameraDistance; + } + + // @return string - The mode of the currently active tool; + // otherwise, "UNKNOWN" if there's no active tool. + function getMode() { + return (activeTool ? activeTool.mode : "UNKNOWN"); + } + + that.cleanup = function() { + for (var i = 0; i < allOverlays.length; i++) { + Overlays.deleteOverlay(allOverlays[i]); + } + }; + + that.select = function(entityID, event) { + var properties = Entities.getEntityProperties(SelectionManager.selections[0]); + + lastCameraPosition = Camera.getPosition(); + lastCameraOrientation = Camera.getOrientation(); + + if (event !== false) { + pickRay = generalComputePickRay(event.x, event.y); + + var wantDebug = false; + if (wantDebug) { + print("select() with EVENT...... "); + print(" event.y:" + event.y); + Vec3.print(" current position:", properties.position); + } + } + + that.updateHandles(); + }; + + // FUNCTION: SET SPACE MODE + that.setSpaceMode = function(newSpaceMode) { + var wantDebug = false; + if (wantDebug) { + print("======> SetSpaceMode called. ========"); + } + + if (spaceMode !== newSpaceMode) { + if (wantDebug) { + print(" Updating SpaceMode From: " + spaceMode + " To: " + newSpaceMode); + } + spaceMode = newSpaceMode; + that.updateHandles(); + } else if (wantDebug) { + print("WARNING: entitySelectionTool.setSpaceMode - Can't update SpaceMode. CurrentMode: " + spaceMode + " DesiredMode: " + newSpaceMode); + } + if (wantDebug) { + print("====== SetSpaceMode called. <========"); + } + }; + + function addHandleTool(overlay, tool) { + handleTools[overlay] = tool; return tool; } - function addGrabberTranslateTool(overlay, mode, direction) { + // @param: toolHandle: The overlayID associated with the tool + // that correlates to the tool you wish to query. + // @note: If toolHandle is null or undefined then activeTool + // will be checked against those values as opposed to + // the tool registered under toolHandle. Null & Undefined + // are treated as separate values. + // @return: bool - Indicates if the activeTool is that queried. + function isActiveTool(toolHandle) { + if (!toolHandle) { + // Allow isActiveTool(null) and similar to return true if there's + // no active tool + return (activeTool === toolHandle); + } + + if (!handleTools.hasOwnProperty(toolHandle)) { + print("WARNING: entitySelectionTool.isActiveTool - Encountered unknown grabberToolHandle: " + toolHandle + ". Tools should be registered via addHandleTool."); + // EARLY EXIT + return false; + } + + return (activeTool === handleTools[ toolHandle ]); + } + + // FUNCTION: UPDATE HANDLES + that.updateHandles = function() { + var wantDebug = false; + if (wantDebug) { + print("======> Update Handles ======="); + print(" Selections Count: " + SelectionManager.selections.length); + print(" SpaceMode: " + spaceMode); + print(" DisplayMode: " + getMode()); + } + + if (SelectionManager.selections.length === 0) { + that.setOverlaysVisible(false); + return; + } + + if (SelectionManager.hasSelection()) { + var position = SelectionManager.worldPosition; + var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation; + var dimensions = spaceMode === SPACE_LOCAL ? SelectionManager.localDimensions : SelectionManager.worldDimensions; + var rotationInverse = Quat.inverse(rotation); + var toCameraDistance = getDistanceToCamera(position); + + var localRotationX = Quat.fromPitchYawRollDegrees(0, 0, -90); + rotationX = Quat.multiply(rotation, localRotationX); + worldRotationX = rotationX; + var localRotationY = Quat.fromPitchYawRollDegrees(0, 90, 0); + rotationY = Quat.multiply(rotation, localRotationY); + worldRotationY = rotationY; + var localRotationZ = Quat.fromPitchYawRollDegrees(90, 0, 0); + rotationZ = Quat.multiply(rotation, localRotationZ); + worldRotationZ = rotationZ; + + // UPDATE TRANSLATION ARROWS + var arrowCylinderDimension = toCameraDistance * TRANSLATE_ARROW_CYLINDER_CAMERA_DISTANCE_MULTIPLE; + var arrowCylinderDimensions = { + x:arrowCylinderDimension, + y:arrowCylinderDimension * TRANSLATE_ARROW_CYLINDER_Y_MULTIPLE, + z:arrowCylinderDimension + }; + var arrowConeDimension = toCameraDistance * TRANSLATE_ARROW_CONE_CAMERA_DISTANCE_MULTIPLE; + var arrowConeDimensions = { x:arrowConeDimension, y:arrowConeDimension, z:arrowConeDimension }; + var cylinderXPosition = { x:TRANSLATE_ARROW_CYLINDER_OFFSET * toCameraDistance, y:0, z:0 }; + cylinderXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, cylinderXPosition)); + Overlays.editOverlay(handleTranslateXCylinder, { + position: cylinderXPosition, + rotation: rotationX, + dimensions: arrowCylinderDimensions + }); + var cylinderXDiff = Vec3.subtract(cylinderXPosition, position); + var coneXPosition = Vec3.sum(cylinderXPosition, Vec3.multiply(Vec3.normalize(cylinderXDiff), arrowCylinderDimensions.y * 0.83)); + Overlays.editOverlay(handleTranslateXCone, { + position: coneXPosition, + rotation: rotationX, + dimensions: arrowConeDimensions + }); + var cylinderYPosition = { x:0, y:TRANSLATE_ARROW_CYLINDER_OFFSET * toCameraDistance, z:0 }; + cylinderYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, cylinderYPosition)); + Overlays.editOverlay(handleTranslateYCylinder, { + position: cylinderYPosition, + rotation: rotationY, + dimensions: arrowCylinderDimensions + }); + var cylinderYDiff = Vec3.subtract(cylinderYPosition, position); + var coneYPosition = Vec3.sum(cylinderYPosition, Vec3.multiply(Vec3.normalize(cylinderYDiff), arrowCylinderDimensions.y * 0.83)); + Overlays.editOverlay(handleTranslateYCone, { + position: coneYPosition, + rotation: rotationY, + dimensions: arrowConeDimensions + }); + var cylinderZPosition = { x:0, y:0, z:TRANSLATE_ARROW_CYLINDER_OFFSET * toCameraDistance }; + cylinderZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, cylinderZPosition)); + Overlays.editOverlay(handleTranslateZCylinder, { + position: cylinderZPosition, + rotation: rotationZ, + dimensions: arrowCylinderDimensions + }); + var cylinderZDiff = Vec3.subtract(cylinderZPosition, position); + var coneZPosition = Vec3.sum(cylinderZPosition, Vec3.multiply(Vec3.normalize(cylinderZDiff), arrowCylinderDimensions.y * 0.83)); + Overlays.editOverlay(handleTranslateZCone, { + position: coneZPosition, + rotation: rotationZ, + dimensions: arrowConeDimensions + }); + + // UPDATE ROTATION RINGS + var rotateDimension = toCameraDistance * ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; + var rotateDimensions = { x:rotateDimension, y:rotateDimension, z:rotateDimension }; + if (!isActiveTool(handleRotatePitchRing)) { + Overlays.editOverlay(handleRotatePitchRing, { + position: position, + rotation: rotationY, + dimensions: rotateDimensions, + majorTickMarksAngle: ROTATE_DEFAULT_TICK_MARKS_ANGLE + }); + } + if (!isActiveTool(handleRotateYawRing)) { + Overlays.editOverlay(handleRotateYawRing, { + position: position, + rotation: rotationZ, + dimensions: rotateDimensions, + majorTickMarksAngle: ROTATE_DEFAULT_TICK_MARKS_ANGLE + }); + } + if (!isActiveTool(handleRotateRollRing)) { + Overlays.editOverlay(handleRotateRollRing, { + position: position, + rotation: rotationX, + dimensions: rotateDimensions, + majorTickMarksAngle: ROTATE_DEFAULT_TICK_MARKS_ANGLE + }); + } + Overlays.editOverlay(handleRotateCurrentRing, { dimensions: rotateDimensions }); + that.updateActiveRotateRing(); + + // UPDATE SCALE CUBES + var scaleCubeOffsetX = SCALE_CUBE_OFFSET * dimensions.x; + var scaleCubeOffsetY = SCALE_CUBE_OFFSET * dimensions.y; + var scaleCubeOffsetZ = SCALE_CUBE_OFFSET * dimensions.z; + var scaleCubeDimension = toCameraDistance * SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE; + var scaleCubeDimensions = { x:scaleCubeDimension, y:scaleCubeDimension, z:scaleCubeDimension }; + var scaleCubeRotation = spaceMode === SPACE_LOCAL ? rotation : Quat.IDENTITY; + var scaleLBNCubePosition = { x:-scaleCubeOffsetX, y:-scaleCubeOffsetY, z:-scaleCubeOffsetZ }; + scaleLBNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLBNCubePosition)); + Overlays.editOverlay(handleScaleLBNCube, { + position: scaleLBNCubePosition, + rotation: scaleCubeRotation, + dimensions: scaleCubeDimensions + }); + var scaleRBNCubePosition = { x:-scaleCubeOffsetX, y:-scaleCubeOffsetY, z:scaleCubeOffsetZ }; + scaleRBNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRBNCubePosition)); + Overlays.editOverlay(handleScaleRBNCube, { + position: scaleRBNCubePosition, + rotation: scaleCubeRotation, + dimensions: scaleCubeDimensions + }); + var scaleLBFCubePosition = { x:scaleCubeOffsetX, y:-scaleCubeOffsetY, z:-scaleCubeOffsetZ }; + scaleLBFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLBFCubePosition)); + Overlays.editOverlay(handleScaleLBFCube, { + position: scaleLBFCubePosition, + rotation: scaleCubeRotation, + dimensions: scaleCubeDimensions + }); + var scaleRBFCubePosition = { x:scaleCubeOffsetX, y:-scaleCubeOffsetY, z:scaleCubeOffsetZ }; + scaleRBFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRBFCubePosition)); + Overlays.editOverlay(handleScaleRBFCube, { + position: scaleRBFCubePosition, + rotation: scaleCubeRotation, + dimensions: scaleCubeDimensions + }); + var scaleLTNCubePosition = { x:-scaleCubeOffsetX, y:scaleCubeOffsetY, z:-scaleCubeOffsetZ }; + scaleLTNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLTNCubePosition)); + Overlays.editOverlay(handleScaleLTNCube, { + position: scaleLTNCubePosition, + rotation: scaleCubeRotation, + dimensions: scaleCubeDimensions + }); + var scaleRTNCubePosition = { x:-scaleCubeOffsetX, y:scaleCubeOffsetY, z:scaleCubeOffsetZ }; + scaleRTNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRTNCubePosition)); + Overlays.editOverlay(handleScaleRTNCube, { + position: scaleRTNCubePosition, + rotation: scaleCubeRotation, + dimensions: scaleCubeDimensions + }); + var scaleLTFCubePosition = { x:scaleCubeOffsetX, y:scaleCubeOffsetY, z:-scaleCubeOffsetZ }; + scaleLTFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLTFCubePosition)); + Overlays.editOverlay(handleScaleLTFCube, { + position: scaleLTFCubePosition, + rotation: scaleCubeRotation, + dimensions: scaleCubeDimensions + }); + var scaleRTFCubePosition = { x:scaleCubeOffsetX, y:scaleCubeOffsetY, z:scaleCubeOffsetZ }; + scaleRTFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRTFCubePosition)); + Overlays.editOverlay(handleScaleRTFCube, { + position: scaleRTFCubePosition, + rotation: scaleCubeRotation, + dimensions: scaleCubeDimensions + }); + + // UPDATE SCALE EDGES + Overlays.editOverlay(handleScaleTREdge, { start: scaleRTNCubePosition, end: scaleRTFCubePosition }); + Overlays.editOverlay(handleScaleTLEdge, { start: scaleLTNCubePosition, end: scaleLTFCubePosition }); + Overlays.editOverlay(handleScaleTFEdge, { start: scaleLTFCubePosition, end: scaleRTFCubePosition }); + Overlays.editOverlay(handleScaleTNEdge, { start: scaleLTNCubePosition, end: scaleRTNCubePosition }); + Overlays.editOverlay(handleScaleBREdge, { start: scaleRBNCubePosition, end: scaleRBFCubePosition }); + Overlays.editOverlay(handleScaleBLEdge, { start: scaleLBNCubePosition, end: scaleLBFCubePosition }); + Overlays.editOverlay(handleScaleBFEdge, { start: scaleLBFCubePosition, end: scaleRBFCubePosition }); + Overlays.editOverlay(handleScaleBNEdge, { start: scaleLBNCubePosition, end: scaleRBNCubePosition }); + Overlays.editOverlay(handleScaleNREdge, { start: scaleRTNCubePosition, end: scaleRBNCubePosition }); + Overlays.editOverlay(handleScaleNLEdge, { start: scaleLTNCubePosition, end: scaleLBNCubePosition }); + Overlays.editOverlay(handleScaleFREdge, { start: scaleRTFCubePosition, end: scaleRBFCubePosition }); + Overlays.editOverlay(handleScaleFLEdge, { start: scaleLTFCubePosition, end: scaleLBFCubePosition }); + + // UPDATE STRETCH SPHERES + var stretchSphereDimension = toCameraDistance * STRETCH_SPHERE_CAMERA_DISTANCE_MULTIPLE; + var stretchSphereDimensions = { x:stretchSphereDimension, y:stretchSphereDimension, z:stretchSphereDimension }; + var stretchXPosition = { x:STRETCH_SPHERE_OFFSET * toCameraDistance, y:0, z:0 }; + stretchXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchXPosition)); + Overlays.editOverlay(handleStretchXSphere, { + position: stretchXPosition, + dimensions: stretchSphereDimensions + }); + var stretchYPosition = { x:0, y:STRETCH_SPHERE_OFFSET * toCameraDistance, z:0 }; + stretchYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchYPosition)); + Overlays.editOverlay(handleStretchYSphere, { + position: stretchYPosition, + dimensions: stretchSphereDimensions + }); + var stretchZPosition = { x:0, y:0, z:STRETCH_SPHERE_OFFSET * toCameraDistance }; + stretchZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchZPosition)); + Overlays.editOverlay(handleStretchZSphere, { + position: stretchZPosition, + dimensions: stretchSphereDimensions + }); + + // UPDATE STRETCH HIGHLIGHT PANELS + var scaleLTFCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleLTFCubePosition); + var scaleRBFCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRBFCubePosition); + var stretchPanelXDimensions = Vec3.subtract(scaleLTFCubePositionRotated, scaleRBFCubePositionRotated); + var tempY = Math.abs(stretchPanelXDimensions.y); + stretchPanelXDimensions.x = 0.01; + stretchPanelXDimensions.y = Math.abs(stretchPanelXDimensions.z); + stretchPanelXDimensions.z = tempY; + var stretchPanelXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:dimensions.x / 2, y:0, z:0 })); + Overlays.editOverlay(handleStretchXPanel, { + position: stretchPanelXPosition, + rotation: rotationZ, + dimensions: stretchPanelXDimensions + }); + var scaleLTNCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleLTNCubePosition); + var scaleRTFCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRTFCubePosition); + var stretchPanelYDimensions = Vec3.subtract(scaleLTNCubePositionRotated, scaleRTFCubePositionRotated); + var tempX = Math.abs(stretchPanelYDimensions.x); + stretchPanelYDimensions.x = Math.abs(stretchPanelYDimensions.z); + stretchPanelYDimensions.y = 0.01; + stretchPanelYDimensions.z = tempX; + var stretchPanelYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:dimensions.y / 2, z:0 })); + Overlays.editOverlay(handleStretchYPanel, { + position: stretchPanelYPosition, + rotation: rotationY, + dimensions: stretchPanelYDimensions + }); + var scaleRTFCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRTFCubePosition); + var scaleRBNCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRBNCubePosition); + var stretchPanelZDimensions = Vec3.subtract(scaleRTFCubePositionRotated, scaleRBNCubePositionRotated); + var tempX = Math.abs(stretchPanelZDimensions.x); + stretchPanelZDimensions.x = Math.abs(stretchPanelZDimensions.y); + stretchPanelZDimensions.y = tempX; + stretchPanelZDimensions.z = 0.01; + var stretchPanelZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:0, z:dimensions.z / 2 })); + Overlays.editOverlay(handleStretchZPanel, { + position: stretchPanelZPosition, + rotation: rotationX, + dimensions: stretchPanelZDimensions + }); + + // UPDATE SELECTION BOX (CURRENTLY INVISIBLE WITH 0 ALPHA FOR TRANSLATE XZ TOOL) + var inModeRotate = isActiveTool(handleRotatePitchRing) || + isActiveTool(handleRotateYawRing) || + isActiveTool(handleRotateRollRing); + Overlays.editOverlay(selectionBox, { + position: position, + rotation: rotation, + dimensions: dimensions, + visible: !inModeRotate + }); + + // UPDATE CLONER (CURRENTLY HIDDEN FOR NOW) + var handleClonerOffset = { + x:CLONER_OFFSET.x * dimensions.x, + y:CLONER_OFFSET.y * dimensions.y, + z:CLONER_OFFSET.z * dimensions.z + }; + var handleClonerPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, handleClonerOffset)); + Overlays.editOverlay(handleCloner, { + position: handleClonerPos, + rotation: rotation, + dimensions: scaleCubeDimensions + }); + } + + that.setHandleTranslateXVisible(!activeTool || isActiveTool(handleTranslateXCone) || isActiveTool(handleTranslateXCylinder)); + that.setHandleTranslateYVisible(!activeTool || isActiveTool(handleTranslateYCone) || isActiveTool(handleTranslateYCylinder)); + that.setHandleTranslateZVisible(!activeTool || isActiveTool(handleTranslateZCone) || isActiveTool(handleTranslateZCylinder)); + that.setHandleRotatePitchVisible(!activeTool || isActiveTool(handleRotatePitchRing)); + that.setHandleRotateYawVisible(!activeTool || isActiveTool(handleRotateYawRing)); + that.setHandleRotateRollVisible(!activeTool || isActiveTool(handleRotateRollRing)); + + var showScaleStretch = !activeTool && SelectionManager.selections.length === 1; + that.setHandleStretchXVisible(showScaleStretch || isActiveTool(handleStretchXSphere)); + that.setHandleStretchYVisible(showScaleStretch || isActiveTool(handleStretchYSphere)); + that.setHandleStretchZVisible(showScaleStretch || isActiveTool(handleStretchZSphere)); + that.setHandleScaleCubeVisible(showScaleStretch || isActiveTool(handleScaleLBNCube) || isActiveTool(handleScaleRBNCube) || isActiveTool(handleScaleLBFCube) || isActiveTool(handleScaleRBFCube) + || isActiveTool(handleScaleLTNCube) || isActiveTool(handleScaleRTNCube) || isActiveTool(handleScaleLTFCube) || isActiveTool(handleScaleRTFCube) + || isActiveTool(handleStretchXSphere) || isActiveTool(handleStretchYSphere) || isActiveTool(handleStretchZSphere)); + that.setHandleScaleEdgeVisible(!isActiveTool(handleRotatePitchRing) && !isActiveTool(handleRotateYawRing) && !isActiveTool(handleRotateRollRing)); + + //keep cloner always hidden for now since you can hold Alt to clone while dragging to translate - we may bring cloner back for HMD only later + //that.setHandleClonerVisible(!activeTool || isActiveTool(handleCloner)); + + if (wantDebug) { + print("====== Update Handles <======="); + } + }; + Script.update.connect(that.updateHandles); + + // FUNCTION: UPDATE ACTIVE ROTATE RING + that.updateActiveRotateRing = function() { + var activeRotateRing = null; + if (isActiveTool(handleRotatePitchRing)) { + activeRotateRing = handleRotatePitchRing; + } else if (isActiveTool(handleRotateYawRing)) { + activeRotateRing = handleRotateYawRing; + } else if (isActiveTool(handleRotateRollRing)) { + activeRotateRing = handleRotateRollRing; + } + if (activeRotateRing != null) { + var tickMarksAngle = ctrlPressed ? ROTATE_CTRL_SNAP_ANGLE : ROTATE_DEFAULT_TICK_MARKS_ANGLE; + Overlays.editOverlay(activeRotateRing, { majorTickMarksAngle: tickMarksAngle }); + } + }; + + // FUNCTION: SET OVERLAYS VISIBLE + that.setOverlaysVisible = function(isVisible) { + for (var i = 0; i < allOverlays.length; i++) { + Overlays.editOverlay(allOverlays[i], { visible: isVisible }); + } + }; + + // FUNCTION: SET HANDLE TRANSLATE VISIBLE + that.setHandleTranslateVisible = function(isVisible) { + that.setHandleTranslateXVisible(isVisible); + that.setHandleTranslateYVisible(isVisible); + that.setHandleTranslateZVisible(isVisible); + }; + + that.setHandleTranslateXVisible = function(isVisible) { + Overlays.editOverlay(handleTranslateXCone, { visible: isVisible }); + Overlays.editOverlay(handleTranslateXCylinder, { visible: isVisible }); + }; + + that.setHandleTranslateYVisible = function(isVisible) { + Overlays.editOverlay(handleTranslateYCone, { visible: isVisible }); + Overlays.editOverlay(handleTranslateYCylinder, { visible: isVisible }); + }; + + that.setHandleTranslateZVisible = function(isVisible) { + Overlays.editOverlay(handleTranslateZCone, { visible: isVisible }); + Overlays.editOverlay(handleTranslateZCylinder, { visible: isVisible }); + }; + + // FUNCTION: SET HANDLE ROTATE VISIBLE + that.setHandleRotateVisible = function(isVisible) { + that.setHandleRotatePitchVisible(isVisible); + that.setHandleRotateYawVisible(isVisible); + that.setHandleRotateRollVisible(isVisible); + }; + + that.setHandleRotatePitchVisible = function(isVisible) { + Overlays.editOverlay(handleRotatePitchRing, { visible: isVisible }); + }; + + that.setHandleRotateYawVisible = function(isVisible) { + Overlays.editOverlay(handleRotateYawRing, { visible: isVisible }); + }; + + that.setHandleRotateRollVisible = function(isVisible) { + Overlays.editOverlay(handleRotateRollRing, { visible: isVisible }); + }; + + // FUNCTION: SET HANDLE STRETCH VISIBLE + that.setHandleStretchVisible = function(isVisible) { + that.setHandleStretchXVisible(isVisible); + that.setHandleStretchYVisible(isVisible); + that.setHandleStretchZVisible(isVisible); + }; + + that.setHandleStretchXVisible = function(isVisible) { + Overlays.editOverlay(handleStretchXSphere, { visible: isVisible }); + }; + + that.setHandleStretchYVisible = function(isVisible) { + Overlays.editOverlay(handleStretchYSphere, { visible: isVisible }); + }; + + that.setHandleStretchZVisible = function(isVisible) { + Overlays.editOverlay(handleStretchZSphere, { visible: isVisible }); + }; + + // FUNCTION: SET HANDLE SCALE VISIBLE + that.setHandleScaleVisible = function(isVisible) { + that.setHandleScaleCubeVisible(isVisible); + that.setHandleScaleEdgeVisible(isVisible); + }; + + that.setHandleScaleCubeVisible = function(isVisible) { + Overlays.editOverlay(handleScaleLBNCube, { visible: isVisible }); + Overlays.editOverlay(handleScaleRBNCube, { visible: isVisible }); + Overlays.editOverlay(handleScaleLBFCube, { visible: isVisible }); + Overlays.editOverlay(handleScaleRBFCube, { visible: isVisible }); + Overlays.editOverlay(handleScaleLTNCube, { visible: isVisible }); + Overlays.editOverlay(handleScaleRTNCube, { visible: isVisible }); + Overlays.editOverlay(handleScaleLTFCube, { visible: isVisible }); + Overlays.editOverlay(handleScaleRTFCube, { visible: isVisible }); + }; + + that.setHandleScaleEdgeVisible = function(isVisible) { + Overlays.editOverlay(handleScaleTREdge, { visible: isVisible }); + Overlays.editOverlay(handleScaleTLEdge, { visible: isVisible }); + Overlays.editOverlay(handleScaleTFEdge, { visible: isVisible }); + Overlays.editOverlay(handleScaleTNEdge, { visible: isVisible }); + Overlays.editOverlay(handleScaleBREdge, { visible: isVisible }); + Overlays.editOverlay(handleScaleBLEdge, { visible: isVisible }); + Overlays.editOverlay(handleScaleBFEdge, { visible: isVisible }); + Overlays.editOverlay(handleScaleBNEdge, { visible: isVisible }); + Overlays.editOverlay(handleScaleNREdge, { visible: isVisible }); + Overlays.editOverlay(handleScaleNLEdge, { visible: isVisible }); + Overlays.editOverlay(handleScaleFREdge, { visible: isVisible }); + Overlays.editOverlay(handleScaleFLEdge, { visible: isVisible }); + }; + + // FUNCTION: SET HANDLE CLONER VISIBLE + that.setHandleClonerVisible = function(isVisible) { + Overlays.editOverlay(handleCloner, { visible: isVisible }); + }; + + // TOOL DEFINITION: TRANSLATE XZ TOOL + var initialXZPick = null; + var isConstrained = false; + var constrainMajorOnly = false; + var startPosition = null; + var duplicatedEntityIDs = null; + var translateXZTool = addHandleTool(selectionBox, { + mode: 'TRANSLATE_XZ', + pickPlanePosition: { x: 0, y: 0, z: 0 }, + greatestDimension: 0.0, + startingDistance: 0.0, + startingElevation: 0.0, + onBegin: function(event, pickRay, pickResult, doClone) { + var wantDebug = false; + if (wantDebug) { + print("================== TRANSLATE_XZ(Beg) -> ======================="); + Vec3.print(" pickRay", pickRay); + Vec3.print(" pickRay.origin", pickRay.origin); + Vec3.print(" pickResult.intersection", pickResult.intersection); + } + + SelectionManager.saveProperties(); + that.resetPreviousHandleColor(); + + that.setHandleTranslateVisible(false); + that.setHandleRotateVisible(false); + that.setHandleScaleCubeVisible(false); + that.setHandleStretchVisible(false); + that.setHandleClonerVisible(false); + + startPosition = SelectionManager.worldPosition; + + translateXZTool.pickPlanePosition = pickResult.intersection; + translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), SelectionManager.worldDimensions.z); + translateXZTool.startingDistance = Vec3.distance(pickRay.origin, SelectionManager.position); + translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition); + if (wantDebug) { + print(" longest dimension: " + translateXZTool.greatestDimension); + print(" starting distance: " + translateXZTool.startingDistance); + print(" starting elevation: " + translateXZTool.startingElevation); + } + + initialXZPick = rayPlaneIntersection(pickRay, translateXZTool.pickPlanePosition, { + x: 0, + y: 1, + z: 0 + }); + + // Duplicate entities if alt is pressed. This will make a + // copy of the selected entities and move the _original_ entities, not + // the new ones. + if (event.isAlt || doClone) { + duplicatedEntityIDs = []; + for (var otherEntityID in SelectionManager.savedProperties) { + var properties = SelectionManager.savedProperties[otherEntityID]; + if (!properties.locked) { + var entityID = Entities.addEntity(properties); + duplicatedEntityIDs.push({ + entityID: entityID, + properties: properties + }); + } + } + } else { + duplicatedEntityIDs = null; + } + + isConstrained = false; + if (wantDebug) { + print("================== TRANSLATE_XZ(End) <- ======================="); + } + }, + onEnd: function(event, reason) { + pushCommandForSelections(duplicatedEntityIDs); + }, + elevation: function(origin, intersection) { + return (origin.y - intersection.y) / Vec3.distance(origin, intersection); + }, + onMove: function(event) { + var wantDebug = false; + pickRay = generalComputePickRay(event.x, event.y); + + var pick = rayPlaneIntersection2(pickRay, translateXZTool.pickPlanePosition, { + x: 0, + y: 1, + z: 0 + }); + + // If the pick ray doesn't hit the pick plane in this direction, do nothing. + // this will happen when someone drags across the horizon from the side they started on. + if (!pick) { + if (wantDebug) { + print(" "+ translateXZTool.mode + "Pick ray does not intersect XZ plane."); + } + + // EARLY EXIT--(Invalid ray detected.) + return; + } + + var vector = Vec3.subtract(pick, initialXZPick); + + // If the mouse is too close to the horizon of the pick plane, stop moving + var MIN_ELEVATION = 0.02; // largest dimension of object divided by distance to it + var elevation = translateXZTool.elevation(pickRay.origin, pick); + if (wantDebug) { + print("Start Elevation: " + translateXZTool.startingElevation + ", elevation: " + elevation); + } + if ((translateXZTool.startingElevation > 0.0 && elevation < MIN_ELEVATION) || + (translateXZTool.startingElevation < 0.0 && elevation > -MIN_ELEVATION)) { + if (wantDebug) { + print(" "+ translateXZTool.mode + " - too close to horizon!"); + } + + // EARLY EXIT--(Don't proceed past the reached limit.) + return; + } + + // If the angular size of the object is too small, stop moving + var MIN_ANGULAR_SIZE = 0.01; // Radians + if (translateXZTool.greatestDimension > 0) { + var angularSize = Math.atan(translateXZTool.greatestDimension / Vec3.distance(pickRay.origin, pick)); + if (wantDebug) { + print("Angular size = " + angularSize); + } + if (angularSize < MIN_ANGULAR_SIZE) { + return; + } + } + + // If shifted, constrain to one axis + if (event.isShifted) { + if (Math.abs(vector.x) > Math.abs(vector.z)) { + vector.z = 0; + } else { + vector.x = 0; + } + if (!isConstrained) { + isConstrained = true; + } + } else { + if (isConstrained) { + isConstrained = false; + } + } + + constrainMajorOnly = event.isControl; + var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(-0.5, SelectionManager.worldDimensions)); + vector = Vec3.subtract( + grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly), + cornerPosition); + + + for (var i = 0; i < SelectionManager.selections.length; i++) { + var properties = SelectionManager.savedProperties[SelectionManager.selections[i]]; + if (!properties) { + continue; + } + var newPosition = Vec3.sum(properties.position, { + x: vector.x, + y: 0, + z: vector.z + }); + Entities.editEntity(SelectionManager.selections[i], { + position: newPosition + }); + + if (wantDebug) { + print("translateXZ... "); + Vec3.print(" vector:", vector); + Vec3.print(" newPosition:", properties.position); + Vec3.print(" newPosition:", newPosition); + } + } + + SelectionManager._update(); + } + }); + + // TOOL DEFINITION: HANDLE TRANSLATE TOOL + function addHandleTranslateTool(overlay, mode, direction) { var pickNormal = null; var lastPick = null; var projectionVector = null; - addGrabberTool(overlay, { + addHandleTool(overlay, { mode: mode, onBegin: function(event, pickRay, pickResult) { if (direction === TRANSLATE_DIRECTION.X) { @@ -665,13 +1622,13 @@ SelectionDisplay = (function() { SelectionManager.saveProperties(); that.resetPreviousHandleColor(); - that.setGrabberTranslateXVisible(direction === TRANSLATE_DIRECTION.X); - that.setGrabberTranslateYVisible(direction === TRANSLATE_DIRECTION.Y); - that.setGrabberTranslateZVisible(direction === TRANSLATE_DIRECTION.Z); - that.setGrabberRotateVisible(false); - that.setGrabberStretchVisible(false); - that.setGrabberScaleVisible(false); - that.setGrabberClonerVisible(false); + that.setHandleTranslateXVisible(direction === TRANSLATE_DIRECTION.X); + that.setHandleTranslateYVisible(direction === TRANSLATE_DIRECTION.Y); + that.setHandleTranslateZVisible(direction === TRANSLATE_DIRECTION.Z); + that.setHandleRotateVisible(false); + that.setHandleStretchVisible(false); + that.setHandleScaleCubeVisible(false); + that.setHandleClonerVisible(false); // Duplicate entities if alt is pressed. This will make a // copy of the selected entities and move the _original_ entities, not @@ -747,7 +1704,8 @@ SelectionDisplay = (function() { }; }; - function makeStretchTool(stretchMode, directionEnum, directionVec, pivot, offset, stretchPanel, scaleGrabber) { + // TOOL DEFINITION: HANDLE STRETCH TOOL + function makeStretchTool(stretchMode, directionEnum, directionVec, pivot, offset, stretchPanel, scaleHandle) { var directionFor3DStretch = directionVec; var distanceFor3DStretch = 0; var DISTANCE_INFLUENCE_THRESHOLD = 1.2; @@ -931,13 +1889,13 @@ SelectionDisplay = (function() { distanceFor3DStretch = Vec3.length(Vec3.subtract(pickRayPosition3D, pickRay.origin)); } - that.setGrabberTranslateVisible(false); - that.setGrabberRotateVisible(false); - that.setGrabberScaleVisible(true); - that.setGrabberStretchXVisible(directionEnum === STRETCH_DIRECTION.X); - that.setGrabberStretchYVisible(directionEnum === STRETCH_DIRECTION.Y); - that.setGrabberStretchZVisible(directionEnum === STRETCH_DIRECTION.Z); - that.setGrabberClonerVisible(false); + that.setHandleTranslateVisible(false); + that.setHandleRotateVisible(false); + that.setHandleScaleCubeVisible(true); + that.setHandleStretchXVisible(directionEnum === STRETCH_DIRECTION.X); + that.setHandleStretchYVisible(directionEnum === STRETCH_DIRECTION.Y); + that.setHandleStretchZVisible(directionEnum === STRETCH_DIRECTION.Z); + that.setHandleClonerVisible(false); SelectionManager.saveProperties(); that.resetPreviousHandleColor(); @@ -945,8 +1903,8 @@ SelectionDisplay = (function() { if (stretchPanel != null) { Overlays.editOverlay(stretchPanel, { visible: true }); } - if (scaleGrabber != null) { - Overlays.editOverlay(scaleGrabber, { color: GRABBER_SCALE_CUBE_SELECTED_COLOR }); + if (scaleHandle != null) { + Overlays.editOverlay(scaleHandle, { color: COLOR_SCALE_CUBE_SELECTED }); } }; @@ -954,8 +1912,8 @@ SelectionDisplay = (function() { if (stretchPanel != null) { Overlays.editOverlay(stretchPanel, { visible: false }); } - if (scaleGrabber != null) { - Overlays.editOverlay(scaleGrabber, { color: GRABBER_SCALE_CUBE_IDLE_COLOR }); + if (scaleHandle != null) { + Overlays.editOverlay(scaleHandle, { color: COLOR_SCALE_CUBE }); } pushCommandForSelections(); }; @@ -1008,7 +1966,6 @@ SelectionDisplay = (function() { vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector); vector = vec3Mult(mask, vector); - } vector = grid.snapToSpacing(vector); @@ -1016,7 +1973,9 @@ SelectionDisplay = (function() { var changeInDimensions = Vec3.multiply(-1, vec3Mult(localSigns, vector)); if (directionEnum === STRETCH_DIRECTION.ALL) { - changeInDimensions = Vec3.multiply(changeInDimensions, STRETCH_DIRECTION_ALL_FACTOR); + var toCameraDistance = getDistanceToCamera(position); + var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE; + changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple); } var newDimensions; @@ -1083,76 +2042,74 @@ SelectionDisplay = (function() { }; } - function addGrabberStretchTool(overlay, mode, directionEnum) { - var directionVec, pivot, offset, stretchPanel; + function addHandleStretchTool(overlay, mode, directionEnum) { + var directionVec, offset, stretchPanel; if (directionEnum === STRETCH_DIRECTION.X) { - stretchPanel = grabberStretchXPanel; + stretchPanel = handleStretchXPanel; directionVec = { x:-1, y:0, z:0 }; } else if (directionEnum === STRETCH_DIRECTION.Y) { - stretchPanel = grabberStretchYPanel; + stretchPanel = handleStretchYPanel; directionVec = { x:0, y:-1, z:0 }; } else if (directionEnum === STRETCH_DIRECTION.Z) { - stretchPanel = grabberStretchZPanel + stretchPanel = handleStretchZPanel directionVec = { x:0, y:0, z:-1 }; } - pivot = directionVec; offset = Vec3.multiply(directionVec, -1); - var tool = makeStretchTool(mode, directionEnum, directionVec, pivot, offset, stretchPanel, null); - return addGrabberTool(overlay, tool); + var tool = makeStretchTool(mode, directionEnum, directionVec, directionVec, offset, stretchPanel, null); + return addHandleTool(overlay, tool); } - function addGrabberScaleTool(overlay, mode, directionEnum) { - var directionVec, pivot, offset, selectedGrabber; + // TOOL DEFINITION: HANDLE SCALE TOOL + function addHandleScaleTool(overlay, mode, directionEnum) { + var directionVec, offset, selectedHandle; if (directionEnum === SCALE_DIRECTION.LBN) { directionVec = { x:1, y:1, z:1 }; - selectedGrabber = grabberScaleLBNCube; + selectedHandle = handleScaleLBNCube; } else if (directionEnum === SCALE_DIRECTION.RBN) { directionVec = { x:1, y:1, z:-1 }; - selectedGrabber = grabberScaleRBNCube; + selectedHandle = handleScaleRBNCube; } else if (directionEnum === SCALE_DIRECTION.LBF) { directionVec = { x:-1, y:1, z:1 }; - selectedGrabber = grabberScaleLBFCube; + selectedHandle = handleScaleLBFCube; } else if (directionEnum === SCALE_DIRECTION.RBF) { directionVec = { x:-1, y:1, z:-1 }; - selectedGrabber = grabberScaleRBFCube; + selectedHandle = handleScaleRBFCube; } else if (directionEnum === SCALE_DIRECTION.LTN) { directionVec = { x:1, y:-1, z:1 }; - selectedGrabber = grabberScaleLTNCube; + selectedHandle = handleScaleLTNCube; } else if (directionEnum === SCALE_DIRECTION.RTN) { directionVec = { x:1, y:-1, z:-1 }; - selectedGrabber = grabberScaleRTNCube; + selectedHandle = handleScaleRTNCube; } else if (directionEnum === SCALE_DIRECTION.LTF) { directionVec = { x:-1, y:-1, z:1 }; - selectedGrabber = grabberScaleLTFCube; + selectedHandle = handleScaleLTFCube; } else if (directionEnum === SCALE_DIRECTION.RTF) { directionVec = { x:-1, y:-1, z:-1 }; - selectedGrabber = grabberScaleRTFCube; + selectedHandle = handleScaleRTFCube; } - pivot = directionVec; offset = Vec3.multiply(directionVec, -1); - var tool = makeStretchTool(mode, STRETCH_DIRECTION.ALL, directionVec, pivot, offset, null, selectedGrabber); - return addGrabberTool(overlay, tool); + var tool = makeStretchTool(mode, STRETCH_DIRECTION.ALL, directionVec, directionVec, offset, null, selectedHandle); + return addHandleTool(overlay, tool); } // FUNCTION: UPDATE ROTATION DEGREES OVERLAY function updateRotationDegreesOverlay(angleFromZero, position) { var angle = angleFromZero * (Math.PI / 180); - var cameraPosition = Camera.getPosition(); - var entityToCameraDistance = Vec3.length(Vec3.subtract(cameraPosition, position)); + var toCameraDistance = getDistanceToCamera(position); var overlayProps = { position: position, dimensions: { - x: entityToCameraDistance * ROTATION_DISPLAY_SIZE_X_MULTIPLIER, - y: entityToCameraDistance * ROTATION_DISPLAY_SIZE_Y_MULTIPLIER + x: toCameraDistance * ROTATE_DISPLAY_SIZE_X_MULTIPLIER, + y: toCameraDistance * ROTATE_DISPLAY_SIZE_Y_MULTIPLIER }, - lineHeight: entityToCameraDistance * ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER, + lineHeight: toCameraDistance * ROTATE_DISPLAY_LINE_HEIGHT_MULTIPLIER, text: normalizeDegrees(-angleFromZero) + "°" }; Overlays.editOverlay(rotationDegreesDisplay, overlayProps); } // FUNCTION DEF: updateSelectionsRotation - // Helper func used by rotation grabber tools + // Helper func used by rotation handle tools function updateSelectionsRotation(rotationChange) { if (!rotationChange) { print("ERROR: entitySelectionTool.updateSelectionsRotation - Invalid arg specified!!"); @@ -1173,61 +2130,76 @@ SelectionDisplay = (function() { rotation: Quat.multiply(rotationChange, initialProperties.rotation) }; + Quat.print("OldRotation ", initialProperties.rotation); + Quat.print("NewRotation ", newProperties.rotation); + if (reposition) { var dPos = Vec3.subtract(initialProperties.position, SelectionManager.worldPosition); + Vec3.print("SelectionManager.worldPosition ", SelectionManager.worldPosition); + Vec3.print("initialProperties.position ", initialProperties.position); + Vec3.print("dPos 1 ", dPos); dPos = Vec3.multiplyQbyV(rotationChange, dPos); + Vec3.print("dPos 2 ", dPos); newProperties.position = Vec3.sum(SelectionManager.worldPosition, dPos); + Vec3.print("newProperties.position ", newProperties.position); } Entities.editEntity(entityID, newProperties); } } - function addGrabberRotateTool(overlay, mode, direction) { - var selectedGrabber = null; + // TOOL DEFINITION: HANDLE ROTATION TOOL + function addHandleRotateTool(overlay, mode, direction) { + var selectedHandle = null; var worldRotation = null; - addGrabberTool(overlay, { + var rotationCenter = null; + addHandleTool(overlay, { mode: mode, onBegin: function(event, pickRay, pickResult) { + var wantDebug = false; + if (wantDebug) { + print("================== " + getMode() + "(addHandleRotateTool onBegin) -> ======================="); + } + SelectionManager.saveProperties(); that.resetPreviousHandleColor(); - that.setGrabberTranslateVisible(false); - that.setGrabberRotatePitchVisible(direction === ROTATE_DIRECTION.PITCH); - that.setGrabberRotateYawVisible(direction === ROTATE_DIRECTION.YAW); - that.setGrabberRotateRollVisible(direction === ROTATE_DIRECTION.ROLL); - that.setGrabberStretchVisible(false); - that.setGrabberScaleVisible(false); - that.setGrabberClonerVisible(false); + that.setHandleTranslateVisible(false); + that.setHandleRotatePitchVisible(direction === ROTATE_DIRECTION.PITCH); + that.setHandleRotateYawVisible(direction === ROTATE_DIRECTION.YAW); + that.setHandleRotateRollVisible(direction === ROTATE_DIRECTION.ROLL); + that.setHandleStretchVisible(false); + that.setHandleScaleCubeVisible(false); + that.setHandleClonerVisible(false); if (direction === ROTATE_DIRECTION.PITCH) { rotationNormal = { x: 1, y: 0, z: 0 }; worldRotation = worldRotationY; - selectedGrabber = grabberRotatePitchRing; + selectedHandle = handleRotatePitchRing; } else if (direction === ROTATE_DIRECTION.YAW) { rotationNormal = { x: 0, y: 1, z: 0 }; worldRotation = worldRotationZ; - selectedGrabber = grabberRotateYawRing; + selectedHandle = handleRotateYawRing; } else if (direction === ROTATE_DIRECTION.ROLL) { rotationNormal = { x: 0, y: 0, z: 1 }; worldRotation = worldRotationX; - selectedGrabber = grabberRotateRollRing; + selectedHandle = handleRotateRollRing; } - Overlays.editOverlay(selectedGrabber, { + Overlays.editOverlay(selectedHandle, { hasTickMarks: true, solid: false, - innerRadius: ROTATION_RING_SELECTED_INNER_RADIUS + innerRadius: ROTATE_RING_SELECTED_INNER_RADIUS }); var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation; rotationNormal = Vec3.multiplyQbyV(rotation, rotationNormal); - var rotCenter = SelectionManager.worldPosition; + rotationCenter = SelectionManager.worldPosition; Overlays.editOverlay(rotationDegreesDisplay, { visible: true }); - Overlays.editOverlay(grabberRotateCurrentRing, { - position: rotCenter, + Overlays.editOverlay(handleRotateCurrentRing, { + position: rotationCenter, rotation: worldRotation, startAt: 0, endAt: 0, @@ -1236,47 +2208,78 @@ SelectionDisplay = (function() { // editOverlays may not have committed rotation changes. // Compute zero position based on where the overlay will be eventually. - var result = rayPlaneIntersection(pickRay, rotCenter, rotationNormal); + var result = rayPlaneIntersection(pickRay, rotationCenter, rotationNormal); // In case of a parallel ray, this will be null, which will cause early-out // in the onMove helper. - rotZero = result; + rotationZero = result; - var rotCenterToZero = Vec3.subtract(rotZero, rotCenter); - var rotCenterToZeroLength = Vec3.length(rotCenterToZero); - rotDegreePos = Vec3.sum(rotCenter, Vec3.multiply(Vec3.normalize(rotCenterToZero), rotCenterToZeroLength * 1.75)); - updateRotationDegreesOverlay(0, rotDegreePos); + var rotationCenterToZero = Vec3.subtract(rotationZero, rotationCenter); + var rotationCenterToZeroLength = Vec3.length(rotationCenterToZero); + rotationDegreesPosition = Vec3.sum(rotationCenter, Vec3.multiply(Vec3.normalize(rotationCenterToZero), rotationCenterToZeroLength * 1.75)); + updateRotationDegreesOverlay(0, rotationDegreesPosition); + + if (wantDebug) { + print("================== " + getMode() + "(addHandleRotateTool onBegin) <- ======================="); + } }, onEnd: function(event, reason) { + var wantDebug = false; + if (wantDebug) { + print("================== " + getMode() + "(addHandleRotateTool onEnd) -> ======================="); + } Overlays.editOverlay(rotationDegreesDisplay, { visible: false }); - Overlays.editOverlay(selectedGrabber, { + Overlays.editOverlay(selectedHandle, { hasTickMarks: false, solid: true, - innerRadius: ROTATION_RING_IDLE_INNER_RADIUS + innerRadius: ROTATE_RING_IDLE_INNER_RADIUS }); - Overlays.editOverlay(grabberRotateCurrentRing, { visible: false }); + Overlays.editOverlay(handleRotateCurrentRing, { visible: false }); pushCommandForSelections(); + if (wantDebug) { + print("================== " + getMode() + "(addHandleRotateTool onEnd) <- ======================="); + } }, onMove: function(event) { - if (!rotZero) { - print("ERROR: entitySelectionTool.handleRotationHandleOnMove - Invalid RotationZero Specified (missed rotation target plane?)"); - + if (!rotationZero) { + print("ERROR: entitySelectionTool.addHandleRotateTool.onMove - Invalid RotationZero Specified (missed rotation target plane?)"); + // EARLY EXIT return; } + + var wantDebug = true; + if (wantDebug) { + print("================== "+ getMode() + "(addHandleRotateTool onMove) -> ======================="); + Vec3.print(" rotationZero: ", rotationZero); + } + var pickRay = generalComputePickRay(event.x, event.y); - var rotCenter = SelectionManager.worldPosition; - var result = rayPlaneIntersection(pickRay, rotCenter, rotationNormal); + var result = rayPlaneIntersection(pickRay, rotationCenter, rotationNormal); if (result) { - var centerToZero = Vec3.subtract(rotZero, rotCenter); - var centerToIntersect = Vec3.subtract(result, rotCenter); + var centerToZero = Vec3.subtract(rotationZero, rotationCenter); + var centerToIntersect = Vec3.subtract(result, rotationCenter); + + if (wantDebug) { + Vec3.print(" RotationNormal: ", rotationNormal); + Vec3.print(" rotationZero: ", rotationZero); + Vec3.print(" rotationCenter: ", rotationCenter); + Vec3.print(" intersect: ", result); + Vec3.print(" centerToZero: ", centerToZero); + Vec3.print(" centerToIntersect: ", centerToIntersect); + } + // Note: orientedAngle which wants normalized centerToZero and centerToIntersect // handles that internally, so it's to pass unnormalized vectors here. var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); - var snapAngle = ctrlPressed ? ROTATION_CTRL_SNAP_ANGLE : ROTATION_DEFAULT_SNAP_ANGLE; + var snapAngle = ctrlPressed ? ROTATE_CTRL_SNAP_ANGLE : ROTATE_DEFAULT_SNAP_ANGLE; angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; var rotChange = Quat.angleAxis(angleFromZero, rotationNormal); + if (wantDebug) { + Quat.print(" rotChange: ", rotChange) + print(" angleFromZero: ", angleFromZero); + } updateSelectionsRotation(rotChange); - updateRotationDegreesOverlay(-angleFromZero, rotDegreePos); + updateRotationDegreesOverlay(-angleFromZero, rotationDegreesPosition); var startAtCurrent = 0; var endAtCurrent = angleFromZero; @@ -1284,692 +2287,26 @@ SelectionDisplay = (function() { startAtCurrent = 360 + angleFromZero; endAtCurrent = 360; } - Overlays.editOverlay(grabberRotateCurrentRing, { + Overlays.editOverlay(handleRotateCurrentRing, { startAt: startAtCurrent, endAt: endAtCurrent }); + // not sure why but this seems to be needed to fix an reverse rotation for yaw ring only if (direction === ROTATE_DIRECTION.YAW) { - Overlays.editOverlay(grabberRotateCurrentRing, { rotation: worldRotationZ }); + Overlays.editOverlay(handleRotateCurrentRing, { rotation: worldRotationZ }); } + } + if (wantDebug) { + print("================== "+ getMode() + "(addHandleRotateTool onMove) <- ======================="); } } }); } - // @param: toolHandle: The overlayID associated with the tool - // that correlates to the tool you wish to query. - // @note: If toolHandle is null or undefined then activeTool - // will be checked against those values as opposed to - // the tool registered under toolHandle. Null & Undefined - // are treated as separate values. - // @return: bool - Indicates if the activeTool is that queried. - function isActiveTool(toolHandle) { - if (!toolHandle) { - // Allow isActiveTool(null) and similar to return true if there's - // no active tool - return (activeTool === toolHandle); - } - - if (!grabberTools.hasOwnProperty(toolHandle)) { - print("WARNING: entitySelectionTool.isActiveTool - Encountered unknown grabberToolHandle: " + toolHandle + ". Tools should be registered via addGrabberTool."); - // EARLY EXIT - return false; - } - - return (activeTool === grabberTools[ toolHandle ]); - } - - // @return string - The mode of the currently active tool; - // otherwise, "UNKNOWN" if there's no active tool. - function getMode() { - return (activeTool ? activeTool.mode : "UNKNOWN"); - } - - that.cleanup = function() { - for (var i = 0; i < allOverlays.length; i++) { - Overlays.deleteOverlay(allOverlays[i]); - } - }; - - that.highlightSelectable = function(entityID) { - }; - - that.unhighlightSelectable = function(entityID) { - }; - - that.select = function(entityID, event) { - var properties = Entities.getEntityProperties(SelectionManager.selections[0]); - - lastCameraPosition = Camera.getPosition(); - lastCameraOrientation = Camera.getOrientation(); - - if (event !== false) { - pickRay = generalComputePickRay(event.x, event.y); - - var wantDebug = false; - if (wantDebug) { - print("select() with EVENT...... "); - print(" event.y:" + event.y); - Vec3.print(" current position:", properties.position); - } - } - - that.updateGrabbers(); - }; - - // FUNCTION: SET SPACE MODE - that.setSpaceMode = function(newSpaceMode) { - var wantDebug = false; - if (wantDebug) { - print("======> SetSpaceMode called. ========"); - } - - if (spaceMode !== newSpaceMode) { - if (wantDebug) { - print(" Updating SpaceMode From: " + spaceMode + " To: " + newSpaceMode); - } - spaceMode = newSpaceMode; - that.updateGrabbers(); - } else if (wantDebug) { - print("WARNING: entitySelectionTool.setSpaceMode - Can't update SpaceMode. CurrentMode: " + spaceMode + " DesiredMode: " + newSpaceMode); - } - if (wantDebug) { - print("====== SetSpaceMode called. <========"); - } - }; - - // FUNCTION: UPDATE GRABBERS - that.updateGrabbers = function() { - var wantDebug = false; - if (wantDebug) { - print("======> Update Grabbers ======="); - print(" Selections Count: " + SelectionManager.selections.length); - print(" SpaceMode: " + spaceMode); - print(" DisplayMode: " + getMode()); - } - - if (SelectionManager.selections.length === 0) { - that.setOverlaysVisible(false); - return; - } - - if (SelectionManager.hasSelection()) { - var position = SelectionManager.worldPosition; - var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation; - var dimensions = spaceMode === SPACE_LOCAL ? SelectionManager.localDimensions : SelectionManager.worldDimensions; - var rotationInverse = Quat.inverse(rotation); - - var cameraPosition = Camera.getPosition(); - var entityToCameraDistance = Vec3.length(Vec3.subtract(cameraPosition, position)); - - var localRotationX = Quat.fromPitchYawRollDegrees(0, 0, -90); - rotationX = Quat.multiply(rotation, localRotationX); - worldRotationX = rotationX; - var localRotationY = Quat.fromPitchYawRollDegrees(0, 90, 0); - rotationY = Quat.multiply(rotation, localRotationY); - worldRotationY = rotationY; - var localRotationZ = Quat.fromPitchYawRollDegrees(90, 0, 0); - rotationZ = Quat.multiply(rotation, localRotationZ); - worldRotationZ = rotationZ; - - var arrowCylinderDimension = entityToCameraDistance * GRABBER_TRANSLATE_ARROW_CYLINDER_CAMERA_DISTANCE_MULTIPLE; - var arrowCylinderDimensions = { x:arrowCylinderDimension, y:arrowCylinderDimension * GRABBER_TRANSLATE_ARROW_CYLINDER_Y_MULTIPLE, z:arrowCylinderDimension }; - var arrowConeDimension = entityToCameraDistance * GRABBER_TRANSLATE_ARROW_CONE_CAMERA_DISTANCE_MULTIPLE; - var arrowConeDimensions = { x:arrowConeDimension, y:arrowConeDimension, z:arrowConeDimension }; - var cylinderXPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET * entityToCameraDistance, y:0, z:0 })); - Overlays.editOverlay(grabberTranslateXCylinder, { - position: cylinderXPos, - rotation: rotationX, - dimensions: arrowCylinderDimensions - }); - var cylinderXDiff = Vec3.subtract(cylinderXPos, position); - var coneXPos = Vec3.sum(cylinderXPos, Vec3.multiply(Vec3.normalize(cylinderXDiff), arrowCylinderDimensions.y * 0.83)); - Overlays.editOverlay(grabberTranslateXCone, { - position: coneXPos, - rotation: rotationX, - dimensions: arrowConeDimensions - }); - var cylinderYPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET * entityToCameraDistance, z:0 })); - Overlays.editOverlay(grabberTranslateYCylinder, { - position: cylinderYPos, - rotation: rotationY, - dimensions: arrowCylinderDimensions - }); - var cylinderYDiff = Vec3.subtract(cylinderYPos, position); - var coneYPos = Vec3.sum(cylinderYPos, Vec3.multiply(Vec3.normalize(cylinderYDiff), arrowCylinderDimensions.y * 0.83)); - Overlays.editOverlay(grabberTranslateYCone, { - position: coneYPos, - rotation: rotationY, - dimensions: arrowConeDimensions - }); - var cylinderZPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:0, z:GRABBER_TRANSLATE_ARROW_CYLINDER_OFFSET * entityToCameraDistance })); - Overlays.editOverlay(grabberTranslateZCylinder, { - position: cylinderZPos, - rotation: rotationZ, - dimensions: arrowCylinderDimensions - }); - var cylinderZDiff = Vec3.subtract(cylinderZPos, position); - var coneZPos = Vec3.sum(cylinderZPos, Vec3.multiply(Vec3.normalize(cylinderZDiff), arrowCylinderDimensions.y * 0.83)); - Overlays.editOverlay(grabberTranslateZCone, { - position: coneZPos, - rotation: rotationZ, - dimensions: arrowConeDimensions - }); - - var grabberScaleCubeOffsetX = GRABBER_SCALE_CUBE_OFFSET * dimensions.x; - var grabberScaleCubeOffsetY = GRABBER_SCALE_CUBE_OFFSET * dimensions.y; - var grabberScaleCubeOffsetZ = GRABBER_SCALE_CUBE_OFFSET * dimensions.z; - var scaleDimension = entityToCameraDistance * GRABBER_SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE; - var scaleDimensions = { x:scaleDimension, y:scaleDimension, z:scaleDimension }; - var grabberScaleLBNCubePos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:-grabberScaleCubeOffsetX, y:-grabberScaleCubeOffsetY, z:-grabberScaleCubeOffsetZ })); - Overlays.editOverlay(grabberScaleLBNCube, { - position: grabberScaleLBNCubePos, - rotation: rotation, - dimensions: scaleDimensions - }); - var grabberScaleRBNCubePos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:-grabberScaleCubeOffsetX, y:-grabberScaleCubeOffsetY, z:grabberScaleCubeOffsetZ })); - Overlays.editOverlay(grabberScaleRBNCube, { - position: grabberScaleRBNCubePos, - rotation: rotation, - dimensions: scaleDimensions - }); - var grabberScaleLBFCubePos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:grabberScaleCubeOffsetX, y:-grabberScaleCubeOffsetY, z:-grabberScaleCubeOffsetZ })); - Overlays.editOverlay(grabberScaleLBFCube, { - position: grabberScaleLBFCubePos, - rotation: rotation, - dimensions: scaleDimensions - }); - var grabberScaleRBFCubePos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:grabberScaleCubeOffsetX, y:-grabberScaleCubeOffsetY, z:grabberScaleCubeOffsetZ })); - Overlays.editOverlay(grabberScaleRBFCube, { - position: grabberScaleRBFCubePos, - rotation: rotation, - dimensions: scaleDimensions - }); - var grabberScaleLTNCubePos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:-grabberScaleCubeOffsetX, y:grabberScaleCubeOffsetY, z:-grabberScaleCubeOffsetZ })); - Overlays.editOverlay(grabberScaleLTNCube, { - position: grabberScaleLTNCubePos, - rotation: rotation, - dimensions: scaleDimensions - }); - var grabberScaleRTNCubePos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:-grabberScaleCubeOffsetX, y:grabberScaleCubeOffsetY, z:grabberScaleCubeOffsetZ })); - Overlays.editOverlay(grabberScaleRTNCube, { - position: grabberScaleRTNCubePos, - rotation: rotation, - dimensions: scaleDimensions - }); - var grabberScaleLTFCubePos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:grabberScaleCubeOffsetX, y:grabberScaleCubeOffsetY, z:-grabberScaleCubeOffsetZ })); - Overlays.editOverlay(grabberScaleLTFCube, { - position: grabberScaleLTFCubePos, - rotation: rotation, - dimensions: scaleDimensions - }); - var grabberScaleRTFCubePos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:grabberScaleCubeOffsetX, y:grabberScaleCubeOffsetY, z:grabberScaleCubeOffsetZ })); - Overlays.editOverlay(grabberScaleRTFCube, { - position: grabberScaleRTFCubePos, - rotation: rotation, - dimensions: scaleDimensions - }); - - Overlays.editOverlay(grabberScaleTREdge, { start: grabberScaleRTNCubePos, end: grabberScaleRTFCubePos }); - Overlays.editOverlay(grabberScaleTLEdge, { start: grabberScaleLTNCubePos, end: grabberScaleLTFCubePos }); - Overlays.editOverlay(grabberScaleTFEdge, { start: grabberScaleLTFCubePos, end: grabberScaleRTFCubePos }); - Overlays.editOverlay(grabberScaleTNEdge, { start: grabberScaleLTNCubePos, end: grabberScaleRTNCubePos }); - Overlays.editOverlay(grabberScaleBREdge, { start: grabberScaleRBNCubePos, end: grabberScaleRBFCubePos }); - Overlays.editOverlay(grabberScaleBLEdge, { start: grabberScaleLBNCubePos, end: grabberScaleLBFCubePos }); - Overlays.editOverlay(grabberScaleBFEdge, { start: grabberScaleLBFCubePos, end: grabberScaleRBFCubePos }); - Overlays.editOverlay(grabberScaleBNEdge, { start: grabberScaleLBNCubePos, end: grabberScaleRBNCubePos }); - Overlays.editOverlay(grabberScaleNREdge, { start: grabberScaleRTNCubePos, end: grabberScaleRBNCubePos }); - Overlays.editOverlay(grabberScaleNLEdge, { start: grabberScaleLTNCubePos, end: grabberScaleLBNCubePos }); - Overlays.editOverlay(grabberScaleFREdge, { start: grabberScaleRTFCubePos, end: grabberScaleRBFCubePos }); - Overlays.editOverlay(grabberScaleFLEdge, { start: grabberScaleLTFCubePos, end: grabberScaleLBFCubePos }); - - var stretchSphereDimension = entityToCameraDistance * GRABBER_STRETCH_SPHERE_CAMERA_DISTANCE_MULTIPLE; - var stretchSphereDimensions = { x:stretchSphereDimension, y:stretchSphereDimension, z:stretchSphereDimension }; - var stretchXPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:GRABBER_STRETCH_SPHERE_OFFSET * entityToCameraDistance, y:0, z:0 })); - Overlays.editOverlay(grabberStretchXSphere, { - position: stretchXPos, - dimensions: stretchSphereDimensions - }); - var grabberScaleLTFCubePosRot = Vec3.multiplyQbyV(rotationInverse, grabberScaleLTFCubePos); - var grabberScaleRBFCubePosRot = Vec3.multiplyQbyV(rotationInverse, grabberScaleRBFCubePos); - var stretchPanelXDimensions = Vec3.subtract(grabberScaleLTFCubePosRot, grabberScaleRBFCubePosRot); - var tempY = Math.abs(stretchPanelXDimensions.y); - stretchPanelXDimensions.x = 0.01; - stretchPanelXDimensions.y = Math.abs(stretchPanelXDimensions.z); - stretchPanelXDimensions.z = tempY; - var stretchPanelXPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:dimensions.x / 2, y:0, z:0 })); - Overlays.editOverlay(grabberStretchXPanel, { - position: stretchPanelXPos, - rotation: rotationZ, - dimensions: stretchPanelXDimensions - }); - var stretchYPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:GRABBER_STRETCH_SPHERE_OFFSET * entityToCameraDistance, z:0 })); - Overlays.editOverlay(grabberStretchYSphere, { - position: stretchYPos, - dimensions: stretchSphereDimensions - }); - var grabberScaleLTFCubePosRot = Vec3.multiplyQbyV(rotationInverse, grabberScaleLTNCubePos); - var grabberScaleRTNCubePosRot = Vec3.multiplyQbyV(rotationInverse, grabberScaleRTFCubePos); - var stretchPanelYDimensions = Vec3.subtract(grabberScaleLTFCubePosRot, grabberScaleRTNCubePosRot); - var tempX = Math.abs(stretchPanelYDimensions.x); - stretchPanelYDimensions.x = Math.abs(stretchPanelYDimensions.z); - stretchPanelYDimensions.y = 0.01; - stretchPanelYDimensions.z = tempX; - var stretchPanelYPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:dimensions.y / 2, z:0 })); - Overlays.editOverlay(grabberStretchYPanel, { - position: stretchPanelYPos, - rotation: rotationY, - dimensions: stretchPanelYDimensions - }); - var stretchZPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:0, z:GRABBER_STRETCH_SPHERE_OFFSET * entityToCameraDistance })); - Overlays.editOverlay(grabberStretchZSphere, { - position: stretchZPos, - dimensions: stretchSphereDimensions - }); - var grabberScaleRTFCubePosRot = Vec3.multiplyQbyV(rotationInverse, grabberScaleRTFCubePos); - var grabberScaleRBNCubePosRot = Vec3.multiplyQbyV(rotationInverse, grabberScaleRBNCubePos); - var stretchPanelZDimensions = Vec3.subtract(grabberScaleRTFCubePosRot, grabberScaleRBNCubePosRot); - var tempX = Math.abs(stretchPanelZDimensions.x); - stretchPanelZDimensions.x = Math.abs(stretchPanelZDimensions.y); - stretchPanelZDimensions.y = tempX; - stretchPanelZDimensions.z = 0.01; - var stretchPanelZPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:0, z:dimensions.z / 2 })); - Overlays.editOverlay(grabberStretchZPanel, { - position: stretchPanelZPos, - rotation: rotationX, - dimensions: stretchPanelZDimensions - }); - - var rotateDimension = entityToCameraDistance * GRABBER_ROTATE_RINGS_CAMERA_DISTANCE_MULTIPLE; - var rotateDimensions = { x:rotateDimension, y:rotateDimension, z:rotateDimension }; - if (!isActiveTool(grabberRotatePitchRing)) { - Overlays.editOverlay(grabberRotatePitchRing, { - position: position, - rotation: rotationY, - dimensions: rotateDimensions, - majorTickMarksAngle: ROTATION_DEFAULT_TICK_MARKS_ANGLE - }); - } - if (!isActiveTool(grabberRotateYawRing)) { - Overlays.editOverlay(grabberRotateYawRing, { - position: position, - rotation: rotationZ, - dimensions: rotateDimensions, - majorTickMarksAngle: ROTATION_DEFAULT_TICK_MARKS_ANGLE - }); - } - if (!isActiveTool(grabberRotateRollRing)) { - Overlays.editOverlay(grabberRotateRollRing, { - position: position, - rotation: rotationX, - dimensions: rotateDimensions, - majorTickMarksAngle: ROTATION_DEFAULT_TICK_MARKS_ANGLE - }); - } - Overlays.editOverlay(grabberRotateCurrentRing, { dimensions: rotateDimensions }); - that.updateActiveRotateRing(); - - var inModeRotate = isActiveTool(grabberRotatePitchRing) || isActiveTool(grabberRotateYawRing) || isActiveTool(grabberRotateRollRing); - var inModeTranslate = isActiveTool(grabberTranslateXCone) || isActiveTool(grabberTranslateXCylinder) || - isActiveTool(grabberTranslateYCone) || isActiveTool(grabberTranslateYCylinder) || - isActiveTool(grabberTranslateZCone) || isActiveTool(grabberTranslateZCylinder) || - isActiveTool(grabberCloner) || isActiveTool(selectionBox); - - Overlays.editOverlay(selectionBox, { - position: position, - rotation: rotation, - dimensions: dimensions, - visible: !inModeRotate - }); - - var grabberClonerOffset = { x:GRABBER_CLONER_OFFSET.x * dimensions.x, y:GRABBER_CLONER_OFFSET.y * dimensions.y, z:GRABBER_CLONER_OFFSET.z * dimensions.z }; - var grabberClonerPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, grabberClonerOffset)); - Overlays.editOverlay(grabberCloner, { - position: grabberClonerPos, - rotation: rotation, - dimensions: scaleDimensions - }); - } - - that.setGrabberTranslateXVisible(!activeTool || isActiveTool(grabberTranslateXCone) || isActiveTool(grabberTranslateXCylinder)); - that.setGrabberTranslateYVisible(!activeTool || isActiveTool(grabberTranslateYCone) || isActiveTool(grabberTranslateYCylinder)); - that.setGrabberTranslateZVisible(!activeTool || isActiveTool(grabberTranslateZCone) || isActiveTool(grabberTranslateZCylinder)); - that.setGrabberRotatePitchVisible(!activeTool || isActiveTool(grabberRotatePitchRing)); - that.setGrabberRotateYawVisible(!activeTool || isActiveTool(grabberRotateYawRing)); - that.setGrabberRotateRollVisible(!activeTool || isActiveTool(grabberRotateRollRing)); - that.setGrabberStretchXVisible(!activeTool || isActiveTool(grabberStretchXSphere)); - that.setGrabberStretchYVisible(!activeTool || isActiveTool(grabberStretchYSphere)); - that.setGrabberStretchZVisible(!activeTool || isActiveTool(grabberStretchZSphere)); - that.setGrabberScaleVisible(!activeTool || isActiveTool(grabberScaleLBNCube) || isActiveTool(grabberScaleRBNCube) || isActiveTool(grabberScaleLBFCube) || isActiveTool(grabberScaleRBFCube) - || isActiveTool(grabberScaleLTNCube) || isActiveTool(grabberScaleRTNCube) || isActiveTool(grabberScaleLTFCube) || isActiveTool(grabberScaleRTFCube) - || isActiveTool(grabberStretchXSphere) || isActiveTool(grabberStretchYSphere) || isActiveTool(grabberStretchZSphere)); - //keep cloner always hidden for now since you can hold Alt to clone while dragging to translate - we may bring cloner back for HMD only later - //that.setGrabberClonerVisible(!activeTool || isActiveTool(grabberCloner)); - - if (wantDebug) { - print("====== Update Grabbers <======="); - } - }; - Script.update.connect(that.updateGrabbers); - - that.updateActiveRotateRing = function() { - var activeRotateRing = null; - if (isActiveTool(grabberRotatePitchRing)) { - activeRotateRing = grabberRotatePitchRing; - } else if (isActiveTool(grabberRotateYawRing)) { - activeRotateRing = grabberRotateYawRing; - } else if (isActiveTool(grabberRotateRollRing)) { - activeRotateRing = grabberRotateRollRing; - } - if (activeRotateRing != null) { - var tickMarksAngle = ctrlPressed ? ROTATION_CTRL_SNAP_ANGLE : ROTATION_DEFAULT_TICK_MARKS_ANGLE; - Overlays.editOverlay(activeRotateRing, { majorTickMarksAngle: tickMarksAngle }); - } - }; - - // FUNCTION: SET OVERLAYS VISIBLE - that.setOverlaysVisible = function(isVisible) { - for (var i = 0; i < allOverlays.length; i++) { - Overlays.editOverlay(allOverlays[i], { visible: isVisible }); - } - }; - - // FUNCTION: SET GRABBER TRANSLATE VISIBLE - that.setGrabberTranslateVisible = function(isVisible) { - that.setGrabberTranslateXVisible(isVisible); - that.setGrabberTranslateYVisible(isVisible); - that.setGrabberTranslateZVisible(isVisible); - }; - - that.setGrabberTranslateXVisible = function(isVisible) { - Overlays.editOverlay(grabberTranslateXCone, { visible: isVisible }); - Overlays.editOverlay(grabberTranslateXCylinder, { visible: isVisible }); - }; - - that.setGrabberTranslateYVisible = function(isVisible) { - Overlays.editOverlay(grabberTranslateYCone, { visible: isVisible }); - Overlays.editOverlay(grabberTranslateYCylinder, { visible: isVisible }); - }; - - that.setGrabberTranslateZVisible = function(isVisible) { - Overlays.editOverlay(grabberTranslateZCone, { visible: isVisible }); - Overlays.editOverlay(grabberTranslateZCylinder, { visible: isVisible }); - }; - - // FUNCTION: SET GRABBER ROTATE VISIBLE - that.setGrabberRotateVisible = function(isVisible) { - that.setGrabberRotatePitchVisible(isVisible); - that.setGrabberRotateYawVisible(isVisible); - that.setGrabberRotateRollVisible(isVisible); - }; - - that.setGrabberRotatePitchVisible = function(isVisible) { - Overlays.editOverlay(grabberRotatePitchRing, { visible: isVisible }); - }; - - that.setGrabberRotateYawVisible = function(isVisible) { - Overlays.editOverlay(grabberRotateYawRing, { visible: isVisible }); - }; - - that.setGrabberRotateRollVisible = function(isVisible) { - Overlays.editOverlay(grabberRotateRollRing, { visible: isVisible }); - }; - - // FUNCTION: SET GRABBER STRETCH VISIBLE - that.setGrabberStretchVisible = function(isVisible) { - that.setGrabberStretchXVisible(isVisible); - that.setGrabberStretchYVisible(isVisible); - that.setGrabberStretchZVisible(isVisible); - }; - - that.setGrabberStretchXVisible = function(isVisible) { - Overlays.editOverlay(grabberStretchXSphere, { visible: isVisible }); - }; - - that.setGrabberStretchYVisible = function(isVisible) { - Overlays.editOverlay(grabberStretchYSphere, { visible: isVisible }); - }; - - that.setGrabberStretchZVisible = function(isVisible) { - Overlays.editOverlay(grabberStretchZSphere, { visible: isVisible }); - }; - - // FUNCTION: SET GRABBER SCALE VISIBLE - that.setGrabberScaleVisible = function(isVisible) { - Overlays.editOverlay(grabberScaleLBNCube, { visible: isVisible }); - Overlays.editOverlay(grabberScaleRBNCube, { visible: isVisible }); - Overlays.editOverlay(grabberScaleLBFCube, { visible: isVisible }); - Overlays.editOverlay(grabberScaleRBFCube, { visible: isVisible }); - Overlays.editOverlay(grabberScaleLTNCube, { visible: isVisible }); - Overlays.editOverlay(grabberScaleRTNCube, { visible: isVisible }); - Overlays.editOverlay(grabberScaleLTFCube, { visible: isVisible }); - Overlays.editOverlay(grabberScaleRTFCube, { visible: isVisible }); - Overlays.editOverlay(grabberScaleTREdge, { visible: isVisible }); - Overlays.editOverlay(grabberScaleTLEdge, { visible: isVisible }); - Overlays.editOverlay(grabberScaleTFEdge, { visible: isVisible }); - Overlays.editOverlay(grabberScaleTNEdge, { visible: isVisible }); - Overlays.editOverlay(grabberScaleBREdge, { visible: isVisible }); - Overlays.editOverlay(grabberScaleBLEdge, { visible: isVisible }); - Overlays.editOverlay(grabberScaleBFEdge, { visible: isVisible }); - Overlays.editOverlay(grabberScaleBNEdge, { visible: isVisible }); - Overlays.editOverlay(grabberScaleNREdge, { visible: isVisible }); - Overlays.editOverlay(grabberScaleNLEdge, { visible: isVisible }); - Overlays.editOverlay(grabberScaleFREdge, { visible: isVisible }); - Overlays.editOverlay(grabberScaleFLEdge, { visible: isVisible }); - }; - - // FUNCTION: SET GRABBER CLONER VISIBLE - that.setGrabberClonerVisible = function(isVisible) { - Overlays.editOverlay(grabberCloner, { visible: isVisible }); - }; - - var initialXZPick = null; - var isConstrained = false; - var constrainMajorOnly = false; - var startPosition = null; - var duplicatedEntityIDs = null; - - // TOOL DEFINITION: TRANSLATE XZ TOOL - var translateXZTool = addGrabberTool(selectionBox, { - mode: 'TRANSLATE_XZ', - pickPlanePosition: { x: 0, y: 0, z: 0 }, - greatestDimension: 0.0, - startingDistance: 0.0, - startingElevation: 0.0, - onBegin: function(event, pickRay, pickResult, doClone) { - var wantDebug = false; - if (wantDebug) { - print("================== TRANSLATE_XZ(Beg) -> ======================="); - Vec3.print(" pickRay", pickRay); - Vec3.print(" pickRay.origin", pickRay.origin); - Vec3.print(" pickResult.intersection", pickResult.intersection); - } - - SelectionManager.saveProperties(); - that.resetPreviousHandleColor(); - - that.setGrabberTranslateVisible(false); - that.setGrabberRotateVisible(false); - that.setGrabberScaleVisible(false); - that.setGrabberStretchVisible(false); - that.setGrabberClonerVisible(false); - - startPosition = SelectionManager.worldPosition; - - translateXZTool.pickPlanePosition = pickResult.intersection; - translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), SelectionManager.worldDimensions.z); - translateXZTool.startingDistance = Vec3.distance(pickRay.origin, SelectionManager.position); - translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition); - if (wantDebug) { - print(" longest dimension: " + translateXZTool.greatestDimension); - print(" starting distance: " + translateXZTool.startingDistance); - print(" starting elevation: " + translateXZTool.startingElevation); - } - - initialXZPick = rayPlaneIntersection(pickRay, translateXZTool.pickPlanePosition, { - x: 0, - y: 1, - z: 0 - }); - - // Duplicate entities if alt is pressed. This will make a - // copy of the selected entities and move the _original_ entities, not - // the new ones. - if (event.isAlt || doClone) { - duplicatedEntityIDs = []; - for (var otherEntityID in SelectionManager.savedProperties) { - var properties = SelectionManager.savedProperties[otherEntityID]; - if (!properties.locked) { - var entityID = Entities.addEntity(properties); - duplicatedEntityIDs.push({ - entityID: entityID, - properties: properties - }); - } - } - } else { - duplicatedEntityIDs = null; - } - - isConstrained = false; - if (wantDebug) { - print("================== TRANSLATE_XZ(End) <- ======================="); - } - }, - onEnd: function(event, reason) { - pushCommandForSelections(duplicatedEntityIDs); - }, - elevation: function(origin, intersection) { - return (origin.y - intersection.y) / Vec3.distance(origin, intersection); - }, - onMove: function(event) { - var wantDebug = false; - pickRay = generalComputePickRay(event.x, event.y); - - var pick = rayPlaneIntersection2(pickRay, translateXZTool.pickPlanePosition, { - x: 0, - y: 1, - z: 0 - }); - - // If the pick ray doesn't hit the pick plane in this direction, do nothing. - // this will happen when someone drags across the horizon from the side they started on. - if (!pick) { - if (wantDebug) { - print(" "+ translateXZTool.mode + "Pick ray does not intersect XZ plane."); - } - - // EARLY EXIT--(Invalid ray detected.) - return; - } - - var vector = Vec3.subtract(pick, initialXZPick); - - // If the mouse is too close to the horizon of the pick plane, stop moving - var MIN_ELEVATION = 0.02; // largest dimension of object divided by distance to it - var elevation = translateXZTool.elevation(pickRay.origin, pick); - if (wantDebug) { - print("Start Elevation: " + translateXZTool.startingElevation + ", elevation: " + elevation); - } - if ((translateXZTool.startingElevation > 0.0 && elevation < MIN_ELEVATION) || - (translateXZTool.startingElevation < 0.0 && elevation > -MIN_ELEVATION)) { - if (wantDebug) { - print(" "+ translateXZTool.mode + " - too close to horizon!"); - } - - // EARLY EXIT--(Don't proceed past the reached limit.) - return; - } - - // If the angular size of the object is too small, stop moving - var MIN_ANGULAR_SIZE = 0.01; // Radians - if (translateXZTool.greatestDimension > 0) { - var angularSize = Math.atan(translateXZTool.greatestDimension / Vec3.distance(pickRay.origin, pick)); - if (wantDebug) { - print("Angular size = " + angularSize); - } - if (angularSize < MIN_ANGULAR_SIZE) { - return; - } - } - - // If shifted, constrain to one axis - if (event.isShifted) { - if (Math.abs(vector.x) > Math.abs(vector.z)) { - vector.z = 0; - } else { - vector.x = 0; - } - if (!isConstrained) { - isConstrained = true; - } - } else { - if (isConstrained) { - isConstrained = false; - } - } - - constrainMajorOnly = event.isControl; - var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(-0.5, SelectionManager.worldDimensions)); - vector = Vec3.subtract( - grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly), - cornerPosition); - - - for (var i = 0; i < SelectionManager.selections.length; i++) { - var properties = SelectionManager.savedProperties[SelectionManager.selections[i]]; - if (!properties) { - continue; - } - var newPosition = Vec3.sum(properties.position, { - x: vector.x, - y: 0, - z: vector.z - }); - Entities.editEntity(SelectionManager.selections[i], { - position: newPosition - }); - - if (wantDebug) { - print("translateXZ... "); - Vec3.print(" vector:", vector); - Vec3.print(" newPosition:", properties.position); - Vec3.print(" newPosition:", newPosition); - } - } - - SelectionManager._update(); - } - }); - - addGrabberTranslateTool(grabberTranslateXCone, "TRANSLATE_X", TRANSLATE_DIRECTION.X); - addGrabberTranslateTool(grabberTranslateXCylinder, "TRANSLATE_X", TRANSLATE_DIRECTION.X); - addGrabberTranslateTool(grabberTranslateYCone, "TRANSLATE_Y", TRANSLATE_DIRECTION.Y); - addGrabberTranslateTool(grabberTranslateYCylinder, "TRANSLATE_Y", TRANSLATE_DIRECTION.Y); - addGrabberTranslateTool(grabberTranslateZCone, "TRANSLATE_Z", TRANSLATE_DIRECTION.Z); - addGrabberTranslateTool(grabberTranslateZCylinder, "TRANSLATE_Z", TRANSLATE_DIRECTION.Z); - - addGrabberRotateTool(grabberRotatePitchRing, "ROTATE_PITCH", ROTATE_DIRECTION.PITCH); - addGrabberRotateTool(grabberRotateYawRing, "ROTATE_YAW", ROTATE_DIRECTION.YAW); - addGrabberRotateTool(grabberRotateRollRing, "ROTATE_ROLL", ROTATE_DIRECTION.ROLL); - - addGrabberStretchTool(grabberStretchXSphere, "STRETCH_X", STRETCH_DIRECTION.X); - addGrabberStretchTool(grabberStretchYSphere, "STRETCH_Y", STRETCH_DIRECTION.Y); - addGrabberStretchTool(grabberStretchZSphere, "STRETCH_Z", STRETCH_DIRECTION.Z); - - addGrabberScaleTool(grabberScaleLBNCube, "SCALE_LBN", SCALE_DIRECTION.LBN); - addGrabberScaleTool(grabberScaleRBNCube, "SCALE_RBN", SCALE_DIRECTION.RBN); - addGrabberScaleTool(grabberScaleLBFCube, "SCALE_LBF", SCALE_DIRECTION.LBF); - addGrabberScaleTool(grabberScaleRBFCube, "SCALE_RBF", SCALE_DIRECTION.RBF); - addGrabberScaleTool(grabberScaleLTNCube, "SCALE_LTN", SCALE_DIRECTION.LTN); - addGrabberScaleTool(grabberScaleRTNCube, "SCALE_RTN", SCALE_DIRECTION.RTN); - addGrabberScaleTool(grabberScaleLTFCube, "SCALE_LTF", SCALE_DIRECTION.LTF); - addGrabberScaleTool(grabberScaleRTFCube, "SCALE_RTF", SCALE_DIRECTION.RTF); - - // GRABBER TOOL: GRABBER CLONER - addGrabberTool(grabberCloner, { + // TOOL DEFINITION: HANDLE CLONER + addHandleTool(handleCloner, { mode: "CLONE", onBegin: function(event, pickRay, pickResult) { var doClone = true; @@ -1988,267 +2325,29 @@ SelectionDisplay = (function() { } }); - // FUNCTION: CHECK MOVE - that.checkMove = function() { - }; + addHandleTranslateTool(handleTranslateXCone, "TRANSLATE_X", TRANSLATE_DIRECTION.X); + addHandleTranslateTool(handleTranslateXCylinder, "TRANSLATE_X", TRANSLATE_DIRECTION.X); + addHandleTranslateTool(handleTranslateYCone, "TRANSLATE_Y", TRANSLATE_DIRECTION.Y); + addHandleTranslateTool(handleTranslateYCylinder, "TRANSLATE_Y", TRANSLATE_DIRECTION.Y); + addHandleTranslateTool(handleTranslateZCone, "TRANSLATE_Z", TRANSLATE_DIRECTION.Z); + addHandleTranslateTool(handleTranslateZCylinder, "TRANSLATE_Z", TRANSLATE_DIRECTION.Z); - that.checkControllerMove = function() { - if (SelectionManager.hasSelection()) { - var controllerPose = getControllerWorldLocation(activeHand, true); - var hand = (activeHand === Controller.Standard.LeftHand) ? 0 : 1; - if (controllerPose.valid && lastControllerPoses[hand].valid) { - if (!Vec3.equal(controllerPose.position, lastControllerPoses[hand].position) || - !Vec3.equal(controllerPose.rotation, lastControllerPoses[hand].rotation)) { - that.mouseMoveEvent({}); - } - } - lastControllerPoses[hand] = controllerPose; - } - }; + addHandleRotateTool(handleRotatePitchRing, "ROTATE_PITCH", ROTATE_DIRECTION.PITCH); + addHandleRotateTool(handleRotateYawRing, "ROTATE_YAW", ROTATE_DIRECTION.YAW); + addHandleRotateTool(handleRotateRollRing, "ROTATE_ROLL", ROTATE_DIRECTION.ROLL); - // FUNCTION DEF(s): Intersection Check Helpers - function testRayIntersect(queryRay, overlayIncludes, overlayExcludes) { - var wantDebug = false; - if ((queryRay === undefined) || (queryRay === null)) { - if (wantDebug) { - print("testRayIntersect - EARLY EXIT -> queryRay is undefined OR null!"); - } - return null; - } + addHandleStretchTool(handleStretchXSphere, "STRETCH_X", STRETCH_DIRECTION.X); + addHandleStretchTool(handleStretchYSphere, "STRETCH_Y", STRETCH_DIRECTION.Y); + addHandleStretchTool(handleStretchZSphere, "STRETCH_Z", STRETCH_DIRECTION.Z); - var intersectObj = Overlays.findRayIntersection(queryRay, true, overlayIncludes, overlayExcludes); - - if (wantDebug) { - if (!overlayIncludes) { - print("testRayIntersect - no overlayIncludes provided."); - } - if (!overlayExcludes) { - print("testRayIntersect - no overlayExcludes provided."); - } - print("testRayIntersect - Hit: " + intersectObj.intersects); - print(" intersectObj.overlayID:" + intersectObj.overlayID + "[" + overlayNames[intersectObj.overlayID] + "]"); - print(" OverlayName: " + overlayNames[intersectObj.overlayID]); - print(" intersectObj.distance:" + intersectObj.distance); - print(" intersectObj.face:" + intersectObj.face); - Vec3.print(" intersectObj.intersection:", intersectObj.intersection); - } - - return intersectObj; - } - - // FUNCTION: MOUSE PRESS EVENT - that.mousePressEvent = function (event) { - var wantDebug = false; - if (wantDebug) { - print("=============== eST::MousePressEvent BEG ======================="); - } - if (!event.isLeftButton && !that.triggered) { - // EARLY EXIT-(if another mouse button than left is pressed ignore it) - return false; - } - - var pickRay = generalComputePickRay(event.x, event.y); - // TODO_Case6491: Move this out to setup just to make it once - var interactiveOverlays = [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]; - for (var key in grabberTools) { - if (grabberTools.hasOwnProperty(key)) { - interactiveOverlays.push(key); - } - } - - // Start with unknown mode, in case no tool can handle this. - activeTool = null; - - var results = testRayIntersect(pickRay, interactiveOverlays); - if (results.intersects) { - var hitOverlayID = results.overlayID; - if ((hitOverlayID === HMD.tabletID) || (hitOverlayID === HMD.tabletScreenID) || (hitOverlayID === HMD.homeButtonID)) { - // EARLY EXIT-(mouse clicks on the tablet should override the edit affordances) - return false; - } - - entityIconOverlayManager.setIconsSelectable(SelectionManager.selections, true); - - var hitTool = grabberTools[ hitOverlayID ]; - if (hitTool) { - activeTool = hitTool; - if (activeTool.onBegin) { - activeTool.onBegin(event, pickRay, results); - } else { - print("ERROR: entitySelectionTool.mousePressEvent - ActiveTool(" + activeTool.mode + ") missing onBegin"); - } - } else { - print("ERROR: entitySelectionTool.mousePressEvent - Hit unexpected object, check interactiveOverlays"); - }// End_if (hitTool) - }// End_If(results.intersects) - - if (wantDebug) { - print(" DisplayMode: " + getMode()); - print("=============== eST::MousePressEvent END ======================="); - } - - // If mode is known then we successfully handled this; - // otherwise, we're missing a tool. - return activeTool; - }; - - that.resetPreviousHandleColor = function() { - if (previousHandle != null) { - Overlays.editOverlay(previousHandle, { color: previousHandleColor }); - previousHandle = null; - } - if (previousHandleHelper != null) { - Overlays.editOverlay(previousHandleHelper, { color: previousHandleColor }); - previousHandleHelper = null; - } - }; - - that.getHandleHelper = function(overlay) { - if (overlay === grabberTranslateXCone) { - return grabberTranslateXCylinder; - } else if (overlay === grabberTranslateXCylinder) { - return grabberTranslateXCone; - } else if (overlay === grabberTranslateYCone) { - return grabberTranslateYCylinder; - } else if (overlay === grabberTranslateYCylinder) { - return grabberTranslateYCone; - } else if (overlay === grabberTranslateZCone) { - return grabberTranslateZCylinder; - } else if (overlay === grabberTranslateZCylinder) { - return grabberTranslateZCone; - } - }; - - // FUNCTION: MOUSE MOVE EVENT - that.mouseMoveEvent = function(event) { - var wantDebug = false; - if (wantDebug) { - print("=============== eST::MouseMoveEvent BEG ======================="); - } - if (activeTool) { - if (wantDebug) { - print(" Trigger ActiveTool(" + activeTool.mode + ")'s onMove"); - } - activeTool.onMove(event); - - if (wantDebug) { - print(" Trigger SelectionManager::update"); - } - SelectionManager._update(); - - if (wantDebug) { - print("=============== eST::MouseMoveEvent END ======================="); - } - // EARLY EXIT--(Move handled via active tool) - return true; - } - - // if no tool is active, then just look for handles to highlight... - var pickRay = generalComputePickRay(event.x, event.y); - var result = Overlays.findRayIntersection(pickRay); - var pickedColor; - var highlightNeeded = false; - - if (result.intersects) { - switch (result.overlayID) { - case grabberTranslateXCone: - case grabberTranslateXCylinder: - case grabberRotatePitchRing: - case grabberStretchXSphere: - pickedColor = COLOR_RED; - highlightNeeded = true; - break; - case grabberTranslateYCone: - case grabberTranslateYCylinder: - case grabberRotateYawRing: - case grabberStretchYSphere: - pickedColor = COLOR_GREEN; - highlightNeeded = true; - break; - case grabberTranslateZCone: - case grabberTranslateZCylinder: - case grabberRotateRollRing: - case grabberStretchZSphere: - pickedColor = COLOR_BLUE; - highlightNeeded = true; - break; - case grabberScaleLBNCube: - case grabberScaleRBNCube: - case grabberScaleLBFCube: - case grabberScaleRBFCube: - case grabberScaleLTNCube: - case grabberScaleRTNCube: - case grabberScaleLTFCube: - case grabberScaleRTFCube: - pickedColor = GRABBER_SCALE_CUBE_IDLE_COLOR; - highlightNeeded = true; - break; - default: - that.resetPreviousHandleColor(); - break; - } - - if (highlightNeeded) { - that.resetPreviousHandleColor(); - Overlays.editOverlay(result.overlayID, { color: GRABBER_HOVER_COLOR }); - previousHandle = result.overlayID; - previousHandleHelper = that.getHandleHelper(result.overlayID); - if (previousHandleHelper != null) { - Overlays.editOverlay(previousHandleHelper, { color: GRABBER_HOVER_COLOR }); - } - previousHandleColor = pickedColor; - } - - } else { - that.resetPreviousHandleColor(); - } - - if (wantDebug) { - print("=============== eST::MouseMoveEvent END ======================="); - } - return false; - }; - - // FUNCTION: MOUSE RELEASE EVENT - that.mouseReleaseEvent = function(event) { - var wantDebug = false; - if (wantDebug) { - print("=============== eST::MouseReleaseEvent BEG ======================="); - } - var showHandles = false; - if (activeTool) { - if (activeTool.onEnd) { - if (wantDebug) { - print(" Triggering ActiveTool(" + activeTool.mode + ")'s onEnd"); - } - activeTool.onEnd(event); - } else if (wantDebug) { - print(" ActiveTool(" + activeTool.mode + ")'s missing onEnd"); - } - } - - showHandles = activeTool; // base on prior tool value - activeTool = null; - - // if something is selected, then reset the "original" properties for any potential next click+move operation - if (SelectionManager.hasSelection()) { - if (showHandles) { - if (wantDebug) { - print(" Triggering that.select"); - } - that.select(SelectionManager.selections[0], event); - } - } - - if (wantDebug) { - print("=============== eST::MouseReleaseEvent END ======================="); - } - }; - - // NOTE: mousePressEvent and mouseMoveEvent from the main script should call us., so we don't hook these: - // Controller.mousePressEvent.connect(that.mousePressEvent); - // Controller.mouseMoveEvent.connect(that.mouseMoveEvent); - Controller.mouseReleaseEvent.connect(that.mouseReleaseEvent); + addHandleScaleTool(handleScaleLBNCube, "SCALE_LBN", SCALE_DIRECTION.LBN); + addHandleScaleTool(handleScaleRBNCube, "SCALE_RBN", SCALE_DIRECTION.RBN); + addHandleScaleTool(handleScaleLBFCube, "SCALE_LBF", SCALE_DIRECTION.LBF); + addHandleScaleTool(handleScaleRBFCube, "SCALE_RBF", SCALE_DIRECTION.RBF); + addHandleScaleTool(handleScaleLTNCube, "SCALE_LTN", SCALE_DIRECTION.LTN); + addHandleScaleTool(handleScaleRTNCube, "SCALE_RTN", SCALE_DIRECTION.RTN); + addHandleScaleTool(handleScaleLTFCube, "SCALE_LTF", SCALE_DIRECTION.LTF); + addHandleScaleTool(handleScaleRTFCube, "SCALE_RTF", SCALE_DIRECTION.RTF); return that; }()); \ No newline at end of file From 55f55cd78bdc52c5c2529d27b26dccb0e19f13eb Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 1 Feb 2018 17:59:38 -0800 Subject: [PATCH 062/569] few more updates --- .../system/libraries/entitySelectionTool.js | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index c68fb6e71c..d294fecf21 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -248,6 +248,7 @@ SelectionDisplay = (function() { var TRANSLATE_ARROW_CYLINDER_CAMERA_DISTANCE_MULTIPLE = 0.005; var TRANSLATE_ARROW_CYLINDER_Y_MULTIPLE = 7.5; var TRANSLATE_ARROW_CONE_CAMERA_DISTANCE_MULTIPLE = 0.025; + var TRANSLATE_ARROW_CONE_OFFSET_CYLINDER_DIMENSION_MULTIPLE = 0.83; var ROTATE_RING_CAMERA_DISTANCE_MULTIPLE = 0.15; var ROTATE_CTRL_SNAP_ANGLE = 22.5; @@ -257,7 +258,7 @@ SelectionDisplay = (function() { var ROTATE_RING_SELECTED_INNER_RADIUS = 0.9; // These are multipliers for sizing the rotation degrees display while rotating an entity - var ROTATE_DISPLAY_DISTANCE_MULTIPLIER = 1.0; + var ROTATE_DISPLAY_DISTANCE_MULTIPLIER = 1.75; var ROTATE_DISPLAY_SIZE_X_MULTIPLIER = 0.3; var ROTATE_DISPLAY_SIZE_Y_MULTIPLIER = 0.09; var ROTATE_DISPLAY_LINE_HEIGHT_MULTIPLIER = 0.07; @@ -674,7 +675,8 @@ SelectionDisplay = (function() { var results = testRayIntersect(pickRay, interactiveOverlays); if (results.intersects) { var hitOverlayID = results.overlayID; - if ((hitOverlayID === HMD.tabletID) || (hitOverlayID === HMD.tabletScreenID) || (hitOverlayID === HMD.homeButtonID)) { + if ((hitOverlayID === HMD.tabletID) || (hitOverlayID === HMD.tabletScreenID) || + (hitOverlayID === HMD.homeButtonID)) { // EARLY EXIT-(mouse clicks on the tablet should override the edit affordances) return false; } @@ -1036,6 +1038,7 @@ SelectionDisplay = (function() { }; var arrowConeDimension = toCameraDistance * TRANSLATE_ARROW_CONE_CAMERA_DISTANCE_MULTIPLE; var arrowConeDimensions = { x:arrowConeDimension, y:arrowConeDimension, z:arrowConeDimension }; + var arrowConeOffset = arrowCylinderDimensions.y * TRANSLATE_ARROW_CONE_OFFSET_CYLINDER_DIMENSION_MULTIPLE; var cylinderXPosition = { x:TRANSLATE_ARROW_CYLINDER_OFFSET * toCameraDistance, y:0, z:0 }; cylinderXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, cylinderXPosition)); Overlays.editOverlay(handleTranslateXCylinder, { @@ -1044,7 +1047,7 @@ SelectionDisplay = (function() { dimensions: arrowCylinderDimensions }); var cylinderXDiff = Vec3.subtract(cylinderXPosition, position); - var coneXPosition = Vec3.sum(cylinderXPosition, Vec3.multiply(Vec3.normalize(cylinderXDiff), arrowCylinderDimensions.y * 0.83)); + var coneXPosition = Vec3.sum(cylinderXPosition, Vec3.multiply(Vec3.normalize(cylinderXDiff), arrowConeOffset)); Overlays.editOverlay(handleTranslateXCone, { position: coneXPosition, rotation: rotationX, @@ -1058,7 +1061,7 @@ SelectionDisplay = (function() { dimensions: arrowCylinderDimensions }); var cylinderYDiff = Vec3.subtract(cylinderYPosition, position); - var coneYPosition = Vec3.sum(cylinderYPosition, Vec3.multiply(Vec3.normalize(cylinderYDiff), arrowCylinderDimensions.y * 0.83)); + var coneYPosition = Vec3.sum(cylinderYPosition, Vec3.multiply(Vec3.normalize(cylinderYDiff), arrowConeOffset)); Overlays.editOverlay(handleTranslateYCone, { position: coneYPosition, rotation: rotationY, @@ -1072,7 +1075,7 @@ SelectionDisplay = (function() { dimensions: arrowCylinderDimensions }); var cylinderZDiff = Vec3.subtract(cylinderZPosition, position); - var coneZPosition = Vec3.sum(cylinderZPosition, Vec3.multiply(Vec3.normalize(cylinderZDiff), arrowCylinderDimensions.y * 0.83)); + var coneZPosition = Vec3.sum(cylinderZPosition, Vec3.multiply(Vec3.normalize(cylinderZDiff), arrowConeOffset)); Overlays.editOverlay(handleTranslateZCone, { position: coneZPosition, rotation: rotationZ, @@ -1275,9 +1278,12 @@ SelectionDisplay = (function() { }); } - that.setHandleTranslateXVisible(!activeTool || isActiveTool(handleTranslateXCone) || isActiveTool(handleTranslateXCylinder)); - that.setHandleTranslateYVisible(!activeTool || isActiveTool(handleTranslateYCone) || isActiveTool(handleTranslateYCylinder)); - that.setHandleTranslateZVisible(!activeTool || isActiveTool(handleTranslateZCone) || isActiveTool(handleTranslateZCylinder)); + that.setHandleTranslateXVisible(!activeTool || isActiveTool(handleTranslateXCone) || + isActiveTool(handleTranslateXCylinder)); + that.setHandleTranslateYVisible(!activeTool || isActiveTool(handleTranslateYCone) || + isActiveTool(handleTranslateYCylinder)); + that.setHandleTranslateZVisible(!activeTool || isActiveTool(handleTranslateZCone) || + isActiveTool(handleTranslateZCylinder)); that.setHandleRotatePitchVisible(!activeTool || isActiveTool(handleRotatePitchRing)); that.setHandleRotateYawVisible(!activeTool || isActiveTool(handleRotateYawRing)); that.setHandleRotateRollVisible(!activeTool || isActiveTool(handleRotateRollRing)); @@ -1286,10 +1292,14 @@ SelectionDisplay = (function() { that.setHandleStretchXVisible(showScaleStretch || isActiveTool(handleStretchXSphere)); that.setHandleStretchYVisible(showScaleStretch || isActiveTool(handleStretchYSphere)); that.setHandleStretchZVisible(showScaleStretch || isActiveTool(handleStretchZSphere)); - that.setHandleScaleCubeVisible(showScaleStretch || isActiveTool(handleScaleLBNCube) || isActiveTool(handleScaleRBNCube) || isActiveTool(handleScaleLBFCube) || isActiveTool(handleScaleRBFCube) - || isActiveTool(handleScaleLTNCube) || isActiveTool(handleScaleRTNCube) || isActiveTool(handleScaleLTFCube) || isActiveTool(handleScaleRTFCube) - || isActiveTool(handleStretchXSphere) || isActiveTool(handleStretchYSphere) || isActiveTool(handleStretchZSphere)); - that.setHandleScaleEdgeVisible(!isActiveTool(handleRotatePitchRing) && !isActiveTool(handleRotateYawRing) && !isActiveTool(handleRotateRollRing)); + that.setHandleScaleCubeVisible(showScaleStretch || isActiveTool(handleScaleLBNCube) || + isActiveTool(handleScaleRBNCube) || isActiveTool(handleScaleLBFCube) || + isActiveTool(handleScaleRBFCube) || isActiveTool(handleScaleLTNCube) || + isActiveTool(handleScaleRTNCube) || isActiveTool(handleScaleLTFCube) || + isActiveTool(handleScaleRTFCube) || isActiveTool(handleStretchXSphere) || + isActiveTool(handleStretchYSphere) || isActiveTool(handleStretchZSphere)); + that.setHandleScaleEdgeVisible(!isActiveTool(handleRotatePitchRing) && !isActiveTool(handleRotateYawRing) && + !isActiveTool(handleRotateRollRing)); //keep cloner always hidden for now since you can hold Alt to clone while dragging to translate - we may bring cloner back for HMD only later //that.setHandleClonerVisible(!activeTool || isActiveTool(handleCloner)); @@ -1453,7 +1463,9 @@ SelectionDisplay = (function() { startPosition = SelectionManager.worldPosition; translateXZTool.pickPlanePosition = pickResult.intersection; - translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), SelectionManager.worldDimensions.z); + translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, + SelectionManager.worldDimensions.y), + SelectionManager.worldDimensions.z); translateXZTool.startingDistance = Vec3.distance(pickRay.origin, SelectionManager.position); translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition); if (wantDebug) { @@ -2215,7 +2227,8 @@ SelectionDisplay = (function() { var rotationCenterToZero = Vec3.subtract(rotationZero, rotationCenter); var rotationCenterToZeroLength = Vec3.length(rotationCenterToZero); - rotationDegreesPosition = Vec3.sum(rotationCenter, Vec3.multiply(Vec3.normalize(rotationCenterToZero), rotationCenterToZeroLength * 1.75)); + rotationDegreesPosition = Vec3.sum(rotationCenter, Vec3.multiply(Vec3.normalize(rotationCenterToZero), + rotationCenterToZeroLength * ROTATE_DISPLAY_DISTANCE_MULTIPLIER)); updateRotationDegreesOverlay(0, rotationDegreesPosition); if (wantDebug) { @@ -2350,4 +2363,4 @@ SelectionDisplay = (function() { addHandleScaleTool(handleScaleRTFCube, "SCALE_RTF", SCALE_DIRECTION.RTF); return that; -}()); \ No newline at end of file +}()); From 3804917cf46abb3f9919e252d68ad41a0aa5b6c3 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 2 Feb 2018 09:40:57 +0100 Subject: [PATCH 063/569] Orthographic octree selection seems to be working --- libraries/gpu/src/gpu/Framebuffer.cpp | 2 +- libraries/octree/src/OctreeUtils.cpp | 8 +- libraries/octree/src/OctreeUtils.h | 3 +- .../render-utils/src/RenderShadowTask.cpp | 50 +++++++++---- libraries/render-utils/src/RenderShadowTask.h | 10 +-- libraries/render-utils/src/Shadow.slh | 5 -- libraries/render-utils/src/ShadowCore.slh | 4 +- libraries/render/src/render/CullTask.cpp | 30 +++++--- libraries/render/src/render/CullTask.h | 3 +- .../render/src/render/DrawSceneOctree.cpp | 2 +- .../src/render/RenderFetchCullSortTask.cpp | 2 +- libraries/render/src/render/SpatialTree.cpp | 74 +++++++++++++------ libraries/render/src/render/SpatialTree.h | 31 ++++---- libraries/shared/src/ViewFrustum.cpp | 4 + libraries/shared/src/ViewFrustum.h | 1 + 15 files changed, 152 insertions(+), 77 deletions(-) diff --git a/libraries/gpu/src/gpu/Framebuffer.cpp b/libraries/gpu/src/gpu/Framebuffer.cpp index 4fcc9ab0f9..8bb9be4a76 100755 --- a/libraries/gpu/src/gpu/Framebuffer.cpp +++ b/libraries/gpu/src/gpu/Framebuffer.cpp @@ -62,7 +62,7 @@ Framebuffer* Framebuffer::createShadowmap(uint16 width) { samplerDesc._wrapModeU = Sampler::WRAP_BORDER; samplerDesc._wrapModeV = Sampler::WRAP_BORDER; samplerDesc._filter = Sampler::FILTER_MIN_MAG_LINEAR; - samplerDesc._comparisonFunc = LESS_EQUAL; + samplerDesc._comparisonFunc = LESS; depthTexture->setSampler(Sampler(samplerDesc)); framebuffer->setDepthStencilBuffer(depthTexture, depthFormat); diff --git a/libraries/octree/src/OctreeUtils.cpp b/libraries/octree/src/OctreeUtils.cpp index c55016d8e2..ca15324d4e 100644 --- a/libraries/octree/src/OctreeUtils.cpp +++ b/libraries/octree/src/OctreeUtils.cpp @@ -64,8 +64,14 @@ float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeSc return voxelSizeScale / powf(2.0f, renderLevel); } -float getAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust) { +float getPerspectiveAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust) { const float maxScale = (float)TREE_SCALE; float visibleDistanceAtMaxScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale) / OCTREE_TO_MESH_RATIO; return atan(maxScale / visibleDistanceAtMaxScale); } + +float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust) { + // Smallest visible element is 1cm + const float smallestSize = 0.01f; + return (smallestSize * MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT) / boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale); +} diff --git a/libraries/octree/src/OctreeUtils.h b/libraries/octree/src/OctreeUtils.h index c257bcd5f1..0f87bb6f68 100644 --- a/libraries/octree/src/OctreeUtils.h +++ b/libraries/octree/src/OctreeUtils.h @@ -25,7 +25,8 @@ float calculateRenderAccuracy(const glm::vec3& position, float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale); -float getAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust); +float getPerspectiveAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust); +float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust); // MIN_ELEMENT_ANGULAR_DIAMETER = angular diameter of 1x1x1m cube at 400m = sqrt(3) / 400 = 0.0043301 radians ~= 0.25 degrees const float MIN_ELEMENT_ANGULAR_DIAMETER = 0.0043301f; // radians diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index c9df67dad1..b576bf774c 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -214,9 +214,10 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende } const auto setupOutput = task.addJob("ShadowSetup"); + const auto queryResolution = setupOutput.getN(2); // Fetch and cull the items from the scene static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); - const auto fetchInput = render::Varying(shadowCasterFilter); + const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterFilter, queryResolution).asVarying(); const auto shadowSelection = task.addJob("FetchShadowTree", fetchInput); const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterFilter).asVarying(); const auto shadowItems = task.addJob("FetchShadowSelection", selectionInputs); @@ -281,14 +282,15 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O output.edit0() = args->_renderMode; output.edit1() = args->_sizeScale; + output.edit2() = glm::ivec2(0, 0); const auto globalShadow = lightStage->getCurrentKeyShadow(); if (globalShadow) { globalShadow->setKeylightFrustum(args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR); - auto& firstCascadeFrustum = globalShadow->getCascade(0).getFrustum(); + + auto& firstCascade = globalShadow->getCascade(0); + auto& firstCascadeFrustum = firstCascade.getFrustum(); unsigned int cascadeIndex; - _coarseShadowFrustum->setPosition(firstCascadeFrustum->getPosition()); - _coarseShadowFrustum->setOrientation(firstCascadeFrustum->getOrientation()); // Adjust each cascade frustum for (cascadeIndex = 0; cascadeIndex < globalShadow->getCascadeCount(); ++cascadeIndex) { @@ -297,19 +299,29 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR, bias._constant, bias._slope); } + // Now adjust coarse frustum bounds - auto left = glm::dot(firstCascadeFrustum->getFarTopLeft(), firstCascadeFrustum->getRight()); - auto right = glm::dot(firstCascadeFrustum->getFarTopRight(), firstCascadeFrustum->getRight()); - auto top = glm::dot(firstCascadeFrustum->getFarTopLeft(), firstCascadeFrustum->getUp()); - auto bottom = glm::dot(firstCascadeFrustum->getFarBottomRight(), firstCascadeFrustum->getUp()); + auto frustumPosition = firstCascadeFrustum->getPosition(); + auto farTopLeft = firstCascadeFrustum->getFarTopLeft() - frustumPosition; + auto farBottomRight = firstCascadeFrustum->getFarBottomRight() - frustumPosition; + + auto left = glm::dot(farTopLeft, firstCascadeFrustum->getRight()); + auto right = glm::dot(farBottomRight, firstCascadeFrustum->getRight()); + auto top = glm::dot(farTopLeft, firstCascadeFrustum->getUp()); + auto bottom = glm::dot(farBottomRight, firstCascadeFrustum->getUp()); auto near = firstCascadeFrustum->getNearClip(); auto far = firstCascadeFrustum->getFarClip(); + for (cascadeIndex = 1; cascadeIndex < globalShadow->getCascadeCount(); ++cascadeIndex) { auto& cascadeFrustum = globalShadow->getCascade(cascadeIndex).getFrustum(); - auto cascadeLeft = glm::dot(cascadeFrustum->getFarTopLeft(), cascadeFrustum->getRight()); - auto cascadeRight = glm::dot(cascadeFrustum->getFarTopRight(), cascadeFrustum->getRight()); - auto cascadeTop = glm::dot(cascadeFrustum->getFarTopLeft(), cascadeFrustum->getUp()); - auto cascadeBottom = glm::dot(cascadeFrustum->getFarBottomRight(), cascadeFrustum->getUp()); + + farTopLeft = cascadeFrustum->getFarTopLeft() - frustumPosition; + farBottomRight = cascadeFrustum->getFarBottomRight() - frustumPosition; + + auto cascadeLeft = glm::dot(farTopLeft, cascadeFrustum->getRight()); + auto cascadeRight = glm::dot(farBottomRight, cascadeFrustum->getRight()); + auto cascadeTop = glm::dot(farTopLeft, cascadeFrustum->getUp()); + auto cascadeBottom = glm::dot(farBottomRight, cascadeFrustum->getUp()); auto cascadeNear = cascadeFrustum->getNearClip(); auto cascadeFar = cascadeFrustum->getFarClip(); left = glm::min(left, cascadeLeft); @@ -319,6 +331,9 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O near = glm::min(near, cascadeNear); far = glm::max(far, cascadeFar); } + + _coarseShadowFrustum->setPosition(firstCascadeFrustum->getPosition()); + _coarseShadowFrustum->setOrientation(firstCascadeFrustum->getOrientation()); _coarseShadowFrustum->setProjection(glm::ortho(left, right, bottom, top, near, far)); _coarseShadowFrustum->calculate(); @@ -326,10 +341,13 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O args->pushViewFrustum(*_coarseShadowFrustum); args->_renderMode = RenderArgs::SHADOW_RENDER_MODE; - if (lightStage->getCurrentKeyLight()->getType() == graphics::Light::SUN) { - // Set to ridiculously high amount to prevent solid angle culling in octree selection - args->_sizeScale = 1e16f; - } + + // We want for the octree query enough resolution to catch the details in the lowest cascade. So compute + // the desired resolution for the first cascade frustum and extrapolate it to the coarse frustum. + glm::ivec2 queryResolution = firstCascade.framebuffer->getSize(); + queryResolution.x = int(queryResolution.x * _coarseShadowFrustum->getWidth() / firstCascadeFrustum->getWidth()); + queryResolution.y = int(queryResolution.y * _coarseShadowFrustum->getHeight() / firstCascadeFrustum->getHeight()); + output.edit2() = queryResolution; } } diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index 1736d07fd5..ce4c3047d8 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -71,10 +71,10 @@ public: float constantBias1{ 0.15f }; float constantBias2{ 0.15f }; float constantBias3{ 0.15f }; - float slopeBias0{ 0.55f }; - float slopeBias1{ 0.55f }; - float slopeBias2{ 0.55f }; - float slopeBias3{ 0.55f }; + float slopeBias0{ 0.6f }; + float slopeBias1{ 0.6f }; + float slopeBias2{ 0.6f }; + float slopeBias3{ 0.6f }; signals: void dirty(); @@ -82,7 +82,7 @@ signals: class RenderShadowSetup { public: - using Outputs = render::VaryingSet2; + using Outputs = render::VaryingSet3; using Config = RenderShadowSetupConfig; using JobModel = render::Job::ModelO; diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index 6575e68090..e87519b5f4 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -79,15 +79,10 @@ float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, ve fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[2]) + fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[3]) ); - return shadowAttenuation; } float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float oneMinusNdotL) { - if (!isShadowCascadeProjectedOnPixel(shadowTexcoord)) { - // If a point is not in the map, do not attenuate - return 1.0; - } float bias = getShadowFixedBias(cascadeIndex) + getShadowSlopeBias(cascadeIndex) * oneMinusNdotL; return evalShadowAttenuationPCF(cascadeIndex, offsets, shadowTexcoord, bias); } diff --git a/libraries/render-utils/src/ShadowCore.slh b/libraries/render-utils/src/ShadowCore.slh index 782e2bc2b8..e49e8d638f 100644 --- a/libraries/render-utils/src/ShadowCore.slh +++ b/libraries/render-utils/src/ShadowCore.slh @@ -53,8 +53,8 @@ vec4 evalShadowTexcoord(int cascadeIndex, vec4 position) { } bool isShadowCascadeProjectedOnPixel(vec4 cascadeTexCoords) { - bvec2 greaterThanZero = greaterThanEqual(cascadeTexCoords.xy, vec2(0)); - bvec2 lessThanOne = lessThanEqual(cascadeTexCoords.xy, vec2(1)); + bvec2 greaterThanZero = greaterThan(cascadeTexCoords.xy, vec2(0)); + bvec2 lessThanOne = lessThan(cascadeTexCoords.xy, vec2(1)); return all(greaterThanZero) && all(lessThanOne); } diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index c6ff224560..b3efc4f1a8 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -14,8 +14,8 @@ #include #include -#include #include +#include using namespace render; @@ -33,7 +33,7 @@ struct Test { _renderDetails(renderDetails) { // FIXME: Keep this code here even though we don't use it yet /*_eyePos = _args->getViewFrustum().getPosition(); - float a = glm::degrees(Octree::getAccuracyAngle(_args->_sizeScale, _args->_boundaryLevelAdjust)); + float a = glm::degrees(Octree::getPerspectiveAccuracyAngle(_args->_sizeScale, _args->_boundaryLevelAdjust)); auto angle = std::min(glm::radians(45.0f), a); // no worse than 45 degrees angle = std::max(glm::radians(1.0f / 60.0f), a); // no better than 1 minute of degree auto tanAlpha = tan(angle); @@ -130,7 +130,8 @@ void FetchSpatialTree::run(const RenderContextPointer& renderContext, const Inpu // start fresh outSelection.clear(); - auto& filter = inputs; + auto& filter = inputs.get0(); + auto frustumResolution = inputs.get1(); if (!filter.selectsNothing()) { assert(renderContext->args); @@ -149,8 +150,19 @@ void FetchSpatialTree::run(const RenderContextPointer& renderContext, const Inpu } // Octree selection! - float angle = glm::degrees(getAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust)); - scene->getSpatialTree().selectCellItems(outSelection, filter, queryFrustum, angle); + float threshold = 0.0f; + if (queryFrustum.isPerspective()) { + threshold = getPerspectiveAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust); + if (frustumResolution.y > 0) { + threshold = glm::max(queryFrustum.getFieldOfView() / frustumResolution.y, threshold); + } + } else { + threshold = getOrthographicAccuracySize(args->_sizeScale, args->_boundaryLevelAdjust); + glm::vec2 frustumSize = glm::vec2(queryFrustum.getWidth(), queryFrustum.getHeight()); + const auto pixelResolution = frustumResolution.x > 0 ? frustumResolution : glm::ivec2(2048, 2048); + threshold = glm::max(threshold, glm::min(frustumSize.x / pixelResolution.x, frustumSize.y / pixelResolution.y)); + } + scene->getSpatialTree().selectCellItems(outSelection, filter, queryFrustum, threshold); } } @@ -371,7 +383,7 @@ void FetchSpatialSelection::run(const RenderContextPointer& renderContext, // Now get the bound, and // filter individually against the _filter - // inside & fit items: filter only, culling is disabled + // inside & fit items: filter only { PerformanceTimer perfTimer("insideFitItems"); for (auto id : inSelection.insideItems) { @@ -383,7 +395,7 @@ void FetchSpatialSelection::run(const RenderContextPointer& renderContext, } } - // inside & subcell items: filter only, culling is disabled + // inside & subcell items: filter only { PerformanceTimer perfTimer("insideSmallItems"); for (auto id : inSelection.insideSubcellItems) { @@ -395,7 +407,7 @@ void FetchSpatialSelection::run(const RenderContextPointer& renderContext, } } - // partial & fit items: filter only, culling is disabled + // partial & fit items: filter only { PerformanceTimer perfTimer("partialFitItems"); for (auto id : inSelection.partialItems) { @@ -407,7 +419,7 @@ void FetchSpatialSelection::run(const RenderContextPointer& renderContext, } } - // partial & subcell items: filter only, culling is disabled + // partial & subcell items: filter only { PerformanceTimer perfTimer("partialSmallItems"); for (auto id : inSelection.partialSubcellItems) { diff --git a/libraries/render/src/render/CullTask.h b/libraries/render/src/render/CullTask.h index a140a86aee..53d46d11b4 100644 --- a/libraries/render/src/render/CullTask.h +++ b/libraries/render/src/render/CullTask.h @@ -53,9 +53,10 @@ namespace render { bool _justFrozeFrustum{ false }; ViewFrustum _frozenFrustum; float _lodAngle; + public: using Config = FetchSpatialTreeConfig; - using Inputs = ItemFilter; + using Inputs = render::VaryingSet2; using JobModel = Job::ModelIO; FetchSpatialTree() {} diff --git a/libraries/render/src/render/DrawSceneOctree.cpp b/libraries/render/src/render/DrawSceneOctree.cpp index 36663a454a..08d6340e43 100644 --- a/libraries/render/src/render/DrawSceneOctree.cpp +++ b/libraries/render/src/render/DrawSceneOctree.cpp @@ -148,7 +148,7 @@ void DrawSceneOctree::run(const RenderContextPointer& renderContext, const ItemS } // Draw the LOD Reticle { - float angle = glm::degrees(getAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust)); + float angle = glm::degrees(getPerspectiveAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust)); Transform crosshairModel; crosshairModel.setTranslation(glm::vec3(0.0, 0.0, -1000.0)); crosshairModel.setScale(1000.0f * tanf(glm::radians(angle))); // Scaling at the actual tan of the lod angle => Multiplied by TWO diff --git a/libraries/render/src/render/RenderFetchCullSortTask.cpp b/libraries/render/src/render/RenderFetchCullSortTask.cpp index a1b4f079e7..23935851b3 100644 --- a/libraries/render/src/render/RenderFetchCullSortTask.cpp +++ b/libraries/render/src/render/RenderFetchCullSortTask.cpp @@ -23,7 +23,7 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin // CPU jobs: // Fetch and cull the items from the scene const ItemFilter filter = ItemFilter::Builder::visibleWorldItems().withoutLayered(); - const auto fetchInput = render::Varying(filter); + const auto fetchInput = FetchSpatialTree::Inputs(filter, glm::ivec2(0,0)).asVarying(); const auto spatialSelection = task.addJob("FetchSceneSelection", fetchInput); const auto cullInputs = CullSpatialSelection::Inputs(spatialSelection, render::Varying(filter)).asVarying(); const auto culledSpatialSelection = task.addJob("CullSceneSelection", cullInputs, cullFunctor, RenderDetails::ITEM); diff --git a/libraries/render/src/render/SpatialTree.cpp b/libraries/render/src/render/SpatialTree.cpp index 1bb3538521..c9f810ebc5 100644 --- a/libraries/render/src/render/SpatialTree.cpp +++ b/libraries/render/src/render/SpatialTree.cpp @@ -12,9 +12,26 @@ #include - using namespace render; +void Octree::PerspectiveSelector::setAngle(float a) { + const float MAX_LOD_ANGLE = glm::radians(45.0f); + const float MIN_LOD_ANGLE = glm::radians(1.0f / 60.0f); + + angle = std::max(MIN_LOD_ANGLE, std::min(MAX_LOD_ANGLE, a)); + auto tanAlpha = tan(angle); + squareTanAlpha = (float)(tanAlpha * tanAlpha); +} + +float Octree::PerspectiveSelector::testThreshold(const Coord3f& point, float size) const { + auto eyeToPoint = point - eyePos; + return (size * size / glm::dot(eyeToPoint, eyeToPoint)) - squareTanAlpha; +} + +float Octree::OrthographicSelector::testThreshold(const Coord3f& point, float size) const { + return (size * size) - squareMinSize; +} + const float Octree::INV_DEPTH_DIM[] = { 1.0f, @@ -520,10 +537,9 @@ int Octree::selectTraverse(Index cellID, CellSelection& selection, const Frustum // Test for lod auto cellLocation = cell.getlocation(); - float lod = selector.testSolidAngle(cellLocation.getCenter(), Octree::getCoordSubcellWidth(cellLocation.depth)); - if (lod < 0.0f) { + float test = selector.testThreshold(cellLocation.getCenter(), Octree::getCoordSubcellWidth(cellLocation.depth)); + if (test < 0.0f) { return 0; - break; } // Select this cell partially in frustum @@ -543,13 +559,13 @@ int Octree::selectTraverse(Index cellID, CellSelection& selection, const Frustum } -int Octree::selectBranch(Index cellID, CellSelection& selection, const FrustumSelector& selector) const { +int Octree::selectBranch(Index cellID, CellSelection& selection, const FrustumSelector& selector) const { int numSelectedsIn = (int) selection.size(); auto cell = getConcreteCell(cellID); auto cellLocation = cell.getlocation(); - float lod = selector.testSolidAngle(cellLocation.getCenter(), Octree::getCoordSubcellWidth(cellLocation.depth)); - if (lod < 0.0f) { + float test = selector.testThreshold(cellLocation.getCenter(), Octree::getCoordSubcellWidth(cellLocation.depth)); + if (test < 0.0f) { return 0; } @@ -580,24 +596,40 @@ int Octree::selectCellBrick(Index cellID, CellSelection& selection, bool inside) return (int) selection.size() - numSelectedsIn; } - -int ItemSpatialTree::selectCells(CellSelection& selection, const ViewFrustum& frustum, float lodAngle) const { +int ItemSpatialTree::selectCells(CellSelection& selection, const ViewFrustum& frustum, float threshold) const { auto worldPlanes = frustum.getPlanes(); - FrustumSelector selector; - for (int i = 0; i < ViewFrustum::NUM_PLANES; i++) { - ::Plane octPlane; - octPlane.setNormalAndPoint(worldPlanes[i].getNormal(), evalCoordf(worldPlanes[i].getPoint(), ROOT_DEPTH)); - selector.frustum[i] = Coord4f(octPlane.getNormal(), octPlane.getDCoefficient()); + if (frustum.isPerspective()) { + PerspectiveSelector selector; + for (int i = 0; i < ViewFrustum::NUM_PLANES; i++) { + ::Plane octPlane; + octPlane.setNormalAndPoint(worldPlanes[i].getNormal(), evalCoordf(worldPlanes[i].getPoint(), ROOT_DEPTH)); + selector.frustum[i] = Coord4f(octPlane.getNormal(), octPlane.getDCoefficient()); + } + + selector.eyePos = evalCoordf(frustum.getPosition(), ROOT_DEPTH); + selector.setAngle(threshold); + + return Octree::select(selection, selector); + } else { + OrthographicSelector selector; + for (int i = 0; i < ViewFrustum::NUM_PLANES; i++) { + ::Plane octPlane; + octPlane.setNormalAndPoint(worldPlanes[i].getNormal(), evalCoordf(worldPlanes[i].getPoint(), ROOT_DEPTH)); + selector.frustum[i] = Coord4f(octPlane.getNormal(), octPlane.getDCoefficient()); + } + + // Divide the threshold (which is in world distance units) by the dimension of the octree + // as all further computations will be done in normalized octree units + threshold *= getInvCellWidth(ROOT_DEPTH); + selector.setSize(threshold); + + return Octree::select(selection, selector); } - - selector.eyePos = evalCoordf(frustum.getPosition(), ROOT_DEPTH); - selector.setAngle(glm::radians(lodAngle)); - - return Octree::select(selection, selector); } -int ItemSpatialTree::selectCellItems(ItemSelection& selection, const ItemFilter& filter, const ViewFrustum& frustum, float lodAngle) const { - selectCells(selection.cellSelection, frustum, lodAngle); +int ItemSpatialTree::selectCellItems(ItemSelection& selection, const ItemFilter& filter, const ViewFrustum& frustum, + float threshold) const { + selectCells(selection.cellSelection, frustum, threshold); // Just grab the items in every selected bricks for (auto brickId : selection.cellSelection.insideBricks) { diff --git a/libraries/render/src/render/SpatialTree.h b/libraries/render/src/render/SpatialTree.h index a89b9847e6..b06053344d 100644 --- a/libraries/render/src/render/SpatialTree.h +++ b/libraries/render/src/render/SpatialTree.h @@ -312,23 +312,27 @@ namespace render { class FrustumSelector { public: Coord4f frustum[6]; + + virtual ~FrustumSelector() {} + virtual float testThreshold(const Coord3f& point, float size) const = 0; + }; + + class PerspectiveSelector : public FrustumSelector { + public: Coord3f eyePos; float angle; float squareTanAlpha; - const float MAX_LOD_ANGLE = glm::radians(45.0f); - const float MIN_LOD_ANGLE = glm::radians(1.0f / 60.0f); + void setAngle(float a); + float testThreshold(const Coord3f& point, float size) const override; + }; - void setAngle(float a) { - angle = std::max(MIN_LOD_ANGLE, std::min(MAX_LOD_ANGLE, a)); - auto tanAlpha = tan(angle); - squareTanAlpha = (float)(tanAlpha * tanAlpha); - } + class OrthographicSelector : public FrustumSelector { + public: + float squareMinSize; - float testSolidAngle(const Coord3f& point, float size) const { - auto eyeToPoint = point - eyePos; - return (size * size / glm::dot(eyeToPoint, eyeToPoint)) - squareTanAlpha; - } + void setSize(float a) { squareMinSize = a * a; } + float testThreshold(const Coord3f& point, float size) const override; }; int select(CellSelection& selection, const FrustumSelector& selector) const; @@ -443,7 +447,7 @@ namespace render { Index resetItem(Index oldCell, const ItemKey& oldKey, const AABox& bound, const ItemID& item, ItemKey& newKey); // Selection and traverse - int selectCells(CellSelection& selection, const ViewFrustum& frustum, float lodAngle) const; + int selectCells(CellSelection& selection, const ViewFrustum& frustum, float threshold) const; class ItemSelection { public: @@ -469,7 +473,8 @@ namespace render { } }; - int selectCellItems(ItemSelection& selection, const ItemFilter& filter, const ViewFrustum& frustum, float lodAngle) const; + int selectCellItems(ItemSelection& selection, const ItemFilter& filter, const ViewFrustum& frustum, + float threshold) const; }; } diff --git a/libraries/shared/src/ViewFrustum.cpp b/libraries/shared/src/ViewFrustum.cpp index 5b016d4e91..0f98e8020c 100644 --- a/libraries/shared/src/ViewFrustum.cpp +++ b/libraries/shared/src/ViewFrustum.cpp @@ -847,3 +847,7 @@ void ViewFrustum::tesselateSides(const glm::vec3 points[8], Triangle triangles[8 triangle.v2 = points[vertexIndices[2]]; } } + +bool ViewFrustum::isPerspective() const { + return _projection[3][2] != 0.0f && _projection[2][3] != 0.0f && _projection[3][3] == 0.0f; +} diff --git a/libraries/shared/src/ViewFrustum.h b/libraries/shared/src/ViewFrustum.h index b55fe8b327..859b5c49c5 100644 --- a/libraries/shared/src/ViewFrustum.h +++ b/libraries/shared/src/ViewFrustum.h @@ -49,6 +49,7 @@ public: // setters for lens attributes void setProjection(const glm::mat4 & projection); void setFocalLength(float focalLength) { _focalLength = focalLength; } + bool isPerspective() const; // getters for lens attributes const glm::mat4& getProjection() const { return _projection; } From 1f4671ba175547f00ccce78b30627bba9de321b6 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 2 Feb 2018 11:09:28 +0100 Subject: [PATCH 064/569] Cleaned up orthographic shadow culling functor --- interface/src/SecondaryCamera.cpp | 2 +- .../render-utils/src/RenderShadowTask.cpp | 25 ++++++++++--------- libraries/render-utils/src/RenderShadowTask.h | 20 ++++++++++++--- libraries/render-utils/src/RenderViewTask.cpp | 8 +----- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/interface/src/SecondaryCamera.cpp b/interface/src/SecondaryCamera.cpp index 5db34c9441..c4199f15b2 100644 --- a/interface/src/SecondaryCamera.cpp +++ b/interface/src/SecondaryCamera.cpp @@ -19,7 +19,7 @@ using RenderArgsPointer = std::shared_ptr; void MainRenderTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred) { - task.addJob("RenderShadowTask", cullFunctor); + task.addJob("RenderShadowTask"); const auto items = task.addJob("FetchCullSort", cullFunctor); assert(items.canCast()); if (!isDeferred) { diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index b576bf774c..da7f6d97fa 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -200,8 +200,10 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con }); } -void RenderShadowTask::build(JobModel& task, const render::Varying& input, render::Varying& output, CullFunctor cullFunctor) { - cullFunctor = cullFunctor ? cullFunctor : [](const RenderArgs*, const AABox&) { return true; }; +void RenderShadowTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { + ::CullFunctor cullFunctor = [this](const RenderArgs* args, const AABox& bounds) { + return _cullFunctor(args, bounds); + }; // Prepare the ShapePipeline ShapePlumberPointer shapePlumber = std::make_shared(); @@ -229,7 +231,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { char jobName[64]; sprintf(jobName, "ShadowCascadeSetup%d", i); - const auto shadowFilter = task.addJob(jobName, i); + const auto shadowFilter = task.addJob(jobName, i, _cullFunctor); // CPU jobs: finer grained culling const auto cullInputs = CullShapeBounds::Inputs(sortedShapes, shadowFilter).asVarying(); @@ -281,8 +283,7 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O RenderArgs* args = renderContext->args; output.edit0() = args->_renderMode; - output.edit1() = args->_sizeScale; - output.edit2() = glm::ivec2(0, 0); + output.edit1() = glm::ivec2(0, 0); const auto globalShadow = lightStage->getCurrentKeyShadow(); if (globalShadow) { @@ -347,7 +348,7 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O glm::ivec2 queryResolution = firstCascade.framebuffer->getSize(); queryResolution.x = int(queryResolution.x * _coarseShadowFrustum->getWidth() / firstCascadeFrustum->getWidth()); queryResolution.y = int(queryResolution.y * _coarseShadowFrustum->getHeight() / firstCascadeFrustum->getHeight()); - output.edit2() = queryResolution; + output.edit1() = queryResolution; } } @@ -365,11 +366,12 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon auto& cascade = globalShadow->getCascade(_cascadeIndex); auto& cascadeFrustum = cascade.getFrustum(); args->pushViewFrustum(*cascadeFrustum); - // Set the cull threshold to 2 shadow texels. - auto texelSize = glm::max(cascadeFrustum->getHeight(), cascadeFrustum->getWidth()) / cascade.framebuffer->getSize().x; - texelSize *= 2.0f; - // SizeScale is used in the shadow cull function defined ine RenderViewTask - args->_sizeScale = texelSize * texelSize; + auto texelSize = glm::min(cascadeFrustum->getHeight(), cascadeFrustum->getWidth()) / cascade.framebuffer->getSize().x; + // Set the cull threshold to 16 shadow texels. + const auto minTexelCount = 16.0f; + // TODO : maybe adapt that with LOD management system? + texelSize *= minTexelCount; + _cullFunctor._minSquareSize = texelSize * texelSize; } else { output = ItemFilter::Builder::nothing(); } @@ -393,5 +395,4 @@ void RenderShadowTeardown::run(const render::RenderContextPointer& renderContext assert(args->hasViewFrustum()); // Reset the render args args->_renderMode = input.get0(); - args->_sizeScale = input.get1(); } diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index ce4c3047d8..15651354f1 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -50,9 +50,22 @@ public: using JobModel = render::Task::Model; RenderShadowTask() {} - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor shouldRender); + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); void configure(const Config& configuration); + + struct CullFunctor { + float _minSquareSize{ 0.0f }; + + bool operator()(const RenderArgs* args, const AABox& bounds) const { + // Cull only objects that are too small relatively to shadow frustum + const auto boundsSquareRadius = glm::dot(bounds.getDimensions(), bounds.getDimensions()); + return boundsSquareRadius > _minSquareSize; + } + }; + + CullFunctor _cullFunctor; + }; class RenderShadowSetupConfig : public render::Job::Config { @@ -82,7 +95,7 @@ signals: class RenderShadowSetup { public: - using Outputs = render::VaryingSet3; + using Outputs = render::VaryingSet2; using Config = RenderShadowSetupConfig; using JobModel = render::Job::ModelO; @@ -107,12 +120,13 @@ public: using Outputs = render::ItemFilter; using JobModel = render::Job::ModelO; - RenderShadowCascadeSetup(unsigned int cascadeIndex) : _cascadeIndex{ cascadeIndex } {} + RenderShadowCascadeSetup(unsigned int cascadeIndex, RenderShadowTask::CullFunctor& cullFunctor) : _cascadeIndex{ cascadeIndex }, _cullFunctor{ cullFunctor } {} void run(const render::RenderContextPointer& renderContext, Outputs& output); private: unsigned int _cascadeIndex; + RenderShadowTask::CullFunctor& _cullFunctor; }; class RenderShadowCascadeTeardown { diff --git a/libraries/render-utils/src/RenderViewTask.cpp b/libraries/render-utils/src/RenderViewTask.cpp index c2e43582cd..68585ac437 100644 --- a/libraries/render-utils/src/RenderViewTask.cpp +++ b/libraries/render-utils/src/RenderViewTask.cpp @@ -17,13 +17,7 @@ void RenderViewTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, bool isDeferred) { // auto items = input.get(); - // Shadows use an orthographic projection because they are linked to sunlights - // but the cullFunctor passed is probably tailored for perspective projection and culls too much. - task.addJob("RenderShadowTask", [](const RenderArgs* args, const AABox& bounds) { - // Cull only objects that are too small relatively to shadow frustum - const auto boundsSquareRadius = glm::dot(bounds.getDimensions(), bounds.getDimensions()); - return boundsSquareRadius > args->_sizeScale; - }); + task.addJob("RenderShadowTask"); const auto items = task.addJob("FetchCullSort", cullFunctor); assert(items.canCast()); From 234cb1e3e612a1fe4b19ac516840b771a020f0da Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 2 Feb 2018 12:15:43 +0100 Subject: [PATCH 065/569] Added anti frustum test to remove lower cascade objects from higher shadow cascades --- .../render-utils/src/RenderShadowTask.cpp | 24 ++++++++++--- libraries/render-utils/src/RenderShadowTask.h | 2 +- libraries/render/src/render/CullTask.cpp | 36 ++++++++++++++----- libraries/render/src/render/CullTask.h | 2 +- libraries/shared/src/ViewFrustum.cpp | 12 +++++++ libraries/shared/src/ViewFrustum.h | 1 + 6 files changed, 63 insertions(+), 14 deletions(-) diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index da7f6d97fa..829c0fbcf2 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -228,13 +228,26 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende const auto sortedPipelines = task.addJob("PipelineSortShadow", shadowItems); const auto sortedShapes = task.addJob("DepthSortShadow", sortedPipelines, true); + render::Varying cascadeFrustums[SHADOW_CASCADE_MAX_COUNT] = { + ViewFrustumPointer(), + ViewFrustumPointer(), + ViewFrustumPointer(), + ViewFrustumPointer() + }; + for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { char jobName[64]; sprintf(jobName, "ShadowCascadeSetup%d", i); - const auto shadowFilter = task.addJob(jobName, i, _cullFunctor); + const auto cascadeSetupOutput = task.addJob(jobName, i, _cullFunctor); + const auto shadowFilter = cascadeSetupOutput.getN(0); + auto antiFrustum = render::Varying(ViewFrustumPointer()); + cascadeFrustums[i] = cascadeSetupOutput.getN(1); + if (i > 1) { + antiFrustum = cascadeFrustums[i - 2]; + } // CPU jobs: finer grained culling - const auto cullInputs = CullShapeBounds::Inputs(sortedShapes, shadowFilter).asVarying(); + const auto cullInputs = CullShapeBounds::Inputs(sortedShapes, shadowFilter, antiFrustum).asVarying(); const auto culledShadowItemsAndBounds = task.addJob("CullShadowCascade", cullInputs, cullFunctor, RenderDetails::SHADOW); // GPU jobs: Render to shadow map @@ -360,7 +373,7 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon const auto globalShadow = lightStage->getCurrentKeyShadow(); if (globalShadow && _cascadeIndexgetCascadeCount()) { - output = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); + output.edit0() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); // Set the keylight render args auto& cascade = globalShadow->getCascade(_cascadeIndex); @@ -372,8 +385,11 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon // TODO : maybe adapt that with LOD management system? texelSize *= minTexelCount; _cullFunctor._minSquareSize = texelSize * texelSize; + + output.edit1() = cascadeFrustum; } else { - output = ItemFilter::Builder::nothing(); + output.edit0() = ItemFilter::Builder::nothing(); + output.edit1() = ViewFrustumPointer(); } } diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index 15651354f1..975f755a48 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -117,7 +117,7 @@ private: class RenderShadowCascadeSetup { public: - using Outputs = render::ItemFilter; + using Outputs = render::VaryingSet2; using JobModel = render::Job::ModelO; RenderShadowCascadeSetup(unsigned int cascadeIndex, RenderShadowTask::CullFunctor& cullFunctor) : _cascadeIndex{ cascadeIndex }, _cullFunctor{ cullFunctor } {} diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index b3efc4f1a8..633465dba3 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -24,13 +24,15 @@ struct Test { CullFunctor _functor; RenderArgs* _args; RenderDetails::Item& _renderDetails; + ViewFrustumPointer _antiFrustum; glm::vec3 _eyePos; float _squareTanAlpha; - Test(CullFunctor& functor, RenderArgs* pargs, RenderDetails::Item& renderDetails) : + Test(CullFunctor& functor, RenderArgs* pargs, RenderDetails::Item& renderDetails, ViewFrustumPointer antiFrustum = nullptr) : _functor(functor), _args(pargs), - _renderDetails(renderDetails) { + _renderDetails(renderDetails), + _antiFrustum(antiFrustum) { // FIXME: Keep this code here even though we don't use it yet /*_eyePos = _args->getViewFrustum().getPosition(); float a = glm::degrees(Octree::getPerspectiveAccuracyAngle(_args->_sizeScale, _args->_boundaryLevelAdjust)); @@ -49,6 +51,15 @@ struct Test { return true; } + bool antiFrustumTest(const AABox& bound) { + assert(_antiFrustum); + if (_antiFrustum->boxInsideFrustum(bound)) { + _renderDetails._outOfView++; + return false; + } + return true; + } + bool solidAngleTest(const AABox& bound) { // FIXME: Keep this code here even though we don't use it yet //auto eyeToPoint = bound.calcCenter() - _eyePos; @@ -331,6 +342,7 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input const auto& inShapes = inputs.get0(); const auto& filter = inputs.get1(); + const auto& antiFrustum = inputs.get2(); auto& outShapes = outputs.edit0(); auto& outBounds = outputs.edit1(); @@ -339,7 +351,7 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input if (!filter.selectsNothing()) { auto& details = args->_details.edit(_detailType); - Test test(_cullFunctor, args, details); + Test test(_cullFunctor, args, details, antiFrustum); for (auto& inItems : inShapes) { auto key = inItems.first; @@ -351,13 +363,21 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input details._considered += (int)inItems.second.size(); - for (auto& item : inItems.second) { - if (test.frustumTest(item.bound) && test.solidAngleTest(item.bound)) { - outItems->second.emplace_back(item); - outBounds += item.bound; + if (antiFrustum == nullptr) { + for (auto& item : inItems.second) { + if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound)) { + outItems->second.emplace_back(item); + outBounds += item.bound; + } + } + } else { + for (auto& item : inItems.second) { + if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound) && test.antiFrustumTest(item.bound)) { + outItems->second.emplace_back(item); + outBounds += item.bound; + } } } - details._rendered += (int)outItems->second.size(); } diff --git a/libraries/render/src/render/CullTask.h b/libraries/render/src/render/CullTask.h index 53d46d11b4..a9695d6281 100644 --- a/libraries/render/src/render/CullTask.h +++ b/libraries/render/src/render/CullTask.h @@ -110,7 +110,7 @@ namespace render { class CullShapeBounds { public: - using Inputs = render::VaryingSet2; + using Inputs = render::VaryingSet3; using Outputs = render::VaryingSet2; using JobModel = Job::ModelIO; diff --git a/libraries/shared/src/ViewFrustum.cpp b/libraries/shared/src/ViewFrustum.cpp index 0f98e8020c..2a2eebc0a7 100644 --- a/libraries/shared/src/ViewFrustum.cpp +++ b/libraries/shared/src/ViewFrustum.cpp @@ -280,6 +280,18 @@ bool ViewFrustum::boxIntersectsFrustum(const AABox& box) const { return true; } +bool ViewFrustum::boxInsideFrustum(const AABox& box) const { + // only check against frustum + for (int i = 0; i < NUM_FRUSTUM_PLANES; i++) { + const glm::vec3& normal = _planes[i].getNormal(); + // check distance to nearest box point + if (_planes[i].distance(box.getNearestVertex(normal)) < 0.0f) { + return false; + } + } + return true; +} + bool ViewFrustum::sphereIntersectsKeyhole(const glm::vec3& center, float radius) const { // check positive touch against central sphere if (glm::length(center - _position) <= (radius + _centerSphereRadius)) { diff --git a/libraries/shared/src/ViewFrustum.h b/libraries/shared/src/ViewFrustum.h index 859b5c49c5..981aabe70c 100644 --- a/libraries/shared/src/ViewFrustum.h +++ b/libraries/shared/src/ViewFrustum.h @@ -104,6 +104,7 @@ public: bool sphereIntersectsFrustum(const glm::vec3& center, float radius) const; bool cubeIntersectsFrustum(const AACube& box) const; bool boxIntersectsFrustum(const AABox& box) const; + bool boxInsideFrustum(const AABox& box) const; bool sphereIntersectsKeyhole(const glm::vec3& center, float radius) const; bool cubeIntersectsKeyhole(const AACube& cube) const; From e9747e9d85336baa56c12f664aa0dcda54ee2e6b Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 2 Feb 2018 15:06:22 +0100 Subject: [PATCH 066/569] Small optimisations in shadow shader --- libraries/render-utils/src/RenderShadowTask.cpp | 4 ++-- libraries/render-utils/src/RenderShadowTask.h | 8 ++++---- libraries/render-utils/src/Shadow.slh | 2 +- libraries/render-utils/src/ShadowCore.slh | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 829c0fbcf2..f3797edc11 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -380,8 +380,8 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon auto& cascadeFrustum = cascade.getFrustum(); args->pushViewFrustum(*cascadeFrustum); auto texelSize = glm::min(cascadeFrustum->getHeight(), cascadeFrustum->getWidth()) / cascade.framebuffer->getSize().x; - // Set the cull threshold to 16 shadow texels. - const auto minTexelCount = 16.0f; + // Set the cull threshold to 24 shadow texels. This is totally arbitrary + const auto minTexelCount = 24.0f; // TODO : maybe adapt that with LOD management system? texelSize *= minTexelCount; _cullFunctor._minSquareSize = texelSize * texelSize; diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index 975f755a48..87d78ffe51 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -82,12 +82,12 @@ public: float constantBias0{ 0.15f }; float constantBias1{ 0.15f }; - float constantBias2{ 0.15f }; - float constantBias3{ 0.15f }; + float constantBias2{ 0.175f }; + float constantBias3{ 0.2f }; float slopeBias0{ 0.6f }; float slopeBias1{ 0.6f }; - float slopeBias2{ 0.6f }; - float slopeBias3{ 0.6f }; + float slopeBias2{ 0.7f }; + float slopeBias3{ 0.82f }; signals: void dirty(); diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index e87519b5f4..abb04a4498 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -89,7 +89,7 @@ float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDepth, vec3 worldNormal) { ShadowSampleOffsets offsets = evalShadowFilterOffsets(worldPosition); - vec4 cascadeShadowCoords[2]; + vec4 cascadeShadowCoords[2] = { vec4(0), vec4(0) }; ivec2 cascadeIndices; float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices); diff --git a/libraries/render-utils/src/ShadowCore.slh b/libraries/render-utils/src/ShadowCore.slh index e49e8d638f..a787c54ca0 100644 --- a/libraries/render-utils/src/ShadowCore.slh +++ b/libraries/render-utils/src/ShadowCore.slh @@ -82,10 +82,10 @@ float evalShadowCascadeWeight(vec4 cascadeTexCoords) { float determineShadowCascadesOnPixel(vec4 worldPosition, float viewDepth, out vec4 cascadeShadowCoords[2], out ivec2 cascadeIndices) { cascadeIndices.x = getFirstShadowCascadeOnPixel(0, worldPosition, cascadeShadowCoords[0]); cascadeIndices.y = cascadeIndices.x+1; - if (cascadeIndices.x < (getShadowCascadeCount()-1)) { + float firstCascadeWeight = evalShadowCascadeWeight(cascadeShadowCoords[0]); + if (firstCascadeWeight<1.0 && cascadeIndices.x < (getShadowCascadeCount()-1)) { cascadeIndices.y = getFirstShadowCascadeOnPixel(cascadeIndices.y, worldPosition, cascadeShadowCoords[1]); - float firstCascadeWeight = evalShadowCascadeWeight(cascadeShadowCoords[0]); float secondCascadeWeight = evalShadowCascadeWeight(cascadeShadowCoords[1]); // Returns the mix amount between first and second cascade. return ((1.0-firstCascadeWeight) * secondCascadeWeight) / (firstCascadeWeight + secondCascadeWeight); From 8a011036ef4f9be5b5412936a3f0cfe6a0de208b Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 2 Feb 2018 18:07:35 +0100 Subject: [PATCH 067/569] Removed warnings on mac and ubuntu --- libraries/render-utils/src/LightStage.cpp | 2 -- libraries/render/src/render/CullTask.cpp | 1 - 2 files changed, 3 deletions(-) diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index 259d0dd665..854ff71e20 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -208,8 +208,6 @@ void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum, // Position the keylight frustum auto position = viewFrustum.getPosition() - (nearDepth + farDepth)*lightDirection; - // Update the buffer - auto& schema = _schemaBuffer.edit(); for (auto& cascade : _cascades) { cascade._frustum->setOrientation(orientation); cascade._frustum->setPosition(position); diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index 633465dba3..c745220ab8 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -390,7 +390,6 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input void FetchSpatialSelection::run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems) { assert(renderContext->args); - RenderArgs* args = renderContext->args; auto& scene = renderContext->_scene; auto& inSelection = inputs.get0(); From 3a7290c3ede88e7e402c1547d6133621c19c62fa Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Mon, 8 Jan 2018 12:15:13 -0800 Subject: [PATCH 068/569] starting 2d image entity type --- libraries/entities/src/EntityTypes.h | 1 + libraries/entities/src/FlatImageEntity.cpp | 74 ++++++++++++++++++++++ libraries/entities/src/FlatImageEntity.h | 41 ++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 libraries/entities/src/FlatImageEntity.cpp create mode 100644 libraries/entities/src/FlatImageEntity.h diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 316bf2b813..dac36f28dd 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -49,6 +49,7 @@ public: PolyVox, PolyLine, Shape, + Image, LAST = Shape } EntityType; diff --git a/libraries/entities/src/FlatImageEntity.cpp b/libraries/entities/src/FlatImageEntity.cpp new file mode 100644 index 0000000000..9eb3f64bcb --- /dev/null +++ b/libraries/entities/src/FlatImageEntity.cpp @@ -0,0 +1,74 @@ +// +// FlatImageEntity.cpp +// libraries/entities/src +// +// Created by Elisa Lupin-Jimenez on 1/3/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include +#include +#include +#include + +#include "EntitiesLogging.h" +#include "EntityItemProperties.h" +#include "EntityTree.h" +#include "EntityTreeElement.h" +#include "ResourceCache.h" +#include "ShapeEntityItem.h" +#include "FlatImageEntity.h" + +const QString FlatImageEntity::DEFAULT_IMAGE_URL = QString(""); + +EntityItemPointer FlatImageEntity::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + EntityItemPointer entity(new FlatImageEntity(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); + entity->setProperties(properties); + return entity; +} + +FlatImageEntity::FlatImageEntity(const EntityItemID& entityItemID) : EntityItem(entityItemID) { + _type = EntityTypes::Image; +} + +EntityItemProperties FlatImageEntity::getProperties(EntityPropertyFlags desiredProperties) const { + EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class + properties.setShape("Image"); + return properties; +} + +bool FlatImageEntity::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class + + SET_ENTITY_PROPERTY_FROM_PROPERTIES(shape, setShape); + + if (somethingChanged) { + bool wantDebug = false; + if (wantDebug) { + uint64_t now = usecTimestampNow(); + int elapsed = now - getLastEdited(); + qCDebug(entities) << "FlatImageEntity::setProperties() AFTER update... edited AGO=" << elapsed << + "now=" << now << " getLastEdited()=" << getLastEdited(); + } + setLastEdited(properties.getLastEdited()); + } + return somethingChanged; +} + +void FlatImageEntity::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + APPEND_ENTITY_PROPERTY(PROP_SHAPE, entity::stringFromShape(getShape())); +} + diff --git a/libraries/entities/src/FlatImageEntity.h b/libraries/entities/src/FlatImageEntity.h new file mode 100644 index 0000000000..08c13f8f9f --- /dev/null +++ b/libraries/entities/src/FlatImageEntity.h @@ -0,0 +1,41 @@ +// +// FlatImageEntity.h +// libraries/entities/src +// +// Created by Elisa Lupin-Jimenez on 1/3/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_FlatImageEntity_h +#define hifi_FlatImageEntity_h + +#include "EntityItem.h" + +class FlatImageEntity : public EntityItem { +public: + static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); + + FlatImageEntity(const EntityItemID& entityItemID); + + ALLOW_INSTANTIATION // This class can be instantiated + + // methods for getting/setting all properties of an entity + virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; + virtual bool setProperties(const EntityItemProperties& properties) override; + + virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const override; + + static const QString DEFAULT_IMAGE_URL; + +}; + +#endif // hifi_FlatImageEntity_h From fdca8ab93eef09e21af8883e20b5072441300b80 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Tue, 9 Jan 2018 17:46:24 -0800 Subject: [PATCH 069/569] added image button to edit.js, working on connecting to cpp --- .../resources/qml/hifi/tablet/EditTabView.qml | 12 ++++++ .../entities/src/EntityItemProperties.cpp | 4 ++ libraries/entities/src/EntityItemProperties.h | 1 + libraries/entities/src/EntityPropertyFlags.h | 3 ++ libraries/entities/src/EntityTypes.cpp | 1 + libraries/entities/src/EntityTypes.h | 2 +- libraries/entities/src/FlatImageEntity.cpp | 43 +++++++++++++++++-- libraries/entities/src/FlatImageEntity.h | 12 ++++++ scripts/system/edit.js | 14 +++++- 9 files changed, 87 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/EditTabView.qml b/interface/resources/qml/hifi/tablet/EditTabView.qml index e94325f399..482469d355 100644 --- a/interface/resources/qml/hifi/tablet/EditTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditTabView.qml @@ -101,6 +101,18 @@ TabView { } } + // for image + NewEntityButton { + icon: "icons/create-icons/25-web-1-01.svg" + text: "IMAGE" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "newImageButton" } + }); + editTabView.currentIndex = 2 + } + } + NewEntityButton { icon: "icons/create-icons/25-web-1-01.svg" text: "WEB" diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index e2a5ddf8b5..a1c51b650f 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1369,6 +1369,10 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI()); } + if (properties.getType() == EntityTypes::Image) { + APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, properties.getImageURL()); + } + if (properties.getType() == EntityTypes::Text) { APPEND_ENTITY_PROPERTY(PROP_TEXT, properties.getText()); APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, properties.getLineHeight()); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 3e0770f386..5bd6836336 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -194,6 +194,7 @@ public: DEFINE_PROPERTY_GROUP(Haze, haze, HazePropertyGroup); DEFINE_PROPERTY_GROUP(Animation, animation, AnimationPropertyGroup); DEFINE_PROPERTY_REF(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString, ""); + DEFINE_PROPERTY_REF(PROP_IMAGE_URL, ImageURL, imageURL, QString, ""); DEFINE_PROPERTY(PROP_LINE_WIDTH, LineWidth, lineWidth, float, LineEntityItem::DEFAULT_LINE_WIDTH); DEFINE_PROPERTY_REF(LINE_POINTS, LinePoints, linePoints, QVector, QVector()); DEFINE_PROPERTY_REF(PROP_HREF, Href, href, QString, ""); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 90438ab01c..4768ebed86 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -42,6 +42,9 @@ enum EntityPropertyList { PROP_ANIMATION_ALLOW_TRANSLATION, PROP_RELAY_PARENT_JOINTS, + // for image + PROP_IMAGE_URL, + // these properties are supported by the EntityItem base class PROP_REGISTRATION_POINT, PROP_ANGULAR_VELOCITY, diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index cb17c28fd7..e53b9d02f6 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -89,6 +89,7 @@ EntityItemPointer EntityTypes::constructEntityItem(EntityType entityType, const EntityItemPointer newEntityItem = NULL; EntityTypeFactory factory = NULL; if (entityType >= 0 && entityType <= LAST) { + qCDebug(entities) << "type: " << entityType; factory = _factories[entityType]; } if (factory) { diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index dac36f28dd..8d986c8090 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -48,8 +48,8 @@ public: Line, PolyVox, PolyLine, - Shape, Image, + Shape, LAST = Shape } EntityType; diff --git a/libraries/entities/src/FlatImageEntity.cpp b/libraries/entities/src/FlatImageEntity.cpp index 9eb3f64bcb..66a5702747 100644 --- a/libraries/entities/src/FlatImageEntity.cpp +++ b/libraries/entities/src/FlatImageEntity.cpp @@ -38,14 +38,14 @@ FlatImageEntity::FlatImageEntity(const EntityItemID& entityItemID) : EntityItem( EntityItemProperties FlatImageEntity::getProperties(EntityPropertyFlags desiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class - properties.setShape("Image"); + properties.setShape("Quad"); return properties; } bool FlatImageEntity::setProperties(const EntityItemProperties& properties) { bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class - SET_ENTITY_PROPERTY_FROM_PROPERTIES(shape, setShape); + //SET_ENTITY_PROPERTY_FROM_PROPERTIES(shape, setShape); if (somethingChanged) { bool wantDebug = false; @@ -60,6 +60,13 @@ bool FlatImageEntity::setProperties(const EntityItemProperties& properties) { return somethingChanged; } +// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time +EntityPropertyFlags FlatImageEntity::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); + + return requestedProperties; +} + void FlatImageEntity::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, EntityPropertyFlags& requestedProperties, @@ -69,6 +76,36 @@ void FlatImageEntity::appendSubclassData(OctreePacketData* packetData, EncodeBit OctreeElement::AppendState& appendState) const { bool successPropertyFits = true; - APPEND_ENTITY_PROPERTY(PROP_SHAPE, entity::stringFromShape(getShape())); + // Using "Quad" shape as defined in ShapeEntityItem.cpp + APPEND_ENTITY_PROPERTY(PROP_SHAPE, "Quad"); +} + +int FlatImageEntity::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) { + + int bytesRead = 0; + const unsigned char* dataAt = data; + + return bytesRead; +} + +void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) { + const float MAX_FLAT_DIMENSION = 0.0001f; + if (value.y > MAX_FLAT_DIMENSION) { + // enforce flatness in Y + glm::vec3 newDimensions = value; + newDimensions.y = MAX_FLAT_DIMENSION; + EntityItem::setUnscaledDimensions(newDimensions); + } else { + EntityItem::setUnscaledDimensions(value); + } +} + +QString FlatImageEntity::getImageURL() const { + return resultWithReadLock([&] { + return _imageURL; + }); } diff --git a/libraries/entities/src/FlatImageEntity.h b/libraries/entities/src/FlatImageEntity.h index 08c13f8f9f..4dd91b1215 100644 --- a/libraries/entities/src/FlatImageEntity.h +++ b/libraries/entities/src/FlatImageEntity.h @@ -26,6 +26,8 @@ public: virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; + EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; + virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, EntityPropertyFlags& requestedProperties, @@ -34,7 +36,17 @@ public: int& propertyCount, OctreeElement::AppendState& appendState) const override; + int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) override; + + static const QString DEFAULT_IMAGE_URL; + QString getImageURL() const; + +protected: + QString _imageURL; }; diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 863c185cb4..4241468de1 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -251,7 +251,8 @@ var toolBar = (function () { // Align entity with Avatar orientation. properties.rotation = MyAvatar.orientation; - var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Web"]; + // added image here + var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Image", "Web"]; if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { // Adjust position of entity per bounding box prior to creating it. @@ -286,6 +287,7 @@ var toolBar = (function () { properties.userData = JSON.stringify({ grabbableKey: { grabbable: false } }); } + print("properties.type: " + properties.type); entityID = Entities.addEntity(properties); if (properties.type === "ParticleEffect") { @@ -538,6 +540,16 @@ var toolBar = (function () { }); }); + // for image button + addButton("newImageButton", "web-01.svg", function () { + print("new image message is received"); + createNewEntity({ + type: "Image", + dimensions: DEFAULT_DIMENSIONS, + sourceUrl: "https://highfidelity.com/" + }); + }); + addButton("newWebButton", "web-01.svg", function () { createNewEntity({ type: "Web", From fc0e87d5eaa13cf7c97a1ab2ab8a8eac264576d5 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Wed, 10 Jan 2018 17:08:55 -0800 Subject: [PATCH 070/569] more infrastructure links --- .../entities/src/EntityItemProperties.cpp | 9 ++++ .../entities/src/EntityScriptingInterface.cpp | 2 + libraries/entities/src/EntityTree.cpp | 1 + libraries/entities/src/EntityTypes.cpp | 4 +- ...latImageEntity.cpp => ImageEntityItem.cpp} | 42 ++++++++++++------- .../{FlatImageEntity.h => ImageEntityItem.h} | 13 +++--- scripts/system/edit.js | 2 +- 7 files changed, 51 insertions(+), 22 deletions(-) rename libraries/entities/src/{FlatImageEntity.cpp => ImageEntityItem.cpp} (70%) rename libraries/entities/src/{FlatImageEntity.h => ImageEntityItem.h} (85%) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index a1c51b650f..fc8ca18fb6 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -590,6 +590,11 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SKYBOX_MODE, skyboxMode, getSkyboxModeAsString()); } + // Image only + if (_type == EntityTypes::Image) { + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IMAGE_URL, imageURL); + } + // Web only if (_type == EntityTypes::Web) { COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOURCE_URL, sourceUrl); @@ -1734,6 +1739,10 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DPI, uint16_t, setDPI); } + if (properties.getType() == EntityTypes::Image) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IMAGE_URL, QString, setImageURL); + } + if (properties.getType() == EntityTypes::Text) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT, QString, setText); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_HEIGHT, float, setLineHeight); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 4342f0e683..cab4251d97 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -266,6 +266,8 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties bool success = true; if (_entityTree) { _entityTree->withWriteLock([&] { + propertiesWithSimID.getType(); + qCDebug(entities) << "check 2 type: " << propertiesWithSimID.getType(); EntityItemPointer entity = _entityTree->addEntity(id, propertiesWithSimID); if (entity) { if (propertiesWithSimID.queryAACubeRelatedPropertyChanged()) { diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index bf29f3bec9..56c3aa216c 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -514,6 +514,7 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti // construct the instance of the entity EntityTypes::EntityType type = props.getType(); + qCDebug(entities) << "check 3 type: " << type; result = EntityTypes::constructEntityItem(type, entityID, props); if (result) { diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index e53b9d02f6..2e4a0e89a9 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -28,6 +28,7 @@ #include "LineEntityItem.h" #include "PolyVoxEntityItem.h" #include "PolyLineEntityItem.h" +#include "ImageEntityItem.h" #include "ShapeEntityItem.h" QMap EntityTypes::_typeToNameMap; @@ -47,6 +48,7 @@ REGISTER_ENTITY_TYPE(Zone) REGISTER_ENTITY_TYPE(Line) REGISTER_ENTITY_TYPE(PolyVox) REGISTER_ENTITY_TYPE(PolyLine) +REGISTER_ENTITY_TYPE(Image) REGISTER_ENTITY_TYPE(Shape) REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, ShapeEntityItem::boxFactory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, ShapeEntityItem::sphereFactory) @@ -89,7 +91,7 @@ EntityItemPointer EntityTypes::constructEntityItem(EntityType entityType, const EntityItemPointer newEntityItem = NULL; EntityTypeFactory factory = NULL; if (entityType >= 0 && entityType <= LAST) { - qCDebug(entities) << "type: " << entityType; + qCDebug(entities) << "check 4 type: " << entityType; factory = _factories[entityType]; } if (factory) { diff --git a/libraries/entities/src/FlatImageEntity.cpp b/libraries/entities/src/ImageEntityItem.cpp similarity index 70% rename from libraries/entities/src/FlatImageEntity.cpp rename to libraries/entities/src/ImageEntityItem.cpp index 66a5702747..a94374f7b5 100644 --- a/libraries/entities/src/FlatImageEntity.cpp +++ b/libraries/entities/src/ImageEntityItem.cpp @@ -1,5 +1,5 @@ // -// FlatImageEntity.cpp +// ImageEntityItem.cpp // libraries/entities/src // // Created by Elisa Lupin-Jimenez on 1/3/18. @@ -22,27 +22,27 @@ #include "EntityTreeElement.h" #include "ResourceCache.h" #include "ShapeEntityItem.h" -#include "FlatImageEntity.h" +#include "ImageEntityItem.h" -const QString FlatImageEntity::DEFAULT_IMAGE_URL = QString(""); +const QString ImageEntityItem::DEFAULT_IMAGE_URL = QString(""); -EntityItemPointer FlatImageEntity::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - EntityItemPointer entity(new FlatImageEntity(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); +EntityItemPointer ImageEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + EntityItemPointer entity(new ImageEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); entity->setProperties(properties); return entity; } -FlatImageEntity::FlatImageEntity(const EntityItemID& entityItemID) : EntityItem(entityItemID) { +ImageEntityItem::ImageEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { _type = EntityTypes::Image; } -EntityItemProperties FlatImageEntity::getProperties(EntityPropertyFlags desiredProperties) const { +EntityItemProperties ImageEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class properties.setShape("Quad"); return properties; } -bool FlatImageEntity::setProperties(const EntityItemProperties& properties) { +bool ImageEntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class //SET_ENTITY_PROPERTY_FROM_PROPERTIES(shape, setShape); @@ -52,7 +52,7 @@ bool FlatImageEntity::setProperties(const EntityItemProperties& properties) { if (wantDebug) { uint64_t now = usecTimestampNow(); int elapsed = now - getLastEdited(); - qCDebug(entities) << "FlatImageEntity::setProperties() AFTER update... edited AGO=" << elapsed << + qCDebug(entities) << "ImageEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << "now=" << now << " getLastEdited()=" << getLastEdited(); } setLastEdited(properties.getLastEdited()); @@ -61,13 +61,13 @@ bool FlatImageEntity::setProperties(const EntityItemProperties& properties) { } // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time -EntityPropertyFlags FlatImageEntity::getEntityProperties(EncodeBitstreamParams& params) const { +EntityPropertyFlags ImageEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); return requestedProperties; } -void FlatImageEntity::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, +void ImageEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, EntityPropertyFlags& requestedProperties, EntityPropertyFlags& propertyFlags, @@ -80,7 +80,7 @@ void FlatImageEntity::appendSubclassData(OctreePacketData* packetData, EncodeBit APPEND_ENTITY_PROPERTY(PROP_SHAPE, "Quad"); } -int FlatImageEntity::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, +int ImageEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) { @@ -91,7 +91,7 @@ int FlatImageEntity::readEntitySubclassDataFromBuffer(const unsigned char* data, return bytesRead; } -void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) { +/*void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) { const float MAX_FLAT_DIMENSION = 0.0001f; if (value.y > MAX_FLAT_DIMENSION) { // enforce flatness in Y @@ -101,9 +101,23 @@ void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) { } else { EntityItem::setUnscaledDimensions(value); } +}*/ + +void ImageEntityItem::setImageURL(const QString& value) { + withWriteLock([&] { + if (_imageURL != value) { + auto newURL = QUrl::fromUserInput(value); + + if (newURL.isValid()) { + _imageURL = newURL.toDisplayString(); + } else { + qCDebug(entities) << "Clearing image entity source URL since" << value << "cannot be parsed to a valid URL."; + } + } + }); } -QString FlatImageEntity::getImageURL() const { +QString ImageEntityItem::getImageURL() const { return resultWithReadLock([&] { return _imageURL; }); diff --git a/libraries/entities/src/FlatImageEntity.h b/libraries/entities/src/ImageEntityItem.h similarity index 85% rename from libraries/entities/src/FlatImageEntity.h rename to libraries/entities/src/ImageEntityItem.h index 4dd91b1215..55e8fc7b36 100644 --- a/libraries/entities/src/FlatImageEntity.h +++ b/libraries/entities/src/ImageEntityItem.h @@ -1,5 +1,5 @@ // -// FlatImageEntity.h +// ImageEntityItem.h // libraries/entities/src // // Created by Elisa Lupin-Jimenez on 1/3/18. @@ -9,16 +9,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef hifi_FlatImageEntity_h -#define hifi_FlatImageEntity_h +#ifndef hifi_ImageEntityItem_h +#define hifi_ImageEntityItem_h #include "EntityItem.h" -class FlatImageEntity : public EntityItem { +class ImageEntityItem : public EntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - FlatImageEntity(const EntityItemID& entityItemID); + ImageEntityItem(const EntityItemID& entityItemID); ALLOW_INSTANTIATION // This class can be instantiated @@ -43,6 +43,7 @@ public: static const QString DEFAULT_IMAGE_URL; + virtual void setImageURL(const QString& value); QString getImageURL() const; protected: @@ -50,4 +51,4 @@ protected: }; -#endif // hifi_FlatImageEntity_h +#endif // hifi_ImageEntityItem_h diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 4241468de1..bcd39ab0a5 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -287,7 +287,7 @@ var toolBar = (function () { properties.userData = JSON.stringify({ grabbableKey: { grabbable: false } }); } - print("properties.type: " + properties.type); + print("check 1 type: " + properties.type); entityID = Entities.addEntity(properties); if (properties.type === "ParticleEffect") { From 72d8f90ec1b8950dc06bacff76ff84c114df07f7 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Thu, 11 Jan 2018 14:43:19 -0800 Subject: [PATCH 071/569] not sure why entities don't render with these changes --- libraries/entities/src/EntityItemProperties.h | 1 + libraries/entities/src/EntityTypes.cpp | 2 +- libraries/entities/src/ImageEntityItem.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 5bd6836336..e121332374 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -67,6 +67,7 @@ class EntityItemProperties { friend class ParticleEffectEntityItem; // TODO: consider removing this friend relationship and use public methods friend class ZoneEntityItem; // TODO: consider removing this friend relationship and use public methods friend class WebEntityItem; // TODO: consider removing this friend relationship and use public methods + friend class ImageEntityItem; friend class LineEntityItem; // TODO: consider removing this friend relationship and use public methods friend class PolyVoxEntityItem; // TODO: consider removing this friend relationship and use public methods friend class PolyLineEntityItem; // TODO: consider removing this friend relationship and use public methods diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index 2e4a0e89a9..47ec408374 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -91,7 +91,7 @@ EntityItemPointer EntityTypes::constructEntityItem(EntityType entityType, const EntityItemPointer newEntityItem = NULL; EntityTypeFactory factory = NULL; if (entityType >= 0 && entityType <= LAST) { - qCDebug(entities) << "check 4 type: " << entityType; + qCDebug(entities) << "check 4 type: " << entityType << ", name: " << properties.getName(); factory = _factories[entityType]; } if (factory) { diff --git a/libraries/entities/src/ImageEntityItem.cpp b/libraries/entities/src/ImageEntityItem.cpp index a94374f7b5..2e6432ba19 100644 --- a/libraries/entities/src/ImageEntityItem.cpp +++ b/libraries/entities/src/ImageEntityItem.cpp @@ -24,7 +24,7 @@ #include "ShapeEntityItem.h" #include "ImageEntityItem.h" -const QString ImageEntityItem::DEFAULT_IMAGE_URL = QString(""); +const QString ImageEntityItem::DEFAULT_IMAGE_URL(""); EntityItemPointer ImageEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity(new ImageEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); From 3d000d3d011396aee7ae664d8c64f7f2ffaf77d8 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Thu, 11 Jan 2018 16:11:01 -0800 Subject: [PATCH 072/569] changed packet number --- libraries/networking/src/udt/PacketHeaders.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index c48c6bfc0b..1ceb9b8e73 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -31,7 +31,6 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityData: case PacketType::EntityPhysics: return static_cast(EntityVersion::SoftEntities); - case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::RemovedJurisdictions); case PacketType::AvatarIdentity: From 6f76650789ba9454e8d8634098e7519d1a3daa43 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Tue, 16 Jan 2018 12:17:39 -0800 Subject: [PATCH 073/569] updated with master --- libraries/entities/src/ImageEntityItem.cpp | 9 +++++++-- libraries/entities/src/ImageEntityItem.h | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/ImageEntityItem.cpp b/libraries/entities/src/ImageEntityItem.cpp index 2e6432ba19..41bd55ee85 100644 --- a/libraries/entities/src/ImageEntityItem.cpp +++ b/libraries/entities/src/ImageEntityItem.cpp @@ -38,6 +38,8 @@ ImageEntityItem::ImageEntityItem(const EntityItemID& entityItemID) : EntityItem( EntityItemProperties ImageEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class + COPY_ENTITY_PROPERTY_TO_PROPERTIES(imageURL, getImageURL); + // Using "Quad" shape as defined in ShapeEntityItem.cpp properties.setShape("Quad"); return properties; } @@ -45,7 +47,7 @@ EntityItemProperties ImageEntityItem::getProperties(EntityPropertyFlags desiredP bool ImageEntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class - //SET_ENTITY_PROPERTY_FROM_PROPERTIES(shape, setShape); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(imageURL, setImageURL); if (somethingChanged) { bool wantDebug = false; @@ -63,7 +65,7 @@ bool ImageEntityItem::setProperties(const EntityItemProperties& properties) { // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags ImageEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); - + requestedProperties += PROP_IMAGE_URL; return requestedProperties; } @@ -78,6 +80,7 @@ void ImageEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit bool successPropertyFits = true; // Using "Quad" shape as defined in ShapeEntityItem.cpp APPEND_ENTITY_PROPERTY(PROP_SHAPE, "Quad"); + APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, _imageURL); } int ImageEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, @@ -88,6 +91,8 @@ int ImageEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesRead = 0; const unsigned char* dataAt = data; + READ_ENTITY_PROPERTY(PROP_IMAGE_URL, QString, setImageURL); + return bytesRead; } diff --git a/libraries/entities/src/ImageEntityItem.h b/libraries/entities/src/ImageEntityItem.h index 55e8fc7b36..229b6f190f 100644 --- a/libraries/entities/src/ImageEntityItem.h +++ b/libraries/entities/src/ImageEntityItem.h @@ -13,6 +13,7 @@ #define hifi_ImageEntityItem_h #include "EntityItem.h" +#include "ShapeEntityItem.h" class ImageEntityItem : public EntityItem { public: @@ -49,6 +50,9 @@ public: protected: QString _imageURL; + entity::Shape _shape{ entity::Shape::Quad }; + ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_BOX }; + }; #endif // hifi_ImageEntityItem_h From c9c55af66153882bc6fec49cbfd736b2d3308bf8 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Tue, 16 Jan 2018 17:37:49 -0800 Subject: [PATCH 074/569] setting up properties page (not complete) --- libraries/entities/src/ImageEntityItem.cpp | 16 +++++++++++++--- libraries/entities/src/ImageEntityItem.h | 4 ++++ scripts/system/html/entityProperties.html | 9 +++++++++ scripts/system/html/js/entityProperties.js | 11 +++++++++++ 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/libraries/entities/src/ImageEntityItem.cpp b/libraries/entities/src/ImageEntityItem.cpp index 41bd55ee85..1a6ab1e146 100644 --- a/libraries/entities/src/ImageEntityItem.cpp +++ b/libraries/entities/src/ImageEntityItem.cpp @@ -79,7 +79,6 @@ void ImageEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit bool successPropertyFits = true; // Using "Quad" shape as defined in ShapeEntityItem.cpp - APPEND_ENTITY_PROPERTY(PROP_SHAPE, "Quad"); APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, _imageURL); } @@ -96,7 +95,7 @@ int ImageEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, return bytesRead; } -/*void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) { +void ImageEntityItem::setUnscaledDimensions(const glm::vec3& value) { const float MAX_FLAT_DIMENSION = 0.0001f; if (value.y > MAX_FLAT_DIMENSION) { // enforce flatness in Y @@ -106,7 +105,7 @@ int ImageEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, } else { EntityItem::setUnscaledDimensions(value); } -}*/ +} void ImageEntityItem::setImageURL(const QString& value) { withWriteLock([&] { @@ -128,3 +127,14 @@ QString ImageEntityItem::getImageURL() const { }); } +/*void ImageEntityItem::computeShapeInfo(ShapeInfo& info) { + // This will be called whenever DIRTY_SHAPE flag (set by dimension change, etc) + // is set. + + EntityItem::computeShapeInfo(info); +}*/ + +// This value specifies how the shape should be treated by physics calculations. +ShapeType ImageEntityItem::getShapeType() const { + return _collisionShapeType; +} diff --git a/libraries/entities/src/ImageEntityItem.h b/libraries/entities/src/ImageEntityItem.h index 229b6f190f..f3f4fbe6c7 100644 --- a/libraries/entities/src/ImageEntityItem.h +++ b/libraries/entities/src/ImageEntityItem.h @@ -42,11 +42,15 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; + void setUnscaledDimensions(const glm::vec3& value) override; static const QString DEFAULT_IMAGE_URL; virtual void setImageURL(const QString& value); QString getImageURL() const; + //virtual void computeShapeInfo(ShapeInfo& info) override; + virtual ShapeType getShapeType() const override; + protected: QString _imageURL; diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index b93974ee77..6e8178d408 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -714,6 +714,15 @@ +
+ + ImageM + +
+ + +
+
diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 7008d0df66..05a1b2576b 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -133,6 +133,11 @@ function createEmitGroupNumberPropertyUpdateFunction(group, propertyName) { }; } +function createImageURLUpdateFunction(propertyName) { + return function() { + updateProperty(propertyName, this.value); + } +} function createEmitTextPropertyUpdateFunction(propertyName) { return function() { @@ -621,6 +626,8 @@ function loaded() { var elModelTextures = document.getElementById("property-model-textures"); var elModelOriginalTextures = document.getElementById("property-model-original-textures"); + var elImageURL = document.getElementById("property-image-url"); + var elWebSourceURL = document.getElementById("property-web-source-url"); var elWebDPI = document.getElementById("property-web-dpi"); @@ -985,6 +992,8 @@ function loaded() { } else if (properties.type === "Web") { elWebSourceURL.value = properties.sourceUrl; elWebDPI.value = properties.dpi; + } else if (properties.type === "Image") { + elImageURL.value = properties.imageURL; } else if (properties.type === "Text") { elTextText.value = properties.text; elTextLineHeight.value = properties.lineHeight.toFixed(4); @@ -1352,6 +1361,8 @@ function loaded() { elShape.addEventListener('change', createEmitTextPropertyUpdateFunction('shape')); + elImageURL.addEventListener('change', createImageURLUpdateFunction('imageURL')); + elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl')); elWebDPI.addEventListener('change', createEmitNumberPropertyUpdateFunction('dpi', 0)); From ecb53192add37a7bb70c88d2c00b6568aab0db7d Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Wed, 17 Jan 2018 11:57:40 -0800 Subject: [PATCH 075/569] edit properties reflect image members --- scripts/system/html/css/edit-style.css | 49 +++++++++++++++++++++- scripts/system/html/js/entityProperties.js | 7 ++-- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 736d42d593..58acc317bd 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -448,7 +448,7 @@ input[type=checkbox]:checked + label:hover { border: 1.5pt solid black; } -.shape-section, .light-section, .model-section, .web-section, .hyperlink-section, .text-section, .zone-section { +.shape-section, .light-section, .model-section, .web-section, .image-section, .hyperlink-section, .text-section, .zone-section { display: table; } @@ -564,6 +564,7 @@ hr { .text-group[collapsed="true"] ~ .text-group, .zone-group[collapsed="true"] ~ .zone-group, +.image-group[collapsed="true"] ~ .image-group, .web-group[collapsed="true"] ~ .web-group, .hyperlink-group[collapsed="true"] ~ .hyperlink-group, .spatial-group[collapsed="true"] ~ .spatial-group, @@ -1466,6 +1467,9 @@ input#reset-to-natural-dimensions { #properties-list.ShapeMenu #text, #properties-list.BoxMenu #text, #properties-list.SphereMenu #text, +#properties-list.ShapeMenu #image, +#properties-list.BoxMenu #image, +#properties-list.SphereMenu #image, #properties-list.ShapeMenu #web, #properties-list.BoxMenu #web, #properties-list.SphereMenu #web { @@ -1497,6 +1501,7 @@ input#reset-to-natural-dimensions { #properties-list.ParticleEffectMenu #shape-list, #properties-list.ParticleEffectMenu #text, #properties-list.ParticleEffectMenu #web, +#properties-list.ParticleEffectMenu #image, #properties-list.ParticleEffectMenu #zone { display: none; } @@ -1527,6 +1532,7 @@ input#reset-to-natural-dimensions { #properties-list.LightMenu #model, #properties-list.LightMenu #zone, #properties-list.LightMenu #text, +#properties-list.LightMenu #image, #properties-list.LightMenu #web { display: none; } @@ -1563,6 +1569,7 @@ input#reset-to-natural-dimensions { #properties-list.ModelMenu #light, #properties-list.ModelMenu #zone, #properties-list.ModelMenu #text, +#properties-list.ModelMenu #image, #properties-list.ModelMenu #web { display: none; } @@ -1599,6 +1606,7 @@ input#reset-to-natural-dimensions { #properties-list.ZoneMenu #light, #properties-list.ZoneMenu #model, #properties-list.ZoneMenu #text, +#properties-list.ZoneMenu #image, #properties-list.ZoneMenu #web { display: none; } @@ -1609,6 +1617,43 @@ input#reset-to-natural-dimensions { } +/* ----- Order of Menu items for Image ----- */ +#properties-list.ImageMenu #general { + order: 1; +} +#properties-list.ImageMenu #image { + order: 2; +} +#properties-list.ImageMenu #collision-info { + order: 3; +} +#properties-list.ImageMenu #physical { + order: 4; +} +#properties-list.ImageMenu #spatial { + order: 5; +} +#properties-list.ImageMenu #behavior { + order: 6; +} +#properties-list.ImageMenu #hyperlink { + order: 7; +} +/* sections to hide */ +#properties-list.ImageMenu #light, +#properties-list.ImageMenu #model, +#properties-list.ImageMenu #zone, +#properties-list.ImageMenu #web, +#properties-list.ImageMenu #text { + display: none; +} +/* items to hide */ +#properties-list.ImageMenu #shape-list, +#properties-list.ImageMenu #base-color-section { + display: none; +} + + /* ----- Order of Menu items for Web ----- */ #properties-list.WebMenu #general { order: 1; @@ -1635,6 +1680,7 @@ input#reset-to-natural-dimensions { #properties-list.WebMenu #light, #properties-list.WebMenu #model, #properties-list.WebMenu #zone, +#properties-list.WebMenu #image, #properties-list.WebMenu #text { display: none; } @@ -1672,6 +1718,7 @@ input#reset-to-natural-dimensions { #properties-list.TextMenu #light, #properties-list.TextMenu #model, #properties-list.TextMenu #zone, +#properties-list.TextMenu #image, #properties-list.TextMenu #web { display: none; } diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 05a1b2576b..54b50de106 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -19,6 +19,7 @@ var ICON_FOR_TYPE = { ParticleEffect: "", Model: "", Web: "q", + Image: "q", // what do? Text: "l", Light: "p", Zone: "o", @@ -134,9 +135,9 @@ function createEmitGroupNumberPropertyUpdateFunction(group, propertyName) { } function createImageURLUpdateFunction(propertyName) { - return function() { + return function () { updateProperty(propertyName, this.value); - } + }; } function createEmitTextPropertyUpdateFunction(propertyName) { @@ -1735,7 +1736,7 @@ function loaded() { } // Dropdowns - // For each dropdown the following replacement is created in place of the oriringal dropdown... + // For each dropdown the following replacement is created in place of the original dropdown... // Structure created: //
//
display textcarat
From dc5f29aa58268a9abdc9b82f6a9bc41828c28832 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Wed, 17 Jan 2018 17:57:09 -0800 Subject: [PATCH 076/569] entity item properties hooked up for image --- libraries/entities/src/EntityItemProperties.cpp | 9 +++++++++ scripts/system/edit.js | 2 +- scripts/system/html/js/entityProperties.js | 3 ++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index fc8ca18fb6..3729c75f3d 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -350,6 +350,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_AMBIENT_LIGHT_MODE, ambientLightMode); CHECK_PROPERTY_CHANGE(PROP_SKYBOX_MODE, skyboxMode); + CHECK_PROPERTY_CHANGE(PROP_IMAGE_URL, imageURL); + CHECK_PROPERTY_CHANGE(PROP_SOURCE_URL, sourceUrl); CHECK_PROPERTY_CHANGE(PROP_VOXEL_VOLUME_SIZE, voxelVolumeSize); CHECK_PROPERTY_CHANGE(PROP_VOXEL_DATA, voxelData); @@ -785,6 +787,8 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(ambientLightMode, AmbientLightMode); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(skyboxMode, SkyboxMode); + COPY_PROPERTY_FROM_QSCRIPTVALUE(imageURL, QString, setImageURL); + COPY_PROPERTY_FROM_QSCRIPTVALUE(sourceUrl, QString, setSourceUrl); COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, glmVec3, setVoxelVolumeSize); COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelData, QByteArray, setVoxelData); @@ -945,6 +949,8 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(ambientLightMode); COPY_PROPERTY_IF_CHANGED(skyboxMode); + COPY_PROPERTY_IF_CHANGED(imageURL); + COPY_PROPERTY_IF_CHANGED(sourceUrl); COPY_PROPERTY_IF_CHANGED(voxelVolumeSize); COPY_PROPERTY_IF_CHANGED(voxelData); @@ -1138,6 +1144,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_VOXEL_SURFACE_STYLE, VoxelSurfaceStyle, voxelSurfaceStyle, uint16_t); ADD_PROPERTY_TO_MAP(PROP_NAME, Name, name, QString); ADD_PROPERTY_TO_MAP(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString); + ADD_PROPERTY_TO_MAP(PROP_IMAGE_URL, ImageURL, imageURL, QString); ADD_PROPERTY_TO_MAP(PROP_LINE_WIDTH, LineWidth, lineWidth, float); ADD_PROPERTY_TO_MAP(PROP_LINE_POINTS, LinePoints, linePoints, QVector); ADD_PROPERTY_TO_MAP(PROP_HREF, Href, href, QString); @@ -2060,6 +2067,8 @@ void EntityItemProperties::markAllChanged() { _skybox.markAllChanged(); _haze.markAllChanged(); + _imageURLChanged = true; + _sourceUrlChanged = true; _voxelVolumeSizeChanged = true; _voxelDataChanged = true; diff --git a/scripts/system/edit.js b/scripts/system/edit.js index bcd39ab0a5..5f976e07d3 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -546,7 +546,7 @@ var toolBar = (function () { createNewEntity({ type: "Image", dimensions: DEFAULT_DIMENSIONS, - sourceUrl: "https://highfidelity.com/" + imageURL: "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/dog.jpg" }); }); diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 54b50de106..7c3af548ba 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -19,7 +19,7 @@ var ICON_FOR_TYPE = { ParticleEffect: "", Model: "", Web: "q", - Image: "q", // what do? + Image: "q", // change this when image type icon added Text: "l", Light: "p", Zone: "o", @@ -995,6 +995,7 @@ function loaded() { elWebDPI.value = properties.dpi; } else if (properties.type === "Image") { elImageURL.value = properties.imageURL; + //elImageURL.value = properties.sourceURL; } else if (properties.type === "Text") { elTextText.value = properties.text; elTextLineHeight.value = properties.lineHeight.toFixed(4); From 576d683d3bb62baef14c4ddc2f3696ca71679258 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Thu, 18 Jan 2018 14:40:33 -0800 Subject: [PATCH 077/569] initial changes to incorporate snap model --- libraries/entities/src/EntityScriptingInterface.cpp | 2 -- libraries/entities/src/EntityTree.cpp | 1 - libraries/entities/src/EntityTypes.cpp | 4 +++- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index cab4251d97..4342f0e683 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -266,8 +266,6 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties bool success = true; if (_entityTree) { _entityTree->withWriteLock([&] { - propertiesWithSimID.getType(); - qCDebug(entities) << "check 2 type: " << propertiesWithSimID.getType(); EntityItemPointer entity = _entityTree->addEntity(id, propertiesWithSimID); if (entity) { if (propertiesWithSimID.queryAACubeRelatedPropertyChanged()) { diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 56c3aa216c..bf29f3bec9 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -514,7 +514,6 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti // construct the instance of the entity EntityTypes::EntityType type = props.getType(); - qCDebug(entities) << "check 3 type: " << type; result = EntityTypes::constructEntityItem(type, entityID, props); if (result) { diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index 47ec408374..6326df3cc1 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -91,7 +91,9 @@ EntityItemPointer EntityTypes::constructEntityItem(EntityType entityType, const EntityItemPointer newEntityItem = NULL; EntityTypeFactory factory = NULL; if (entityType >= 0 && entityType <= LAST) { - qCDebug(entities) << "check 4 type: " << entityType << ", name: " << properties.getName(); + if (getEntityTypeName(entityType) == "Image") { + entityType = getEntityTypeFromName("Web"); + } factory = _factories[entityType]; } if (factory) { From ceb621a52151d20bfa66dd4eb09779c45f003901 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Mon, 22 Jan 2018 11:36:58 -0800 Subject: [PATCH 078/569] reverted protocol change for images --- libraries/entities/src/EntityItemProperties.cpp | 16 ++++++++-------- libraries/entities/src/EntityItemProperties.h | 2 +- libraries/entities/src/EntityPropertyFlags.h | 4 ++-- libraries/entities/src/EntityTypes.cpp | 7 ++----- libraries/entities/src/EntityTypes.h | 2 +- libraries/entities/src/ImageEntityItem.cpp | 8 ++++++-- libraries/entities/src/ImageEntityItem.h | 4 ++++ scripts/system/edit.js | 10 +++++++++- 8 files changed, 33 insertions(+), 20 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 3729c75f3d..48ad9601ba 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -350,7 +350,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_AMBIENT_LIGHT_MODE, ambientLightMode); CHECK_PROPERTY_CHANGE(PROP_SKYBOX_MODE, skyboxMode); - CHECK_PROPERTY_CHANGE(PROP_IMAGE_URL, imageURL); + //CHECK_PROPERTY_CHANGE(PROP_IMAGE_URL, imageURL); CHECK_PROPERTY_CHANGE(PROP_SOURCE_URL, sourceUrl); CHECK_PROPERTY_CHANGE(PROP_VOXEL_VOLUME_SIZE, voxelVolumeSize); @@ -593,9 +593,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool } // Image only - if (_type == EntityTypes::Image) { + /*if (_type == EntityTypes::Image) { COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IMAGE_URL, imageURL); - } + }*/ // Web only if (_type == EntityTypes::Web) { @@ -1144,7 +1144,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_VOXEL_SURFACE_STYLE, VoxelSurfaceStyle, voxelSurfaceStyle, uint16_t); ADD_PROPERTY_TO_MAP(PROP_NAME, Name, name, QString); ADD_PROPERTY_TO_MAP(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString); - ADD_PROPERTY_TO_MAP(PROP_IMAGE_URL, ImageURL, imageURL, QString); + //ADD_PROPERTY_TO_MAP(PROP_IMAGE_URL, ImageURL, imageURL, QString); ADD_PROPERTY_TO_MAP(PROP_LINE_WIDTH, LineWidth, lineWidth, float); ADD_PROPERTY_TO_MAP(PROP_LINE_POINTS, LinePoints, linePoints, QVector); ADD_PROPERTY_TO_MAP(PROP_HREF, Href, href, QString); @@ -1381,9 +1381,9 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI()); } - if (properties.getType() == EntityTypes::Image) { + /*if (properties.getType() == EntityTypes::Image) { APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, properties.getImageURL()); - } + }*/ if (properties.getType() == EntityTypes::Text) { APPEND_ENTITY_PROPERTY(PROP_TEXT, properties.getText()); @@ -1746,9 +1746,9 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DPI, uint16_t, setDPI); } - if (properties.getType() == EntityTypes::Image) { + /*if (properties.getType() == EntityTypes::Image) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IMAGE_URL, QString, setImageURL); - } + }*/ if (properties.getType() == EntityTypes::Text) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT, QString, setText); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index e121332374..14544cd4d1 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -67,7 +67,7 @@ class EntityItemProperties { friend class ParticleEffectEntityItem; // TODO: consider removing this friend relationship and use public methods friend class ZoneEntityItem; // TODO: consider removing this friend relationship and use public methods friend class WebEntityItem; // TODO: consider removing this friend relationship and use public methods - friend class ImageEntityItem; + //friend class ImageEntityItem; friend class LineEntityItem; // TODO: consider removing this friend relationship and use public methods friend class PolyVoxEntityItem; // TODO: consider removing this friend relationship and use public methods friend class PolyLineEntityItem; // TODO: consider removing this friend relationship and use public methods diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 4768ebed86..ec8f4e08c8 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -42,8 +42,8 @@ enum EntityPropertyList { PROP_ANIMATION_ALLOW_TRANSLATION, PROP_RELAY_PARENT_JOINTS, - // for image - PROP_IMAGE_URL, + /*// for image + PROP_IMAGE_URL,*/ // these properties are supported by the EntityItem base class PROP_REGISTRATION_POINT, diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index 6326df3cc1..6bf5a176a7 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -28,7 +28,7 @@ #include "LineEntityItem.h" #include "PolyVoxEntityItem.h" #include "PolyLineEntityItem.h" -#include "ImageEntityItem.h" +//#include "ImageEntityItem.h" #include "ShapeEntityItem.h" QMap EntityTypes::_typeToNameMap; @@ -48,7 +48,7 @@ REGISTER_ENTITY_TYPE(Zone) REGISTER_ENTITY_TYPE(Line) REGISTER_ENTITY_TYPE(PolyVox) REGISTER_ENTITY_TYPE(PolyLine) -REGISTER_ENTITY_TYPE(Image) +//REGISTER_ENTITY_TYPE(Image) REGISTER_ENTITY_TYPE(Shape) REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, ShapeEntityItem::boxFactory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, ShapeEntityItem::sphereFactory) @@ -91,9 +91,6 @@ EntityItemPointer EntityTypes::constructEntityItem(EntityType entityType, const EntityItemPointer newEntityItem = NULL; EntityTypeFactory factory = NULL; if (entityType >= 0 && entityType <= LAST) { - if (getEntityTypeName(entityType) == "Image") { - entityType = getEntityTypeFromName("Web"); - } factory = _factories[entityType]; } if (factory) { diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 8d986c8090..6fe4274820 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -48,7 +48,7 @@ public: Line, PolyVox, PolyLine, - Image, + //Image, Shape, LAST = Shape } EntityType; diff --git a/libraries/entities/src/ImageEntityItem.cpp b/libraries/entities/src/ImageEntityItem.cpp index 1a6ab1e146..e30d85257e 100644 --- a/libraries/entities/src/ImageEntityItem.cpp +++ b/libraries/entities/src/ImageEntityItem.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +/* NOT IN USE + #include #include @@ -127,14 +129,16 @@ QString ImageEntityItem::getImageURL() const { }); } -/*void ImageEntityItem::computeShapeInfo(ShapeInfo& info) { +void ImageEntityItem::computeShapeInfo(ShapeInfo& info) { // This will be called whenever DIRTY_SHAPE flag (set by dimension change, etc) // is set. EntityItem::computeShapeInfo(info); -}*/ +} // This value specifies how the shape should be treated by physics calculations. ShapeType ImageEntityItem::getShapeType() const { return _collisionShapeType; } + +*/ diff --git a/libraries/entities/src/ImageEntityItem.h b/libraries/entities/src/ImageEntityItem.h index f3f4fbe6c7..276d21e6f0 100644 --- a/libraries/entities/src/ImageEntityItem.h +++ b/libraries/entities/src/ImageEntityItem.h @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +/* NOT IN USE + #ifndef hifi_ImageEntityItem_h #define hifi_ImageEntityItem_h @@ -60,3 +62,5 @@ protected: }; #endif // hifi_ImageEntityItem_h + +*/ diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 5f976e07d3..1d02fed787 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -540,7 +540,7 @@ var toolBar = (function () { }); }); - // for image button + /*// for image button addButton("newImageButton", "web-01.svg", function () { print("new image message is received"); createNewEntity({ @@ -548,6 +548,14 @@ var toolBar = (function () { dimensions: DEFAULT_DIMENSIONS, imageURL: "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/dog.jpg" }); + });*/ + addButton("newImageButton", "web-01.svg", function () { + createNewEntity({ + type: "Model", + dimensions: DEFAULT_DIMENSIONS, + modelURL: "http://hifi-content.s3.amazonaws.com/alan/dev/Test/snapshot.fbx", + textures: JSON.stringify({ "tex.picture": "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/dog.jpg" }) + }); }); addButton("newWebButton", "web-01.svg", function () { From 308e481e63f5d19db4f8548c3a8689d60a22958f Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Mon, 22 Jan 2018 17:46:12 -0800 Subject: [PATCH 079/569] switch to image property list not working --- scripts/system/edit.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 1d02fed787..6f48e1167a 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -252,7 +252,7 @@ var toolBar = (function () { properties.rotation = MyAvatar.orientation; // added image here - var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Image", "Web"]; + var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Web"]; if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { // Adjust position of entity per bounding box prior to creating it. @@ -287,7 +287,6 @@ var toolBar = (function () { properties.userData = JSON.stringify({ grabbableKey: { grabbable: false } }); } - print("check 1 type: " + properties.type); entityID = Entities.addEntity(properties); if (properties.type === "ParticleEffect") { @@ -540,20 +539,21 @@ var toolBar = (function () { }); }); - /*// for image button - addButton("newImageButton", "web-01.svg", function () { - print("new image message is received"); - createNewEntity({ - type: "Image", - dimensions: DEFAULT_DIMENSIONS, - imageURL: "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/dog.jpg" - }); - });*/ addButton("newImageButton", "web-01.svg", function () { createNewEntity({ type: "Model", - dimensions: DEFAULT_DIMENSIONS, + // make constant for this later + dimensions: { + x: 4.16, + y: 0.02, + z: 2.58 + }, modelURL: "http://hifi-content.s3.amazonaws.com/alan/dev/Test/snapshot.fbx", + imageURL: "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/dog.jpg", + // will this work? + /*get textures() { + return JSON.stringify({ "tex.picture": this.imageURL }); + }*/ textures: JSON.stringify({ "tex.picture": "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/dog.jpg" }) }); }); From 42151b8fd47f6b32d3662d9d599096ec4d290408 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Tue, 23 Jan 2018 14:50:41 -0800 Subject: [PATCH 080/569] creating new image entity opens image property options --- .../entities/src/EntityItemProperties.cpp | 12 +- libraries/entities/src/EntityItemProperties.h | 2 +- libraries/entities/src/ImageEntityItem.cpp | 2 +- scripts/system/edit.js | 6 +- scripts/system/html/js/entityProperties.js | 123 ++++++++++++++++++ 5 files changed, 136 insertions(+), 9 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 48ad9601ba..7fcfc00ee8 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -592,8 +592,10 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SKYBOX_MODE, skyboxMode, getSkyboxModeAsString()); } - // Image only - /*if (_type == EntityTypes::Image) { + + + /*// Image only + if (_type == EntityTypes::Image) { COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IMAGE_URL, imageURL); }*/ @@ -787,7 +789,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(ambientLightMode, AmbientLightMode); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(skyboxMode, SkyboxMode); - COPY_PROPERTY_FROM_QSCRIPTVALUE(imageURL, QString, setImageURL); + //COPY_PROPERTY_FROM_QSCRIPTVALUE(imageURL, QString, setImageURL); COPY_PROPERTY_FROM_QSCRIPTVALUE(sourceUrl, QString, setSourceUrl); COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, glmVec3, setVoxelVolumeSize); @@ -949,7 +951,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(ambientLightMode); COPY_PROPERTY_IF_CHANGED(skyboxMode); - COPY_PROPERTY_IF_CHANGED(imageURL); + //COPY_PROPERTY_IF_CHANGED(imageURL); COPY_PROPERTY_IF_CHANGED(sourceUrl); COPY_PROPERTY_IF_CHANGED(voxelVolumeSize); @@ -2067,7 +2069,7 @@ void EntityItemProperties::markAllChanged() { _skybox.markAllChanged(); _haze.markAllChanged(); - _imageURLChanged = true; + //_imageURLChanged = true; _sourceUrlChanged = true; _voxelVolumeSizeChanged = true; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 14544cd4d1..5ca28a12c5 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -195,7 +195,7 @@ public: DEFINE_PROPERTY_GROUP(Haze, haze, HazePropertyGroup); DEFINE_PROPERTY_GROUP(Animation, animation, AnimationPropertyGroup); DEFINE_PROPERTY_REF(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString, ""); - DEFINE_PROPERTY_REF(PROP_IMAGE_URL, ImageURL, imageURL, QString, ""); + //DEFINE_PROPERTY_REF(PROP_IMAGE_URL, ImageURL, imageURL, QString, ""); DEFINE_PROPERTY(PROP_LINE_WIDTH, LineWidth, lineWidth, float, LineEntityItem::DEFAULT_LINE_WIDTH); DEFINE_PROPERTY_REF(LINE_POINTS, LinePoints, linePoints, QVector, QVector()); DEFINE_PROPERTY_REF(PROP_HREF, Href, href, QString, ""); diff --git a/libraries/entities/src/ImageEntityItem.cpp b/libraries/entities/src/ImageEntityItem.cpp index e30d85257e..9969a39ed0 100644 --- a/libraries/entities/src/ImageEntityItem.cpp +++ b/libraries/entities/src/ImageEntityItem.cpp @@ -35,7 +35,7 @@ EntityItemPointer ImageEntityItem::factory(const EntityItemID& entityID, const E } ImageEntityItem::ImageEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { - _type = EntityTypes::Image; + //_type = EntityTypes::Image; } EntityItemProperties ImageEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 6f48e1167a..a5c236a6c9 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -253,6 +253,8 @@ var toolBar = (function () { // added image here var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Web"]; + //var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Image", "Web"]; + if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { // Adjust position of entity per bounding box prior to creating it. @@ -539,6 +541,7 @@ var toolBar = (function () { }); }); + // for image button addButton("newImageButton", "web-01.svg", function () { createNewEntity({ type: "Model", @@ -548,8 +551,7 @@ var toolBar = (function () { y: 0.02, z: 2.58 }, - modelURL: "http://hifi-content.s3.amazonaws.com/alan/dev/Test/snapshot.fbx", - imageURL: "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/dog.jpg", + modelURL: "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx", // will this work? /*get textures() { return JSON.stringify({ "tex.picture": this.imageURL }); diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 7c3af548ba..2b90f8ddfa 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -511,6 +511,10 @@ function loaded() { var elPropertiesList = document.getElementById("properties-list"); var elID = document.getElementById("property-id"); var elType = document.getElementById("property-type"); +<<<<<<< HEAD +======= + debugPrint("the type is: " + JSON.stringify(elType)); +>>>>>>> creating new image entity opens image property options var elTypeIcon = document.getElementById("type-icon"); var elName = document.getElementById("property-name"); var elLocked = document.getElementById("property-locked"); @@ -661,16 +665,28 @@ function loaded() { var elZoneKeyLightDirectionY = document.getElementById("property-zone-key-light-direction-y"); // Skybox +<<<<<<< HEAD var elZoneSkyboxModeInherit = document.getElementById("property-zone-skybox-mode-inherit"); var elZoneSkyboxModeDisabled = document.getElementById("property-zone-skybox-mode-disabled"); var elZoneSkyboxModeEnabled = document.getElementById("property-zone-skybox-mode-enabled"); +======= + var elZoneSkyboxModeInherit = document.getElementById("property-zone-skybox-mode-inherit"); + var elZoneSkyboxModeDisabled = document.getElementById("property-zone-skybox-mode-disabled"); + var elZoneSkyboxModeEnabled = document.getElementById("property-zone-skybox-mode-enabled"); +>>>>>>> creating new image entity opens image property options // Ambient light var elCopySkyboxURLToAmbientURL = document.getElementById("copy-skybox-url-to-ambient-url"); +<<<<<<< HEAD var elZoneAmbientLightModeInherit = document.getElementById("property-zone-ambient-light-mode-inherit"); var elZoneAmbientLightModeDisabled = document.getElementById("property-zone-ambient-light-mode-disabled"); var elZoneAmbientLightModeEnabled = document.getElementById("property-zone-ambient-light-mode-enabled"); +======= + var elZoneAmbientLightModeInherit = document.getElementById("property-zone-ambient-light-mode-inherit"); + var elZoneAmbientLightModeDisabled = document.getElementById("property-zone-ambient-light-mode-disabled"); + var elZoneAmbientLightModeEnabled = document.getElementById("property-zone-ambient-light-mode-enabled"); +>>>>>>> creating new image entity opens image property options var elZoneAmbientLightIntensity = document.getElementById("property-zone-key-ambient-intensity"); var elZoneAmbientLightURL = document.getElementById("property-zone-key-ambient-url"); @@ -787,7 +803,11 @@ function loaded() { } else { properties = data.selections[0].properties; +<<<<<<< HEAD +======= + debugPrint("props: " + JSON.stringify(properties)); +>>>>>>> creating new image entity opens image property options if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null && editor !== null) { saveJSONUserData(true); } @@ -796,6 +816,14 @@ function loaded() { lastEntityID = '"' + properties.id + '"'; elID.value = properties.id; +<<<<<<< HEAD +======= + // image is not yet a separate entity type + if (properties.type === "Model" && properties.modelURL === "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx") { + properties.type = "Image"; + } + +>>>>>>> creating new image entity opens image property options // Create class name for css ruleset filtering elPropertiesList.className = properties.type + 'Menu'; @@ -1024,9 +1052,15 @@ function loaded() { } else if (properties.type === "Zone") { // Key light +<<<<<<< HEAD elZoneKeyLightModeInherit.checked = (properties.keyLightMode === 'inherit'); elZoneKeyLightModeDisabled.checked = (properties.keyLightMode === 'disabled'); elZoneKeyLightModeEnabled.checked = (properties.keyLightMode === 'enabled'); +======= + elZoneKeyLightModeInherit.checked = (properties.keyLightMode === 'inherit'); + elZoneKeyLightModeDisabled.checked = (properties.keyLightMode === 'disabled'); + elZoneKeyLightModeEnabled.checked = (properties.keyLightMode === 'enabled'); +>>>>>>> creating new image entity opens image property options elZoneKeyLightColor.style.backgroundColor = "rgb(" + properties.keyLight.color.red + "," + properties.keyLight.color.green + "," + properties.keyLight.color.blue + ")"; @@ -1038,6 +1072,7 @@ function loaded() { elZoneKeyLightDirectionY.value = properties.keyLight.direction.y.toFixed(2); // Skybox +<<<<<<< HEAD elZoneSkyboxModeInherit.checked = (properties.skyboxMode === 'inherit'); elZoneSkyboxModeDisabled.checked = (properties.skyboxMode === 'disabled'); elZoneSkyboxModeEnabled.checked = (properties.skyboxMode === 'enabled'); @@ -1046,14 +1081,30 @@ function loaded() { elZoneAmbientLightModeInherit.checked = (properties.ambientLightMode === 'inherit'); elZoneAmbientLightModeDisabled.checked = (properties.ambientLightMode === 'disabled'); elZoneAmbientLightModeEnabled.checked = (properties.ambientLightMode === 'enabled'); +======= + elZoneSkyboxModeInherit.checked = (properties.skyboxMode === 'inherit'); + elZoneSkyboxModeDisabled.checked = (properties.skyboxMode === 'disabled'); + elZoneSkyboxModeEnabled.checked = (properties.skyboxMode === 'enabled'); + + // Ambient light + elZoneAmbientLightModeInherit.checked = (properties.ambientLightMode === 'inherit'); + elZoneAmbientLightModeDisabled.checked = (properties.ambientLightMode === 'disabled'); + elZoneAmbientLightModeEnabled.checked = (properties.ambientLightMode === 'enabled'); +>>>>>>> creating new image entity opens image property options elZoneAmbientLightIntensity.value = properties.ambientLight.ambientIntensity.toFixed(2); elZoneAmbientLightURL.value = properties.ambientLight.ambientURL; // Haze +<<<<<<< HEAD elZoneHazeModeInherit.checked = (properties.hazeMode === 'inherit'); elZoneHazeModeDisabled.checked = (properties.hazeMode === 'disabled'); elZoneHazeModeEnabled.checked = (properties.hazeMode === 'enabled'); +======= + elZoneHazeModeInherit.checked = (properties.hazeMode === 'inherit'); + elZoneHazeModeDisabled.checked = (properties.hazeMode === 'disabled'); + elZoneHazeModeEnabled.checked = (properties.hazeMode === 'enabled'); +>>>>>>> creating new image entity opens image property options elZoneHazeRange.value = properties.haze.hazeRange.toFixed(0); elZoneHazeColor.style.backgroundColor = "rgb(" + @@ -1319,15 +1370,24 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', +<<<<<<< HEAD submit: false, // We don't want to have a submission button +======= +>>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-color-control2').attr('active', 'true'); }, onHide: function(colpick) { $('#property-color-control2').attr('active', 'false'); }, +<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); +======= + onSubmit: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + $(el).colpickHide(); +>>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); } })); @@ -1343,15 +1403,24 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', +<<<<<<< HEAD submit: false, // We don't want to have a submission button +======= +>>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-light-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-light-color').attr('active', 'false'); }, +<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); +======= + onSubmit: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + $(el).colpickHide(); +>>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); } })); @@ -1400,15 +1469,24 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', +<<<<<<< HEAD submit: false, // We don't want to have a submission button +======= +>>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-text-text-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-text-text-color').attr('active', 'false'); }, +<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); +======= + onSubmit: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + $(el).colpickHide(); +>>>>>>> creating new image entity opens image property options $(el).attr('active', 'false'); emitColorPropertyUpdate('textColor', rgb.r, rgb.g, rgb.b); } @@ -1424,15 +1502,24 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', +<<<<<<< HEAD submit: false, // We don't want to have a submission button +======= +>>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-text-background-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-text-background-color').attr('active', 'false'); }, +<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); +======= + onSubmit: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + $(el).colpickHide(); +>>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('backgroundColor', rgb.r, rgb.g, rgb.b); } })); @@ -1449,15 +1536,24 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', +<<<<<<< HEAD submit: false, // We don't want to have a submission button +======= +>>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-zone-key-light-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-zone-key-light-color').attr('active', 'false'); }, +<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); +======= + onSubmit: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + $(el).colpickHide(); +>>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b, 'keyLight'); } })); @@ -1518,15 +1614,24 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', +<<<<<<< HEAD submit: false, // We don't want to have a submission button +======= +>>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-zone-haze-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-zone-haze-color').attr('active', 'false'); }, +<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); +======= + onSubmit: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + $(el).colpickHide(); +>>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('hazeColor', rgb.r, rgb.g, rgb.b, 'haze'); } })); @@ -1543,15 +1648,24 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', +<<<<<<< HEAD submit: false, // We don't want to have a submission button +======= +>>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-zone-haze-glare-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-zone-haze-glare-color').attr('active', 'false'); }, +<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); +======= + onSubmit: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + $(el).colpickHide(); +>>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('hazeGlareColor', rgb.r, rgb.g, rgb.b, 'haze'); } })); @@ -1585,15 +1699,24 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', +<<<<<<< HEAD submit: false, // We don't want to have a submission button +======= +>>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-zone-skybox-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-zone-skybox-color').attr('active', 'false'); }, +<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); +======= + onSubmit: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + $(el).colpickHide(); +>>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b, 'skybox'); } })); From fe3bc00baa0cb1bcaaa3090e5ca3e438ba47874a Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Tue, 23 Jan 2018 16:45:54 -0800 Subject: [PATCH 081/569] loading and rendering atp and http image files --- scripts/system/html/js/entityProperties.js | 131 ++------------------- 1 file changed, 7 insertions(+), 124 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 2b90f8ddfa..e7526fa2e0 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -136,7 +136,8 @@ function createEmitGroupNumberPropertyUpdateFunction(group, propertyName) { function createImageURLUpdateFunction(propertyName) { return function () { - updateProperty(propertyName, this.value); + var newTextures = JSON.stringify({ "tex.picture": this.value }); + updateProperty(propertyName, newTextures); }; } @@ -511,10 +512,6 @@ function loaded() { var elPropertiesList = document.getElementById("properties-list"); var elID = document.getElementById("property-id"); var elType = document.getElementById("property-type"); -<<<<<<< HEAD -======= - debugPrint("the type is: " + JSON.stringify(elType)); ->>>>>>> creating new image entity opens image property options var elTypeIcon = document.getElementById("type-icon"); var elName = document.getElementById("property-name"); var elLocked = document.getElementById("property-locked"); @@ -665,28 +662,16 @@ function loaded() { var elZoneKeyLightDirectionY = document.getElementById("property-zone-key-light-direction-y"); // Skybox -<<<<<<< HEAD var elZoneSkyboxModeInherit = document.getElementById("property-zone-skybox-mode-inherit"); var elZoneSkyboxModeDisabled = document.getElementById("property-zone-skybox-mode-disabled"); var elZoneSkyboxModeEnabled = document.getElementById("property-zone-skybox-mode-enabled"); -======= - var elZoneSkyboxModeInherit = document.getElementById("property-zone-skybox-mode-inherit"); - var elZoneSkyboxModeDisabled = document.getElementById("property-zone-skybox-mode-disabled"); - var elZoneSkyboxModeEnabled = document.getElementById("property-zone-skybox-mode-enabled"); ->>>>>>> creating new image entity opens image property options // Ambient light var elCopySkyboxURLToAmbientURL = document.getElementById("copy-skybox-url-to-ambient-url"); -<<<<<<< HEAD var elZoneAmbientLightModeInherit = document.getElementById("property-zone-ambient-light-mode-inherit"); var elZoneAmbientLightModeDisabled = document.getElementById("property-zone-ambient-light-mode-disabled"); var elZoneAmbientLightModeEnabled = document.getElementById("property-zone-ambient-light-mode-enabled"); -======= - var elZoneAmbientLightModeInherit = document.getElementById("property-zone-ambient-light-mode-inherit"); - var elZoneAmbientLightModeDisabled = document.getElementById("property-zone-ambient-light-mode-disabled"); - var elZoneAmbientLightModeEnabled = document.getElementById("property-zone-ambient-light-mode-enabled"); ->>>>>>> creating new image entity opens image property options var elZoneAmbientLightIntensity = document.getElementById("property-zone-key-ambient-intensity"); var elZoneAmbientLightURL = document.getElementById("property-zone-key-ambient-url"); @@ -803,11 +788,6 @@ function loaded() { } else { properties = data.selections[0].properties; -<<<<<<< HEAD - -======= - debugPrint("props: " + JSON.stringify(properties)); ->>>>>>> creating new image entity opens image property options if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null && editor !== null) { saveJSONUserData(true); } @@ -816,14 +796,11 @@ function loaded() { lastEntityID = '"' + properties.id + '"'; elID.value = properties.id; -<<<<<<< HEAD -======= - // image is not yet a separate entity type + // HTML workaround since image is not yet a separate entity type if (properties.type === "Model" && properties.modelURL === "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx") { properties.type = "Image"; } ->>>>>>> creating new image entity opens image property options // Create class name for css ruleset filtering elPropertiesList.className = properties.type + 'Menu'; @@ -1022,8 +999,9 @@ function loaded() { elWebSourceURL.value = properties.sourceUrl; elWebDPI.value = properties.dpi; } else if (properties.type === "Image") { - elImageURL.value = properties.imageURL; - //elImageURL.value = properties.sourceURL; + var imageLink = JSON.parse(properties.textures)["tex.picture"]; + debugPrint("image url is: " + imageLink); + elImageURL.value = imageLink; } else if (properties.type === "Text") { elTextText.value = properties.text; elTextLineHeight.value = properties.lineHeight.toFixed(4); @@ -1052,15 +1030,9 @@ function loaded() { } else if (properties.type === "Zone") { // Key light -<<<<<<< HEAD elZoneKeyLightModeInherit.checked = (properties.keyLightMode === 'inherit'); elZoneKeyLightModeDisabled.checked = (properties.keyLightMode === 'disabled'); elZoneKeyLightModeEnabled.checked = (properties.keyLightMode === 'enabled'); -======= - elZoneKeyLightModeInherit.checked = (properties.keyLightMode === 'inherit'); - elZoneKeyLightModeDisabled.checked = (properties.keyLightMode === 'disabled'); - elZoneKeyLightModeEnabled.checked = (properties.keyLightMode === 'enabled'); ->>>>>>> creating new image entity opens image property options elZoneKeyLightColor.style.backgroundColor = "rgb(" + properties.keyLight.color.red + "," + properties.keyLight.color.green + "," + properties.keyLight.color.blue + ")"; @@ -1072,7 +1044,6 @@ function loaded() { elZoneKeyLightDirectionY.value = properties.keyLight.direction.y.toFixed(2); // Skybox -<<<<<<< HEAD elZoneSkyboxModeInherit.checked = (properties.skyboxMode === 'inherit'); elZoneSkyboxModeDisabled.checked = (properties.skyboxMode === 'disabled'); elZoneSkyboxModeEnabled.checked = (properties.skyboxMode === 'enabled'); @@ -1081,30 +1052,14 @@ function loaded() { elZoneAmbientLightModeInherit.checked = (properties.ambientLightMode === 'inherit'); elZoneAmbientLightModeDisabled.checked = (properties.ambientLightMode === 'disabled'); elZoneAmbientLightModeEnabled.checked = (properties.ambientLightMode === 'enabled'); -======= - elZoneSkyboxModeInherit.checked = (properties.skyboxMode === 'inherit'); - elZoneSkyboxModeDisabled.checked = (properties.skyboxMode === 'disabled'); - elZoneSkyboxModeEnabled.checked = (properties.skyboxMode === 'enabled'); - - // Ambient light - elZoneAmbientLightModeInherit.checked = (properties.ambientLightMode === 'inherit'); - elZoneAmbientLightModeDisabled.checked = (properties.ambientLightMode === 'disabled'); - elZoneAmbientLightModeEnabled.checked = (properties.ambientLightMode === 'enabled'); ->>>>>>> creating new image entity opens image property options elZoneAmbientLightIntensity.value = properties.ambientLight.ambientIntensity.toFixed(2); elZoneAmbientLightURL.value = properties.ambientLight.ambientURL; // Haze -<<<<<<< HEAD elZoneHazeModeInherit.checked = (properties.hazeMode === 'inherit'); elZoneHazeModeDisabled.checked = (properties.hazeMode === 'disabled'); elZoneHazeModeEnabled.checked = (properties.hazeMode === 'enabled'); -======= - elZoneHazeModeInherit.checked = (properties.hazeMode === 'inherit'); - elZoneHazeModeDisabled.checked = (properties.hazeMode === 'disabled'); - elZoneHazeModeEnabled.checked = (properties.hazeMode === 'enabled'); ->>>>>>> creating new image entity opens image property options elZoneHazeRange.value = properties.haze.hazeRange.toFixed(0); elZoneHazeColor.style.backgroundColor = "rgb(" + @@ -1370,24 +1325,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', -<<<<<<< HEAD submit: false, // We don't want to have a submission button -======= ->>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-color-control2').attr('active', 'true'); }, onHide: function(colpick) { $('#property-color-control2').attr('active', 'false'); }, -<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); -======= - onSubmit: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).colpickHide(); ->>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); } })); @@ -1403,24 +1349,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', -<<<<<<< HEAD submit: false, // We don't want to have a submission button -======= ->>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-light-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-light-color').attr('active', 'false'); }, -<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); -======= - onSubmit: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).colpickHide(); ->>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); } })); @@ -1432,7 +1369,7 @@ function loaded() { elShape.addEventListener('change', createEmitTextPropertyUpdateFunction('shape')); - elImageURL.addEventListener('change', createImageURLUpdateFunction('imageURL')); + elImageURL.addEventListener('change', createImageURLUpdateFunction('textures')); elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl')); elWebDPI.addEventListener('change', createEmitNumberPropertyUpdateFunction('dpi', 0)); @@ -1469,24 +1406,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', -<<<<<<< HEAD submit: false, // We don't want to have a submission button -======= ->>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-text-text-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-text-text-color').attr('active', 'false'); }, -<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); -======= - onSubmit: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).colpickHide(); ->>>>>>> creating new image entity opens image property options $(el).attr('active', 'false'); emitColorPropertyUpdate('textColor', rgb.r, rgb.g, rgb.b); } @@ -1502,24 +1430,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', -<<<<<<< HEAD submit: false, // We don't want to have a submission button -======= ->>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-text-background-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-text-background-color').attr('active', 'false'); }, -<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); -======= - onSubmit: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).colpickHide(); ->>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('backgroundColor', rgb.r, rgb.g, rgb.b); } })); @@ -1536,24 +1455,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', -<<<<<<< HEAD submit: false, // We don't want to have a submission button -======= ->>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-zone-key-light-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-zone-key-light-color').attr('active', 'false'); }, -<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); -======= - onSubmit: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).colpickHide(); ->>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b, 'keyLight'); } })); @@ -1614,24 +1524,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', -<<<<<<< HEAD submit: false, // We don't want to have a submission button -======= ->>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-zone-haze-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-zone-haze-color').attr('active', 'false'); }, -<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); -======= - onSubmit: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).colpickHide(); ->>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('hazeColor', rgb.r, rgb.g, rgb.b, 'haze'); } })); @@ -1648,24 +1549,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', -<<<<<<< HEAD submit: false, // We don't want to have a submission button -======= ->>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-zone-haze-glare-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-zone-haze-glare-color').attr('active', 'false'); }, -<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); -======= - onSubmit: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).colpickHide(); ->>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('hazeGlareColor', rgb.r, rgb.g, rgb.b, 'haze'); } })); @@ -1699,24 +1591,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', -<<<<<<< HEAD submit: false, // We don't want to have a submission button -======= ->>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-zone-skybox-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-zone-skybox-color').attr('active', 'false'); }, -<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); -======= - onSubmit: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).colpickHide(); ->>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b, 'skybox'); } })); From d390e20139efdd55dea99abe44959bbf2e203a46 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Wed, 24 Jan 2018 11:31:47 -0800 Subject: [PATCH 082/569] removed extraneous commenting and image class --- .../resources/qml/hifi/tablet/EditTabView.qml | 1 - .../entities/src/EntityItemProperties.cpp | 24 --- libraries/entities/src/EntityItemProperties.h | 2 - libraries/entities/src/EntityPropertyFlags.h | 3 - libraries/entities/src/EntityTypes.cpp | 2 - libraries/entities/src/EntityTypes.h | 1 - libraries/entities/src/ImageEntityItem.cpp | 144 ------------------ libraries/entities/src/ImageEntityItem.h | 66 -------- scripts/system/edit.js | 9 +- scripts/system/html/js/entityProperties.js | 1 - 10 files changed, 1 insertion(+), 252 deletions(-) delete mode 100644 libraries/entities/src/ImageEntityItem.cpp delete mode 100644 libraries/entities/src/ImageEntityItem.h diff --git a/interface/resources/qml/hifi/tablet/EditTabView.qml b/interface/resources/qml/hifi/tablet/EditTabView.qml index 482469d355..ea5762d00a 100644 --- a/interface/resources/qml/hifi/tablet/EditTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditTabView.qml @@ -101,7 +101,6 @@ TabView { } } - // for image NewEntityButton { icon: "icons/create-icons/25-web-1-01.svg" text: "IMAGE" diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 7fcfc00ee8..e2a5ddf8b5 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -350,8 +350,6 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_AMBIENT_LIGHT_MODE, ambientLightMode); CHECK_PROPERTY_CHANGE(PROP_SKYBOX_MODE, skyboxMode); - //CHECK_PROPERTY_CHANGE(PROP_IMAGE_URL, imageURL); - CHECK_PROPERTY_CHANGE(PROP_SOURCE_URL, sourceUrl); CHECK_PROPERTY_CHANGE(PROP_VOXEL_VOLUME_SIZE, voxelVolumeSize); CHECK_PROPERTY_CHANGE(PROP_VOXEL_DATA, voxelData); @@ -592,13 +590,6 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SKYBOX_MODE, skyboxMode, getSkyboxModeAsString()); } - - - /*// Image only - if (_type == EntityTypes::Image) { - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IMAGE_URL, imageURL); - }*/ - // Web only if (_type == EntityTypes::Web) { COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOURCE_URL, sourceUrl); @@ -789,8 +780,6 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(ambientLightMode, AmbientLightMode); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(skyboxMode, SkyboxMode); - //COPY_PROPERTY_FROM_QSCRIPTVALUE(imageURL, QString, setImageURL); - COPY_PROPERTY_FROM_QSCRIPTVALUE(sourceUrl, QString, setSourceUrl); COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, glmVec3, setVoxelVolumeSize); COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelData, QByteArray, setVoxelData); @@ -951,8 +940,6 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(ambientLightMode); COPY_PROPERTY_IF_CHANGED(skyboxMode); - //COPY_PROPERTY_IF_CHANGED(imageURL); - COPY_PROPERTY_IF_CHANGED(sourceUrl); COPY_PROPERTY_IF_CHANGED(voxelVolumeSize); COPY_PROPERTY_IF_CHANGED(voxelData); @@ -1146,7 +1133,6 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_VOXEL_SURFACE_STYLE, VoxelSurfaceStyle, voxelSurfaceStyle, uint16_t); ADD_PROPERTY_TO_MAP(PROP_NAME, Name, name, QString); ADD_PROPERTY_TO_MAP(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString); - //ADD_PROPERTY_TO_MAP(PROP_IMAGE_URL, ImageURL, imageURL, QString); ADD_PROPERTY_TO_MAP(PROP_LINE_WIDTH, LineWidth, lineWidth, float); ADD_PROPERTY_TO_MAP(PROP_LINE_POINTS, LinePoints, linePoints, QVector); ADD_PROPERTY_TO_MAP(PROP_HREF, Href, href, QString); @@ -1383,10 +1369,6 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI()); } - /*if (properties.getType() == EntityTypes::Image) { - APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, properties.getImageURL()); - }*/ - if (properties.getType() == EntityTypes::Text) { APPEND_ENTITY_PROPERTY(PROP_TEXT, properties.getText()); APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, properties.getLineHeight()); @@ -1748,10 +1730,6 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DPI, uint16_t, setDPI); } - /*if (properties.getType() == EntityTypes::Image) { - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IMAGE_URL, QString, setImageURL); - }*/ - if (properties.getType() == EntityTypes::Text) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT, QString, setText); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_HEIGHT, float, setLineHeight); @@ -2069,8 +2047,6 @@ void EntityItemProperties::markAllChanged() { _skybox.markAllChanged(); _haze.markAllChanged(); - //_imageURLChanged = true; - _sourceUrlChanged = true; _voxelVolumeSizeChanged = true; _voxelDataChanged = true; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 5ca28a12c5..3e0770f386 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -67,7 +67,6 @@ class EntityItemProperties { friend class ParticleEffectEntityItem; // TODO: consider removing this friend relationship and use public methods friend class ZoneEntityItem; // TODO: consider removing this friend relationship and use public methods friend class WebEntityItem; // TODO: consider removing this friend relationship and use public methods - //friend class ImageEntityItem; friend class LineEntityItem; // TODO: consider removing this friend relationship and use public methods friend class PolyVoxEntityItem; // TODO: consider removing this friend relationship and use public methods friend class PolyLineEntityItem; // TODO: consider removing this friend relationship and use public methods @@ -195,7 +194,6 @@ public: DEFINE_PROPERTY_GROUP(Haze, haze, HazePropertyGroup); DEFINE_PROPERTY_GROUP(Animation, animation, AnimationPropertyGroup); DEFINE_PROPERTY_REF(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString, ""); - //DEFINE_PROPERTY_REF(PROP_IMAGE_URL, ImageURL, imageURL, QString, ""); DEFINE_PROPERTY(PROP_LINE_WIDTH, LineWidth, lineWidth, float, LineEntityItem::DEFAULT_LINE_WIDTH); DEFINE_PROPERTY_REF(LINE_POINTS, LinePoints, linePoints, QVector, QVector()); DEFINE_PROPERTY_REF(PROP_HREF, Href, href, QString, ""); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index ec8f4e08c8..90438ab01c 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -42,9 +42,6 @@ enum EntityPropertyList { PROP_ANIMATION_ALLOW_TRANSLATION, PROP_RELAY_PARENT_JOINTS, - /*// for image - PROP_IMAGE_URL,*/ - // these properties are supported by the EntityItem base class PROP_REGISTRATION_POINT, PROP_ANGULAR_VELOCITY, diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index 6bf5a176a7..cb17c28fd7 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -28,7 +28,6 @@ #include "LineEntityItem.h" #include "PolyVoxEntityItem.h" #include "PolyLineEntityItem.h" -//#include "ImageEntityItem.h" #include "ShapeEntityItem.h" QMap EntityTypes::_typeToNameMap; @@ -48,7 +47,6 @@ REGISTER_ENTITY_TYPE(Zone) REGISTER_ENTITY_TYPE(Line) REGISTER_ENTITY_TYPE(PolyVox) REGISTER_ENTITY_TYPE(PolyLine) -//REGISTER_ENTITY_TYPE(Image) REGISTER_ENTITY_TYPE(Shape) REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, ShapeEntityItem::boxFactory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, ShapeEntityItem::sphereFactory) diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 6fe4274820..316bf2b813 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -48,7 +48,6 @@ public: Line, PolyVox, PolyLine, - //Image, Shape, LAST = Shape } EntityType; diff --git a/libraries/entities/src/ImageEntityItem.cpp b/libraries/entities/src/ImageEntityItem.cpp deleted file mode 100644 index 9969a39ed0..0000000000 --- a/libraries/entities/src/ImageEntityItem.cpp +++ /dev/null @@ -1,144 +0,0 @@ -// -// ImageEntityItem.cpp -// libraries/entities/src -// -// Created by Elisa Lupin-Jimenez on 1/3/18. -// Copyright 2018 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -/* NOT IN USE - -#include - -#include -#include -#include -#include - -#include "EntitiesLogging.h" -#include "EntityItemProperties.h" -#include "EntityTree.h" -#include "EntityTreeElement.h" -#include "ResourceCache.h" -#include "ShapeEntityItem.h" -#include "ImageEntityItem.h" - -const QString ImageEntityItem::DEFAULT_IMAGE_URL(""); - -EntityItemPointer ImageEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - EntityItemPointer entity(new ImageEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); - entity->setProperties(properties); - return entity; -} - -ImageEntityItem::ImageEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { - //_type = EntityTypes::Image; -} - -EntityItemProperties ImageEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { - EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class - COPY_ENTITY_PROPERTY_TO_PROPERTIES(imageURL, getImageURL); - // Using "Quad" shape as defined in ShapeEntityItem.cpp - properties.setShape("Quad"); - return properties; -} - -bool ImageEntityItem::setProperties(const EntityItemProperties& properties) { - bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class - - SET_ENTITY_PROPERTY_FROM_PROPERTIES(imageURL, setImageURL); - - if (somethingChanged) { - bool wantDebug = false; - if (wantDebug) { - uint64_t now = usecTimestampNow(); - int elapsed = now - getLastEdited(); - qCDebug(entities) << "ImageEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); - } - setLastEdited(properties.getLastEdited()); - } - return somethingChanged; -} - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time -EntityPropertyFlags ImageEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { - EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); - requestedProperties += PROP_IMAGE_URL; - return requestedProperties; -} - -void ImageEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const { - - bool successPropertyFits = true; - // Using "Quad" shape as defined in ShapeEntityItem.cpp - APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, _imageURL); -} - -int ImageEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData, - bool& somethingChanged) { - - int bytesRead = 0; - const unsigned char* dataAt = data; - - READ_ENTITY_PROPERTY(PROP_IMAGE_URL, QString, setImageURL); - - return bytesRead; -} - -void ImageEntityItem::setUnscaledDimensions(const glm::vec3& value) { - const float MAX_FLAT_DIMENSION = 0.0001f; - if (value.y > MAX_FLAT_DIMENSION) { - // enforce flatness in Y - glm::vec3 newDimensions = value; - newDimensions.y = MAX_FLAT_DIMENSION; - EntityItem::setUnscaledDimensions(newDimensions); - } else { - EntityItem::setUnscaledDimensions(value); - } -} - -void ImageEntityItem::setImageURL(const QString& value) { - withWriteLock([&] { - if (_imageURL != value) { - auto newURL = QUrl::fromUserInput(value); - - if (newURL.isValid()) { - _imageURL = newURL.toDisplayString(); - } else { - qCDebug(entities) << "Clearing image entity source URL since" << value << "cannot be parsed to a valid URL."; - } - } - }); -} - -QString ImageEntityItem::getImageURL() const { - return resultWithReadLock([&] { - return _imageURL; - }); -} - -void ImageEntityItem::computeShapeInfo(ShapeInfo& info) { - // This will be called whenever DIRTY_SHAPE flag (set by dimension change, etc) - // is set. - - EntityItem::computeShapeInfo(info); -} - -// This value specifies how the shape should be treated by physics calculations. -ShapeType ImageEntityItem::getShapeType() const { - return _collisionShapeType; -} - -*/ diff --git a/libraries/entities/src/ImageEntityItem.h b/libraries/entities/src/ImageEntityItem.h deleted file mode 100644 index 276d21e6f0..0000000000 --- a/libraries/entities/src/ImageEntityItem.h +++ /dev/null @@ -1,66 +0,0 @@ -// -// ImageEntityItem.h -// libraries/entities/src -// -// Created by Elisa Lupin-Jimenez on 1/3/18. -// Copyright 2018 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -/* NOT IN USE - -#ifndef hifi_ImageEntityItem_h -#define hifi_ImageEntityItem_h - -#include "EntityItem.h" -#include "ShapeEntityItem.h" - -class ImageEntityItem : public EntityItem { -public: - static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - - ImageEntityItem(const EntityItemID& entityItemID); - - ALLOW_INSTANTIATION // This class can be instantiated - - // methods for getting/setting all properties of an entity - virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; - virtual bool setProperties(const EntityItemProperties& properties) override; - - EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; - - virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const override; - - int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData, - bool& somethingChanged) override; - - void setUnscaledDimensions(const glm::vec3& value) override; - - static const QString DEFAULT_IMAGE_URL; - virtual void setImageURL(const QString& value); - QString getImageURL() const; - - //virtual void computeShapeInfo(ShapeInfo& info) override; - virtual ShapeType getShapeType() const override; - -protected: - QString _imageURL; - - entity::Shape _shape{ entity::Shape::Quad }; - ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_BOX }; - -}; - -#endif // hifi_ImageEntityItem_h - -*/ diff --git a/scripts/system/edit.js b/scripts/system/edit.js index a5c236a6c9..20425c7ea6 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -251,9 +251,7 @@ var toolBar = (function () { // Align entity with Avatar orientation. properties.rotation = MyAvatar.orientation; - // added image here var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Web"]; - //var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Image", "Web"]; if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { @@ -541,21 +539,16 @@ var toolBar = (function () { }); }); - // for image button addButton("newImageButton", "web-01.svg", function () { createNewEntity({ type: "Model", - // make constant for this later dimensions: { x: 4.16, y: 0.02, z: 2.58 }, modelURL: "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx", - // will this work? - /*get textures() { - return JSON.stringify({ "tex.picture": this.imageURL }); - }*/ + // change to another default image textures: JSON.stringify({ "tex.picture": "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/dog.jpg" }) }); }); diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index e7526fa2e0..59b54b1020 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1000,7 +1000,6 @@ function loaded() { elWebDPI.value = properties.dpi; } else if (properties.type === "Image") { var imageLink = JSON.parse(properties.textures)["tex.picture"]; - debugPrint("image url is: " + imageLink); elImageURL.value = imageLink; } else if (properties.type === "Text") { elTextText.value = properties.text; From bd7204e6efd507be41c4a45d3ec4e21d61d44db2 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Wed, 24 Jan 2018 12:01:59 -0800 Subject: [PATCH 083/569] image entity now has grabbable and dynamic options --- scripts/system/edit.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 20425c7ea6..c298da38bc 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -547,6 +547,7 @@ var toolBar = (function () { y: 0.02, z: 2.58 }, + shapeType: "box", modelURL: "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx", // change to another default image textures: JSON.stringify({ "tex.picture": "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/dog.jpg" }) From 2ff47fa454b494ce64d2afa7e4396f0233e32ea7 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Thu, 25 Jan 2018 14:39:45 -0800 Subject: [PATCH 084/569] images auto-add to asset server, started asset browser add link --- interface/resources/qml/hifi/AssetServer.qml | 3 +- interface/src/Application.cpp | 42 ++++++++++++++++---- interface/src/Application.h | 1 + 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 37c3c2adab..6a4e703413 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -142,8 +142,9 @@ Windows.ScrollingWindow { }); } + // Elisa note - need to link this with specific add entity call function canAddToWorld(path) { - var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i]; + var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i]; if (selectedItems > 1) { return false; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 99bd4d5758..b53ba61e61 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -335,15 +335,17 @@ static const int MAX_CONCURRENT_RESOURCE_DOWNLOADS = 16; // we will never drop below the 'min' value static const int MIN_PROCESSING_THREAD_POOL_SIZE = 1; -static const QString SNAPSHOT_EXTENSION = ".jpg"; -static const QString SVO_EXTENSION = ".svo"; +static const QString SNAPSHOT_EXTENSION = ".jpg"; +static const QString JPG_EXTENSION = ".jpg"; +static const QString PNG_EXTENSION = ".png"; +static const QString SVO_EXTENSION = ".svo"; static const QString SVO_JSON_EXTENSION = ".svo.json"; static const QString JSON_GZ_EXTENSION = ".json.gz"; static const QString JSON_EXTENSION = ".json"; -static const QString JS_EXTENSION = ".js"; -static const QString FST_EXTENSION = ".fst"; -static const QString FBX_EXTENSION = ".fbx"; -static const QString OBJ_EXTENSION = ".obj"; +static const QString JS_EXTENSION = ".js"; +static const QString FST_EXTENSION = ".fst"; +static const QString FBX_EXTENSION = ".fbx"; +static const QString OBJ_EXTENSION = ".obj"; static const QString AVA_JSON_EXTENSION = ".ava.json"; static const QString WEB_VIEW_TAG = "noDownload=true"; static const QString ZIP_EXTENSION = ".zip"; @@ -382,7 +384,9 @@ const QHash Application::_acceptedExtensi { JS_EXTENSION, &Application::askToLoadScript }, { FST_EXTENSION, &Application::askToSetAvatarUrl }, { JSON_GZ_EXTENSION, &Application::askToReplaceDomainContent }, - { ZIP_EXTENSION, &Application::importFromZIP } + { ZIP_EXTENSION, &Application::importFromZIP }, + { JPG_EXTENSION, &Application::importImage }, + { PNG_EXTENSION, &Application::importImage } }; class DeadlockWatchdogThread : public QThread { @@ -2899,6 +2903,27 @@ bool Application::importFromZIP(const QString& filePath) { return true; } +bool Application::importImage(const QString& urlString) { + qCDebug(interfaceapp) << "dragged image"; + QString filepath(urlString); + filepath.remove("file:///"); + //<> + addAssetToWorld(filepath, "", false, false); + //emit uploadRequest(urlString); + /*auto upload = DependencyManager::get()->createUpload(urlString); + QObject::connect(upload, &AssetUpload::finished, this, [=](AssetUpload* upload, const QString& hash) mutable { + if (upload->getError() != AssetUpload::NoError) { + QString errorInfo = "Could not upload model to the Asset Server."; + qWarning(interfaceapp) << "Error downloading model: " + errorInfo; + //addAssetToWorldError(filenameFromPath(urlString), errorInfo); + } else { + //addAssetToWorldSetMapping(urlString, QString("/" + filenameFromPath(urlString)), hash); + } + upload->deleteLater(); + });*/ + return true; +} + // thread-safe void Application::onPresent(quint32 frameCount) { bool expected = false; @@ -6403,6 +6428,7 @@ void Application::addAssetToWorld(QString path, QString zipFile, bool isZip, boo // Automatically upload and add asset to world as an alternative manual process initiated by showAssetServerWidget(). QString mapping; QString filename = filenameFromPath(path); + qCDebug(interfaceapp) << "Filename is: " << filename; if (isZip || isBlocks) { QString assetName = zipFile.section("/", -1).remove(QRegExp("[.]zip(.*)$")); QString assetFolder = path.section("model_repo/", -1); @@ -6425,6 +6451,8 @@ void Application::addAssetToWorld(QString path, QString zipFile, bool isZip, boo } void Application::addAssetToWorldWithNewMapping(QString filePath, QString mapping, int copy) { + qCDebug(interfaceapp) << "mapping request is: " << mapping; + qCDebug(interfaceapp) << "file is located: " << filePath; auto request = DependencyManager::get()->createGetMappingRequest(mapping); QObject::connect(request, &GetMappingRequest::finished, this, [=](GetMappingRequest* request) mutable { diff --git a/interface/src/Application.h b/interface/src/Application.h index ddb8ce11e5..0e9bf95081 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -469,6 +469,7 @@ private: bool importJSONFromURL(const QString& urlString); bool importSVOFromURL(const QString& urlString); bool importFromZIP(const QString& filePath); + bool importImage(const QString& urlString); bool nearbyEntitiesAreReadyForPhysics(); int processOctreeStats(ReceivedMessage& message, SharedNodePointer sendingNode); From 62437dcc2265886c5d0fb7349c4625d4e68d18d7 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Thu, 25 Jan 2018 15:26:26 -0800 Subject: [PATCH 085/569] drag and drop works --- interface/src/Application.cpp | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b53ba61e61..b203487547 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2904,23 +2904,10 @@ bool Application::importFromZIP(const QString& filePath) { } bool Application::importImage(const QString& urlString) { - qCDebug(interfaceapp) << "dragged image"; + qCDebug(interfaceapp) << "An image file has been dropped in"; QString filepath(urlString); filepath.remove("file:///"); - //<> addAssetToWorld(filepath, "", false, false); - //emit uploadRequest(urlString); - /*auto upload = DependencyManager::get()->createUpload(urlString); - QObject::connect(upload, &AssetUpload::finished, this, [=](AssetUpload* upload, const QString& hash) mutable { - if (upload->getError() != AssetUpload::NoError) { - QString errorInfo = "Could not upload model to the Asset Server."; - qWarning(interfaceapp) << "Error downloading model: " + errorInfo; - //addAssetToWorldError(filenameFromPath(urlString), errorInfo); - } else { - //addAssetToWorldSetMapping(urlString, QString("/" + filenameFromPath(urlString)), hash); - } - upload->deleteLater(); - });*/ return true; } @@ -6428,7 +6415,6 @@ void Application::addAssetToWorld(QString path, QString zipFile, bool isZip, boo // Automatically upload and add asset to world as an alternative manual process initiated by showAssetServerWidget(). QString mapping; QString filename = filenameFromPath(path); - qCDebug(interfaceapp) << "Filename is: " << filename; if (isZip || isBlocks) { QString assetName = zipFile.section("/", -1).remove(QRegExp("[.]zip(.*)$")); QString assetFolder = path.section("model_repo/", -1); @@ -6451,8 +6437,6 @@ void Application::addAssetToWorld(QString path, QString zipFile, bool isZip, boo } void Application::addAssetToWorldWithNewMapping(QString filePath, QString mapping, int copy) { - qCDebug(interfaceapp) << "mapping request is: " << mapping; - qCDebug(interfaceapp) << "file is located: " << filePath; auto request = DependencyManager::get()->createGetMappingRequest(mapping); QObject::connect(request, &GetMappingRequest::finished, this, [=](GetMappingRequest* request) mutable { @@ -6519,7 +6503,8 @@ void Application::addAssetToWorldSetMapping(QString filePath, QString mapping, Q addAssetToWorldError(filenameFromPath(filePath), errorInfo); } else { // to prevent files that aren't models from being loaded into world automatically - if (filePath.endsWith(".obj") || filePath.endsWith(".fbx")) { + if (filePath.endsWith(OBJ_EXTENSION) || filePath.endsWith(FBX_EXTENSION) || + filePath.endsWith(JPG_EXTENSION) || filePath.endsWith(PNG_EXTENSION)) { addAssetToWorldAddEntity(filePath, mapping); } else { qCDebug(interfaceapp) << "Zipped contents are not supported entity files"; @@ -6536,8 +6521,17 @@ void Application::addAssetToWorldAddEntity(QString filePath, QString mapping) { EntityItemProperties properties; properties.setType(EntityTypes::Model); properties.setName(mapping.right(mapping.length() - 1)); - properties.setModelURL("atp:" + mapping); - properties.setShapeType(SHAPE_TYPE_SIMPLE_COMPOUND); + if (filePath.endsWith(PNG_EXTENSION) || filePath.endsWith(JPG_EXTENSION)) { + QJsonObject textures { + {"tex.picture", QString("atp:" + mapping) } + }; + properties.setModelURL("https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx"); + properties.setTextures(QJsonDocument(textures).toJson(QJsonDocument::Compact)); + properties.setShapeType(SHAPE_TYPE_BOX); + } else { + properties.setModelURL("atp:" + mapping); + properties.setShapeType(SHAPE_TYPE_SIMPLE_COMPOUND); + } properties.setCollisionless(true); // Temporarily set so that doesn't collide with avatar. properties.setVisible(false); // Temporarily set so that don't see at large unresized dimensions. glm::vec3 positionOffset = getMyAvatar()->getWorldOrientation() * (getMyAvatar()->getSensorToWorldScale() * glm::vec3(0.0f, 0.0f, -2.0f)); From 9f8e2017ceab378aec06a75c6f447317f68b1e32 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Fri, 26 Jan 2018 14:39:04 -0800 Subject: [PATCH 086/569] can add image directly from asset server --- interface/resources/qml/hifi/AssetServer.qml | 167 ++++++++++-------- .../qml/hifi/dialogs/TabletAssetServer.qml | 167 ++++++++++-------- .../entities/src/EntityScriptingInterface.cpp | 5 +- .../entities/src/EntityScriptingInterface.h | 2 +- 4 files changed, 183 insertions(+), 158 deletions(-) diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 6a4e703413..04fceec1f3 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -182,92 +182,103 @@ Windows.ScrollingWindow { return; } - var SHAPE_TYPE_NONE = 0; - var SHAPE_TYPE_SIMPLE_HULL = 1; - var SHAPE_TYPE_SIMPLE_COMPOUND = 2; - var SHAPE_TYPE_STATIC_MESH = 3; - var SHAPE_TYPE_BOX = 4; - var SHAPE_TYPE_SPHERE = 5; + if (defaultURL.endsWith(".jpg") || defaultURL.endsWith(".png")) { + var name = assetProxyModel.data(treeView.selection.currentIndex); + var modelURL = "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx"; + var textures = JSON.stringify({ "tex.picture": defaultURL}); + var shapeType = "box"; + var dynamic = false; + var position = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))); + var gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0); + Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, position, gravity); + } else { + var SHAPE_TYPE_NONE = 0; + var SHAPE_TYPE_SIMPLE_HULL = 1; + var SHAPE_TYPE_SIMPLE_COMPOUND = 2; + var SHAPE_TYPE_STATIC_MESH = 3; + var SHAPE_TYPE_BOX = 4; + var SHAPE_TYPE_SPHERE = 5; - var SHAPE_TYPES = []; - SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision"; - SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model"; - SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes"; - SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons"; - SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box"; - SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere"; + var SHAPE_TYPES = []; + SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision"; + SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model"; + SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes"; + SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons"; + SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box"; + SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere"; - var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH; - var DYNAMIC_DEFAULT = false; - var prompt = desktop.customInputDialog({ - textInput: { - label: "Model URL", - text: defaultURL - }, - comboBox: { - label: "Automatic Collisions", - index: SHAPE_TYPE_DEFAULT, - items: SHAPE_TYPES - }, - checkBox: { - label: "Dynamic", - checked: DYNAMIC_DEFAULT, - disableForItems: [ - SHAPE_TYPE_STATIC_MESH - ], - checkStateOnDisable: false, - warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic, and should not be used as floors" - } - }); - - prompt.selected.connect(function (jsonResult) { - if (jsonResult) { - var result = JSON.parse(jsonResult); - var url = result.textInput.trim(); - var shapeType; - switch (result.comboBox) { - case SHAPE_TYPE_SIMPLE_HULL: - shapeType = "simple-hull"; - break; - case SHAPE_TYPE_SIMPLE_COMPOUND: - shapeType = "simple-compound"; - break; - case SHAPE_TYPE_STATIC_MESH: - shapeType = "static-mesh"; - break; - case SHAPE_TYPE_BOX: - shapeType = "box"; - break; - case SHAPE_TYPE_SPHERE: - shapeType = "sphere"; - break; - default: - shapeType = "none"; + var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH; + var DYNAMIC_DEFAULT = false; + var prompt = desktop.customInputDialog({ + textInput: { + label: "Model URL", + text: defaultURL + }, + comboBox: { + label: "Automatic Collisions", + index: SHAPE_TYPE_DEFAULT, + items: SHAPE_TYPES + }, + checkBox: { + label: "Dynamic", + checked: DYNAMIC_DEFAULT, + disableForItems: [ + SHAPE_TYPE_STATIC_MESH + ], + checkStateOnDisable: false, + warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic, and should not be used as floors" } + }); - var dynamic = result.checkBox !== null ? result.checkBox : DYNAMIC_DEFAULT; - if (shapeType === "static-mesh" && dynamic) { - // The prompt should prevent this case - print("Error: model cannot be both static mesh and dynamic. This should never happen."); - } else if (url) { - var name = assetProxyModel.data(treeView.selection.currentIndex); - var addPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))); - var gravity; - if (dynamic) { - // Create a vector <0, -10, 0>. { x: 0, y: -10, z: 0 } won't work because Qt is dumb and this is a - // different scripting engine from QTScript. - gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 10); - } else { - gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0); + prompt.selected.connect(function (jsonResult) { + if (jsonResult) { + var result = JSON.parse(jsonResult); + var url = result.textInput.trim(); + var shapeType; + switch (result.comboBox) { + case SHAPE_TYPE_SIMPLE_HULL: + shapeType = "simple-hull"; + break; + case SHAPE_TYPE_SIMPLE_COMPOUND: + shapeType = "simple-compound"; + break; + case SHAPE_TYPE_STATIC_MESH: + shapeType = "static-mesh"; + break; + case SHAPE_TYPE_BOX: + shapeType = "box"; + break; + case SHAPE_TYPE_SPHERE: + shapeType = "sphere"; + break; + default: + shapeType = "none"; } - print("Asset browser - adding asset " + url + " (" + name + ") to world."); + var dynamic = result.checkBox !== null ? result.checkBox : DYNAMIC_DEFAULT; + if (shapeType === "static-mesh" && dynamic) { + // The prompt should prevent this case + print("Error: model cannot be both static mesh and dynamic. This should never happen."); + } else if (url) { + var name = assetProxyModel.data(treeView.selection.currentIndex); + var addPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))); + var gravity; + if (dynamic) { + // Create a vector <0, -10, 0>. { x: 0, y: -10, z: 0 } won't work because Qt is dumb and this is a + // different scripting engine from QTScript. + gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 10); + } else { + gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0); + } - // Entities.addEntity doesn't work from QML, so we use this. - Entities.addModelEntity(name, url, shapeType, dynamic, addPosition, gravity); + print("Asset browser - adding asset " + url + " (" + name + ") to world."); + + // Entities.addEntity doesn't work from QML, so we use this. + Entities.addModelEntity(name, url, "", shapeType, dynamic, addPosition, gravity); + } } - } - }); + }); + } } function copyURLToClipboard(index) { diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index a02496a252..ef4fb8d177 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -182,92 +182,103 @@ Rectangle { return; } - var SHAPE_TYPE_NONE = 0; - var SHAPE_TYPE_SIMPLE_HULL = 1; - var SHAPE_TYPE_SIMPLE_COMPOUND = 2; - var SHAPE_TYPE_STATIC_MESH = 3; - var SHAPE_TYPE_BOX = 4; - var SHAPE_TYPE_SPHERE = 5; + if (defaultURL.endsWith(".jpg") || defaultURL.endsWith(".png")) { + var name = assetProxyModel.data(treeView.selection.currentIndex); + var modelURL = "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx"; + var textures = JSON.stringify({ "tex.picture": defaultURL}); + var shapeType = "box"; + var dynamic = false; + var position = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))); + var gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0); + Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, position, gravity); + } else { + var SHAPE_TYPE_NONE = 0; + var SHAPE_TYPE_SIMPLE_HULL = 1; + var SHAPE_TYPE_SIMPLE_COMPOUND = 2; + var SHAPE_TYPE_STATIC_MESH = 3; + var SHAPE_TYPE_BOX = 4; + var SHAPE_TYPE_SPHERE = 5; - var SHAPE_TYPES = []; - SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision"; - SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model"; - SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes"; - SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons"; - SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box"; - SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere"; + var SHAPE_TYPES = []; + SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision"; + SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model"; + SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes"; + SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons"; + SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box"; + SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere"; - var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH; - var DYNAMIC_DEFAULT = false; - var prompt = tabletRoot.customInputDialog({ - textInput: { - label: "Model URL", - text: defaultURL - }, - comboBox: { - label: "Automatic Collisions", - index: SHAPE_TYPE_DEFAULT, - items: SHAPE_TYPES - }, - checkBox: { - label: "Dynamic", - checked: DYNAMIC_DEFAULT, - disableForItems: [ - SHAPE_TYPE_STATIC_MESH - ], - checkStateOnDisable: false, - warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic, and should not be used as floors" - } - }); - - prompt.selected.connect(function (jsonResult) { - if (jsonResult) { - var result = JSON.parse(jsonResult); - var url = result.textInput.trim(); - var shapeType; - switch (result.comboBox) { - case SHAPE_TYPE_SIMPLE_HULL: - shapeType = "simple-hull"; - break; - case SHAPE_TYPE_SIMPLE_COMPOUND: - shapeType = "simple-compound"; - break; - case SHAPE_TYPE_STATIC_MESH: - shapeType = "static-mesh"; - break; - case SHAPE_TYPE_BOX: - shapeType = "box"; - break; - case SHAPE_TYPE_SPHERE: - shapeType = "sphere"; - break; - default: - shapeType = "none"; + var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH; + var DYNAMIC_DEFAULT = false; + var prompt = tabletRoot.customInputDialog({ + textInput: { + label: "Model URL", + text: defaultURL + }, + comboBox: { + label: "Automatic Collisions", + index: SHAPE_TYPE_DEFAULT, + items: SHAPE_TYPES + }, + checkBox: { + label: "Dynamic", + checked: DYNAMIC_DEFAULT, + disableForItems: [ + SHAPE_TYPE_STATIC_MESH + ], + checkStateOnDisable: false, + warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic, and should not be used as floors" } + }); - var dynamic = result.checkBox !== null ? result.checkBox : DYNAMIC_DEFAULT; - if (shapeType === "static-mesh" && dynamic) { - // The prompt should prevent this case - print("Error: model cannot be both static mesh and dynamic. This should never happen."); - } else if (url) { - var name = assetProxyModel.data(treeView.selection.currentIndex); - var addPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))); - var gravity; - if (dynamic) { - // Create a vector <0, -10, 0>. { x: 0, y: -10, z: 0 } won't work because Qt is dumb and this is a - // different scripting engine from QTScript. - gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 10); - } else { - gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0); + prompt.selected.connect(function (jsonResult) { + if (jsonResult) { + var result = JSON.parse(jsonResult); + var url = result.textInput.trim(); + var shapeType; + switch (result.comboBox) { + case SHAPE_TYPE_SIMPLE_HULL: + shapeType = "simple-hull"; + break; + case SHAPE_TYPE_SIMPLE_COMPOUND: + shapeType = "simple-compound"; + break; + case SHAPE_TYPE_STATIC_MESH: + shapeType = "static-mesh"; + break; + case SHAPE_TYPE_BOX: + shapeType = "box"; + break; + case SHAPE_TYPE_SPHERE: + shapeType = "sphere"; + break; + default: + shapeType = "none"; } - print("Asset browser - adding asset " + url + " (" + name + ") to world."); + var dynamic = result.checkBox !== null ? result.checkBox : DYNAMIC_DEFAULT; + if (shapeType === "static-mesh" && dynamic) { + // The prompt should prevent this case + print("Error: model cannot be both static mesh and dynamic. This should never happen."); + } else if (url) { + var name = assetProxyModel.data(treeView.selection.currentIndex); + var addPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))); + var gravity; + if (dynamic) { + // Create a vector <0, -10, 0>. { x: 0, y: -10, z: 0 } won't work because Qt is dumb and this is a + // different scripting engine from QTScript. + gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 10); + } else { + gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0); + } - // Entities.addEntity doesn't work from QML, so we use this. - Entities.addModelEntity(name, url, shapeType, dynamic, addPosition, gravity); + print("Asset browser - adding asset " + url + " (" + name + ") to world."); + + // Entities.addEntity doesn't work from QML, so we use this. + Entities.addModelEntity(name, url, "", shapeType, dynamic, addPosition, gravity); + } } - } - }); + }); + } } function copyURLToClipboard(index) { diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 4342f0e683..06a141a95e 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -299,7 +299,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties } } -QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QString& modelUrl, const QString& shapeType, +QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QString& modelUrl, const QString& textures, const QString& shapeType, bool dynamic, const glm::vec3& position, const glm::vec3& gravity) { _activityTracking.addedEntityCount++; @@ -311,6 +311,9 @@ QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QStrin properties.setDynamic(dynamic); properties.setPosition(position); properties.setGravity(gravity); + if (!textures.isEmpty()) { + properties.setTextures(textures); + } return addEntity(properties); } diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index d1b321dbca..919d0e3489 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -158,7 +158,7 @@ public slots: /// temporary method until addEntity can be used from QJSEngine /// Deliberately not adding jsdoc, only used internally. - Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const QString& shapeType, bool dynamic, + Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const QString& textures, const QString& shapeType, bool dynamic, const glm::vec3& position, const glm::vec3& gravity); /**jsdoc From cc4bafb46fdcd4b599e2d24d2fd09393bec555dd Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Fri, 26 Jan 2018 15:40:22 -0800 Subject: [PATCH 087/569] image entities shown as images in entity list --- interface/resources/qml/hifi/AssetServer.qml | 1 - scripts/system/html/js/entityList.js | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 04fceec1f3..b173b3826d 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -142,7 +142,6 @@ Windows.ScrollingWindow { }); } - // Elisa note - need to link this with specific add entity call function canAddToWorld(path) { var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i]; diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 7b25e66c67..dde91dc694 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -156,6 +156,10 @@ function loaded() { var urlParts = url.split('/'); var filename = urlParts[urlParts.length - 1]; + if (url === "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx") { + type = "Image"; + } + if (entities[id] === undefined) { entityList.add([{ id: id, name: name, type: type, url: filename, locked: locked, visible: visible, From f71d9e4d6a4a994cb26187476ac78e86c6f5b998 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Tue, 30 Jan 2018 17:49:30 -0800 Subject: [PATCH 088/569] snapshot referenced locally, won't work on OS --- interface/resources/qml/hifi/AssetServer.qml | 2 +- .../qml/hifi/dialogs/TabletAssetServer.qml | 2 +- interface/resources/snapshot/snapshot.fbx | Bin 0 -> 51420 bytes interface/src/Application.cpp | 2 +- scripts/system/edit.js | 4 ++-- scripts/system/html/js/entityList.js | 4 +++- scripts/system/html/js/entityProperties.js | 3 ++- 7 files changed, 10 insertions(+), 7 deletions(-) create mode 100644 interface/resources/snapshot/snapshot.fbx diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index b173b3826d..42b8118115 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -183,7 +183,7 @@ Windows.ScrollingWindow { if (defaultURL.endsWith(".jpg") || defaultURL.endsWith(".png")) { var name = assetProxyModel.data(treeView.selection.currentIndex); - var modelURL = "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx"; + var modelURL = "qrc:///snapshot/snapshot.fbx"; var textures = JSON.stringify({ "tex.picture": defaultURL}); var shapeType = "box"; var dynamic = false; diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index ef4fb8d177..315def6f14 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -184,7 +184,7 @@ Rectangle { if (defaultURL.endsWith(".jpg") || defaultURL.endsWith(".png")) { var name = assetProxyModel.data(treeView.selection.currentIndex); - var modelURL = "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx"; + var modelURL = "qrc:///snapshot/snapshot.fbx"; var textures = JSON.stringify({ "tex.picture": defaultURL}); var shapeType = "box"; var dynamic = false; diff --git a/interface/resources/snapshot/snapshot.fbx b/interface/resources/snapshot/snapshot.fbx new file mode 100644 index 0000000000000000000000000000000000000000..641a7f82ebd0a67a9364c21867bad4a02d246048 GIT binary patch literal 51420 zcmeEvcUV-%_xHtyCN{(p3xZhCAR;1F*hN7Q0YOlt2xM7yVPy+!p_e2!EI<^MCXgsX zj1-kBH7c+nv?vM!3etl}M|ydmx%UDWVS$+M@1OU{JU%{`d++(onKNh3Idf*_t`@=B ziohg@s2?&CIYglon9d?1&<5!5O%P^q>W7SukO)>J=3yr`iO!-h=%*nF;)5V) zAp}7y#~&M#nBbvi^Lz+$!o2a7`XQsSX8=3^^KAKHI+5vY$EGmoC!Fm_nxNJn05n|}8)MfQR}rT570*>{{Dg60ACAqZMxKqS#gnshS5U=0L88dQcQfeQYF_$Hn#h9Kx9 zi^NnTunAk|A%a*2LC^xw1(TjCp-+Lx#)WMKLD16qV?6{xi*-nB0>C=M>+K&f4;G%F zu&E>iKpup-&2PZ5JVPR~;RlZ~4;CHdux%Jj_a1Q{(FG`Xb3`+uY582Ww5rt7gV3vbdn8YtZ0;;|Mf*_!3e0p$i z=`tzS6gq(l|DnpD*)ceDD})We%b^8G6SNTupy^{2ZU^n`s1zbR!VF1tD+Y5?i{O^P z|E~WCG!hTobr{^ww7nYz*FeBG@&HFo01y175CmbzjP622 z7CHzh7$f7dP$RHOz|2@^=$wE>Id%gh2f*)MDapN3l2QtCdqwu{RfPZT*}HddAPS9k zF)$tD(CQQ_X$C0(t->fv{z9aSw&ejg$P#PCk!9 z>ml5%=LJ1O`)26n3Kabag1(6t`akPtIetVO#}L%DlTgRH^$YtMjE6at3;c*-1uC`( z^uMN+{z?dfcE)|%oVYFjS>_)Pzr8O$)RBt52U@;#lD7j=16n4rS4v{9l!z3nYl?gK z?wz+70soht<9jeX(6Suy6$T_Wn?ko{$)Yt(vi!jIurM@}Eaip8h|G5(GG7F&q1{0z z3JW%j6gnFoqMCFz$(n?ib4}n7pQ228D+j1zSxgU+70Qs62T zmLR->&g9cG0M2iMa@KlTC^e>DRR)7;HFLc{&X1Xz3nKq6v8!dB4ZY2Hc5|; zj*d!yg)~|QAqOV?GxAwEa7H2I5r`F_v*;AI0g*r@sS}7GegXY%#o$;{N$}+{4$Ttn zZZ$;H+VNZzMFl|tdOps`KSvY#8iIfuMlA?X&x15e3W?5EWl$MR)L>E>Onr?*20)#W z+khFAqvrHv#AMl#h-``j zDGY@}O*)9cfUD7ASdk2XHz(0KvY*h}6&7i3>2{e*EfsOR=jGZkH z`j|&~41|i7C1arwD|+cfWWG?9!J)HZGjVSj1c3lc2n3vCFKa_C4}msAa9iL2cjC$g z13$@7?Ouks*BujbF<@|*M3OENfdUO?Oa!B_P-)8$S)!dG2$T%&*n@Nm&45iH+Rotq z#u_mp&z(<@Xm(VfhOp(gbV5d^25>-Vnbhz26^NlghxCLg z0HYB$3e#AKpg>TX@KZ0Ypl~=ujR1%qg9Qs4Rv|+YY!3k^20$JMd0HFI-%F=rowP#% zfI7~K)s7!W7hj1qQyt+8#2mosGD$4<7xD)|7Aq0;M~4isH~I`Jb>d>0Rz{*UiU=RD zx3XhXhy#nmLdm~H+5$$=Nft?m0Y>^EDu>C7PI}cOmjGhZlPr=xi9iLOVP<-u z!!9U2IvWP`2$2Y=RRri6okd|g&)E8Qd^`$+22dc3I0$=6eK4)$#NW+1j zL?};Dtk^biwKMu7wu*uvA_6oDg2M&4?jt0MwGDfA2#U)0CZjuGMPnU8_g&}2pX7)*mxFV#nqG405+c#Ds5I7HtmpF9JT2K-W8=s zy&(t|0R8L|lzg{s(GNFk@lR0zOlScB<|x3EaQTE^yas6orXK)`fjx&rasdGfWlsHY z2!+9DBY;t7Flhv~Cf$nU1P`arLNuU_Zj&it7r-RJ(eR8t(2h?-Dbd&r7)Ma&2_}Kg z0xoT~?jM?kel`tIT02Pt#_9(~U610xm{tAUW}%<0{#2A6wST~Xf%b#?4GC1v%;JNf zE|eT&|HjC5nN}pk{!PyTNjm}Zw!4Fb>9460D97G?dQ8F~=24))?1U5;X!sewJ zp(q8$PN`E!R4ZMwA%)}!+b!(h743Mw@yf-GVk0IL`Zqp3G0V*rPuvf~@1 zxzl49e~vMY+f-8_q8L~!#UK)>+9bL)YH=}hJk-tYxVGTEA@iV05K4@ZHxi^FgNv8+ zUZP}}?mfvO!P5@JQG)tpxst2Isw7S(2ch3V4x0h;_8c}zl}fSGBhX1KULxs2$9 zrxow<=TS6_%L3L7AR;*m9$auyI&?$=Uoo!9bDIe)5t%fmr$Lx47}G`(&@nw}K;#-ff5LO90bso6k_JWyhBL{;HH8mvL+fhc|^9e;unVuAz=A)F!M zr8B)KDY|+AGISH9v+BKyDAL>m2*Uy#oQr(Kk0rdfs?e`qj{yd6-8NCxNnUmggXGNH~9qo9& zZBrB<)1Nab{()_X1TYg{Gzn!uAd?OjA0`o0Q6$XhKglBPQ_~?>($wfCH0sL zuptNCnFUK=ni=)^$<>2kT9?VTVT`wY79;W;rN`JZOh!^oSN$N}>fk|M@*hU2F&cc5 zMN%W#v2Aq8@N}6MrL-6#ww;I-1c^j-29d);R)To7mW@(j6JLPpAl({G`Oetfr7FoN z0BUsLyaxc76fe$*K~d>MF(xi!kJ%*{wz!>0qtPV`5H@4UCZ}(CaPjVg zLX&&#-*{IP3o~x8SUM!O4Fi$>G+L925@X``lPuCfB9TMmP~mvv5Qj`g^ElJQlOrfS zI_?nL4R#q3Xm(Tz-5Opu-egT67`Go^1ZwEtpQ$IcA=<)gc4 z57Ju9K%3055wEsxm7Imrm=~qlY?LOvDE()qBslTp_Qh2I@BM?dI45l`PMq%sHsnZ%>poxE>7?}C!I0lnViAw9G;PkCzEb7lJR8n zF-nG+ereKeNK6W{$&5!p@d2g6m?_kc&a7j28AKZ}%Sb5QzfiFoyg}!tVdG0un2SGU zkj}%%Wr9VXQJZSb5!0`GicYHnW%Vj_+}<5o*c@_A-#PZiMGH3 z8#>#f5HS(Mms&bM6dpZW0J7!d@MG5COR-x~Z1i{v$mNe?qk1qGXpBOQFMsZnM|1>} zdC;NI)d(~@eFEJY&Ck#1ks!!nW{f#G*OZwt=HypLrokXM{j;_lQkW_a491+=a$#nS zIklw##lUQ0Li@#l&4l+@%qjR;t%x*-H_V(`WI8L%oEmj!8W=>l|EN(-v%<`&QF{+e zmCa;s=F}(-3L`f$oz-K)o3O3m{NJ4VDP7LYkLM@9)*H$a^RSh+$r4~%WE zo_4W}hEky}7+9#Woys%P-Bv>S0J8!N7K^5vK7p)C4f;B|K@gm0J4OP_(hej&5|aqd zaM}>;NCq4`I|h^Ic88tHNTnzW$akWLIL7yzBm4V6zGT`HO5og#F^Ym&jaFxZ9fs2} z4<;j*rxIxEsvTk=`v|8=XErD!9t(9TVD(g+NEN|4tLg56x<1=iJU$s3zfi(WiiANn z2w;zUswp?-g!&VTif%Foa}$6%=A%CUdkW@s9_lX~(pzY{3FNylX}izi3I`2fU4oH|$2-NdvIu4Vi1Dj#uP*M=3OilEH zURSqtGT<=D1R_b#hC#QE03t`rq-@=C6{Pv-LJsIxkh-MMt>G*oJo_9wsto9n{{TBM zt}057*$Sq^p|UC31ZUoPcs~_Hxeg&B2Z^}DGzyDFaX|On0eEDy7F;D5+Pu#}Ku|Ht z1ZFX73=ea>jMp}VcD(38gda5N4P-FsEZDT{AV)lM$BuYlE(ixFi4-&sg_{#M52tdY zMj?|qteJ$v`@l)tRBlkXIdOC75F#6NcmT@{8yGtRbNsB_wDK9#wy75v4k7ZvETK<8 z5}oJ376fIY7--@EkgMhb@xOf;5B*tcq;bEBKt;f$$ddw*z0~-pR0`i_S?j6{#vQQnV8ngXiob<4h z9fJ{mIZjz|M&3VJF^pG2l23;#37gSTP@qD*1-#7rR0Ng`v|vy^--#Onep zk6jo8CIsE~GJtwl(52|=DsZ})C?(i7GS%tAd$=r}z?pp%<}p~oLdztd>!yKp04D2+ zl7QvolPsPa2_B<_n2Zg)<&gLG1Hg9=N`;xjqt%HJz=p-ybJFd2sUrx>n0;q@OcD*a zdONB!!UT^cQWi>z+4ZB!VFK@gashw0W_<7IGz;esQ3T8iEsCH+u(PAkd9Jzx;eSAh z(FGvTrzZ^!NXT~urbaeE4XltO+nY6!He&anqidNaGWsydZ*0*2g}M0MM-f!i(*u<8aQ_P-Jy1y0OaR%A#rktVoG#jb zhazIKbf^}M@%pdUMh|EqGC;pt0!BItkMjMm(CxJlg~AM9aQ+ev^`{#@K&9@XR2Ve` zR1`Z9u}m}OP>VB{!qw%yNx+a|MPe+_Mqc>L5~iSZAhexo2|?GYTROo2(U^1PlWd^7 z_9R(=3Xp?@JSWwlLGdURx|9N(6ncDcMzSW944tI_q3IFWHn2jDnE(JV{v9Sz*Jd4r zL(J6XAdyI7v3Qv(KxB*(p>`5XKjF_VAe)3{WYP~MMGX~v(uK{in*(LRWJ(|vbmsvO z>r5YeSau9)BN~N(I`yaoX9GJDX%73>AxIUa$7nQ2a_DpD^cgmkF;OZ^R!5t`dtd)= z=2qC;gnV`q|442)pm zj-1=~FCge=lpdoD;LA*NP=+rl#n}^7{K!`uSfTpJr!zp1vTV}F7PM>$2LFTiT24bDX$)0?m#E5m3 zM8rpUq3&n5(c;Wz6zhw=-yvdaitzXQf;fb~_pPSqRFqF3ZAHVkvEwu+n@CV7GSU@qU4y(T$p|Py!*}J`JVE40=F6`v(*rygrj)65W8GXNa`n z%RZnW#I@hVwI9MCMo~~n09gVy5Vy!{Icstb*J~ySUW-|iAu>@2%&wZ*4&?)n-GP$L z?NB~Y9qChu&@iflIZ}@{f8vlnY+?{rE9w>r@4I$H#Suk70}7xz7=)P*<&R;c&WJJd zq5OI8-u}cWRZaRF+zm#TT|LRnNHImOm{8AYD0*F(l4XBEY5eEp?J{OODxd|9C9zLia^ZvY(cHm7O9!3mx ztpIruvgQlk{ywd<9`;Aj60r-gKYE{Yq!F)aG7Kd1FoRivh-zG$P_p*P8N#C!L{Q*7^5U`3R9h)~DF^}} zIGqPhNvhpWJju&8-B-(<{n*os5ps&z z$22b}3v-S)`(fWbI?E;h$_Vf)e7-BR^F{NP4`OcNq0V}nBD=hQ6U=pf{hai% z$f-j~!Lh)=-N;?Jy(uuY&GFA>vaI0f5BCrM{gm>leO1jaUx$HTJ)*5E(>6p_5&HZ0 z-|wO_mSn6Cli1-bqx-3Pfd6&ur~LZiJ1JSica%EP2PL>T=e9Ruk{YO&rs%XMd=2G!>@4f-gll3_morU zwHS2#v;M$&m;QH6=W3PQan1^#qO%8obuVuc@c;A>x5B?|clQvzp?aBWtb&_qdg-mr zgI3BPw>m%Q4RKR$JJ2$^&>uI{+MIS>Yt%t0STL&pX-&y#g1v3!L#GeVSw{>bYoZ$j zyMJh0=})8|40CsBdd|91DE_3I!i{^99pt&2UT}DAQp5HukvJs{66fg1{49Zu{CVbx z8g1f(pjCPds zqPK1ww~Z$_N2LinH$1vjwGw~e$rDz20l|Jt%g?v2e0{jfda<~K2kxS7RmQ>}RDSlB zynXR`^;bfKtN4+GjQVAz8G7RPt6#38$Z$^gxk>zTJ98vN_OE9dp0Qg>*uq&N&`5d@4SSnqR)1B6>~p)y(+mwYJ={k zB##r^K&uLkGph`SI&~Beibjr{jrb`akB{zhBjw4R*&uzp|J#u5A@eOF?x)pdjz;9U ziPhcxwB}%sTl${sxn~)#ckk%3w?OUyRREF-51Ih2!7}r+U_rGXtcCEQ@J<{Xr zsoeWaD>khs$Fwc2_o+tMP-C2A#z2+aSFEjuCoGXR0|&ASUZp=wg;xcURZ!Uc4xw@Z z?~H%+kpu|7n;>2Qf6E58bm(2M8DTA&kq6cudEDCmhy8# zV2#x%OcIf;fpVV02SNM@w-e?v8rJTFTj^TQRaXDL;@XeG@?K{TpJ%O7luur~Cp}*% zD_1A(fY+TX%MI<4-t*JR_I5i*<{$b{6LnVWV$e~ERfFf>pGk9U5^obRHD@^2t?PT2 zmzS5iSjwo=Ii|K*oRK+nOIUA9vXlLN4P+>rPdzD}8@*tEbtA#+pj)eyH-2$M2gwoa(qj9 zDzk3I9<7{MhqWdR3IhwPYm6-V+h0nJTu>Iw?^)jVg5UVrpw9ExgTKA@ud#cwsGr636JGmcpMw0EvpYYSWQ=&n zzg-slL2rfBpv|XUVsg%6eG2j=7enLXk1f5;sU&86Q%~#qy7QCaV{fHHLQj$%1qaV2 zP|uc}zrt_O(=RHvsVm*yUjO5O@%SyiO}sHswtLsQQ+E?BZ15ks+1Zq#(T0{aH42x#pFbVtTN`YhH_2B% zBTwkna;#FGG=vq>8>6(76!KT}0m!o=3JG-fq@fPMt_pqh54CbF5n%-!4&|+tAn^^G z*AomxITJ|;*|m8PH1DLL)d*@w&cSY+c2n8r#POBq+g6E+YKkvdv-yz7+8>U5_&Y&h zNcG&dZw|T(td3u7bN5Z(Zz>zEWF5Nm$JU!`f4oV3lAykL%XyPO=8rz!#TKCc_}Hbf zK+b+`aoVQVw+E~X0|S?BIbqO?7hbmIMo5F$rkODo7nX*8 zy#v?0=crrrtBbw6Vw4=StM~RFu}khZXkF)h@?u^~RE{{K4%a3id{HA~ND}XM`4T^K zdDsv$PNL&a{?pn%uhRI-nUzO{?y3p%Hdc+;o>;w{DS!&7duRAk_ZEhIo+p9vRh_1FLADdqS+xX)?e zw|rp_wNHM%?Hg~e$Ygt%dhWCsxH9zW?B>3M_MML(&BYev2s+SghB$wa9(ilT* z+_Glep0bN6MZd1V8s-B%;_rYE&f(!>5(=8x<78+-@r&U#8lnAbc_(^bx2 z*ZA?u&qFouq>@H|Zzl?$&CBp84g1KbY<&B$XXCK-$e9uC!FM&EdgqM{jo1|`{ZQ8U zx9ae|Oi$=QrAKoDI>MTPNk3^gIW(w*ot;GCNrScDtv>(4_tqkcno4-;aXo#Nl+@I` zc?M-euK`=QPcY>ZM!9O%KNhp~pm?4fa z%%XrlL6irg6(>HT)py^0w{8CXgrDxM@Hc~&oP%GVD9V%x2`q#q05`cC)3WgxVg@|;Bp~^33CZ#98VB<9aEJaZ;D;u*M5(5Kg zad3uzoJc1i|gMH0^5Q7J`l3thG+#CPw;X{EQYH|YH^A-0X|7Bwowta_*c8fMMj*R zk&ax)`VqePy;0QocNY}!j>?Xk8Hff1q_dQkm^2_LpegL&x??cV#&hHMN?p z`vdX_Dr%xX+u3g4cu+-+fpGjqS~pqQP%CPza+&eJ(7NAIlz*mm{U{Y?(N&+sVo*6? zt`0hw*P^SO9l|vBSUS7_b_&*rsVfJd^=OkrRXDt;kIYK_{wI|OC^e?3m@jX_9DjB| z2H5mElnT?-iI4lu)YNVENK?_Z3E+q(K8`lASDojkf=NZkeqxY(?o4bj&psR39_XXV zBl{Qh?p#OqPaI@qgHBsK7Q1LKk=_9IQbGn`L7YZl@3Es0!G{hdSjdqPh;QswxD%H$ zCt5H*%z@LAL?3|Z_tHtnMu2024~S@CXg_cLSY4h(Z0%QN(YRM&RIKP+@oVI^qr=3z!=^f zI^Zw7H%k8xM!UQmC|gzjLP@`1w2wiEI{&XnyOs0*-DodxMMgW=l+2HAP1lxd`34M9 z2u@b8Nls(#9O4_>ybhjLIV0m4L^DWk13t`$eBv11tPQ>*--o^hhV;0v{}Y0sT`mYY zNR51rQo&0=AVV@;rG_X0*e|e9hh#+|z$a*7LnL8IB@k`*oUyZp_~5ED(U(sED>nYS zr(pBAB>2r?mL!?rLZQ(}Od5eM37)egk5H^_MARu(B=CJc$&;EACygWx=ma~KBn$j+ z!(i_rThb&yt8W;s2<$yxsZRo*jdUOZKsxvkBuKu@ME$$#*uej6>0i?o>2vh!=0JKg zoiYI^o+t?>rk&+90I=wBC>3V2=!~ZUrsg)VMactEClHE^tJ8r@5?GUi3^tGTEbtwN zfbWn%(s`2=OWtiRigg@)Yl>oBMIV4P*d)c8z4~;NRCq$g!0!}~^&rYEM$Nu;M_4tQ zq-LNy{;ir#QzsRz4CusERTIH53M=X~2$CKXoi)jaCcFP*b!$aQ{+YUodLR}3Z`3XJ z9HMT((l3O^y|z=eAc$O`8EgYQy#u!CrU%k{AQy&MFt9%uJi&YN5Y#?TgntJlmYe@*(Z;CEVJ7Fev21PLPWQ>XeQ@>Wolog(~`(+uVb27@{#MLpmpST+nh5mi-KY9c#z z2t*1U+}s6I>F6kG(ve?k(Nt3wb)wnrp-`+856Q|MmXecKl|Cr1E+utX?x4Dqtb(kX z>LK|aAh$m(}s z&_*kua}eKnna9V+H*elN@ZbD-pv+$|A1(_PELgB`!GZ;g;IbH$CF2E<%%4Ah;erJV z7cW}42=io)W%PGQU@^1>Qkl;u0L>HNn=imOngwY=e4oc2Uro6Kt^E8QNMOpH(Q;_% zd_HKNzcmu!lPxA?&oArL^!C+`AZmV{)V7gVuf+%vh+MJx07;yqOM8) zIr==}c3kG_cK(k-kO}r62zrgSV;a|U=mOHj@wfaFg+w3dwr2U63NLE%&?Vvosla-m?9U|^)Iv0e}9^^^; z|Fa942hB^Iq@p)a5nTRD zvEy(mD=k6J$jM{H1#a)4F!^0o3pH`0(@3ILQ6YOM!Pj_1KzikHRa*5M{ge_WDOZum zH7e;%x}kByDZkx!T}o}j9rD(5?p4Lz{fCyN3}A9G6 zN4|yJHCU3+U|rkwoOVDmA-;G|VZU`Dtr+i4-rBADdh?0SR$-d!C}dKjbdlR6@T7`r zU$JN)p|m#oQ$l3>JzHT@Rh{9ar0gzgr`2kO@07A}y?$ z6qVTG>O*zBYWk};nH^o*UhWnl=1pt*wr5b%p=V?i5@xi$dEis#`Sh)U&(iYFmv3EY zI05PKqQxQPE%nDDCx3zApOwuScw$PuTm?>!c$wAa*l%B5UhUg#m1`4C?5~fs58J%- zRNi&=JyEjtpQ19i-hLwyyxqPuBtjG)dBbTy)SOn|_=njYPr9ftfzxDMB zb9<|gw|mZHXKGS!281OTeai3~77~v-bFrteu-r72ZaWGICi`9&8!nM7i*nZH7C3jr z_p6q&-j?>?FRLE<#>vF3 zX@6Ea>%vRcoHK3?;3nZxvQ}QWSi# ztZI{G?!eA4`fmSlPjx0muDE_xXm*@;^wy>3j5QsBCGMiu+#}Voch0w@C(<%rQrwDz zu2F6~1eSQ*?ugRvD{#umkouaH++k)A{;5^YJmfx4|N{4WE$Z8gn9*S zqUwrkjV`@($FE+*O`|8dUO6mVJQQzYyMD)usC>J?%dDo-bJ{~@m5py(2iG>Pkvrgg z+jvJpupuW%XB1jm9v9v{P-^5=6WeK@QSiyxd|%v^>wV-1`BA7|-<^ER_guN>1H6T1 z_Yp^lcnhKEh%|X_o0^jwF~s_|?)J8X`Zv9_n}ht0KIs=4&y$@Bm-;<>vI{2=6LLJS zCz+x+>`?J%-COGl6Jpho7;S?MgEZf5w#G3Tae{(^!soJzvO_C^7*`^!f3uAgULVyN z_^CaqLoLI)?0!Z|MS7K0t8`2o!NceNDCF$p`8>Bd$}X@rDknqIjY~pLtDJ2_u_z1?GO3-Jtel9@fzGC zfqw;hHRL>0SGwhG>r>(zrIsgX2(#W7>*XxZ6q3%>KK)* zA9NN~F7|n05u3;+{-#twEQq$XJ$5sy+~eAYIB9|WK(8H%QB7wGted35uPHvx*YEM= zP-J6!?(87GxD>B(Wy5o$OMx%98i$=z-dAZ)^7Ro{4-?LlROWPDP`q0ZWmpm-ZX7=> zDszi$xjyhk0QWlCQmUMclL~KM%Fzhkzzr5OHDo7{+HxO#ZL zs^IPdNu!ci3B+cpy;&w=H?B$TR?~?*ngv+PlU9`8S5%Q3RoYTc6W-9I_+03CM9QNm z=SOGC2@B7;nw8Z_x-eJkA?Pd2*xRAh+8A22IfO;Xtf~(CSoicoWuE6ZfhG7g zj&HjMQjflGFYw!}&}Z@@wn|{{<3aOXIytoLyG9k&x&FHHQRwMm$MUty-J+bkVqL({BSn*{oi7^ReXz5-?V4|Jfm8gTL*IJv_%pE_OLlQ~XF*~c>y1N&dy->? zg{`2`Wm3S0r?>W3pCQF0yd?VgN!^jI%LwK+Gz=XyH)SLzY7dTBU$5%ue{8bLy{A-Y z*X6QZc;O^zS+B5W?L1GLccV}i{ecg$X()w~=WS`N>+_&D?ZCZWC)Wk#uXESiH%3Q4 zb9r#t(!N=$DDsA`vi!08)&r|z_{9@zL&f9SX76lG4B~0M^23idXrv^BV_X}*eK9c@2Ehcqo*)6zo{YT*%F&P;*-2Km#^)6+lwf58S`qp=aPnQZ=ik-I< zC7M!l$ktZ}G=$7QJ}(<&xNs|aJ1F;Rh_}_WyR-6brKHw+wflrPfB4OH9eemCv#0D+ zWm}%TLXT;5VdYTkD73b8$I=Q~=&nJ(D?R8|w0`_Yau9NUtX{HXPSq4pcOZ`zDs(xN4eLL@enlz6klHgtJ!r}W}? zGKMI%o7Y`S+*LO)3az`cW6<$clSyr)_>-tC!qcGFQC10zTei{e<+?@1C)79RCRO!H zyl#tTW|btQX@An+Un^4^ek`a-(J3Omzbw_t!RK9Cu5RS5giE(?1V%?{sj0^q`~3RS zskyo_cf*I!)5Cc^-X*(3iaAd^hBh}Zi?jH(!_?IS^!3&=U4=IJ3&3?153<6<2N6b+HFW;!j=8rX35s zLQ#?l&*}_0t{g^)l*x0Dh_*kq$U_nzCn3JUaL0>|?(R<^WKrD8$1Qv7vM=v)N9n)%gtYdrKx%_ss?8we~oXeSqvF|cDLq59gqtNnC z$As^*my7O7s#RED8^`D!?91+_g(f+@14rLRxCb)Y}j(isf(e2|=$pKr0P zB%$<%Vfx9JqWitsHyhJR%bv&y90*7=d>xxtY7`J6KO!MkeXl|6h<$1-t&?B4#E!Z7 zL=7dWQOfEvJxMlOzRS(MvZ;h@xg$=XB{p1p;6qc(0keB7omBU3uCZ5w=(`gEp#m~t zOFBw(w(4%zs5GyraCKZ>oaUhO`te6rK8KhPVJj2NF5e%Rq<&QQo1lt_zXHE&No|d- zX=$ys%_Y?&hqTu8A6U~fl5gBa+gn2O+eGduXlf1D;Y4K(47j8*T*=GYoQfq}A_8hC zQH3`XGcSwo5G9Lg*4CxCUVP-x(iZ<97^l2XU90JO=AI*7J~3-AU25DUdNS!`S6i!O zipIL!kMf0Ib4T!X!*N+`vkOZ?56O>0-i$D%EDu+rx3!i|hWYifv>tz(irU1}2^ozN zPUXGn38Rpqs0&fERv=vCL2yOr>z=k`sp^VW>+LLZOhBh?XR}o2Cv6?3nl@LNAthb( zG*hqKqq8ZF--Py?<|{RN zI~|J-v&}x0R8#w~rTx?9v)3s}wY{ekJ3T7DZgszTDfhHVL415uSJ?F@<-_TTl~wlF zOA?h5b8Qlj=6~weP|V8DKV>curT%D>zi&Q0x4dVt<-&(u-H&zboA(DsnKJ|h*Lek8 zD!nKq<`a~aJnWb|3MKnIr8VG1I#0{?`sQ9P@e#XJUMLazvc&hOw#hEJ%Ozqu#EMU) zhQr*%RN0q(wQCQQl!R;*GfVl@6&M)m;o%{$?8zu3d0>Te%iz@tQ_|D4r$*@+^uU06 z*?ae8q6(}vgUYW(DQ(8Jz2ypJG|1gv)I7BDT6z8LjSmK;>Jsd=4>tr=-tu~xc*TUM z`*+HLQhG$KwQd9PgSxoei>NG9&0;fA;>W(^QRrLZ3XkVu@x4@b-mP~|mR%dP$=s;g zG;NAZF!yUpxP9IGh6d*t-Gr{H+O!Mir~Im{t|libkljmPii#iQhErAtHHTB8?zuWF zJ#fZ-)A3>-Q3<1VuTYQ7%VHl54lG(4lA6Xy)ouI@{aaekLk$E;PqvBCrxq%St*v{Aw3Y%^feC*B2*Cv+i*#CNdOY4jH zR=K>lz48Msa)DggV2VizJK|bQ=!VY5Y|2npi|IzXY_JK@H+D^wPOV8fyEdwTEM}&l z8=`Mk#LBI)SYJeJdZ$Z!>vk=#vb)AMHJlt%D#VW2g)Y_IYI0J|Aq& zF6|kN(PI5kEO+KjM`B|0&6rINzOSl`bN5F#%XpPm|6TMX@l71@>5m=Km&?QjH{{z2 zG=^tIWfXi2kfzF+(&~x!-91AsTw@=@OVv`iwC!e=!_S>7Jo8!ZjyWf)llN?mw!Z5A zAd?wj?f554DfGx-V0w@tJ6h&4_t~TN3l*-C@%bEbbf-rHE>&I8P(e*iw@NzdzIk~I zt}=m1>phw0xBNi9UA&2HRP*x(SF>1NS4uA~_qRyvHGitbv1#2OVNSf6sN@>*>Qq6;}lq@$XprJKSBiSl$uNB`G)c1QDKh#E*a=8Dsv6nl9#lcUkgWgA5iu=aiiBoEzDduFD*uGDu<6HXQ5WTxi z_8Ink=)WWSI@E-tmh9}%Y<$Y>g>EBI|{w2tZEgC#M^z`m=@q@ zN>hALDj1P@p;*9HzdNwnCg4(so108|T&03}!E>va3ir73R@;QP!zosSj^PEgmaErn zhE{E^eY(**qiU^9Nm8y+yseaA(=$u9mdEL1n+MAs*Hu>EYmt|DZR~9+Y5bC$A(bPq zP5E{dTGzUwAT`D!xY>M9h5aU6QA~qZYp7;@6RlSwpXq$J)rxyTG(|t4#&W+ejaa>` zr^nhh@m4|U^|bp}etXS5+GlsE#JYrR{ld7Xi1JZcA+h4uJikS7Prokpx%QfSq`fIY z-Y&r3Hl-I=@4`@zk(Z2iFCNvVpCnuLyWrD#)a-A}sZ2`RlBGpah?HSQ^* zQTuw7_;>j^x^VA_Q$604c&-l-#o5sNgKmr%CWh=N>&Vgw^^0n1_X_lX=_*qbkW@ZW zQmjlYDG6^9UjLGv0MF|O_5?8jVYa2u^xpeP|w(gQ* z%}b7hd-lB86nl}`?hNbI!~P&a8plhogdq#;Bn?!NA^HpgwkVDauc0 z-F#ZuJ)+AmboRP$dSc}Ev&xi@8UCZt*`DTzqfJsWy(XeEzPd+lkd>1d1()AQ6korl zWc$p;+PKswO}@EWy2$s(qPMAymTi5_z9}su5iabKQm#yBOz4_~Ci*Ea?;Vzw*1F4& zRXMh8K3+ZKn!_^Jy2>fawTdvi^-a`a<|*8f%Ovg^U*lZL<$?&ZhvG4>%Bl)icH6#V zIqB=3+eD|3$#I)xF8Lm*{d2#6q5E))&_&Yogx2)n-Q1IR$d963?wU#ZZkGwlZ^}vZ zkz9A%`dy9ryy6Y6kE#QGhfTC1<42()20p>!_1AI@;ms{V=SHCgo-Trw=SQJBN8w?I z+TrpvSKkryu!iDK(VMeJq1(BmP;=eL$8UANX-PEA+~zQxxjnXz)2}3$=@qiW?q!^9 z&ZG8{M|=HRpL7z+tTOQ2H6b`f^)PmZ{YTnOM~}L9?~}wEqC75z;3_pw-Ap^*c&xr| zcZlP!j?rB20bTQ_l}Nol8t6W*iKdm-rJAM9dqN9>DF;Fdl`|Ai z-FfkPf4yvoyi6U5;eNg?OBtG}Gkb#+~sOVd<8^_X$ zkChr{o|k)dd9Yy=`qe8Us9D;_eZLb|(J^=wx+xrEXg&%lx-PG($m})kvyNqLx6&Nb8 zST`@Kue;@m=nEOI@aEW?o{{BIPewHOw>J9dGU9W4A9b9@+0vOB&%_*40=Yw0AfKJKYtjF#00FCTXqAL({qyi8d6V3ZRj zJ)!YP;`%5zvXiIosfMC&+M50#x4C63zFMJNicj;-h>K9JVVc-*aIwZgKIQ zw-I>L2HdMWzeSCXPq>QOt+;z)C8BY{5$o{g)jh$nTe@3yox{p<`y4TwO6Q3s?YZIIm03PDoZ{S8@9k-9&8(_@KJtUHaN0j6nfq&UswE0 zQdn+}+4?&YQq`NkGG*kFI)#JX&R03g-X7eXWNSAh)WC`Tu+OxOA!*^CV;MBEK_XZ= z+QD_x0hjidj|R3F6r_aKRyrQIKO_@hf{*lXG4#AlIM66h|CIKI*pTAUP@!cnJLp*C zzge@)HKCW{mfTFach{&-J;kj$FQO#%(iIcYULTndvELLl@}xJXIXh>?q)IYBK6va? z_SRmVs#x%-$tLEgPO@pFQ9y!3t+9_M^Hj+J-LRxh!uYh-jnNTpvG(8C{^&S(n&r!I z&3C=Kw4kP4xzC4&UFGH%vi5rox`sqFBu5K`n;O&zQPts1r*A14@Vrp4_ErX2f6f^OogLm-P)|U^`xtx@u z^wu?3Z(S+awXW90P~K2f?3}FyIP|hVdNQ{$Qj^TMJ+_(e7_#vd1xYfO2IcRc8H^z6kS!VE?l}68Uq!p4 zP==C`RO$Aba(0$lmWD!gIHfwz!eT-C0c%1*MXzaeV!3Q^+DWgHTP08IixkWEU#eCj z8pkW(lfp(Jy#~+DCoB3(TzXe1C-oE=_iK>M~EDJlAsV zO^mN%`BwM3cgYSW_<~*C|GNS82YiZSnYS(U-nJwT79Q=3 zZ>a*UmpBR&go5iMC^FyO!0P>Vo9#(4mgz$Yq|ZDT>a=Lh-*)KZaCMF4B@K zTI~xutS{%A_z9dbiHp*O!bDQ zn4qG~tUVWRx!wEA;cb=5hcx~*e%C&}RVMn>^l+~F7x~+#CS^LOC0=WZ7ES2>^isST zm*KU%phE6eA}zHm&c3j_tR@qZmlkId*ik-{8 ze@k>AhhSN)mbV=^L65r|zEeU%uU5o5kOa(zdM z+g4rSi@uUV7Ga8>y9)Z(#W~{T9lY-)Zt}k_{qmjbr8HN@qxXsBImMew2jZLp)3~)O z!z!ruRN3T2r_ANwHpSlV%OA+^2s@!>v7_11-`T&zs#>7vXwEeO!S>5J8B#`JYi=4} zE>8@~u89-eA9g;z|5(_mC*Fiogf3&4kIagaR>Kvdw zYI87%sbc6JwG1a_Q@p$e%Jl?VmgmYhYrX%_)KT(6TDn$xt+z)V*Qwl9HDZtOZNDw@$|JY?Oo(afJwF}y7Fn{12jt}r38TjOPE*QA!7?9)aR=X=ha z!nknerIM1;W2bBOSKm#ji%#-vO+B_T+Fqg0L-IE&aiL>HNVenpCtqhX*5=AL>(b@B zee?<->nn>+myv0*d5TTXMEBR%a$j$0-4v3U*0b9zbH5eWe+^muOm}%4*Qg{e zoV!QysN+Sy`aZMBEy1xh732mcZ6B>~6e=wOcG zVu7_+?7Xe(Ya`D(CJx5nDpCdlcgfv*RPHArC3x?y4$&x}WPd&0CH-%=71q^`n?@mt z^4Q>T0qr$Y_7IA^P?beB?p%HMW(gf)Qmn%dF;1WbJ25e+q8bJ`Ns?>Db?xwd zz&Z&$yF66RR-=@aCfs3oiBlg~TQXO*@18=ToF^a2|8+Rz;J-`q0ACTLS-97yM zfX}d@%D*!zpljtOA|aHUq6mZdmL;l<)$5;k?IOKSV`7<*Mq9WVbt|@bbu8Z4dkeb< z@TTPORkLdSGb;CvY%*4l_Gi(N2>9qdv!$RXFkCDrZu1R!QoV7qfsPveCf2ZUIkNFjc;B?{a?i_>-(d%!1X3bTmde}|GL3b|HoaljXE`gbBNmY8 zePAT$muV=js=#-6S4U6 zY<~MZzN+hC0a43Flp(%qsEe++yB>{S51cKpvKOeKO_KC_jFV~cnb?#OSBON0a;@L4 zN$v}(S4+3h*!IVjri98)qCo?)>x;M&Q3=hpVIe8St4c(mmpOrwLVfWvY54M}F>&mU zl`$BvHwn#$cY&j|+6ih5ki#!xSJ(X;iD z#bNwh9>IL?dIZ$JSc^2+Ft$06`SjzBCDA7I--k$?jF9L0XUeW#r6&0C3@h!-*Nz4l z$$#b>R$b(`{hW!2`TjWxr5r5dRG?_1&?KRYvTVEHM3G^#;UgZF=5^|tPa73gpM<&+ z!9*^Q?OiM}iJuwr@&%W*U8dQck(-%u_~1T^A+>gRc4vYM3FB_Exfo%Bz96{pIVMF@ zXva%JT|u1O7^SGRuj|c)(xJYDph5vHB+&gSq3%LPVqJs-L>>eN!7^7Rs9@(0!r#X%5CT(rPTam4QO({c6-w$P2S-#P=bJLC z=;8n|-Bp&4_CVbNg2Aw~V&Qp1-_p+qGg$hBMLB8664Iw>o8$+XQ5saKEv9pe8^F4_ z#u%Hf7_Y@9WUbvG?zG~#(yrY^B_FCZP`ipdu`cj*M#(DE{e2RDRG34Z{{b%`Q9a8S z`t0-ns=;xTgxU%z{JK@)lVY7IVQeh4mhw|qQkT_VL$FA+dbwDExfT5C@z&suP^+tsA3q=mD|p@T;NTw_@sPC==;; zHtvrRA@FVMc1&mFAH;2t#$4%L{$H%t=Zy4gc>3$ao|}5v3x3}W2=|3nM2WN~{C@u4 zhTR?jX@Jrlo-Zyhb)O;m3`XtF_tY+tc1FW3t+4<`oatTb9|z|UoB9UE~Z~FHCK*OnfXXhW6h}eymhUxe*#0OBz@1z z$<0_DObuA54UgcAJGKl133$#kZGPnKSrT~nt$95L;dmxc<*F=cS|fEG0B& zmh;XvF-)~Jiz6hf)6$ihoKcr#5m|({f3$1T{ZN3qXAnhMccWerV`O5udIHyuIOx1?RTRq1<+yf|<_G4TAh<@2Q76tQ$KeY2O>eFFvOD(Be;hamOhbqQW;OO?b6Ps>;WiMYeI_x zi0NKOE;FaGh~-2+ZNN29y+wboC&#V99|r`7-q@OfJ#?AQyK8Nc3%Iq^xo%ikPE^>` zXxrMTf(V!^z3Jd9b}~wyv`gOA>#QN&viQS@#i{r*5fC`4no;xS_Hs*Q>lUT4eX|oZRoz{M~q^ zQmHbyY!{UjjjM@;&Ga(IREC*cQe$abg1LV{`=Hz#=wiY6dpBN7{EpUZ+XKYg)DsGr zNEYf5_Ukv;w_Iib;1?L7eyMNtddt-X>r6mx)aW!5L4H>B?WVt!9iG(eT)bMcDXq1a zI^_^2kZ3g4Np^N_zFqi&#ot2p7;{Xuk7mnpDWx(j?7>-GT#9sNVY#nh1seH@SQGfh zNAgO1Uo|uEvQNhjABx~M3&rCQ_9KlO8^?dS{B$kioH$z^l~wL9r?N;*U^cukHfsp` z0W4p=zPO_9*^O6i6hWHPPcA2mAMhs7vt}jUu@OEh_!zFytx^K)+P4M=2~HCyP^q+5+k_9pTI>K5Kl_GlX(mgP(V|I0UqTFwy7WLn)F=?B(V9+|$J1 z-ekU}zJ;J6g9W3pCB@aHG1znBRyO?3!|thL&%>^p>hKr~ji|9kvEHKtbr0Uu6`iT( z=S*mK4It9owxW#eBI7tI$2~RHUf)T+Ed&at2JJG*Ct1O|`3{QxI$mSL((os}-0~tW zDYBMJn620rim&U5O8L9nLK;86mqAQOHMN6+q1Vj*ai^Xqus%y@^T1e;V-G-

&G zn)U3&NLVlYg3k%9i=l-QG?rwY&lPFRdfN-$!ojKHtN?jwpZweFP!jW1fNRrMz*Wl=AuKoqy55AyU3eH^f^$WO?uQ|Q3)87W! zM%VQ3#OnI)jA6SHckwNm0;qmkMw6tW@TM|j;ZwBA76W$`f zes=!aTM{V(amXl#A5029(ssbCKD7PiNZSEt_|Qf<(st0xb(r3Mr0t-Y_Aq_?NZUab z=wbTyk+uU&>S3s-Z5{UDtKcKM}3o>GeMF{{KcB$g(?AYpNpS!mo7M IwBH~9Z+ICcSO5S3 literal 0 HcmV?d00001 diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b203487547..53c1e3fad5 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6525,7 +6525,7 @@ void Application::addAssetToWorldAddEntity(QString filePath, QString mapping) { QJsonObject textures { {"tex.picture", QString("atp:" + mapping) } }; - properties.setModelURL("https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx"); + properties.setModelURL("qrc:///snapshot/snapshot.fbx"); properties.setTextures(QJsonDocument(textures).toJson(QJsonDocument::Compact)); properties.setShapeType(SHAPE_TYPE_BOX); } else { diff --git a/scripts/system/edit.js b/scripts/system/edit.js index c298da38bc..ece8516567 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -548,9 +548,9 @@ var toolBar = (function () { z: 2.58 }, shapeType: "box", - modelURL: "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx", + modelURL: 'qrc:///snapshot/snapshot.fbx', // change to another default image - textures: JSON.stringify({ "tex.picture": "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/dog.jpg" }) + textures: JSON.stringify({ "tex.picture": 'qrc:///snapshot/img/no-image.jpg' }) }); }); diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index dde91dc694..85cd77bd28 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -156,7 +156,9 @@ function loaded() { var urlParts = url.split('/'); var filename = urlParts[urlParts.length - 1]; - if (url === "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx") { + var snapURL = 'qrc:///snapshot/snapshot.fbx'; + + if (url === snapURL) { type = "Image"; } diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 59b54b1020..acaff333e2 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -797,7 +797,8 @@ function loaded() { elID.value = properties.id; // HTML workaround since image is not yet a separate entity type - if (properties.type === "Model" && properties.modelURL === "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx") { + var snapURL = 'qrc:///snapshot/snapshot.fbx'; + if (properties.type === "Model" && properties.modelURL === snapURL) { properties.type = "Image"; } From d7a847930d3f8d88cbbb505a6f6b6bafbbad8ac6 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Wed, 31 Jan 2018 11:44:22 -0800 Subject: [PATCH 089/569] added image icon --- interface/resources/fonts/hifi-glyphs.ttf | Bin 31232 -> 31500 bytes .../fonts/hifi-glyphs/fonts/hifi-glyphs.eot | Bin 0 -> 31678 bytes .../fonts/hifi-glyphs/fonts/hifi-glyphs.svg | 148 ++++++++++++++++++ .../fonts/hifi-glyphs/fonts/hifi-glyphs.ttf | Bin 0 -> 31500 bytes .../fonts/hifi-glyphs/fonts/hifi-glyphs.woff | Bin 0 -> 20032 bytes .../fonts/hifi-glyphs/icons-reference.html | 94 ++++++++++- .../resources/fonts/hifi-glyphs/styles.css | 35 ++++- .../resources/icons/create-icons/image.svg | 23 +++ .../resources/qml/hifi/tablet/EditTabView.qml | 2 +- scripts/system/edit.js | 1 - scripts/system/html/js/entityProperties.js | 2 +- 11 files changed, 298 insertions(+), 7 deletions(-) create mode 100644 interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.eot create mode 100644 interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.svg create mode 100644 interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.ttf create mode 100644 interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.woff create mode 100644 interface/resources/icons/create-icons/image.svg diff --git a/interface/resources/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs.ttf index 4cc5a0fe4f098a287c1df067c8bff37695f96d34..8907cf7858dd304cdad024bfa1ff4ca5cd94caf9 100644 GIT binary patch delta 724 zcmX|8O=uHg5Ph>h-=@iK`jgG3nn;_(>^4eivq_UGO~Jo*03;0QVN) zX6G`+!tO43VEF|A+uTBVcJti01h6S!VKbM_%s5l^Fo2H$;<;S5k`V@J8z2?H!Cbzy z)bq8w1E5C$1r`>kGZRPhIY1l%$d}J76|h4W0UB;CbTyOD?i_ub28u0Dt*}@u&77$F zfYNh-k#!sa-mUbPOYI{+;NZ}T_jeoG9&-)t4kc;5HcC}WC9NtCSnyeP&}HzJ@u(EYPvN^4f+E^ov|n-DW(Vg0d|0kC*nP%B`gw6E$TNDaYHwBiAl^DrG#$iK__|C zgvNqQH)8RGsp$dcRy}4sMszda|BrZC7X`baPejBPrbc<$R?GqWn-6M7gWo&g@Y| z_0ABp$wt^A$>#-+s4~gUOI;p%;PR4JZB63kHo?uwYRz#?q0T5G=(k{E5D^Sl!^PgV zXrw>VZw^L=tM`g}b*VT{>$OkCZsO=x?RV*^Ad2((%v^P|?5DNrhqBl4|CbHzFM6Dk ACjbBd delta 475 zcmX|-Pe>F27{!0z?6_>Hl;lBV(GkK38QNLUDR4XK+M&==V(DOwy1BW#IkZFUivMnc zu9t>xA`)~ow8=vt6p=S0FG+_EdkD#nVnUZTW~4s&c<=Jw_uf}Nk@;hpLx6hb2{DjN z4&5p){#k?4cOcr=)0^ZiVJLkBwEn>>@&4_bHb@G%JDRubp^+~yLD>UkV%#d&)hZz< zZ3B_ zt$}N19n^G#H0Q0N%}*Hyx#|acY~|BOiC6dFNgar1PZpdz_VZ;ZyoZ|KK{OBq3$*y0~&1KYOszK#GhJ3Ro0XZUASApy0dARxVdMs@1@+ zt-^nf_8?G1_yAr@=z@5iYMY{fmkMuXy@o=qG??fZ80f@_VPLwtqn$Bz!_ea~!*o}j oc6Zu&BxUcwX_D7Z->L4-ly+$eL51KxrwMlxFZ_S{m45ugzdev~Hvj+t diff --git a/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.eot b/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.eot new file mode 100644 index 0000000000000000000000000000000000000000..d3591e6499c6afaf63e4a2c0f144f4722c2f8d9f GIT binary patch literal 31678 zcmd?S37lLeM;^bE@X|Zv*gd13+Lv9|qDkCxGxE(%ISj%ojkqmri$jpYI&} zgTiXs=}!z-!!>XfTm#eaLbwiYf$QOFxDf!nXI;l|5Uzw9;6}IxUJS=!1mbdu}-v|0}?)-v@x=_T{s-!AC&>-1=t#FtYc+Y-8`o&b}T14*|fz z%a2`p{YxkQd=`Lx1%O@k!b@+w{^_TI1Kj#)0I*(o-7Qyr_=3F}Kpg_O?!#AKdFd6w zo)1m{z&8M(bM@6%UMlABn*bOBz}VHtPTV~F)Ib^le+K}|*S+}iOZTlib~S)p4}isE zm)?9m`~w~Vz_nDLPMKA2k>Gjnk35J zD{oUj3o^h5L4t*6<5d8#z5o3$FbH>_$r<$IDP{nGeOH}z1#E)RZ%*MipZ+G*!F>8` z(!Vm_44@2)P=$jq4{M@N_!ruN* z6^>GUp$SLfhw#_11P{U!@Bq9W?uP5(A^0WeK7A721D}OA!pGqQ@Bw%nmSHDc2Ad#+ zKZ6V4Yw$I=9o_)c*4PFZC;Q_b@4#Q?R z4h!%;sK5q8Ee>G*iaomFQQ+pR2m+UhH&6BUo!^{#`f2mb=r^H%)*F-xo&NKhu}iE01m?o;0Rm-N8wVq3@(Q&jW~G` zTnESD#c(~m#0ZQN@KU%5UIsVAEpRK`4tKzv@N&2degR$quY|kdRqz_P$MDOm;k9r- z0IJh+3yCFH*>d$m+`6#1e#6pjo3?M+dhWIzJ9o|Po!Wi&RWCei^TzJl&Ut&zIlovM zzG7tb+GFL)ShZFkpO|c|nVy+#&o`R;F516*VBHWGQfbR}T+a_u=}a~kMsYGQm@gc> z_|S!`CH;Tlvkb4mUHD11gMEvy;jiGI6bKjwmarbS58EGevd$6b?ap`H z5qFDw*nPYE8TY&1oVUZf-h0&h8-K|E?cm(tFH%>f9#5a0{_X6|+3)2p%6&BVRA_}a zg-=8?(Y4W|aU5SC|3PwI^0tA_z}pA@Y%m_YV(_;H|2e-aeieaUj_-x15_*JdabV2 z6qainu+>?_MqI*B)f-8?h>gw$Y}KZ)tV$Ttb+^;RJGSK_SEL4VTgTU}8~@Sx+O?0Q z!!+Mknw%{Chz?tIDhlo&O|$H1cYJz$?b`9_`r181@kD95G+_=&2^cKG5&RUs0tzq# zfTOErM3r*5h>g}1wwt~3lTb~GUiF%zjX1*lSH3q`sSLh+Ul4rM_xImESgGioe-C4? z@pCyJLqCVF7#ln>SS{r9h3epm!Li4JTrN042ja^DRN>wDHM|BmXmB9~IY^)YWtf0z zn1?p3gAK45w!VszX&Tt0*CcX?JI^5|=k%t5L$$@1K^vRjWL}eV_X&a~yP&f>RlE z2P;mY9E8q*JK$jGe=eUdq<*%)>N(5#JA8*bMvOVgRhnAdNR&Ssf2?qJ}YU zHoBc|r#6c8)pGuC8bE}zfk za6}GHcrLTDwqvIz(izVRYx!&PHJr;83M;>tvs~B8Z4jC<#V1m>R9cS*n0Bo=wp=_s zHaj~unA@Jq*9*nWpk_RsuGo27a4TDQ>tMAy_*S0JZO;wf2xV4WnAR`mem zB<_;PqOTdO8`1@+15EntQa{Z$hK-u1(%Y?a3C+-n!o~({Hd-%A`w_YtQCKn?B>ocv z)s!o2R|U?%P?VVr>Os9-)(-PBi?iW)IJXdZ!qusK@`OL=I>`T*;Um-I{v`Fjzs96| z#LN4>@8>=LJ}0xinnq@)oV@D}mGZhcv&OfD=ehYp`GIt8&hhKRLp9%?uLaf7I2)o> z&F8!Me93TH?=?Z-<$*&J9>Fi;FF^&SpbgM%_eTZmv&0lWo0aPA)*{K*(=72oHGl13 zbuL#d<|5v#Tw7_e9P!5VQZ|>%;d8zaKieA- zHN-}DB&fihl02S)j>ro&Nb`L%Ah=KIrI zWjXNupe(l-t!Nxt_;vg+D4Kh!b={1*?QWTdR;8?HrfXNqwU!x2H13*>m}(z28=Y1S zcO5vdwPA8`X3X_6GzCsi7stnonM{s}>ezg1_wJVCY&v}S69*4IklgfxiP2fHZr?&_ zV(qx?CNq`M-B<4}9lq-@07w{zefV*F3`SrU*1=h@w>Klt;4EvlN24%STA3NT5}Wxr z3RS(C#7R#cY+$2FrA(dLhvv1w~ zc0?;($@)H9`N3a*BnaqNS;0pFOPQ;EYwSp=)W1HZ@{@ivkqR7V#FzR?FO@3#L9u{e zT_mx;!InWF?e#=r)2=e?z%S$bAc0}n4(G!q09`X|Q>aWsmau8QyPbNs)oF*r*sWE{ znnWE9=4K;_OQ_3QMPa|OCi@0#=~lP1gi+WdOj2%VT8${qgx}cDgd^AiWSpltFYGFC zk>-rwnrCd_lc~~RQ8I~~i73n{D;bNUAU%)`YzvumtSjB1nDe|SQ2L{(A-v~1Li{}! zT<+Z~1zxukIWo3ueKz1K;E2p~+*n{bT`Kr#OIg0QZXCTTZP|!U-uJb-IE$Nd*?8s8 zv!rhbXuul$0)8AqC_)V;0kB$5F%@OPL^c~s*zUA5Ev_To?na6yy54PenvG_z-cIn3 zR(`11ohUq1$kd%;lwEik{{5E^|K&kc64~|t{lDWQd3FeeTKS>N^6Wz73bniPeZ-C5 zKI<=S^rFr22C?$)?HvTOHFab*dSUZEt?OL_dRa%uWPMYmjXNhEM5@4lV zt9PWT*Ji1Il3Kl1kCT|@B!%x>esb&b^46o5Ze3<856rB0oY2#1G>7{Z)-iFzx%;oz z4A0weU@$c(wPmlLnszgTOCR2tgf6~gd3npFN0+y5-FD>CH8XZFv~;n<&_DNjg$wKU z<;tVE{eQEbrT1ew97wxU(;HnKMatg@kb@Sq@sIJ75W^7EjLxtXcEbU<7%qnw!HsYm zywX^?zXtDt$Eck&fOWePHACX1cBLCN+a1>G(BP)g{#@UatX^xkx}8Q6hx&Q)oO*Wb zx!-Z>{%E;gYxS5r)au_Uxr}#6Z^zKP#*lIUz{EQ%Y}(KIe)c>+>v`Gp&p7X7mDY+5 zRg>!f#1S)RWIyv!tlL^CZCi94GH>>O;)o~Q^1H_NGT~N!ZE&Mr`SNq*p{K|wr1xai zIi?QhnN)Lp#`#ZF&+pZqny~Uep65{-M=p#*4Zn$h2^z961mnZ55u_G=(JlTEQ}UVDa#s3$j&08ES++_wuGG|)&XiP*J~v56Qe)nNT2L9 z8&McXA)^cPQWf#?EY`Xmqh=+fsnApA3MZ41I36iov}x1y%%u-Z8fM?ejuf7`vb1+y4jp~ZD6SYu^1>DZ&PB-bsTrhb@>mJ zPy@=gUAyYI9CK7q@K`XGOU9oUlj8PrNTw6RX)S#k^s^bW7;A}VtpTIYP2LmtyG*!oc_^n;Y23j8n#Zktg`Ia=Fk-?DAna~*4@EQPkDywbI8TMyWtXIWCFrIadA zcu&e!D_;!)EZc!?2R5cNnY2x>U-_4l_pYCwo}OMmkyi8y+tNccl6=%s2pQT|;MmCjKDt;O=P&CNa@74oGmV<6~J4>w7 zXKGy|TVjcn?*LO+uhnY-#=|&-G)^K#lQtPAOGtwtX>>PYyHl&uFr>kwk|CDpTD|i} z`*Ztar#2M0$ZWO4l1DFzJgwI$6?-$NMbZ`CtcM)CS4!@W#^}Ap9p}b9sK$dy6${NuJhY>uk$oY%-S_NT*d$M3EM)EpMW_r0`Y|FTr`qfc_Rjh7pBo;sf+0JV#GhJA*(4}vE!95y6GBY3>nr?lv8P(b5ko1 zFq{<<%WUO~TwB~KBjOyFST-&K34;*hcko*f7#(m976Gc|Q`!Z|INtBqM%T<%#sneo zgHQ}(+?}c9BVI~s*OSYndmUI@SXg{?VezKipOPH>(;ztGJN2hQymk5LrCa*S7QTOR z;lOzdi;If~&b{eXwr3BWM~D5k3W5~zIEMn{@Vodo;6vV6NUQZcqniC%>Tah-0znO< zR`C)hai@;#5~|gc_bo3UJ+QEN)1wOuClnbs@aK8)6K2Ve1Pi3U;*avGx%}vph_AYSq%O8K*uDZw4$lpXj#KZlXwd8!NmjT zE-o%EKDw~*!XQWm%Ue@HI=ywP)CqiL9lge5CrM1 zTUAyC!SZqtq=T*f^0(qA@vATlQvlUYy-UW50cD1fWMp`X3S`-A#H3qK>N-{$=g_`% zaKH<4Co*wlSvRhowC&9Y+}JyJi<7qtpGi-L4+STt@63wqgGqd&rK31=A{PY7;8od4 zyI^lV*Nfc)TO6nG#*Fk|F@3^+Q#_p|xPLdCiyy>K!0>a`C_t(a4G5|!PH_8h6nTF7 zvP?GA>e~6bjaGM$V>`~CwPBPz9A&SSDwj)Np7Q-LKANiA16KEJ*LK{q*XE*x`ry~# z&3FwS0ZqQrYPS=oP}S=F?=GrsUf$p44_x?uFYlYb?cRV-fA8=A0m8T8_ppf_@Bn(+ zS5JM?|Lkzvo}996%U-|!_qb(GuCZ*(+OP-+kHN3t5xk15r)pV6#wa1xysMJ9K?9yT z!Z3s)vyo(Fw^PG&w9AIVcqp(_mLt-#$b~M+KuS3j4mK<0d4bNq**Yu?M}xj4JV(f) zLfbA$DbiBp25Xh(nzD9{_vsJd|G;x_7bE~k!kjptjMmd6u482?2z-Cd8s86ssWZ-z zuklxJ_-m#Dzn94m0O5V`D9+#uh@waWE$l;n8?aT!G21?()g?-D;W+pLskY0s+9#NF z-r`6a7YO<MGPw6xPu5`ZQ^%USwZ(43`@F`b^?f@K~DhW~JI1Or}$=E43x27!%eS z5lGKVEsT!sXwID_(bgzgraaTCP7K66?fOElVZyQ22;q4tJ~G;E?pT^7A|c!d{|j%x zJoRnv3`MZ{woh7)W0?cyowVbm9aHz$!dvlbJOKrOZZisFLcNW?K85X8jbKoN4*lvL zCvbLaC$rlLoIM%mpLc7=(Yqa;J)2I>&T4ucFgOPe;cwvM;F65UovHp;qOiPyY*`Yf zZ8FwMXlnT59amntWBXNC#d}8f(98DNflVj)_*L6`w|@WZOy+DSaC&7qfhmi_tKl|$ zIlcs3C_tT%Bx3~_>w(1gGdwEg5)w|m0b3Qc)@yBBQ&Ys;Lm%@p8IKO1mhxj#erD0O zENg0wW!cvKy&H7*FX`ULOn?7Gxo7fUH+m+}rm4g!q|%$uD0lJnyGy2EET2*G@l&<> z+^K>kq4M}A_$bW40xZKhaK&l7YIGHrs&BwiB60IM@T)Lq;69qwdWhBOX{~ow?@uRZ z4ysNaCm)*bp;+i5T0uGI=ETZBNz0O0W3C&P=3|R}nd#inL;ktfOWT&$2f;7W_b&!P z|N85be%_m!^a9_Xn)3a?o1F6U{@zJDR4VPKg1i?^fSkCYOGFvJ; z6)aeuRWyeklYZWxob){3pPcl4&zqzK;=R-K88?{Mi{NDdxiiP35mk%?72%A-XAZ`M zaK3hZH0q;!b>dXASDy<7?7Kx`1NHSt_pI~pWf+#is1)e5wq+m-Tv|n`v?FmSH#lDz znd5Bcp_zP|NNNR@tQ+aCdEP%V*UJtyVky7GRBreo|J?h7;C>?H3r`(hOlNe!I?Lqp zr6}=j?%7fnm8Xi*2_*7dT&v8l8A*S2st}~SRFHQEC-DoO|9=|Vj=O2;l>#4m(ef;! z>glgO9rxg;@exSFB5a5A;8K7bN!k(`W09nN>p2KET5dH*qxhMaX&f1@GyK&fQXeUg zderM*h3boYZEe|1D85e~cxbxNBTcHDb0WR+KYbtTx|kiBPc;7$>Tu{GX`TB4B855J z8(8YzQ&*IP?e$U!`%YUTUzW=HyETO%?FXs6H!z8x&wsl+98Cm)^oMQfqwLXOF-5OX z_5vMj&mx|hfPMIB{B@Y@qa82mdu@qV`rxP;azA_1303aZG+zCwkvhAxL|Ey6`MY3=t*7b5TM``=WL_-F5<}opyA~2`GMx!PRgK z-+^xf54r$AvOdvWV3!hJnHiehX`uw=`GGN0& zIE0VlulIHR7HomD;X=3?uIp*7r%>&7hmeg`gptrH12L5_tewURd+>+JWY8y_oXS-< zXcgI?p;lxruHq*lX~W$FZ<^n>{j&M_Z98V?+19C>rzR)q@O!rHu(Xm=S&lHs1fANB z-0nEG-2?MAK^EGUO3B{2Iv)?>P4ky++dex#KY!Npd#9!**DtP_np(5CQL8((zC+)n zWX3#T$v4vrrF3kqxxPi~TlCH3BakvH<%3dYOyRbaJIAx*y?J{VoP{64kHS28kE#`A zOyhQiun0AUtxj9j>gAg8h7i7Ba8xq9o2sJfL&#$ts)#_O8En_O9ju}wm~_!*%o1p6 z#ImiIY)>(k(uE!9C&Q_NlerdE?%ZhqZh z7I)CGc!smFCvdOIbDL$eJm4PZ6#91o%;UT8i?ADRhKJyT@MZW8{2OL)0yp7)ya=G% zi=t?E>b1JkaS{(>s4L{xoWk-fwrh1ACnZ!ruGOPT3BxH?FSl#WHbDr@8LS(c zkl&=$*}z-1PIDuY!=Ouy(;(nM(;EpvTJDLcFdzq#NomZrn4w1Gml2u{keiu@nwkh!_l@q&`EXv=E5c zC5S;HLu@j}oWX#6DY$NOhOr+Imc&)8C<8|(kf%gO2!)(1e3l9WhUn&^LApc?EVh&J z37=k1Ab@s4#%;+758zG8VQ>{`>oiAfF=WxKgN*rVz(buztRq9$&UnbMl;Kg9Bf~&j zS;gtu4H&Maf^(jElgA^)EadY9@0b+9^1h89mH~^n8xKZfOtMKVq~kPZ;>+e|XO_=u zdR|s|m?X&1qUIxx1Sv11im_cz8ZTvxO6?W{##2H~%xv8jGmRqP1sjnGMx>&lpfJki z6v}?-X(aVG70X|SOI4g4bhv5EDeD4(^k9yY-;>>%qk(ePfpkO`#Sg>@1>j14p5bOPEj`Mmy7z-9#tdM9Z$!^>!t~jd!N*d~4w*w(=uSD1q9u zp8nqV@RpSy3MC|Q7H8jeFk+I+l^5QTzVionrtVz%M~0f8DD1j0K6LBlM{mtD7qt=s z^Zv?@l+{eWmt2%bAymW#Cf~CZladLpQTdw=#fQY9_|TPwe17A_6!oj14u|l+;in)4 zG1OohHo&rpqNpt~0%>QFe6RHsM$LAYJRU!hN+{1yrZ$w+?!Z|Wx|ZecY7eNp*38ED zUS@Ia3@%^jTH4#$84!2QOvm@GeAnXI9az55(c0bB9%Q#u>id@ zGeM@k23@gicT5us}aGKNpDODq#tI=uE>}|YIDO4DZwOV7YoE$iWmeVbOR|&poAMHXGp-`FxGV)-AVa0Mxq_n@}llz`lw1>@XYM53`xV zUf502i?BfP(&>DL|EllbPNv#R{i{7M=SCV=p7wlfkRg}+ZV;UMO%w2T`&mJ7R`8O2 z`{y@aJe5zS2edmJ_}Um0pZ^=*|M9ntN%3#R;)3}t7PlWba9@0b!7&fRHTWUC9b8DD z0??-~4TpO5G;-5DllF&qIj-xrJFe@vnBL{Mjzbr&^G3#OewlP^emVQ6opig^aUIv) zea87l6wZpxg|niK-~R$Qgzv%kn7B5IZGthf)*5kms@LnaiVpY`)_b85NlZG8CMOHU zLeLy(oC0V79P%4pMDe+iNes{I!=~ck2Zg+C zq&PJOa+k1dp;o>@BTDR&{c9ZSU8C~!PAQch8`Db4o%)%V219xT054`hbOo=jN64u!|z#n1anQvyEnQ|d%@r155q5C(AI8SSk^&ry22LQ46wsL*Ub zqfV5Aq7;6DHpiaT^kdH?)$`mwO}YPA{#DBNUk8lVE#P0^U%(K>?^jnF&~HY&MNoT> zwiJRFrm*L$HToZ^fBgU|6OM>LQR8yr5-vC&<@}NfrKEM2((*6PIDbZ-r|u##J8_B0 zFZd-B(jpGfA3ft-osp;49aX5}NjwP!vI%>YGGsMyT$lR3SFgBB?J1Mf!1PM5Ifhdj z@xSsT6H-d+F3XYH(^Ln+NBCczd2u?+m#8_~)6%i-vZRy~M>rqh|MQG`pf7F-EXZTF(Q$e|IL0g?;}0|8e_*Uj|n-_DRKOl^pz>5E>)jSG>2*T|GKWG7Ff zv5!_G#X2#boUaXJii6S6{FWUpH|u0oK<+oo%Z#o(E``mQ%t~P?={h4JKH>=6uqL0g zGM<$Uz5HagC@txb2Yh%SOrqh^(2(bsyNcP_KmVP)<6G9)Z;p^dO)(+3Q2**FORcB_9n)F*t9%(#&cB@^h?&`FwwK3mI28u)FN@*yGDSk3ehDw$4 zP;t)npK6_5tF}A4sP(`SS{Q$Mf%QQUWm&_FYE`VyZsR$d0F+~-o+v~w&K z#Y34eI%FSk4%m3oaZ^vjLdtd0p7Z*E3w2GQw9#>F%W<-S?Krld%Ll&exT)g1GS1wZ z;Ux^!Aj{@wDbBFeAe1L$qLzooY`8 zMk!X?#Ax?xM=K?gXV>s$rz=q0bVdz6v|0ewaOTW11nh-#@G;}RAfH0htc@V>e$+%i zo~8;hTqhYleXrh!`6(D4A6|Jflfg}yDMx>oFskor$6EPTHjBmVRQ8kE>?gDMc!q*m zGWVwGJ{{iOOE{lWGe&>e3w!Y~d<=Xjzy!(D{yL3bWoo*&YUA`3EfeXlThOLvt5;MX z?NEgredbxl(YQ%FnU&Que9Y15Y&NZ(D^H!!6DO(QPJ>W=Hl5_DAZV(SJcCNr zXWThOril(R%`ItOgdxE&+mO@fI}5_ z;z|4vF-WeliRz6+6Kb23>s>;ram(FD zj@*6Z;V}1dYwdWHeYv$3A3j29L)Oc4;rQAo?K|nqjMYuB37^C#A%GG9l1^Rk){|~b zQ8?9&nCPUgYl<9AHN4N6uUs}>drfX~?L&=4zVcURUafU7w>(?13uC!^#wVS|!g+_v zcK+0bOA8l)!6@8~e}%V06{Z2qB83@}5Isk9GYXsYOj z*)%b~ZQJ~s&DOLt?Bwkcixni}D}TOre0=RV<`u6uwq3Su-Q;H5-h72^U%|Lo7^j=2 z{3bNn*`L&3qt}>#M(S)6E@MI_a`CEyZZwKs%uI`BQ+ldzizc7Zj%%e- zap|0e(%ECxTG6ty*}-~};(c1D z!?3ha8!w$*5h8Cnwv$fhr*;Q5F9>jl$Zt9F&1#`{kM#E+!sl@g$4G^*>Nd`Ge>ioJ(*GJhj{{hx^d*e`y8ELkT5HQ- z2`*W=;S@2s@xo&6PeMncd#|0cZbcNP|$AaH(%4hKC8mkY=48wi~H z==zuwWP1MeC*YUyd_2!kt2SlA6e?v>&B=u98DAlBVheW%$?}M8b1CHFdJ0t>-jWj1 zb1@r?ST^VD*OL!=SW9=e=k1I^zyoj}-hnrR4{=ZCR@I`>pO>&)?>4)%nuA!6g89#M za+mlN~5{0QfahCD=a%)I#e1qhp#&+F|d|p23$+I zsmeCGU#T?a=BA6o!=*Ko!^6YFB!AxyufyB%MaCE30a}?(aQGA&XUVfEsyj`(ODK6% zsgpN>=5L}?C2lnvVFj<|{0YvVc*62Liw+Ouqw$Q)@Q~|}yV~_#mWzT^3NPXxs&xWy7f;@Ks6`@FidPYx@hf`F!D1`TVi1TO(j_J=}`F zhOY%hNE0TNsM4-KJO;mDRz;U}rA*PMy#P#ODMhtMuP;W6%v(b$j-I$_^=q5FbLJboYQIwGh!EDtjp+T;Dg2!t03Y8W& z$rDL@)e}_Yea53gpb*?rjEf+U87WW%E@NKWWh`(RM^52GT%h2ZD=q}lUNI?zQmB~c zk(q{!xxuN2DBu%iR(fWMh*ZKn=DL@q2M5Xn0~q#lBNeD90Mz297PP+aRCNTE8*nC8 zUyM9$bH*5X?F5CCp017%TyZTKDwHw^kaK3I8FMYv-iE<+W?-N^U~tVhTIv4NeLJ~cYa6JiZ&y%8Q^v57et*(8 z82Vp3f9hrRX<&@;r-%+u-W8y@NZ^l|;Dx?bQRrC>heGK(-eJ#iWq61ShBJ={M&`&P zWR);su1MUT>Izh=_@S|}mDi7r;fJcPKmIfvM>zSR58>F(C7oWnp2cAIkyNZ>`rFcvOH#+l@nv>jn_$+%#iby$)gk(_Rdd~Y;D zrDir;Sg=r{V2m@;rCSTq)Qr}mDVtmEWv01nbD<0d`dRoAc5#mO0m%_|FvOxJg-7-& z6b`YdPa@Jwi9R9GW0hn^EEj+)^q{-NG;>gryh?j@PG%CAGF4 z%aXPdG9WS|GAV5>rC@^mv0MpDYt?hl3N8hwZZ$nH!pah!< zZ^}i@Q#fc3nm*N=QKkJ%mLMi+)2fU?{D=Ow~@ z6iUD`NJg@~fC+o$1Fz)w?5yVlq(;!myC$avdWnyM~;@Heu zy=lGY_lWuvS-nZQJ4vX`&nho>Xgy6&B=>ol3W{QKzV?^gP{h#?MscV^676KiQlB== z1k$RvDimoqU!G;_v27N&nXVird#V?zzY*JKOe!=>kdu+B)0TV}rkhbLHb0zxG9L$u(6-~9IMUWt;&>^BNT_9%x1}ILL zup~D-7??f~oKa{GS0pGIUQh2a@{6Pk%`&6_(TYXtIn$rSIA%sS!)EeQuNQf5+JVLD@_MqAurH9K(#dwy@ zSY~p#V$`fY`wBiXVfCg;brF?nZl;tXPqr{C`WYGng)tUU%S zUTO%@zr!Qa;|v!g*VH^;bF$s(om>f;ddM4&qKHy*9+;(d$dnOk0<|`K15k?CWm(|C zGOWix#Ye%03>2VZVk&57heL1~9EY38m(`)5WL2)Iez1x%D3;j^qn|=c?1^tlR1njz zrgu+08=rcn(|e}BV}*H5eH9j~lqn-6mho&q^!?E9n6KS|V+VoltT$K7nxRy-tLfXa z?2nt&%X$xN?`M4zT{`w1flYvu{o#Ic@DC>2-e+etKw?WA&MT0MD29(??Kt@rHJ`h2a=(dRRVov+W) z#l^+5b}p7Yi)+D3>tD5a{^H{67KdFWG!w&%>$m;N_Qj&Blw_i~h_}rPF+IciywsOj za{5A>;`ipceP~)(ms{tRmn*kU?yD?kl%p_HzIaP{%Nxqe@ROk z(^a|*3@(E%K8EiydmTWvO-Pk0VSs0VgfnL6u1dRwuiDXU?a1bbqi@Hw-0YuCCPh2g%CEGgRV z!RXRvxvEZxd(8?OWCu3{iK8v%S0=ZP-M(FGI|-J)H=r8JI~o};9kjMybiv;iYsF%% zc%td%1N2;;9m9kX0n?Lk_pq)Hxsr~}2TD?+kPqFz*G=)()g;6C+vEH&LM&LeK0ZE*gjYpb|PP5x<2t`N7 z6jsx1(Zc4At}mgk$F%jvFzXIsQcLOyExrwt`i!V2dWqpv!oFx)DrNuq=s8AjHnSZf z7DlDynz6=sUPx`_-BjX=^o&c!Bg5$omx^0}Dil%a`&nBr zUh>K%+UgSF&)`wqk4GS5#uizFr`B84>bUC?0`LV_@)DAe@ir}``({@%xtD^yLHz$N z-sx*_5~ne1{0H?WvZ;e7$w>-dgKyz97APH)3Z-MA3k+_DJMnJ32Q<*WXRDEaO=F7o zM7uT%(y)7c&z|FZcHkz#Qp`r=TTcCC72MlwwaX`!U^9< zp~4m?RGE8>r5Wij+>!x2Ei!_qge~1nZ8Qu?6|&>CS*82|AJKQVTLGfT)6$@Dacb=^ zqqapxFx)Jh_$<^+1frPJWbrw}qhn(P>!3}xnt*HJ8hi`B8MN_UO`Y!ZRq+{x_5K!E zWE%9m#AfAcyH&%b+b_KE_CuE#R=8j=lN}t)W(J*&(a6}uz`R$$H(z-Bp+mQSVo#^L zXHU0tRqy`bp2lX=XQfllvj;Twudr$=?~ ziAhh;6HdK=wqQctF&q?;IaA-rJm#vvO7CqMI^7Kq;VpQr@&C?~{}Q#LXjoFwX-5fF zZyAoF)eC)&qVR`P5D&2w32U=C9PhIf-m=cl+Ux9Wq5pk#*^7N-=vi*sx1{4TrV^!n zt};cG%ob_Ec%U6_<%9@b%=Vw#(*OR2oM%`917|1k1XF{`VJf$Ndfhuva$BY)+bZJz zTJ;hfgm2>`P$NV(r=~bnoW{T+GiDBP_6A&!80!&OV_-tuoA?&cvM8`m-rE)NKXdL9(t!e zI2dbPw4Jb~vn&6m6%&CHL;PBm)(&v0FIxTj5{CFQ*ElApN*FhO#+tt&FVVlsYrE_@C;?RYFKZhj*W%!%N_npF1Vw>INn~kxk2vNY^69 zuJw7~=Sn2hWKF8Hv|Y=$A~qldw}lAQ;4d4V`8km9Fn72+P?>O%Ydh1OUBA{-7H8@8 zlYItLP=amv6n>)5Uqsgj{@#FS!OfzmWQd{%{~x9HC#2L;41}3sVpFG*OY-{v z{vNoCi?p72;q4ur7EWgUYj2!&0HFYHhcDtm(yF@k(;jED9f#Q+!Z>EXxNdDaol31+ zmr7~9uANS+tm>>&S(W;ug>|WPD!pzYMQIE4r54sHl>$cZMhg#uAb)yJC!Lml{v!VO z++4c*yv5-BTqC{Z1@#cf2SJH%?O>JTDj-!JMZ7Cuz&*G zfbYgvLkP3bGCNp4m%L4$Usn8Vjk)RhWWjI#=UZ8SzEOYUzt?<_P&g$IW??JrpnbId zvlMu)vHWwUO7=NM>!*H>l)CvR$K^l#d&FaA-;&$#Hi)21KE7sRcJD3MrdZV6fUPBr zXanu2($*BC)4KOQWSY4`X|`3qiHB;_waG)7fujSj`XnK8F2lFTa|I7y^OplJ+Wb&v z;L-uJM1#Yd;5&F34uAz2BP058cdGdlC%^3msW1qZpYipbAPk=M+l%=-AI`(y#P@>J zv$<#qvcW}?+I(NnAj61$>a-1nRtVd##4BER#g((7ZiQAh7mnvLW7`xHnT!{#3mCrl z${pJ-r@tpv-9nwoMdO)FWrwu0miB^mDWw0B@G7_s@56gddj???CSU{Xr`>@@uxX&w~J+dDIke&-Uob~zvGeZ8~i=q*#gHRoFhkLF^m7q5*sI!fCkBeqt~A3A|OI%)^b zB`IycMWCM3gd?jt#l#i#&B*yDDfKAlYL5IoT+T`3SWvJJF2qmZuR;c9U_I=FgSZud zwhtuo8`#Pyf%PGbl4eK-Y1f1(&0=DVPI8IYDb%1#!EyCkMbS>Ey~pb{bJ6e3dMybI zs$^{5UdPg{jb@2*PuJ{0wo0qfls?U=BHm5y0sRSW5~s;!1fUSQmLhNYAn&*L7Y%OFXJWan^B5!y~0C zX9G!ddHv>eYTeo$e%80N-)ST{DRx6j*OlZZ>!s7X$bZpa$!z=*#{03FfjkC;^PB9aK1_0MT||#+Noa#o zTB-^?L-2tTny5hW8%#J0mKG9ImgbV}3bHA|{J;$q=X;`z^tkri$Z?&Ws+1aGyi}-+ zq*AUuKV0l)CbF3m>(>p$gThvUr}K(=#!EphY#z9D?R!0!!$OJWS*n;YfSElUv5@B-lUp=-NN_ z$k)3-WPu=j9-#uucoJV@_9iKtNV-Z{PnpP2DoYV{v|DW@cn~4SOY9Gh|K^F_S@=iN+$2Ar>ij#w_L!Frv(VSu5Givyl0v@nSKw zsIANni^cJhgP5b&rl&VekCjT*=}ptAdEpkq3?;H$zBpcVFv}QaFm$;Kj^bVTFyVkX zAMZkV8t~zVAMW3~3-3bu4^L1X&5Yt*PXoeTG-0t*dtuDd4$ZU|#s;h;v?0S8n_w8s zrrAgq45-j-M5<2vU_`3^?42;OXiFQ>(&^J%U|7LrEQp6w!nPe5U)kFP166$HE*RTT zTKB%z`q&m~v?CZQ?H~&2Vg@;LrrpJVde#mY#0zhO592Z14Fdq(=bHxq{&{A_IG z$Ka)K7rqYfgCSS|pw&XHZfBSo?^wOIf%P|+?=E7j`~Ss6Pcbu_k$5*te@lpqv^OT( zsP$y%aFid?+!7yP*Q+PAu*CB-2lQH(0COBUiS=1Z0- zRA+QpsU^u#RRCc?%Xl$+0bZ6YE$`e>8;cw-Kbad8JVX5U z0Njfw@D5195bc@v%zXgJe^|n1n|2V0n!ju*J1CuZjJ$srZ5|SP{j|S(WYhBKKFny} z$t~`+{lM8gljG;uo^P$caA0O8qW?Q{4;;t)@uA+Drd)3)k0JVJ4RuXE^c1RD3{caa zjr4yjXsT;<=)ZKBZ9bAl=c&jq=9iLK=EAL`8<@-rVGABGn+Kdbf=vpM-kK4wtmFqP zI7SCOdR8tc<79Kb;6)oaE%YM1fIEzyU~H0$^wu=;R=!dh%vW%X^!dYZ13rLnh5=Xu zEm$;rq#Jiz*X$l`V#-OkGJ(DQ9qP`A*6*0beU)~rOIx{g6GGeZ*jIvFL48HaYp?Zl zxkra4hq9i1iS4odak~5EEeAZkaruTDZooI^{EJg(OZgR5$OT^svR_O`LqkzoWb3x? z+x6^R+DoUs&GQZIR2x3|5IAgx1NaEOA4)Jmds+}6(8G~??!lxN^4)E3WbKwb-Gs6v z&ChJLM=hbv_7>5|lBh9+QHT#p$MgO+i0D5lM8UWIug0$B$8F<`&rc#LN~E+8J=VKx zdu7YJYuRP6(!QKcT{o{yAFkUtf!n$dhazVsdinAUxhpy8BWWAkKnfHokW)`Zfga+Z z=%GCoJrw96K#`(Ad+Di%ptt@3E{aVtBxSGLIEetw{APw6ei#mChMe#Fe)XxGsvZ1R zyP|1VG#pcv_lp-M>-EVC#rK8U2~aHICsPF#waQkmUb$AO=hiE=1xWMTOE0{3P1CMv z&rPc9jfra$jV)EE5j3A!yi7oRV&M*6CQFHxPNH@t>{OQ5KO^?##BwUxfE0Rv$x3>2 z1GnUSMXjtV74>+f*eXsarQ)e+xhmyy$IhuJOvlJKOeo>wN3E8H$fa!$T2HtyaN z3jIe(i(JBAG58-oq!VO0(JEd|^olQ#?GNEYdV{=4-V$@vM=~Pa=Q{cbKBPmeNe?q3 zH9j#T(gW_Lns7X{*+QJMAtlU6f*&aX{}4Xr6I!IH|12z|Y)C1=QM=jv`-4I>5B?|^ zMexMzNwf~`mTL(wQngxny4h?eXP!-Xk=hFj|9)`V^WF8`dxtoVW@qQ-<_B~(eT zoSMBj*J{mOoIQ2-!?}@ADbHQ*ZV3M;=gEus1MZSCStRR;Zlf?$TJ6>wg<9)`aB93N zb`h3@6ZT`7*KRSNEI{rG%cSf@MJXuaJzBgBRWMWAY$(Fb*4oBqw~^XGbT`(3pDhB* z2k$Po@sw0P_=_%U>TGqQpx5YJZT{e&QZa|q<-DS|mrIk4-zb&R8BNvmg&7^UaI{*T zPlCnia=lc>-Nn~$EuBW8eQ>YxURjd$cXUbCrzc8EX|-INx1p%|a$76tSEU)brU+-$ z(=+&GbsqCIQBz(o)^oZbOCmaXEA_ZQKs0hB!#SdmZie$@ncT^+L^SelhGn9We`Q!9 z8iaKuq#^+ke3L285uKdMaGqQuk7rmS4f5*@%cMd6mSKf7$h}9AfDW7;$LPDGaKOpK zbfDwR?Su<7N@7O(gpwxdCcL0cvPGJYs5mWK6|_;wze`pUib=8^_|bO2`n1`7Y^Ax? zJS0GKtGU`SI!1S;bB;Ws-iHN#1YhYD%D5Bw&CX*hpR69S>OJ&JKE*ur`*E^fOf&`| zRA&_wprVKpH0UTpctM%O6sA!{4RtgygCm&59OiKpVl!<4O`O0IEjVCkLi)8&=>)EO1@Qctw!6hJ(PT-0&Q$&^J9Ans(_ycHVO=B@W%dv=weKCybAG54SAx}^ zY3=Tt%vRDPRL}7p&$&re&m1OTY7FjQ>xk{}arM18iUL2o%H01KSb^)BA*Wi}8U~Mo zu$U*mt6l z)@NofYYBx@lUal6A<4!fjm1cBmt+MtRnn?eld)i*8-p>}Q_>vO3cQd}&Xu%Twfhnn z9mR5(Z#3rLgprk-IU8nCG7EO*w6W3!NIXk0LnsVt*q@|FF zej@cu#`Uy*gS#dlNPOg5`CU4aT*u#41xaw^1b)FXnHd=@i{0W*;M$ZKZhwrpr@C~H zx<(vIj=vMgrb}5Q4FZqKl(B%RoJNu3_qiM~lMnQucb!_1(Pu#%<}6S0_+CG6(|#@- zN}j=Q6g)$8Zd*4ki@KC0auh?8MULfCuIvRv>T<~qtlh$%!=0W>WiPO)Ti6Mh=y>DX z*-abUu>B0gp%ghD)gxy5JTw{gtx+NNy*Q$_6m!Zn@z59{^)1JhlWch}aE+BD>?C0~ z30ISFtsqK=#!41qzfF`7mbWC;Ixp`VsunGv<9Z%r}@E-rHCdsI$Fq_Ju{-lE* + + +Generated by Fontastic.me + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8907cf7858dd304cdad024bfa1ff4ca5cd94caf9 GIT binary patch literal 31500 zcmd?S37lLk#87REac z7~5bBwpl|GZ~!}jgcv(umhc6W5Fmsd0{I~x0Ydl);rWrgK05|^#01DfdPTL9}1_f~Q z7XV;r&;IH9o{yY7==+NK^6y68#gU4YBcEH832}1Y+oC9BjufeVGI=Ba}fPF0P|Pq(hZLQH)jP9xJ0~pvbS&l*2L3K zn`ef<1-&!F0ifVX0GNY$SPvUu0d9d!uno4rBAf|ZVLR-AoiGD?U;=i*>2Nu`08WF= zun{`27TRzY?1nSoY$!kx2H{c|f?>D{jzS5_Fai~*LJdY?491}eYhV(lU>aI53w3C~ zUN{f-!4m9;bua)N1V~Vz!2%l`aKQr~0!Tm-QjmrWgb+a%V(5c@$UzWsr2(E+c;W)e)Zh)7-jc^m(47b87PIv{p3hpxe@=AC$+zWuJOCtIpvE&L{u6&4F=jPXMSh!`=wk?ZiZr#3P z=ggjoU8i6Eg3~r{?5u5{wfl^-3&p`phlZ~@S}KoJsDypY6Z2 z{}22BCATwoJoksWALb9{@5(=ze{23z`5zSq3fB~#Ec~cgDqc6x9{BFymcbtm?HxWg z{H@Z>Mwa{6@=Z zeZ74M7?j{%{BQUYh+!6>QZCi1HLWJFRNa8h_B__JMGRG~9%tvV-rj)C>I9Zl5ktD} zv>SNa)=cDzL|r0`1jp$> ze3^#|yc55MR{{qOE+ilWG322HV=xJ`(1LZa0XD-n*bNuJQFsx&7;b{w;5XnEa4$SS zRBJW#^Zdn)daF5s!{t&G&SU5Kb9FO?^C*W;HI8Us>5`%`+--hYH==UwIsUp-8aDL8 zm*jHge10UC8(DSo?Ue7Qmw!sfpA8O<4GoQVf6e)cQ#WyN`7ip)JH2mRE>&B#6+YFWs+Cj}6T`GRQ&`THHej<} z#Fg(~l)YIk-^YEQ`w4UGcjLTM?sxmkPQDa`PM_Q7VCa7~m&@mJeNJ4k%l%G&*)9wR zq22HH*_i;n9DUw3RCsOwNVy;H&ksEn2Cprb?hnG#eCGRX+^2W{=5u8L00FZwiC@G= zp#z&?ADj<>>u-7W~FV%PK+f}o)uPeSLUiXlga0o|0H9% zu9ev!G-Ha7C2Xm*9t|+*TG_1S;-Qi0>5=}-woI;;FQoc4HM4fE0z8? z^IT?IrvENaZ?oJ_N`}1OPAK|vZ?W8?t_1)Z*1|pb=Xe}OU=wVIy>JkYz;S?1sWye} z1#EXVig~OLU_8K-4sZ%6agSYUr`X1@QR8HKt63_d z89Gr|-++yJ^XjA@p{o&vCDTFdKhal7xWaZ-;Peedsqvr|)LJF&FfTPf9gc=GbAcyZ zoyf&c`2DVf{C^ofGCl5(Q}6q0Ov;D6obUU7&hzhaQrjv?WOl;Ix$ZzQrwdbSd|P;) zo6DE(OIBwbzcx5f_3hbeP#MmqL$oTnTql<+8cyrJCJ4M7aA?58_+|V}D8mG_06MMS zs9-&on80VVQmxgTC;57cCGM-_uIjJMWD12$#2e+S%1xFb-k4lSXEJHL%Co}I@_zr# zxmqpvCXvr<&E(26Gv!=nYbHOnHFHhIa<4Jn2psN(i|{4*9!S6-YzBDd#L;fmh!0no zw4ROPW|c&wq5<8cvuqr7Wu}pzt&NBqOZ}?9WDfmdphSYNu26{mDgH3Vc5($t^}J8igi)9X|+)=AKGTH=<6fQ=*|& zE-9MnTIEu;X~q$ayGA`rwT~M0cC(5*_n+0=Fg`yu;(95X0w*U6qoajXD#Ju&WVX3$ zSJQDe9Xj-}0|)MlZ}{=p@U&RBcdj_LcGPy`sq*lyD|QtR-Ejy2B#gpd{3t#KLof~N z;569Nosp+-nl)O(QJ7U)nHjpAHS=*4s#+t?#$9=^fepvy5_M`l>Pp7$DM^psDdyie zol_P=em>4K*UJpnV~c%*t8nOzzIEqY5v^o7?fY!`$KU=?5YVr(f)53jGFN-o*pX6+ ze|t*h#{Fn45jf6}FZE?!B2n;zLLR?5Phx+AErUSX>xsmsT_xC#U&i-941=%@&V~yC zI%e1=P??4-V8eWO+OQ!SS?i8>n0je49dqAqC_g}ug_>>IGDo1OLoMq!sQ zajBJR)}w4H{Kh^e9KrS@<2=cEerKMGBxn5Q9AkZ-NEG`El1b!DL}5x<@kllbl6~pG zwvb83y37p<8PAIXr9Ye)z`MRL#6NJs<(@rK;59ptBV#+)rvt76j>tU6%?eB=i+Mk3 zDa+T^^~0AZEgR9v`My@?r*Tszon8LxH0c`x>aYesj~|5)3Q&b{0IZY}Ohu_MmW}!X zw%V;!lj}&gI+5bBu5}viM!k`#wPO7E@=p}I9fgMqnYvvJvvV)TfB5pDZyrD;kzM=W z|2saMV+T>F<)64L$IeBrP`k@NLfrVh)4pk=7j4dN5XJc-Dsf{fU05EqndMq?_tr_~6Dkbn$IVOIt2F zvb4Ck_3%Y&rtDx~;e3aof9ACc=hp4bl!i0=zO$Ys_hBjQOS%)28(kem%HIf(fhM%@ zFYptPg#oAb%EA@NeH+=&{kHfy$N zaMNgiuJ3VHtG1e*c0JC9`g!u4e0Joy-*NK(aH&>pc9}cW>OUyCgttj=`@lO!ka2(i z*xSo&(og$-`Yb=~dFiuPoww6UYek2ON%ep3h^bZCS3in1TPvk)i;e^4&Hm3F@wi)h z$H*Qg-16`CZ`8|QevUl!6d8r|p0ql{)Zr|XYL3r1|GDb<-P%(VmjB1|JWAupg;A*D zxA2>wAq@jC3T;?~eQ+4Af*T0Eft8M=IfbNl6q0@zW*hZ(t4YGbXaVJttdfLm&oj!> zF4d|F*p9P0K#iqZl|+7Q^rsBzlkG-53bRqj=)$~IMZ7eP)lS=}SutrU^pv^6@pvek z9V(u;Y18D?renj?)5E3d>6?O}83g6!KS`AXTVg|5%Z{||5y!P{)vuO+ETwJxeXi}g z>5O=FV5tDH5GWgOQDVe(9CyTZ`Hz)Qeag07yW+SUGgMIgdManr)a32crEXytC4vHC zpe$P^j!N4dacsv`eQNpNrR{os^mryMa81yZc3_vSVEN5KGq9yyws2C6P#s2GTRbgP zzoLtZ;|OrzbT|W_z&C&geJ}*0um;w_9ykbBkhX@+@^FddS!1}~;=TEj7{6AJLZWr4 zH@a}RHLT0EM%GJW!tvr zI@VN43T;VwnQPm&?z275vZPE(DOI5Gu7s_YzZwKsvIE-=Y)qz7Nt<53{I4hOUOzcG zIk|o;spu89r3Yvv`KYB3GPJG0w=LV=U-J3_Eu{{$QXrp$j=voB{!^{~#TD=A9(DXG zehN}hFv!*=03BWH)B;A9gKl)%3#{E^YF#B;Vu6(J025fN)~W$!2XO#toJ5KyZ8A<4 zkOo0q?`*_YyIP@PNP|bk11#3nTKnVsGW)Vlbs%t&*=oBbk6ajeTCYrJ5+aYuO5 z9&+qlCb>VHMej}akj+u>vGPCx#ds>wmrkif0TH>}yL>|0LBf8LS&G-RAAEIRW?$q~ z2YuHK*CtRM`5%$1^*T#u-I?D;db>a4qoB7lm*On_azDC9nIU<=^$c2z7q)7WZTDPmhd?5u#=`kj_5W+e1q>v1eA-A-{wY2Aj zre`?Q!cHdf%A8`I?EE-GM+spu&Y0NZYb)vbDO=wbXv@-pm$Eco$GFd=V%+B}l-%b! z>8DU&!gd^*xf~vjIy`A5v=mvcm_Q~pvn|b#EG=6yJmZi+RKgq5p0vDoYRmJJ$v(?c zj_X@i%5xk|dJE0>U%-z;8D?N30BMKiYPnWTAjzFm4&me}$axwg1fLc|#^v1D8X68a&F-^cGl zV06G4mm4&&854xS4?;1FakpB@N4$vCt|ymB_u4-{H#h&t-24rh zzalyKS3$7qJMmXRc5&&*MO%8x7Ct&ZxBslU`T6<%XWsA%+p`DGqQgF01wn#%oI@Tm z_yhb7_>eOe(n>v7RkK%1-Dy`zAgDpqD!YJjwp~MZA=T>1dzO}t?4O&z;gPwy^%L1J+!NCd$m z-Z?+N?)TTt&8>a+o0CBhEG`B?kX&3;X%z%ZOF@tf7JKC{;wSK{FbER>m3FN|#)<)D z29acBc#;Za*=)qPQ;Talt2EA_eNlg(7i5m7vXNz7zjoZVH}7||-kDpRoSpx4ax#1% zI6irMTBPrfv)5ZX%BGHIf*|g{JUwpb?agO;S$F>y$H~7wCHFV;XUvOPT>VaQ6zyD_8`9v z*sS4*Z6DU^LM6Fy9K1lPZ8E9$3MQR5Ig-W&!rkx+ycHjTHT1kbMH;FX*_Um@rN+Kq zO`I|wO)}mnSDO9tWWsf&w!{Qu!dfE&>3NB{;gRi)nbRcN8YN4Wrka(pzAR6=zL0B} zaI7^#cwT}J4R;#b7p93w2=~DM!s{?ceVbXO2sYmG3CnRTbHJRFbeyDP>i%kYGhTtm zArH`LL}8XtZ=q%l|AbOJbF|Zdjbn zTI|bAX9gbd&%9RJw!Ah7UQ6Gv4T9eF*T?;wH!hB?)#oMP6@<&r|2_k zFt4lOB>hd(IFRX|Ef39bHub<%E=eS{f^yo8^w&J^pP1{V2kKcVzr<8# z@B#nKdxPL!BIFBB9$rXibif)*Wpc$R_HFLjQWlh_3epKA@=Uf`o?SDP{OUwLNO*}L z=k|}|=RN=bG_)OclhP{&KJtR)Swz)SUp+eR#!unHkc4^I24}%V02z|BMKs1DN&Dt= z5Nx>AYz#-)XJV$=$Z(zEuP%{#NO{zyUhgVYU)*DB%Vt9Hy>kBplld-b5~Yk2>E-|F z`&iS3^uTPa`Ik_K0}n{+%=Zx~%;E09Qg@%cq9kmsmqOUL+YP;t9x#tft;CB=n7lJ{%%Q)WS68l%C#M47yy%YH$>G{e0vnVk@ln~EF z2_^0GTFGR~37l5a(M>0yC>;h@z)^e~z6Ct!007DQSa*z-q{;T|v}#e7@CS2#5);x< zOcx}A=r(S<^0WOv~FL@rm<>1Eh(4MgW;H__Sw9lu8*K$AY zYFochx-?z*d~CQOymZ><$E2468xFuhd<1{5r|UOi3!Dz;!WD2$S8F|qYPZ^iY^)%R zgjN}dsfc0q6js=UKTIZrKI!;mu9`us$o>qqB6D#CKM6@2?!{JyXWd`Jb*XMUc7bN^z7{H zX-n^(m>6F_zh+`$&HP5KZrA!YeS?xI^MEDaNH3Jqv9;#T~a_=3Sv$?$HdvZ@Ur z&+1S`1R_mgtJ-N}1s%bpi#B7HKuaT*Y`tK6im`;wZ%02KOyr%^Rj4v&PNEo0FyyH; zS;fAy4Y^Y}p9weIUa>0gA3;QP1V^;EgO0^hoQ*tzdsL3wES=^7_c#ae;2fC6cib120vSspCa_#BSE(VbTBqHp zksGHT)BB>Ztjo0$b$F|!JIzL1kK)i&U>xb0YOC5Ih_zO2ciP#QcwMzgH`HOae%JJN z+&VqOX1t8W64-AcV$g?@`V5uQLLg#?AO?vHvB4N~`UCQ%;JWcC#(qp#5?5J888|Y5 zJRwp-DCA_}vqTs$L^l)l($2%0q_56pzvz83x))D^AaDz;G=Uobk+?JRT`#A)h69$D|0B^KJZ~ z3|Pe7Y=1PuBpb(kGMmIy_Tt&;sio5zo|hIL#xXLqsQHLPLBb2EVr-|A#ETfCQago! z@q|!gQ;S=(Orr>R-bQ4C5vgb>D2y^0g|b(AQc8i06*7@Stwe^|VRaTK7bez+qI}lE zde{U@u$`>eSl6OPD=t^6gcP-7S&veos>Q9^B!yZOu|1E*4IG6!Tf~^+Fj}dm?8G|m z#9DTwuC>Y$ZoEBl`?_Ko+Vn2^(O^L{U^17=g6YNWRxv0;5K&LmrQx zOC^-&=TqxSYPavSb6v}FceeV}9c!kucVBFA?es65>ss2|(e4wsPfcd;UjBi_wcEFJ zuA{ZPv(?XTrPN22tCKUEwCCtnCCQwjpg@zDmw))^0baJ2aTsxSr+) z1z&?laRK*%h6su<4s{dH+jY0Ca?w#I^?Yzfo9;R8)N>_jg?(Pqb(3{75hkTl!{&NU zSyIxqSLe*{=gM0B9UIi{>3{G>( zKc%XqbJg2Tn!Sw|DuD{4k!EwG(rj9DV`FpUA7Dc34N7UXfq%%PS4Ns|8>f5kU|Mam zETuMavb{e8pTH%|fcs2a^ci;FCstW`6o1o%0)7-9hYUm`4hQBvQN8%fv=50@wxB#{?EQ=Op1Rm z6z0rtp|EZL{(G|5865KvT!|mRTfv1G$^bq3(r~C%P9Zm))wDmf({Ww5)plLS#pF)M zbsV~Io!2vF^NXcp^Gn!2@1Wbww(Gd=u2tt7Q8>#s7tXRae*bgeAifLVW#ZZ>wh2ba zTB~Qnlf7Q6mUX}QiwxHQdPWuv>%`{`ewWJU%3YlPBYMZ&7G08eo>`3*cQhf(6 zrZ;{4oSP3ExS69sRbU3LarCq@pN40rTA#&lYaZ8RTFw4C*y&+|yiyvly z=MxqJVFHay&u|HuWYbt}E|4^(*p6WuoD`?VK<*TlE!6TiXheyfvUiOm-D^~y-XW#Z zBO_W#xkEqm(qKpr1K{}#s9X$5>q${55mkYTrLyKR)s+bg*rxC}JpXxLX-dF{e?gt7 zjx^qk5yAlWKcl_n=s8Mnl}Rao78M$;XVi&uP?W;Y(dOv0ntt?|q$dFl=lvtt*U{DNONCN1Is{oz&TYE_WfoZzC_K@ zo|cYvhb5&PJIwhI|DUVQ#mO8-E~TNu08c;xns6CFrPYn{B8Ns~21q<03sjs?Z+|%x>A}CS zywvdW<5Jj+$+Q%flCCop;=_)>4Qp~4E9F_~(94Zi3eu7edB6wz!Z;c%4h(pHsiT;k z{_FSV9N)4={$Pk4YKjTLh59#7S!(%BTreT=N@ZD2!ga#Z*ImVi9bri!h3};}7v5KF zWeE%%p=>81hd7hu>|wA2cHo2fAozr8S14Mr2kUp*9b*Adzfs$}H0iaNJkogV^k%DC z+1YMYsw2J^_Z0?8<>Ek`rTEEgJWwo`1`0E-|5WqzYNgfQS*f;u?si}7`Mx_-7%+Gg z37HcNU>;roSHlb8dVn-lC)Ev7hzPGY;qeP-F#B$0%jBJpD$Hq1`>ti#w&S~=ZM)8p<2cT` zjpIV*V?QyRQBFRSwsJB^WImfnC!M2-C_9h}ql5N-XTOan95?Ya%q3hW={XMuT&OGa z#f^?*TaJ?sY{#+vOfK+U$4wO8k#c6%3@%`(`dKEE-&o0Cm2o`F@*m9f4@Oz%w~O)e z%a585k&HtJcHm#)37CKlup7XH?M?KKhFPfx4a*WB-;3oXP(IGat&=McdXa*eXCd2o z2S<4s<036MyG$ZCmx8CcFqf4=`l)mw@H0^V1v4V7JTzl?a-}^f7^YZl6QkX$9j%mzpIyV3oT@-!)2bSLV5I=6 z;neCf1nhw`@G;}RAfH0Rtc@V>e%M4mo}vmdTss~d<=ZZ!x+ia z-a3tLWvaTnYU9)uEfeXjThOLvD_2w>?NEgrz4|QWXxyZo)bh$1KIZ6TI-S(cWhYPQ ziY`u`^y;=kr+%nBn@;j%5H!_EuA)-q8Fx<7Y5CDEnNm2l{CBAo7E{Y#`Xm+m$sPvH67}=l@&IpdM?$gP^w!W z?Kn>ASv9(Rr3Rl#B~#}7?Cw_zI8kvwfTkbr3_|C%*g_)OHYe%E>ORcr|&|yj&uwI%8N7p`S z-%ejsn)#e1CD^2MXoS7pZ6K2V?M z%YVE2YORBrrRlPrAIaP`I_}ix&N^7Kb0;rcTDS-dhT%^9Tf7x2FbQB5DNK=s=sKbs zQP`Mm&_V-R6;$uE43Hpt)&Tx3=i<}Orm@+rTW8m7wkDlHCua{?EH4>f{_C})qiaVo zr+BTt_2RAT#y8vc=1Xn+QpUyHDBU#WH=u!^#m~YpYy;@d{-pjIy~YGIQfHfR851&* z$*w5qMx*G)%rt2>rKftfX#8pIxK<*OEuJw~Jbk26Em&4M-9ND_2!fH|--^Y3mC?bR z)^^&?jqFP#yksiK>8wtra=BWyaF5o>Ff7hhM~kPIg~(Zs?Ie@AiCsa}3j&-go>AOa z$x#tn=c;>ybm;k<2mSO|e}Svu(P~x=#^1%n6~dOo$x3k{ zkyt2Jk{2xIs?}V+Qekj-OK$Fr_OV>-_agQs4u1cusUsXS} z{Kv@2c2eBRn^RIStm;lZdMk9x##cewl7Pr`lWWKVH`H6*cy|J}i zt~ZCvEIn8}SR6EmuR96Rx0a>)TuZr$@>aTEF4t#fCJTdu#WmxDgM))4f8PqP!CUca z3Gawm>IMSt zg!}P9d^s>kLYCI4V_a^ZTo^%}q)ZJux?F2D(7o|TFALR6loIZbT%puU1`|QxtAxtq zi$4Et`}4N>eEyTU+|k9w2pC)oH{C>D$RiiN_uAwsf-hqSb;p&_ND9vW6k z>(>sKC#Saz56w)K9xoP0tAoYjKy9pW+OSee4G&9#?uKac8K&$C82l!@4zI_f@Ec}T zbV-*>6n)waz%-UpRIRl)kQcdX_%sS*rHv_Jf-oD|1}z2ZE(U7UJ5jxprKlphNzI~A z!&pTWWn@Ayn>9+Plk1+~v1+YMrDYrBi6p-23M%qG;}IcH2yQ9HMG(l86et3hF)!&d z7PyQfr|=;zP;ku^7lLT7m=r=ORLt|pOhd-p;N(LT@QE@lJ+nkaDq$XT-HVg`eWkuW z47<6J3RDyTYH?EwTHklFI)cg#I1?)`MxL}eV~o6ZfW*H`K@xNQe2M*r{XAUj|m+y;=LiRctYVAk)&|Jk(Ea4IBE6NP)Wd4p)J zbnoe&om{K74Aj%JD=4EWW6(&yzw8+dy{{d=@UrqWFvj>(M29Eu2vD3S@Mlc$e9x-L zcddqlp>!SZkmtBEJjeyZsfPt4bL3&NN|-QLr@mGt;#V-S{7=G?jB_R}?35^ucY;iq z<$vlUMZ6D_J)366)mm$JhLO%>pWGp%yLOo(5~ra9^JeW`In{{Dtzin8Pf^(XaI2vs zT2a<$rCQ~vVv>g=$|hUoh*OSoYgjj0Wx6g$tWv2QTYm6ZrE(099ot!1hDrrLFfy|I z;K&Gmpz`3cr{NgFi4S}LM|K>;2baHLa*iCUR4OAQBO}W@M@B{}I8v!Be}fWm zA9&q2UU#fw=FxY-=kaWufhoAT>!Wy<6`!SOabr5uprbH{E|k&k#Ht5u4Dg5s^v15S zNb%N1EYo14SCva7*?V?(v)Uq;FfF(;hIo~3+7peB>54^>88l{Mk7>eX0y3D3ndE1I3r!UIVVlcXf2wuxz$=?n!7d^ z%3z?MfiGbPXJ{Xg3}FWYENW1AWRF7O0E>DgBE6L86B0dENM^*MAsMB#8etr@zpE{R za|BWZK4EbbA&r<66i&Xn#UzlrZN>>n0fZctX$e!vX$07Fa~8J*HH2y2Gz}xt3&s_- z#A6mJt~DrlJ^UWN#O&a*2`-20;rHRww7RuSQH|8M&wzWfajOxTVER?v-s2d8f@qrS zv^&jqHy(cl424jmZRI?wlz}(wrQd#?@)yG|D*JmT=M-n6Jnh+zW~gi?v2wn4EJek0?J7oI3tLzL5r}K%)9A64rWeT}TSz5{^8~FS zVBBUjlQCMKZVAGhJ_^5wyKx4Ju$l0tOw>4ugLa|mlf4<0ThC+(Vv-iE%IGl?Werus ztW?r*xu(mXGZdtXNn&?HL0c;7In$9uK@(ztC%{vh#iLCESfy!Vb9Z*EiBTp^hkgh5|%r z>ga4_Ewi|U^mx*o8g-&z_A|e{M7W1S2{;DvP`Vp1A-S?pDihYg{)_UWVMtFRnT6Ww zBMgvBE{qRelHlTk>^7p=8HQ%B`?b<6)TQp?SR))v5IWh=X2S(zQjkC)$y#e}WQ{g0 z>ZLU*M%JiVLhLC9=vN@>D@jqL!DW1VCRBwB$ry#95DL#x(+MdrG>}Q4bblo0|3#8h zG=kzxX(LA{?1d}NY55S%n)Jiz*F=nPn$;x931q_fXEZzxpT-5e2zJ5s@C1AvzJ~#3 zd%a==anxzoiB!EwtT$BLte(bkq}is(Lc>zJ4`aPXc`xY(~?O}kwDBrGdB?PnR$%V z8sS_m#MJ^1Q)`GlsP;zb;j&ydo@FzZnH($|HLJ(If)9;Z-KkQYN2QvZ38l!BEzF93 zhQ>f)j78Kk6NyEu%cyE*E@d<{s1Dti8bb8$@QCy{!^OxoHP6?aY+!Gf5wIZzc_^Eh3fkG>AY2T`;0E$#wJ9iB zm8z;2tfCBxW%k18rO*<4;#(3G#Ploaos-XIPd?M`KGWN=!n~%o0*jSPl#vokc)B0@ ze(1N&*RH^^gTQvyo2wX9s9PxCcw%5U@tlNN0aUE z&Fg|-$rK{ZafdlxFI>x}O=Og9+ZN(qnAFqEOM>7+bG6wV8|L_;-km;kW%h{K0qgNY z_#PO563jpo*1}de%b={Z0w;llmeF7wM6(oVLl@7{wN|WatxmME3?|mCIGV;*Twa}4 zi=UAPA3s~`-Fvh?TkA9Q+00>Q>oas=e*Ux_^F`0%TCn2!SInP1KmVHfL01XQ#Nhn; zt-rl(zThe)nJCQTEwe&QPH{df^~IK)JlCf9y;*J_oD|k2)>);c^3CIW%S$QcC`^^k z-%{H0y3$gqw3L>fV(C(8>?33QN{dOlN|u1Z#n8dW@EvBa1E{nJsZvD@a1}^6Wp?f= zx0?8h?TzO4bZ#*EUUsnm-D_)i|HH<{jvbASd?2$kJ1~&l85ZxX6*g=zTOE46m9olbZN6(Rma4=W(5th0~>&*bCQXKo&Q|Nrjm8#vca=+hk4mc$fm(AEt_FBQ9dLs~f&rV~P1c4EVMBIcGQwx55CxT+KFKQL^2 zX}?%yd)D-wKs(nGDTGSe%jXa7ruOfwz@?40z88I@Gzvz*dlB2Bh z0KVW#UPux$yH!i+zR{IT?xA3B5dXi6clsKfz)4IS|3R&RY~sKPa+1Q=;JY}9c}mB) zOzD{K0E1iMcDxhs0u8k9*-GSJ!1ap%fuCY81@7hht zXQN0MM0UHEH0xpoybd0~7vV93eGgKk+b_hv8&SE_h{|}uvmow2KXT;okt2A)&jH%k z9Qo4=jgRvlcmv*qS3n3I*bP@ww3Uf8SL9u&RcljbB^qfw#?Vp|tn0`iM3uJiGBRWX zt3}g{;<;(zduyJNjUM+<8nV6&2Al03N$B7>X$r`duY7?PbEPHpa5CX#JBuXlYP5Ke6_=P}?FU7;Y9$_B7N?1fr19WbrwJ!y_XE>!3}x znt-d|N_-Q(5w!7MO`PiU74aE`wcZw3WEyn6#AfAct69Z`ThBfB)`J%sRye0WmG19P zr}~|Z(a^|P->jF%H=cXz!GpJcY}JXW|>F$6OUy$vsU& zr#s;Rya}%|{@+>hU!qnN4NEFI?I@vYO~X;NdZEWr6#j4$;vtqIVQn^t<2{zbo7UNB zd!3!m_r9+vd08JBdX}5?E$O(7saR>Bt5g9cvqe%c9%zSK86iR!)4k`m^uB*1;~AE~ zz}Z1O!BoF;n98i5T=zDV+?Gkn7DcwVR=o%Z;CuKmR0)yIs0mILr!g?kjG056y#d$b zMx<4~#Ppq%+3SJge`s9L#u1(NQ6bV=rTQ@KTM2^Rl2tD)tQfIB&J0f`ghJsfp64(*iKm0>E(adiitpp0e+QAY6m#g7p-1> z2}AsqYn&ygN|>$xiZ#DFEQDdNa{=<+RrqSXw!q^&@#(N}U>+|D`%|g%Hxv z;hiVx@FKYB*G>t!vVlogWYe-E(zS@Ot34k0wGs(6S(7R$ZP)Uxi1i7(**RS=I#aVLwc#pvp6k#hqg&*tj7t!^;|2HUWcAcV7aI+{X z9-!#K|3|6)F)6haePL>l*wiU!;++1!zX$H%BB{q-aBEv9g_BzU>g%T+K*+;e;fr{H zw5m?+l*id@$63&_KD_)dH!gfIZr?7W~04 z-^$wajr!~VqvnHz{7HE*4U4dy_R;!fDezol`PWR9^mB~XPyQMyb>q*E%YXcjh{w#n zCAZ)$5J8E2e2v)b-dn0pu&A*Cn+q7x2HH`%r71?IdG|fYG;@X0Y*D^}hicPR@dK&8 zBYm&<1R-)R!#Bw@1rJ~K&AzKQKalFXsLw3X;P3|cK3CYW&>EAGtvy z41%R+e0@I%gJ=DAWB$&Dv+xh_-QaX>E?RedrHC}O@d2I5D@L~P7zS6A~6~Si$6O} zKS$b!$`lz}{?kh?(T?-(jdZc`9ox2P$JzS!l%wCiK(1ZRN4j5c?>c%*6mZS?7Q&;s z80p4qM1BKX86~hbfKl8C$sp~R5T$91jnPRi@fw91 zbSOBkRxK;q3AOupt!gfMy;-ZqfkBmw&D-r*y0y_PQSR!RUFg=t1P!ow#w?DG>QS7H zqioUGTuRg0=YS=!+%=YaTfG@U*{aS~TlCYc(IRgo?__6&Tn}b5x!Jl|3r-%A8S>0E zvfW*Q$w${DSAUVkw4rjNh+#R@5p9=5E3j1p)og%e+FMSQOA|;Ns7piWScK;>ObY&B?^NwcGu)Z)v|>k2A)T(OY`0Xr(Z@ z1yn%(0@^!{aDa5|JC2`AX-RNDIYEr$+CXE*2W3<2hLo-=$xYTxr+1P6qPLRS_$7?@ zV&x~FTLqrZDdrh31+|PL zx5L@k!OS>?e$J>8MBjM@7Sel@7N_@_X3dxg7A5SGA!W8LxXGB%h{R%a~g}O6v|XI5_t@< zK)F+9F@Jy&W&UQhXg5wn<`+i`h0vn5GCM33MvD$&hF+VT+%!2-ELJ8rO(tfAn-5cz z$TGRYXu-iWW0b+r`@1L0}FhaP&Uckd3o1L;3JL1j2KjCVW@2zStg z#ZK;pF-8H24qCGyDDEdFMO^FNQntHFysUz#IUr7HW3dgUooxYSj&_x4C?0 z9<#dlUrh7_GqV|qceC`jh&WGsW3r7}kB1IN`2o!>@jiB~dO{0ZyiW-s2ZxnjpxA+f zSznti3^pn~9$7YuVa0XPLcGa*$r6R?3=b-`AUUcsAgqTG{2YFtYFII`CRDUh0OD+1 zYd7L{r&Z53I;Cp4)oJOtq-&bo#$+~k^@L8frtrG-a5|b>*GZ)ZGJ`UO$u#;EOeeGR zT+2<_NB$Dy0+w*8RLN!+PupGyYq6`cu6Rv3^i+CaVEx?Mkqo#0-j+XO%iQ`G9L9B6 z!t>b$cyYY2v}1d9Byzmmc&1aPVG$cG+Cd;{{FbHc zpm^3%^8TH-c|h#(lm4!uO-sXjF{OPcGr!07184J8hM!@3zP0|`zNx8*{_o6Pa18Io z2fAyTGTog#2I!wP)HM0f6R4&!Kuvo#(*LcXsjk_k|I%T$`H1W7ry{?QTZpqV6D|&K zU@|R)EqK6e9&qjmHZDYRF(qDJ&h?jZgbsT6v`j{3x?Xi8?WarCU_IrBc(skEehi}aI z=O<2=@+&Hz3BD4fznF{$2BM@$*KFUnYw4M!mrQz_XY1Oj)P3?HaM%p{@nL)viZDid zS`Z-6#gV)2!MGdp-Dz!Pt)@KHgpwr9&uq0vEuqc!=F!NKs6K#Ei1$m!^ZqV~=szh$ z!FQt+v)tt;+ykzAz{R4azH2WeqbRvxf0x2e5N!wVPiYIoV2(w>g+atxK{$vazxDrW z>{@=@NXmG9?uXm%w&!7wCo_{|obkMz(9zhQM|PF~`(Tzw2yAw>KzL-^-8FW{)m@#c z?io9~ycXCb(sBW5Pn-}Ehn*GTzzuOg;;<50gaj8(oVf7^1ff|})$Pdy_GQUczpCo4 z{#0FERo(S{-*5GW*KV2SE%UiaW2-iCYofMm2sMKGvnw|cpb)w60A4|>(n?3FT?spt zwXIKyeL1n5k{ggh?@z9zcedf4rj(6xM=u-a%Y{Z^LN6AUr?rZj%bmMoKym73>tzdu zdhy~+^Jh7uIC15iYF;W%7mE7%viQiGE1y@3MMYnnT1pwocG2(P_pphkP(xU5Rkk`a z!ox>isEG-m$U8#K@S}42wT9Vfn2j`gYc@@#(b4RZ)o55*8JNvxwNkBCtLy6`tW-ZD zEymTikpl_*63s~;Qo>PQLuhO1VreNx5b5U;62)lk=z4(_l4fX(L->$Ru_isvh*bO7j7X2Tm#V_?&`txAlnqHTBME*a3H~8`%u8CNssAi4 zrff(l!BMkb|NEmts2~4PFp9vFb4$=TJ}OluFH)saex_b;$}=xXUZm#Y;=doA_I!J5 z@bDDJ(cIkp{QPNVBuSO@>hj#R`9@>@+T8Nd`*R~fxio*Hy)FEoTtzR!AK(a;&>{iRC+z1kuiaukSp=;uER(Vq6+N$u_h|7lREF8=PE8kX zwl=qS+O^aUqP@Kd@beXbwc~fzn((w*I{u5LnZ{gYB5zgke0AaYpK2io>m^0Enrp?$ z+Hdu8@uF#1N`BUYdvLZ=S&+fXbZMqof}@q!@2##wzIptx{BB9rthX&yv!*ACda+Zg zE_k3D)>_ldTQ}8Nt*Q%W)YG%@tI7f_R7FjSRhY?Hf-DJH=*`sQ0s>^BGa1ey9kny8 zpf&U$!zwb-I~mrHiT;&g9ho4kBS9@7K!R^F#W`f5U&);^(=V51%)XoMov7q;Pjf8V!zWEAx|dc0(uxrZnUQ7mJIdI+OBYNN|&1J%(k zs((PmX<0|mM#+C0ZAgkqv`52ukFp-Fw=Zwhck8DFsPEQ0ExTp6H(FQF2h{txz%L;z z=?8r&V;!}mT<9gtxKG1+>+;6Ot4FMQPyOVln8$uUk?TdNF$h9+R);(oP=F$sU_l9l z7nDhuf@!Eg6=t9Yvv3CHU>+9WEQrmtMX19CSb_#D!wRgz8mvPTF2W`F1lZCy*#@+r z4IS8oE!c(~xB^eWE<6dJgsUJXn9smd@H9LF&%$S651xb1!RO%%@I|-=UxMrKWwZoe zf#=}{yZ|qPaDew!_!@j2Zo)U#zo zwEge^vlx3ye%CE*x`_^>D+s(s3mU_wGddo&Z-IdEc!*{woE92bj5Q@V6maA^=Ep&yUT9&@@` zOXwUs%d6AA2=?)uCn z*pi(lqJ9`-R*Fc%ZGIo6y_Sr(JRJ|!MzHtCHK4-A42FtL+J^}~It zIRvvj^o%5dt8f|!}}KP-o|ch_ZUs0oExYCKjM#B*4miyP{a;nvG!p;- literal 0 HcmV?d00001 diff --git a/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.woff b/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.woff new file mode 100644 index 0000000000000000000000000000000000000000..152e5b4cfc7c4c4a93501e1299cd76b08f99f111 GIT binary patch literal 20032 zcmV(#K;*x7Pew*hR8&s@08T&v3jhEB0B^_u0RR91000000000000000000000000( zMn)h2009U907)DG0BExiZDpiJMpR7z07@hP000^Q0010$Z5I4TL`6mb07^gr0012T z001BWxBvuCQ!g?A07_s0002q=003Z7X-+0%ZDDW#07|R?00C_P00L%2aiwr*Wnp9h z08AVJ001rk001@)O|>a#Xk}pl08C5(0015U001NeFah3ZZFG1508DHE007(o009~u z(HG-wVR&!=08K~$000I6000I6i%I<{7DZ*z1208TIf z000mG001BW0{{VdoUFVDm>kuWC_Ftg)qOp}vo=uLHOAe>1YB*y+QH}^n1c_h}U>mSO7;Ix~FUA4vUFVwrmG|v`PF0U2u>ZHa z-}}2~x~r>hIQN`$PrBz`UrBj+iO=UdslK7MVM$$MUu%6wiLbQ8S91OQ#?twjviW~0 zmrwH1lgj8x<-v-Q&wS(L|D^QT#Yur$I{)~*#hlK({7Ejhd7(vqu6ZYo(?a%agmN**j(SyEL}Q?kCKsieK6yJWB=RU($CC8H%1 zCEH34mb_GQwB)BHzbg4%$zMwT!&m7$$9J9YcHau$T3@5D+n4ewzN~N9H|^Wud(?Nt z_lEB+-%ot+`+n#9lg}yjm7Y|3a%r&i{L+g{uP(i<^uE&7rPZY^r5&Z+rTwMxQn6Go z9WI?N-CDY{^wH7-rB9VUU;1+Co274+{9WOTpDR1N zY)RSWWmlHnTy{s<^0Jj>YszZM)|a)Eb(Zy&Ma%dywJcLMS~gp@wQNt><7H2meYfnb zvR{;aSoWu~zn1;2JWzg0`De;MSAKT+x#d@uUsHZ-`F-_G>n>Thq@%jMrlxsAP2bv< zs`hpDRn6;`RJSyDwzo7k*0guj*Ho=*t#4_r=~&X#P+eEmRMT3u?)Xi(*VWqCQne0R zX=v^2tm*Bn?pj;Z(OO^AUegaH8XM~BJ9}!@!rzihYSuM$HdVE6sOenS&|XvB+0x$E zRMXs5-PqDm<9;k@Xzs3Q@2pwZ+RzMt$9}fgbTstWEJ5HJp%E(oHvC!<=uFlSu=3?uAf4>?q zQ`Op0;|+O9M`ufG@zU#R;YU|<@v6C|$K~qUmiDeD_o}g_uEm40s=KPQs(s1Dt&LUH zHHe$FExpw(O-)_R4V`^;?Nw`wgYKxQYOk(80ho_L;ZdQwWnGQeVpV&4OHT(>_P)El zZ)mv{aM|Gv?O2~nF0O89uWqbyiQn4N(A-(m-rCsJv81D|s|xyR@xH5om@xbreC%DX z@9OAmsO|H9G}hF1dROg8^tDy(9bU6bIvT4w>O1; z@2j!4rEy(N`;x}G;zdVOV@-EWBMhyfxwfSWxUsXorKzR{M$+C<)7jb3T-VXr4lTE= zT@TE(q^`ZCtF^kRsiV7YT}@qUZxevuv;^64T~&2;4b*o#ZLMnW1P-d{=x*t)Y3u-4 zHgq?1G^}l`X#(s46z#}0MY>$#;%&*gX79VJwX>nA20Cr-fM#l%tNXf|o4SB|x;lX0 zfxDNq!p(+8_g;5PJ1%@4Si+5;Nz^gVZeeMtq~bG2ggv!Z+!g0x7!D zH{+Z2&G|O@Hv6{tw)(dDwu5Bd>D%So?c3wq3o>?}?=jzg-vQr2-{Za~d{6qG@*VOW z_C4);#`mo6Ip4Q@&--2gIsBsUCEv@wZ~MOE`>yX5->bgYeBbkZ-}gGm=c6T`_r2-+ z0m$jMeLwX5$oFI4J0P*&_5IZMGvCjB|LS|s_Y08Uzx4ge_iNv8d>{Be^!*m3`M>%8 z-S>OnAAElVx&CM0UwnV{*}i$-|Ks~#KBuJ5_cz~v`2M%=KTAtM;+K||m6n$>rT)@D zDJlI#X+`NG&;p+OY$v;g{nO&>7Vr4yr$1Bi znfFh7;<=u08WpXMXRjFP?SLS^w+ouCwVmmFE~=%%5BJrSs0~J)gZG zzGUeV{lbz9PrERF;U6yAbJ2f%dBvB1aq)YX{L3Y0UUK0jjhAe?l)3cD%f4_Kz3g|F z=e~N)6-TbT^{PWxy>RuHuJK(Hy|(#=AKy53Q}(7G-n{0P{kQyaDOq~T(%{l{OC!s+ z-b!vAxvl56{<}N=cF#ZGd-1*3-h0Qr zKf3o{zmd7`?)zr$zvTXxALx5v{=p3ozOv%a4}DlU|7knKKC^%S)1iZbXV&b0An2(6 z2Uf3nAhas*z=5h~g0K1~Xl^W9&=^T zzO{LM(D_vBJATvP5{VR-3^{lClSw_DHuQAp$8*jp{`IY!w})&J*gm&ud(eK#xtWC@ zV?%|$ptITEH#FFX4fPEVjs@)?Rp4!(MwzkU!dR$sevz%RGi~jG_~MyX_Y}DF9{r94oi>Q6`x(%(P*cA$uyv&NR9;HvHY^vHP~R`12Lc_E}bL zD^@n$$}c$Q!N}*fhu#isZ=HjIzvkc2+PXe;O$aeMZMprLf4nf*3wQbk2YRtU z-(X=J%8wTcFh1p6wtd@7;QGb?r%$`3orUhVE$;Il>w45*X`f+FvL;~FOc}|L;Kvar z49*N~on_Cch&xxaTPCJ=g);V8RAyZ8qJa9=hET>ilc^o=+#0mw~;yLG(xnAf|7y-ytL9g!11p>H1gjxY3GIsaAr_w3AMZ+qybKznyjdvK+n z%8DGaF9_^t-c%p_g1^4Gr6J^85TJeOfm~GAO)ahD)U23Ek$r8W@3CMIN?{H+`ID$O8)GBg6^$X zR}yEpa~f;=dIPa|JO;qTtav8)sS}U0mGL*#ZGgw8H zHANks8`-{@68kp)b~@ZLT9xUZUe?(ajx>(9jCB+`3O$rm`+I2TV8=jnZ&OcyzIAHi zmCQzB(>h=x^*Q)xYi83rOczd1ghYRW;}S^X(2>PRdt?ch_u#IPGDFVQvrsyfOmeA2 zC=&4}fGL4#)4^OgaEy8Lc|U%`v`hxW0c7N_v%;!Cd(Y%d==(r{iJ9QIUwvZlE}+0) zfC78!v~QA0|IAcRd+6psdsjE|fhk=kK6_yQGocL4NS0u39o;lNxp{VXVK2yPd)JR~${Mc7aL)60 z)4ptYk~alYG>F~mmlJBth-UgGJ0GKybi~XL&u-=qOD3`7&TEeT<$yZKP^xw3yGNU6 z2e(i%{d?B#vB#YC0VzPK9FYd4Fy9x8CK53&Ogm_QCN`ERNCirXlw)26LZ@A>F%lu? zPUH?`n{>#&^DcN`e(XUKAKZ;KP{u7h03io8D(zPLG*%Xdd@Jp2x()y$8 zY3tV+xGf0YZBdbcT_jaj$h-CsBP)ucQj+njR1sy7taBE#&(e-J87L)FMG`5rN$l=0 zYen?|+DV*1fQJ{zFt-bhNO>le;`kIF=b}P{_S3Kt%fu{xn2yqMvoM?;&Wsx~fFGG^ z3@r#*&g9L68l_=6#1ADz3Er|OBUFSNW&koxRmQ9_9jenpDia}>Ig3~tSCTrfai(a| zVVc#_R@&0?bd=Ccuf+67Lwtfua(seDiG9}>SQ^L}=}d@DQG1ItMKWAE7Nmg~2NKIU z8i?tM3`o=!^H;Klq;oV$oRI(p@gazWO3ulbFakgY2$QH&m}QbOr&Az2ltJKBBFpy4 z?@`Ji>Hv9m%6W#>HCdGnLhbf1-9nxIU%X6#2^go#o@GJe$s*+`NGNPSaQR(!d)KeN z%ber;zKOu~h8Hvf{zans^6D{frueLvLA8dO&_P1h8-poU=Z8gPY@qAALW$e03L63VG5 zxXadsfBkK^T%oE#gS46XxF{Nkaf zUVQ1{r|!D@;fL?K`>BUt4B7XVAKX>9YIPm{?>caB*RBI0`=axJbw}kfm^%a~yDJ6g zg8`HR36uiPON&7|fDkYfp%tJ@8PF*SR}73&s3ifHf=<<_s;Fu%m(S&Lx=fVOqyN^(1QyleD-VDt*oeChoo*7lQIxQj-zoS zMRwIO(4&MsnofG5EDlAF`r^0DTuwR5vnLW2+ z#o{kj*aw`=tli?g!}ItQSX~hmUIk3)#41Nt1sh1!WSznY zBvnx1Ddotkb1byUa|yB|az)MxKyyNxn$%RlDKS)&S~Me=(7lm@?nzH0OATnM$ez%c zsPQTfO#yR?yvv>vM+rxHnb&|&8a4h~XsEPT*z4J?8_qwU5d==)By!_miZa@)HlxnS zmIiug>y|BBw{9g<-B(>T-pkn4_C9uF`*g>2M=pym4Zz`ba_{pD%ofV?k!Um;i;-#P zR`!*NSME>VA|=Fxl#oCMQ93j{G(0#gDxxaN(M*^|W2B>liO1x@Oq4uU#eg)BWU)8V zEA{A)QhGYmjr*m3L zNy%%eAMjKMYYD9qnxe^dXE2ub`G3jga zAxfSD_telB=j%7J7BxlST~1BH)ENjz`T@8EKm~B6X_{Iv>2;#(hGIcap~Y}dgl8!= z_XCeHITAp<}T z*#lBh26hXgSs}=%eI;1{??8~qxiw7RP_7WNzZIZ`LUvRqzud_bOqhKFu}KXBR*9~i24Y2W^ z1FOlwRY$sBq-6fa^279S&)#)=Y9=3|577I&AF8Rj`fBGCdi4)$_VgU0&(dcm4(-_k z?0b0PX-ai}c7efU*A=7zZwXAHleI z_3C3_tU)k3kGNnwO%L}X7!wcC`{@HcCxX#^h(1G~oh*VeewdOO=bP-Sw=!muMw9T0 z(xge2y>jcVjCY+hY1TB!E3e%8D&t+7W|o>s^2)MTUuC>&uPzY&toeUo_tb((^8j?a zqNaAC+uhxVq1$IB5AWHHgNI(9b}>a(IqO(x=T#=1rul@GNQY@6L6$CE_8Q||#}jnO zN|0BVEnCLK<1`FK5_y_VlUHATZ5iWTdv$?g&)a9QVpkLtdnDXL!+CP+D_35@BvUa8 zQre&y!zAzi}IBRCp1rdh-yK@;Ut_&q2$!Itg)=0O0(HV>pQNrR7bW*TyWRpMwBm|JdB;S7> z2t%_-JRl*M&ez`b(E|33_KhDaa-(x2Q)wscyIGW&ib7sIa`#INr-8=gL4_xDp6p*; zRmH${Qs+R{yL#OISKDASk{Orc+^X93tLQiBy1n!XI%7?ZjO25p=?Rt40Iz|_1<@<( zplj6y{iq6Hp)z0@g60Ot2z(6E(!zLp5;U+Zs3}9TbTcL-c(8P$;sA}(mg$bol+4ko z33ZNImWlGlJ^&Wo{x!f26kNGvGJ-6;`oQYdjCY+dX%0AM|Nb=x81LFNbI8I6Rv$RP zc-K%jk^mOA588iV4M9T_)j2;vQ+z4~P8&EIs9nHvAQ$`?^r(nK&{Y@@`OWK$p_x<{ zfd75=Fq@$UU?X+MgWtTJE~WQ9M_;2m^V_B;rzW@N_9$eqfYT_0v4t!JE9$^%M(1SE z7wBY(B3ZMV5qQukt}^4*6c7o_m%<3sEOsUa2D^LsHrh)cezf{2`Z(RYQ#&YU(uO4g zI@6y?r!$#E8r-Re{gw8`w!p>{X)9xb)AjaS@YJ-dbTSsa_6LEC znTW-ciCE~m>-=$;b676;128?b`CHfzet7i-=U;uz1?OM$_J<$5{lgDKzjp3rt#sOg zsTdDk^OoOArsGgM9=!INKn%X)CiMUIwf>ly1Ol0v;9JnrsJ)6kzrsGx39=*k{7A@N z9vBJd2ZGLWKdO_Evpg`6503=x_HuiShEJ0CqcpZ2Uk$jiM7?!u4Qw62>bMwFbh z{XMil8<_y_LKk5k)%oSP8aLvZ-ifY#bOM7A`I*i9Aq+wcI!^?WU~?ypB-k+1PL*E@ z$PrpM&=Rhv(e{9WTjCY;( zr2J!5`yV?lK3vz(2 z4Nf(XMM1LkCX;a>OD7#j$Ht0e$vG2l6iL#0a!3`7A^}x$EX?0DSYyz4$nSZ67#IrO zbNs$pA+G+yJOO<*Z#8K5%7m z_*QH*QILjV_<0=u!13V^c*7rNFQ~AWEoYZh*q>b(8p5&cID^8x`SHgg%>4fdArfd9 zEWX^wAS@Cn|1k*BqxcwvH^0w5BQYQXUEvkF|3N=~$CIWNeCGZ@0z{x?rmfJk&-g)z z!F4hY!eDIvLiXKPmtJw@vZYsqnrMsFIYPeHGxsAVHj$d98_7>zeeK=g9!F+Dz-K}< z%^69R*rfsR8o_lWnPfT!A~F`FNi7BTEM0N^cDj#2pOU6YCSk@w4&zfJse#p67U03D zrAZRA5*ZZSLAOPP&~LrDo<72W`;o+_>7*5`oUfR#06%f8N?Ju%^{?Hqp>e~Sez>Gn zqZ_tu*xLUXJxC9ZKDKQe*|zPm(Z}J}WBuDUkbb9)otrJ}rhDn$!CkGJS~k@dYUnyz zH`vnB+A>%NmoV2gk&M=+<0z2U5XdHBa zm42qMI2}UvCj;PTb1Cq!gU%G9WD@u+B*h(`~JPb!;buts$)M8G> zg!5q`N(bcuI!MDbE)PW|{`O?Ol$6lxB8mXskEkWV)1#yG2@p70X`2N8%MH@!>Gj0) zo8ZjIrnXz#rR|claz@Dz%kRnsQLob17;BU;rNc65oDZ-UR#e)j{f=$i*8iCNxV(S# z(Jfmx&FvW3t*J>hp(X*Qgph)1)zzM8aLM2m0w3|WG6qJ~ zxSG)8hO3EO>pHs0I#!bPIQ=pKT6*GL6m<7fdR0q^Sf<+k&OoRB4{a{H_Mb0aE zHD|(48M~2DJ8;9{vB6>RD1oAyse%KUO=c5Te9MN>D(Mlrrhjc?6KQN*JFp6LqyG_L z;>K;ne)tVm0#^;Xmk9jJLF&4J01?0CP@KpC z3}pv#6^i028#EskB4SiXNx)&yt^~eqD?^b;Nibe1F2wOf>1R0!7v++T@j>~Ncr4GO$Tw18~VG^jD0CvY2A3WAbfg04ddQCM0l3Y3w+m<(N1 zPH&;2>u1Z9sL*Iy)xoXT4K1x%nx$GqRvB55kw}O}ASt@lE{y;pxd`?OVWGtbm$Nbs z2Pmj8(L!>J+X&hS9QcS9Q6m}&YmAE)sfZomgyD6&8|G3(ak&DiC6h;E?DqmlJy9n8 zar@K4@h2Dm5@YnFy9&KVGS36(h#Us}dVsLozLKp~7|Dy7Q@0~3_k*N}$o)zm z6%<~hmSjn3iI~G84at7$j*4L4p&taW-=ITwV8~wyN&$<{h-uM~FvLP+H?DwVmqT*E zm^C+z%@7PBF)oQI5hSJ|($o@ZB=KIB9}xTGUYb;SgU=-L+z79_!5m;3#GVVezc^Xk zCdJo1f<2sPM8HwVzIh*u_zo7f7q$&=)6%j3(z27)%e4 zIr|eVxRHZ{;oL}2F643rN(#B~ATAHS$rifpliwShVw{m2J15ZdTPb6o-R3%TAlDQH zj4xsr0w(B>pffVd2r{m92p)6e+Db}KnGzIfV;uP~E_&F}PF(b`&j63e)N#GA5OA#& z(W^#8+70ZjxNDmVRVBp)846ks7fiy_jEHfag*6q)@6I-nOa{OYM2k?-6jdV5X&1AF zE&zRCim^ZE_`M%6j=!Dk)UkGOuq=^c=6E+T?({aplC-gB&h!@G4(a zo{v`@tBF?je^C?eKutjxyqZxfk zJk1yeTia>MluFGy}D zGFX0^8+akRoP%|Kw*)Yx zm&^+zbe3+mwvCd%oqC9+8Nos~7^>44jSCnQ;^T1XE>CGG4Qv|X{6_$(qu?8t(8l~O z$N;m8XZt?_2F?aU)4@D7$^4alECmNY1=Vp$9?WHt7wfv4z>tX11OVKX?Me3;q}S+C zyXg=e6bJbMVTc<_g;P;J!NvI`yup`4ch9L0fN7O71=Wo;yM~N1mYvGXq-WDp`UK5V z@I6G56)eFJOu^#wDU#-ye6k>n(oJ+%cHa~lFhR*MH47Kvc*9$aj*~tMpBZTFn<_XA)C8({t9Rti0PPj~xL(rs1;)+sgL#Hi zfH+nD2#smU3~@^Ba~RGPv1UfKFnnTZ8JeS*q(k=KLv4$p8O?hDfRWwhJ+v=5G&D4r z?591%`Enp)M6@u4y9u~El;aMcw{zII?X&!EIzBdL`!fEcaN*vmoIlmxRTL1CD;&r<|L(`%_{;s>=Xb>t zx;@Z5`QkYN{4I%|*du)bcPdM=JB`Wj&+(%q#L4acb|;EloG1$U{qF<#8*jK1XSY+z z(g+78S3Dl&BY^ripgyX_t+>TyX^z=4P?p`#1 zJNuE_4pjReHGA|C78|;3{ucHt74{R(XW3XH84o&Pe>`DkLL&iOB?#JKKduyn($3v% z1~0&+8K9Jbvaz7E+aEhtHWRdSKVt`IztB64_xBo;eG!Ss1JM3J@X3P`7NMc`m{Ku+5Ur(2x_{(1D4Zs5|9y~VUqi1_9 zLCbCZtEzU&g-o#*LR= zdL#b7@!orHyzyS>_VNeTta%_rht#khA@(WuDNIBUt3$wd_mw}hfB!QfIxOV59C1!@ zPG)kcyfBQ(0jJxW*l$G{WkuhE)$5in{XBQ=bEK-e*FMb-_SV!f&(^=8Kk>x2t@-__ zeZ(1ho88bDUSq5!-^eoJ;jyP5-TT^Wf7Rc*k39D1xO18l9N)8xxqs)4sc$|4O?TKk zSs>L0AQenj0TV4z?-SbkMX>NWo`yQ_fO z-&nb3IVHE<|I91jefHV!hMuEO)Z|wXdzD}DM28!QBhHU6X7_B_vS&}zmYUkerka|q zO}oJ*UuB=pcJ(meRdHb&rZF96VVcX(Jj}qXWsx8JfZ4QZ+8FK6MRPQl)eY6a>>{w? z_1>{=vrT9hI>H^D9C8ppqsc*|}E>+3eR z>}EFe z{W>c4Gn(XP#U;MM2z}X97Gry|sS>r2z${Ejl}%Fk4GVqh3B*4yB_;GQkzX`;_TA0w zbVsg9YNQ(?O`V-xolTMTz~)W4&gsr6ux+-}ZMiMe(`0&j3z&u5=$6QICmH(+OXs4q zJrlhXZP^yuLffLfJv}|qHsG|DY}-Wdc+YHf6D6DIY<7HNVk|pHHv!atv;U2~vSPIS zs*0!XV_U}qN{#_TLk1gQSQEk_K7gs12_YfI_=rH%rU^<%8B0woX*H`ECLJYuU}GfH z66^yF-jgJgpbFzUZ)J4NvN$ak=XhLanV4r-=Om}vPEy7>$?jqeii`5ZUgT6WXk79s zAq9URYm$EWsR{-UMKl9S*igd2=|l#d2eI*tpd-}$s8*z?hXU4|J_{5AgLrMNi zEVvUIh;j6oQdCgEv)ZtpK_glKflqZ!BipuZ*bYt)AV^ig7KTx6TfcofqXLptK@$L- z0zmp_do!D{;<1o~dJ+aGpphdJ8W7xcI5957xuk0)2}Jf|8kQoO2^vWzK(rq7Nk#Vo z#e6ET%`lpyCb)zU7mFEnG~k+80>O|IA~1-iy{urY9WT_ll28*m=3IckhVgX}Vs(s%)vz?6F0^KOXb z2Z-PzY0|uDrqel^PI#6emUjU+bxX6{!GHlL2Z|S2D-EqDqCs3GkO10%cVUTXKFwi@ zuY%)}F#@ZY>HI>bNy61l2#x4wz3B-pp~Nw!sp7Ip@#5mU*y*wHsp-D4&aU3R&aUy^ zDUgzn*w3+E4xH!MF4(FtaP-!QbEW6CNgp>xJ>&DJedRx{E=kg{>bOMmf2vMZ@qbud z!6-hMzEv!G$P&ha!9gT~;KYGX8Gr_ua>p!g-O7QbJWNLbq&e4ySLCAeTRH#u{5h;$>Xg?0trQG;2R;tgg6CAa zd%m;GHJF86ykI3=~acKhegSq!x>6osYTfY#9)+$YZMw#6QG zET_dTy}`EZmVomUkdgKm?c1F%25jFAtaJNrZ2hPojf`qghK35-Jek8SB$D?=&PV@5 zKpYkE>ZV}n#LL6NtzR;#D8=PWS5vuhSH!7A(pe0eC_L^5D~7^cV^t7%5o`=GCBYk4 z#}c?vK}jhX>EPV7W&vYM9Qc=1 zv6$H{VD>B`M{;wzFd!tlKAq_P256;)7TtfB!4zD@a5pcYlB+)MH^iOywxzN;-zCr0j0%z{t59&Jr;h-d)C1a6Lo?qQKPOT29>rIBx?U z&Jqtc!40;#PNyoW4kiQ-pkQ;|U1ee|-?%qhyK{Wz%EN<}`*ND_G0Zvy95-}KrRKTSn3GoYLQ#@xF z{?WK`K_%OB?0P?Si4BxzillV6=zt~?am`)97{!lvvtcO!XjMI^QkW1ZAx1GMh=~0d zT9uT146VS1(3uGy3?}v~fEXeV`Eemig5kswQW15FfVYq%k$EWBnWCp%*<;|2#$z98IDXQVXu(gyI z#Kk}l2gkD{TA9 zQ980~q;8aUJUO+$pN2OKZtF*@YmGaHZ1zA=j(Z_tToEq5-PkO0lnZehY=9!GD8)H1 zyS-8u-=aI4MG)#8G_j3Rvy%=_FtQhLaYeS9h2Sl6V`g`;`^Cd7@+HEK(*yOiTgZJW zvYTQK=IU|L=m{s$6EnC=7DGb#FA>x$lJ_AJQ}lY5J!Ro$C*Wp1cfsGy72u0@f~)<( zN=hnzjk>c1Tjt#zxL(VsB60PdO2=3WK_N-#0~j8p2w+|J2nPbdi!mwQ(xw*-ueSfl z@@PVVWTd#kPjUC64*6v_#|*{g+F?q!F~vGcu?@KxzVh03wJ)Yec+0wg1{g;D@YaAA z^Y`$q{9P@g3YZPz_QTWq0;M|{cd;1h?(w}Rn<(cIQ}Wa_af59|l%f)mC2v=xyARRR z4oLnK$_0_w+0kjr}=goCdyfwt|hKFx-+v0Ofr z$>(DkcoGW-ow7aN%`DuEX2Ovu-n{00Hj<6zb6IF3nvJ;e4?EHc%~#IfGXGDk?(Km) zH-JVDCy=XG(MuW12^=qyuRP3nVKo#uP)z7mol+S*cW!(y`#CG@i-mR>_yOXO4?A#=280E@_PC; zMm(22(m-EXzJqCdf`xlKpym}jSbF96*S@%8`Hsdu#+|vSsBo{D;nvpagM zfg&vf$BLk>RxGk<1i)yZS230IpRtG7S$?dGwvp=t%^jW1L8sr}(lNa`^k#t0PE2jo z$jgD(p8MAKUVH8vOM}ioy~@@$Hr0mivOoJ$V0Y7&+93M+gr$Ku=%KmI(_3c?vsBhh zYGJCv^7oz3s1B>#z621Oq;U-_7ipwzYx};2!~M@u;+$(U?8MYl2i_46(nrHP+J|FS z+)SntK;BqG>)P-)F|BA7U6*f|1A1nYCTW#fyN73IyC>V)y1U!jC%b27CnslumGklW z5UT(e0p*-qoPS#NW&89S8IPteV*wF61$2mbbbhaWf8egsL3bAd$GIxk^Bz>vUs%=0 zc&Z5Wu?kj&p_+iS$Hzuq0zz|o$^bsg5018NYTpck1r)!kk=eu5;zLMH6k;wlktGax zvi;Gyr(Ly*DOs4(Mdc7GT{lC)6JZp*c`Ey5KTgyjl>>VRp6@Df#BTTkDzeG`KI#;_TTMGom;P1+s(9%v0HM?pwVd!7^Hq3yAuZ4x@WMNIe0NU z(=yvU-V@o<|9Ia)VqfdT*(dW~c#f&v&K9Cie&Bra2xIn7uAoX}ZuiCd5V1?_lUSSi z(ai%4o_?^E)ruCf$niY6;OmUwuJpRf2n8V8VD@Xc1-YUF@3sevX&c{AujbBg=2NVLAREIePTSk)xp(ov*N2E0f8_t!QX9mEU0S zDu!Vw@gl@!+)Sih>7-Ed9dHf*VE>^QsN9D7<5W;F_1TFOUHI(%USM>Wu5|xMRe?Q#z53lQhf{Sz_$Zzs*XmTwAPNNZ|%^ z*EmB1)-_Af-OWtvGtx#PP4N@)Dcm25n~~r?x+gF-%mHmc9h4L3PY~QkO3~TN%%A7x zdtb3^*%enTef8aUUwiG{kh8NKX!6F<M@TUPVg4RDe zFw#GQNg_Ot2e`!Jq`i}!>cWyRr9eyJl4^M9{`)Vzth?LgK&hMS=6iVJT8WZ2txl>_ zavGRWpPK&DAD?=P%+4~NWk8%uoB?)p?7e?IbO_g#O_MWlD;jZbc7RdmKWWc_MMKlL z6Es0Nj@)}M<8J?g8)>TRWbb0{!YhQkioJqfAyV^gmflTQ(Yt{^Qt>D+6wk3qs+<`C z{)py0dy7QA1@qR-6HkANQO9hg8}=`_Jq(6|W4l*H(Pw$D7Wn>~$5m{9v-z zS2z#;?!SCs_dAQ(8!DVHEB}q>_VvzP?2Q$;*<%HJ6MW0|3+&Am)?MxoukJSvfBcS3 z7DEHq{p0(i&K=OAbB`T4{`j-@XIOiM9rWWFHpyghp_*PvIp<51+UM)^Rm!->CMe+5 zx``X8pfNoa$l;vsw^wasWniGt%9Sf0Ub#}|3|wTf&$iE|w@p*}*E1;X04&~M53;vZ z{BW`NkK*bY_QI>Lz3}|EuKD0MZ@=~Hko@5_Z(l%3r*j{>6rSHXA7D4s2jU6SN~hCS z$nIKfrEz0tG9Eg2oBwv_L3aI}?qlotVNTkw|im1QaoNA{xOoc%Zqoq8_$ndS6u@Vmjljwp9SH9HVOv7 zTt6ODVbN=LEpSg93Ai}`JR1pvZWxS_cvq|frS99sVl>M;yvcRky)+`%RM()Yo30Oz zIat2#Nir(#Z1>V|1v>ZnVS=)J}cCs%BO(_%ttr#^5o^kEchM)SXULJCu%KjQ%uk`Q6c>v{ zqlqY=O7ICisR@`#wO6 zspc+fL`joHBBHM-g(%=lfxD_8Dsu4?omk9YM@*^`^)kybKVHR^0`NhAB7)>$Q-v$J zXsmc%l_+YOYo~agF3+i&XUZ&iT?n9;KTkXt%(ZiH$2UVO$4OrWO( z(BpXHaZzM|Jw&YGI;dEZhdY9s$1S)S^|&bySr5ZyfMS_yA&~o~42E8>2C=O_I z*Be~^hJOrod7B3ia=@tPy=R7vyf&g(;6|B*7O3G`lk^$-(YxfH3<>AMd{jQR8H*0Z+|5`bW;Y}jRuKMvGZwiO#*_Yf+VLIRkVUCrFUGxC z&S(8JrU*HiMCOUR18c|*yGrjh`?st&NrIQ4%+dTB`av(4u&jE z(e;cskrd9Lx;8P1FEFgYv+ADcne3jV-b8l|S)8pHrob~rlEfj-nJhIhnx>+J2Ue8? zf!uKiT5E#FPl2&G+2uyxF%qMIu%S?lO` z-40p4kQtW8MDJzx(%pbdl3dS3XuVtumWB|BaS=WOGR8KUUfM^) zB9R@9iKr1hX29J(Cc!6#B-b78?~Qc!)^lrfHQ9Bu8+P{Y>fOif$qj3HBbO$V`LXdF zW=>vY&wBe@6tZRfyN|zr8)I@=Sx@Vxlu3YP>-*7HmpAt^iIf-vb;G%v7Jhm)d-vCV z71%!c+T-7OWLpQl({Tx5RWFfN8o^*z_Xjs zdP3z3F7(haqw5;j#AJ8~O-`z+#IhI_4|Y*W-x%YnQI)*(60^VQ>EW3jGdqPRo3=J> zUa2*-*R>Bluw@NQ!NctO)|U0bdbxF$?v(MUPD*ypwl;(+=Y8OZj13R?8Hcd}&+BDtS5?rx~9SlbXLIcl@1V$pcL15IUSlYPs(4DmmXX36@ z^6nvK<%MjU?VkS zy~nwkRrJv(=-cEcz0XNOnFfU6B)S056?o%(=$>x}CP(^vLKg&j2Ku{$cRb+7O)^2d z*N>)HdITiwXXj61p;4L?SN5;$f4ILsQWvQUr$nB|X$7-T*fh9laPtsxwXXeE9aJqj5oNYD(638zv&+DCh&N~$67aFv9L&$QbkiS$g}q8H9GDGQ$##J z42Frf7>o?)GO=+78>3TQP&kFdBsy~6T~8(OBqa>JU=PYi54k;dutnHvjy)o+i+KPZA6AOU2!_m|3aDHs;h-35Q!k&Pbp&AD#``-|^4p z^D`m&efkUOeY!E6@1P{>jIfV9-a5w&79vV-th#4yPjz>KPsyB#hldR1!*YLu6H-C~ zyt^E=uV+SNc_e6m%RfqKKW4%??<{2vG@F#ycD%Oz)ty=0r0Mu<>s;$xn~~C!TvFr{ zBIZs#g}d_PBqfPi)=SwDw;Jg!mP&Qdh~WQm>11cLH`X1)q^7Ge*4Q3wle)WUyWBR} zJtNQ1sYz*uJTk@RGVyRY77vFaa@?YMlFL}RVCB4ZDrFCR*Y19A<4%9D!guoOlRhzV zQpM>dpFO4ITs$B9Qzd7Ye6{4(l9eS*C2Gk`$^Md;O5XMPeE;lQ;(OTF=!^R--(2ak z(uYg)rTa?1SNhwsPnMluURnP6@=MCIvKF}NEulNmf31@6Q{i_q7_J^XmG zg_XcN-ce9y@hGUIcNEm37hhyFji!0sJC%p5S-oaI!^5?k*$SGCEINM7#IobZOf16V z4@~#`gU|Z$6hQZwLg<##n2->GwUWRCn4AzxC$cm{)25PjSBP~Y=ak_AgDi?#@odJj zj&BuQbo`W&rN{TYE&BCaKlt^puYK#n3$MNQ!VBNJ?$@D3@4R~Jl~*mh^{T6uz4Fda zUVY`A&?0n5i{}fAK0!%3AR#KJ{`HZ}V~$;VEXr1sCzf^c)JF zskgm`4Em`XDj+mMmlh~}hVhbgPRP1Zyxro*vTh_n+Z&gjZ6nIlN;m^3>ZdF7R6)nSR&>gJd)rs z2?I~94buT!HU(hm8Jp=OMlLlN2$^eQRCj%fd&dV2?Fw{0uT zj*l1GHUt+*v)oL4qmWh7X+2{O^I9a`qj#y@S~XWQRLczw@ezxpbP*g@EvMy_oRY`m z7|0@NO}uJg9Un`i65eu;oWg7ZUC?EiMk9O7z0zJncP6%ncW_z7g4wPcvMJ(WylN`b zo^I3Hs(w7XE@^K ziBPgAosfs(@kC;XCeq~I=kLFV@vc#SSQdHag?nFM;%PdJalv7lPLSvCdH#9EySCyq z9FLQ4+{-M=#Ed@LO}kV7?@tQX?3*?4Ry8C8fVILgA-!U@)awV zFJCcv^0c0*?Ni$(G;}t0wSi^}GSV~B(=#|48*927rk2kK!UYY}x@S!Vg4tbD8#p$l zfR`}z0+SQ~me}~rc${NkU|?hbf-|;@&V%S{(|3u%XaG$G2KE2|c${NkWME+617ZmV z5MW|p1i~&LW&!gU017w&y#N3Jc${NkW@2ERz`)AD!RW)7#=yYf4yC^`NHVlAFfcK& zax%aGqW}W}1f;p9FgP$MKG0@-@c#jW9urgzn3&4g&7c5;i~#6F3nc&mc$~G6S5iV@ z428do2zC(_0SiUNf>=<&jv!(eyI}8P@4ff3DwpC;^!MtsKAB8TP9~YjNq{K6BMV6& z(u}AixmHSkkz<4_zB*jK+dn>JW_jm_Aq0GJn+a9ue^x&sJ>tHtJT!j z)i<6rwKjYE{DHRi(w?G@&V|JmPeZkAS>W^#9uo3{0wEaSjGB1pq-73Y-7{c${NkWME)o z00KQGhX1$!-)2%}U}QiAOaNR31QGxMc$__sJxhXN6oxPS2fCPuLzeJ5`o#-|Mj(VQ zVlL|9AWq`o=H_6Os9B(?Ll|xjfeuPE1AB@k&DA~!^63+V*_m9Pgz}-CdfHkURi6mKwh$` z#*oWFQpprI2YeK{lr^OHmz{Et51yECOO-w^n1A2Av0=!Hcd;a)mJ%`Se5rja>I`&G zMYel9N8CqRjL6a?!x;@>@k~wEJ#eR;E19Cq^cUL$G)XhJ002+`0E++sc$|%oJr06E z6od!)NenhN#&Qc2D+r5?dH_mGZ-B4~0Rl80!3%f~kKqA4h6k{7z-OX~g`2$W_hx6` z&H|X=3=93T;A1X4f`?ex#u6uChn}mjOTG(x@U1DGmNTX@8*cSH;{1iMjU6_^4m}rP zmwXfU$PeT&IIsd_C~!ngr3G?xFhN!v>zvKk$y-cNp+q

HiFi Glyphs

-

This font was created inHigh Fidelity

+

This font was created for use inHigh Fidelity

CSS mapping

  • @@ -520,8 +520,52 @@
  • -
    - +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    +

Character mapping

@@ -1034,6 +1078,50 @@
+
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • + + + + + + diff --git a/domain-server/resources/web/base-settings.html b/domain-server/resources/web/base-settings.html new file mode 100644 index 0000000000..13787b5fbe --- /dev/null +++ b/domain-server/resources/web/base-settings.html @@ -0,0 +1,51 @@ +
    +
    +
    + +
    +
    + +
    + +
    + + +
    + +
    +
    +
    +
    +
    diff --git a/domain-server/resources/web/content/index.shtml b/domain-server/resources/web/content/index.shtml index 0e48c1eff8..9b507f7826 100644 --- a/domain-server/resources/web/content/index.shtml +++ b/domain-server/resources/web/content/index.shtml @@ -1,45 +1,19 @@ -
    -
    -
    - -
    -
    + -
    -
    -
    -
    -

    Upload Entities File

    -
    -
    -
    -

    - Upload an entities file (e.g.: models.json.gz) to replace the content of this domain.
    - Note: Your domain's content will be replaced by the content you upload, but the backup files of your domain's content will not immediately be changed. -

    -

    If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:

    - -
    C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz
    - -
    /Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz
    - -
    /home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz
    -
    - -
    -
    - -
    -
    -
    -
    -
    + - - + + + + + diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js index 2e6e084164..7b2ed37b15 100644 --- a/domain-server/resources/web/content/js/content.js +++ b/domain-server/resources/web/content/js/content.js @@ -1,5 +1,7 @@ $(document).ready(function(){ + Settings.afterReloadActions = function() {}; + function showSpinnerAlert(title) { swal({ title: title, diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 158008fc2b..22de75a778 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -18,6 +18,14 @@ body { margin-top: 70px; } +/* unfortunate hack so that anchors go to the right place with fixed navbar */ +:target:before { + content: " "; + display: block; + height: 70px; + margin-top: -70px; +} + [hidden] { display: none !important; } @@ -345,3 +353,75 @@ table .headers + .headers td { text-align: center; margin-top: 20px; } + +@media (min-width: 768px) { + ul.nav li.dropdown-on-hover:hover ul.dropdown-menu { + display: block; + } +} + +ul.nav li.dropdown ul.dropdown-menu { + padding: 0px 0px; +} + +ul.nav li.dropdown li a { + padding-top: 7px; + padding-bottom: 7px; +} + +ul.nav li.dropdown li a:hover { + color: white; + background-color: #337ab7; +} + +ul.nav li.dropdown ul.dropdown-menu .divider { + margin: 0px 0; +} + +#visit-domain-link { + background-color: transparent; +} + +.navbar-btn { + margin-left: 10px; +} + +#save-settings-xs-button { + float: right; + margin-right: 10px; +} + +#button-bars { + display: inline-block; + float: left; +} + +#hamburger-badge { + position: relative; + top: -2px; + float: left; + margin-right: 10px; + margin-left: 0px; +} + +#restart-server { + margin-left: 0px; +} + +#restart-server:hover { + text-decoration: none; +} + +.badge { + margin-left: 5px; +} + +.panel-title { + display: inline-block; +} + +#visit-hmd-icon { + width: 25px; + position: relative; + top: -1px; +} diff --git a/domain-server/resources/web/header.html b/domain-server/resources/web/header.html index 1e32e9f02f..aff82d557e 100644 --- a/domain-server/resources/web/header.html +++ b/domain-server/resources/web/header.html @@ -17,30 +17,47 @@
    - diff --git a/domain-server/resources/web/images/hmd-w-eyes.svg b/domain-server/resources/web/images/hmd-w-eyes.svg new file mode 100644 index 0000000000..c100de2f4e --- /dev/null +++ b/domain-server/resources/web/images/hmd-w-eyes.svg @@ -0,0 +1,10 @@ + + + + + diff --git a/domain-server/resources/web/js/base-settings.js b/domain-server/resources/web/js/base-settings.js new file mode 100644 index 0000000000..5e32463d61 --- /dev/null +++ b/domain-server/resources/web/js/base-settings.js @@ -0,0 +1,1140 @@ +var DomainInfo = null; + +var viewHelpers = { + getFormGroup: function(keypath, setting, values, isAdvanced) { + form_group = "
    "; + setting_value = _(values).valueForKeyPath(keypath); + + if (_.isUndefined(setting_value) || _.isNull(setting_value)) { + if (_.has(setting, 'default')) { + setting_value = setting.default; + } else { + setting_value = ""; + } + } + + label_class = 'control-label'; + + function common_attrs(extra_classes) { + extra_classes = (!_.isUndefined(extra_classes) ? extra_classes : ""); + return " class='" + (setting.type !== 'checkbox' ? 'form-control' : '') + + " " + Settings.TRIGGER_CHANGE_CLASS + " " + extra_classes + "' data-short-name='" + + setting.name + "' name='" + keypath + "' " + + "id='" + (!_.isUndefined(setting.html_id) ? setting.html_id : keypath) + "'"; + } + + if (setting.type === 'checkbox') { + if (setting.label) { + form_group += "" + } + + form_group += "
    " + form_group += "" + + if (setting.help) { + form_group += "" + setting.help + ""; + } + + form_group += "
    " + } else { + input_type = _.has(setting, 'type') ? setting.type : "text" + + if (setting.label) { + form_group += ""; + } + + if (input_type === 'table') { + form_group += makeTable(setting, keypath, setting_value) + } else { + if (input_type === 'select') { + form_group += "" + + form_group += "" + } else if (input_type === 'button') { + // Is this a button that should link to something directly? + // If so, we use an anchor tag instead of a button tag + + if (setting.href) { + form_group += "" + + setting.button_label + ""; + } else { + form_group += ""; + } + + } else { + + if (input_type == 'integer') { + input_type = "text" + } + + form_group += "" + } + + form_group += "" + setting.help + "" + } + } + + form_group += "
    " + return form_group + } +} + +function reloadSettings(callback) { + $.getJSON(Settings.endpoint, function(data){ + _.extend(data, viewHelpers) + + $('#panels').html(Settings.panelsTemplate(data)) + + Settings.data = data; + Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true); + + Settings.afterReloadActions(); + + // setup any bootstrap switches + $('.toggle-checkbox').bootstrapSwitch(); + + $('[data-toggle="tooltip"]').tooltip(); + + // call the callback now that settings are loaded + callback(true); + }).fail(function() { + // call the failure object since settings load faild + callback(false) + }); +} + +function postSettings(jsonSettings) { + // POST the form JSON to the domain-server settings.json endpoint so the settings are saved + $.ajax(Settings.endpoint, { + data: JSON.stringify(jsonSettings), + contentType: 'application/json', + type: 'POST' + }).done(function(data){ + if (data.status == "success") { + if ($(".save-button").html() === SAVE_BUTTON_LABEL_RESTART) { + showRestartModal(); + } else { + location.reload(true); + } + } else { + showErrorMessage("Error", SETTINGS_ERROR_MESSAGE) + reloadSettings(); + } + }).fail(function(){ + showErrorMessage("Error", SETTINGS_ERROR_MESSAGE) + reloadSettings(); + }); +} + +$(document).ready(function(){ + + $('.save-button.navbar-btn').show(); + + $.ajaxSetup({ + timeout: 20000, + }); + + reloadSettings(function(success){ + if (success) { + // if there was a hash in the URL, jump to it now + if (location.hash) { + location.href = location.hash; + } + } else { + swal({ + title: '', + type: 'error', + text: Strings.LOADING_SETTINGS_ERROR + }); + } + }); + + $('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_ROW_BUTTON_CLASS, function(){ + addTableRow($(this).closest('tr')); + }); + + $('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_ROW_BUTTON_CLASS, function(){ + deleteTableRow($(this).closest('tr')); + }); + + $('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_CATEGORY_BUTTON_CLASS, function(){ + addTableCategory($(this).closest('tr')); + }); + + $('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_CATEGORY_BUTTON_CLASS, function(){ + deleteTableCategory($(this).closest('tr')); + }); + + $('#' + Settings.FORM_ID).on('click', '.' + Settings.TOGGLE_CATEGORY_COLUMN_CLASS, function(){ + toggleTableCategory($(this).closest('tr')); + }); + + $('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_UP_BUTTON_CLASS, function(){ + moveTableRow($(this).closest('tr'), true); + }); + + $('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_DOWN_BUTTON_CLASS, function(){ + moveTableRow($(this).closest('tr'), false); + }); + + $('#' + Settings.FORM_ID).on('keyup', function(e){ + var $target = $(e.target); + if (e.keyCode == 13) { + if ($target.is('table input')) { + // capture enter in table input + // if we have a sibling next to us that has an input, jump to it, otherwise check if we have a glyphicon for add to click + sibling = $target.parent('td').next(); + + if (sibling.hasClass(Settings.DATA_COL_CLASS)) { + // set focus to next input + sibling.find('input').focus(); + } else { + + // jump over the re-order row, if that's what we're on + if (sibling.hasClass(Settings.REORDER_BUTTONS_CLASS)) { + sibling = sibling.next(); + } + + // for tables with categories we add the entry and setup the new row on enter + if (sibling.find("." + Settings.ADD_CATEGORY_BUTTON_CLASS).length) { + sibling.find("." + Settings.ADD_CATEGORY_BUTTON_CLASS).click(); + + // set focus to the first input in the new row + $target.closest('table').find('tr.inputs input:first').focus(); + } + + var tableRows = sibling.parent(); + var tableBody = tableRows.parent(); + + // if theres no more siblings, we should jump to a new row + if (sibling.next().length == 0 && tableRows.nextAll().length == 1) { + tableBody.find("." + Settings.ADD_ROW_BUTTON_CLASS).click(); + } + } + + } else if ($target.is('input')) { + $target.change().blur(); + } + + e.preventDefault(); + } + }); + + $('#' + Settings.FORM_ID).on('keypress', function(e){ + if (e.keyCode == 13) { + + e.preventDefault(); + } + }); + + $('#' + Settings.FORM_ID).on('change keyup paste', '.' + Settings.TRIGGER_CHANGE_CLASS , function(){ + // this input was changed, add the changed data attribute to it + $(this).attr('data-changed', true); + + badgeForDifferences($(this)); + }); + + $('#' + Settings.FORM_ID).on('switchChange.bootstrapSwitch', 'input.toggle-checkbox', function(){ + // this checkbox was changed, add the changed data attribute to it + $(this).attr('data-changed', true); + + badgeForDifferences($(this)); + }); + + // Bootstrap switch in table + $('#' + Settings.FORM_ID).on('change', 'input.table-checkbox', function () { + // Bootstrap switches in table: set the changed data attribute for all rows in table. + var row = $(this).closest('tr'); + if (row.hasClass("value-row")) { // Don't set attribute on input row switches prior to it being added to table. + row.find('td.' + Settings.DATA_COL_CLASS + ' input').attr('data-changed', true); + updateDataChangedForSiblingRows(row, true); + badgeForDifferences($(this)); + } + }); + + $('#' + Settings.FORM_ID).on('change', 'input.table-time', function() { + // Bootstrap switches in table: set the changed data attribute for all rows in table. + var row = $(this).closest('tr'); + if (row.hasClass("value-row")) { // Don't set attribute on input row switches prior to it being added to table. + row.find('td.' + Settings.DATA_COL_CLASS + ' input').attr('data-changed', true); + updateDataChangedForSiblingRows(row, true); + badgeForDifferences($(this)); + } + }); + + $('#' + Settings.FORM_ID).on('change', 'select', function(){ + $("input[name='" + $(this).attr('data-hidden-input') + "']").val($(this).val()).change(); + }); + + var panelsSource = $('#panels-template').html(); + Settings.panelsTemplate = _.template(panelsSource); +}); + +function dynamicButton(button_id, text) { + return $(""); +} + +function showSpinnerAlert(title) { + swal({ + title: title, + text: '
    ', + html: true, + showConfirmButton: false, + allowEscapeKey: false + }); +} + +function parseJSONResponse(xhr) { + try { + return JSON.parse(xhr.responseText); + } catch (e) { + } + return null; +} + +function validateInputs() { + // check if any new values are bad + var tables = $('table'); + + var inputsValid = true; + + var tables = $('table'); + + // clear any current invalid rows + $('tr.' + Settings.INVALID_ROW_CLASS).removeClass(Settings.INVALID_ROW_CLASS); + + function markParentRowInvalid(rowChild) { + $(rowChild).closest('tr').addClass(Settings.INVALID_ROW_CLASS); + } + + _.each(tables, function(table) { + // validate keys specificially for spaces and equality to an existing key + var newKeys = $(table).find('tr.' + Settings.NEW_ROW_CLASS + ' td.key'); + + var keyWithSpaces = false; + var empty = false; + var duplicateKey = false; + + _.each(newKeys, function(keyCell) { + var keyVal = $(keyCell).children('input').val(); + + if (keyVal.indexOf(' ') !== -1) { + keyWithSpaces = true; + markParentRowInvalid(keyCell); + return; + } + + // make sure the key isn't empty + if (keyVal.length === 0) { + empty = true + + markParentRowInvalid(input); + return; + } + + // make sure we don't have duplicate keys in the table + var otherKeys = $(table).find('td.key').not(keyCell); + _.each(otherKeys, function(otherKeyCell) { + var keyInput = $(otherKeyCell).children('input'); + + if (keyInput.length) { + if ($(keyInput).val() == keyVal) { + duplicateKey = true; + } + } else if ($(otherKeyCell).html() == keyVal) { + duplicateKey = true; + } + + if (duplicateKey) { + markParentRowInvalid(keyCell); + return; + } + }); + + }); + + if (keyWithSpaces) { + showErrorMessage("Error", "Key contains spaces"); + inputsValid = false; + return + } + + if (empty) { + showErrorMessage("Error", "Empty field(s)"); + inputsValid = false; + return + } + + if (duplicateKey) { + showErrorMessage("Error", "Two keys cannot be identical"); + inputsValid = false; + return; + } + }); + + return inputsValid; +} + +var SETTINGS_ERROR_MESSAGE = "There was a problem saving domain settings. Please try again!"; + +function saveSettings() { + + if (validateInputs()) { + // POST the form JSON to the domain-server settings.json endpoint so the settings are saved + var canPost = true; + + // disable any inputs not changed + $("input:not([data-changed])").each(function () { + $(this).prop('disabled', true); + }); + + // grab a JSON representation of the form via form2js + var formJSON = form2js('settings-form', ".", false, cleanupFormValues, true); + + // re-enable all inputs + $("input").each(function () { + $(this).prop('disabled', false); + }); + + // remove focus from the button + $(this).blur(); + + if (Settings.handlePostSettings === undefined) { + console.log("----- saveSettings() called ------"); + console.log(formJSON); + + // POST the form JSON to the domain-server settings.json endpoint so the settings are saved + postSettings(formJSON); + } else { + Settings.handlePostSettings(formJSON) + } + } +} + +$('body').on('click', '.save-button', function(e){ + saveSettings(); + return false; +}); + +function makeTable(setting, keypath, setting_value) { + var isArray = !_.has(setting, 'key'); + var categoryKey = setting.categorize_by_key; + var isCategorized = !!categoryKey && isArray; + + if (!isArray && setting.can_order) { + setting.can_order = false; + } + + var html = ""; + + if (setting.help) { + html += "" + setting.help + "" + } + + var nonDeletableRowKey = setting["non-deletable-row-key"]; + var nonDeletableRowValues = setting["non-deletable-row-values"]; + + html += ""; + + if (setting.caption) { + html += "" + } + + // Column groups + if (setting.groups) { + html += "" + _.each(setting.groups, function (group) { + html += "" + }) + if (!setting.read_only) { + if (setting.can_order) { + html += ""; + } + html += "" + } + html += "" + } + + // Column names + html += "" + + if (setting.numbered === true) { + html += "" // Row number + } + + if (setting.key) { + html += "" // Key + } + + var numVisibleColumns = 0; + _.each(setting.columns, function(col) { + if (!col.hidden) numVisibleColumns++; + html += "" // Data + }) + + if (!setting.read_only) { + if (setting.can_order) { + numVisibleColumns++; + html += ""; + } + numVisibleColumns++; + html += ""; + } + + // populate rows in the table from existing values + var row_num = 1; + + if (keypath.length > 0 && _.size(setting_value) > 0) { + var rowIsObject = setting.columns.length > 1; + + _.each(setting_value, function(row, rowIndexOrName) { + var categoryPair = {}; + var categoryValue = ""; + if (isCategorized) { + categoryValue = rowIsObject ? row[categoryKey] : row; + categoryPair[categoryKey] = categoryValue; + if (_.findIndex(setting_value, categoryPair) === rowIndexOrName) { + html += makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, setting.can_add_new_categories, ""); + } + } + + html += ""; + + if (setting.numbered === true) { + html += "" + } + + if (setting.key) { + html += "" + } + + var isNonDeletableRow = !setting.can_add_new_rows; + + _.each(setting.columns, function(col) { + + var colValue, colName; + if (isArray) { + colValue = rowIsObject ? row[col.name] : row; + colName = keypath + "[" + rowIndexOrName + "]" + (rowIsObject ? "." + col.name : ""); + } else { + colValue = row[col.name]; + colName = keypath + "." + rowIndexOrName + "." + col.name; + } + + isNonDeletableRow = isNonDeletableRow + || (nonDeletableRowKey === col.name && nonDeletableRowValues.indexOf(colValue) !== -1); + + if (isArray && col.type === "checkbox" && col.editable) { + html += + ""; + } else if (isArray && col.type === "time" && col.editable) { + html += + ""; + } else { + // Use a hidden input so that the values are posted. + html += + ""; + } + + }); + + if (!setting.read_only) { + if (setting.can_order) { + html += "" + } + if (isNonDeletableRow) { + html += ""; + } else { + html += ""; + } + } + + html += "" + + if (isCategorized && setting.can_add_new_rows && _.findLastIndex(setting_value, categoryPair) === rowIndexOrName) { + html += makeTableInputs(setting, categoryPair, categoryValue); + } + + row_num++ + }); + } + + // populate inputs in the table for new values + if (!setting.read_only) { + if (setting.can_add_new_categories) { + html += makeTableCategoryInput(setting, numVisibleColumns); + } + + if (setting.can_add_new_rows || setting.can_add_new_categories) { + html += makeTableHiddenInputs(setting, {}, ""); + } + } + html += "
    " + setting.caption + "
    " + group.label + "
    #" + setting.key.label + "" + col.label + "
    " + row_num + "" + rowIndexOrName + "" + + "" + + "" + + "" + + "" + + colValue + + "" + + "" + + "
    " + + return html; +} + +function makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, canRemove, message) { + var html = + "" + + "" + + "" + + "" + categoryValue + "" + + "" + + ((canRemove) ? ( + "" + + "" + + "" + ) : ( + "" + )) + + ""; + return html; +} + +function makeTableHiddenInputs(setting, initialValues, categoryValue) { + var html = ""; + + if (setting.numbered === true) { + html += ""; + } + + if (setting.key) { + html += "\ + \ + " + } + + _.each(setting.columns, function(col) { + var defaultValue = _.has(initialValues, col.name) ? initialValues[col.name] : col.default; + if (col.type === "checkbox") { + html += + "" + + "" + + ""; + } else if (col.type === "select") { + html += "" + html += ""; + html += ""; + } else { + html += + "" + + "" + + ""; + } + }) + + if (setting.can_order) { + html += "" + } + html += "" + html += "" + + return html +} + +function makeTableCategoryInput(setting, numVisibleColumns) { + var canAddRows = setting.can_add_new_rows; + var categoryKey = setting.categorize_by_key; + var placeholder = setting.new_category_placeholder || ""; + var message = setting.new_category_message || ""; + var html = + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + return html; +} + +function getDescriptionForKey(key) { + for (var i in Settings.data.descriptions) { + if (Settings.data.descriptions[i].name === key) { + return Settings.data.descriptions[i]; + } + } +} + +var SAVE_BUTTON_LABEL_SAVE = "Save"; +var SAVE_BUTTON_LABEL_RESTART = "Save and restart"; +var reasonsForRestart = []; +var numChangesBySection = {}; + +function badgeForDifferences(changedElement) { + // figure out which group this input is in + var panelParentID = changedElement.closest('.panel').attr('id'); + + // if the panel contains non-grouped settings, the initial value is Settings.initialValues + var isGrouped = $('#' + panelParentID).hasClass('grouped'); + + if (isGrouped) { + var initialPanelJSON = Settings.initialValues[panelParentID] + ? Settings.initialValues[panelParentID] + : {}; + + // get a JSON representation of that section + var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID]; + } else { + var initialPanelJSON = Settings.initialValues; + + // get a JSON representation of that section + var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true); + } + + var badgeValue = 0 + var description = getDescriptionForKey(panelParentID); + + // badge for any settings we have that are not the same or are not present in initialValues + for (var setting in panelJSON) { + if ((!_.has(initialPanelJSON, setting) && panelJSON[setting] !== "") || + (!_.isEqual(panelJSON[setting], initialPanelJSON[setting]) + && (panelJSON[setting] !== "" || _.has(initialPanelJSON, setting)))) { + badgeValue += 1; + + // add a reason to restart + if (description && description.restart != false) { + reasonsForRestart.push(setting); + } + } else { + // remove a reason to restart + if (description && description.restart != false) { + reasonsForRestart = $.grep(reasonsForRestart, function(v) { return v != setting; }); + } + } + } + + // update the list-group-item badge to have the new value + if (badgeValue == 0) { + badgeValue = "" + } + + numChangesBySection[panelParentID] = badgeValue; + + var hasChanges = badgeValue > 0; + + if (!hasChanges) { + for (var key in numChangesBySection) { + if (numChangesBySection[key] > 0) { + hasChanges = true; + break; + } + } + } + + $(".save-button").prop("disabled", !hasChanges); + $(".save-button").html(reasonsForRestart.length > 0 ? SAVE_BUTTON_LABEL_RESTART : SAVE_BUTTON_LABEL_SAVE); + + // add the badge to the navbar item and the panel header + $("a[href='" + settingsGroupAnchor(Settings.path, panelParentID) + "'] .badge").html(badgeValue); + $("#" + panelParentID + " .panel-heading .badge").html(badgeValue); + + // make the navbar dropdown show a badge that is the total of the badges of all groups + var totalChanges = 0; + $('.panel-heading .badge').each(function(index){ + if (this.innerHTML.length > 0) { + totalChanges += parseInt(this.innerHTML); + } + }); + + if (totalChanges == 0) { + totalChanges = "" + } + + var totalBadgeClass = Settings.content_settings ? '.content-settings-badge' : '.domain-settings-badge'; + + $(totalBadgeClass).html(totalChanges); + $('#hamburger-badge').html(totalChanges); +} + +function addTableRow(row) { + var table = row.parents('table'); + var isArray = table.data('setting-type') === 'array'; + var keepField = row.data("keep-field"); + + var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS); + + var input_clone = row.clone(); + + // Change input row to data row + var table = row.parents("table"); + var setting_name = table.attr("name"); + row.addClass(Settings.DATA_ROW_CLASS + " " + Settings.NEW_ROW_CLASS); + + if (!isArray) { + // show the key input + var keyInput = row.children(".key").children("input"); + + // whenever the keyInput changes, re-badge for differences + keyInput.on('change keyup paste', function(){ + // update siblings in the row to have the correct name + var currentKey = $(this).val(); + + $(this).closest('tr').find('.value-col input').each(function(index){ + var input = $(this); + + if (currentKey.length > 0) { + input.attr("name", setting_name + "." + currentKey + "." + input.parent().attr('name')); + } else { + input.removeAttr("name"); + } + }) + + badgeForDifferences($(this)); + }); + } + + // if this is an array, add the row index (which is the index of the last row + 1) + // as a data attribute to the row + var row_index = 0; + if (isArray) { + var previous_row = row.siblings('.' + Settings.DATA_ROW_CLASS + ':last'); + + if (previous_row.length > 0) { + row_index = parseInt(previous_row.attr(Settings.DATA_ROW_INDEX), 10) + 1; + } else { + row_index = 0; + } + + row.attr(Settings.DATA_ROW_INDEX, row_index); + } + + var focusChanged = false; + + _.each(row.children(), function(element) { + if ($(element).hasClass("numbered")) { + // Index row + var numbers = columns.children(".numbered") + if (numbers.length > 0) { + $(element).html(parseInt(numbers.last().text()) + 1) + } else { + $(element).html(1) + } + } else if ($(element).hasClass(Settings.REORDER_BUTTONS_CLASS)) { + $(element).html("") + } else if ($(element).hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) { + // Change buttons + var anchor = $(element).children("a") + anchor.removeClass(Settings.ADD_ROW_SPAN_CLASSES) + anchor.addClass(Settings.DEL_ROW_SPAN_CLASSES) + } else if ($(element).hasClass("key")) { + var input = $(element).children("input") + input.show(); + } else if ($(element).hasClass(Settings.DATA_COL_CLASS)) { + // show inputs + var input = $(element).find("input"); + input.show(); + + var isCheckbox = input.hasClass("table-checkbox"); + var isDropdown = input.hasClass("table-dropdown"); + + if (isArray) { + var key = $(element).attr('name'); + + // are there multiple columns or just one? + // with multiple we have an array of Objects, with one we have an array of whatever the value type is + var num_columns = row.children('.' + Settings.DATA_COL_CLASS).length + var newName = setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : ""); + + input.attr("name", newName); + + if (isDropdown) { + // default values for hidden inputs inside child selects gets cleared so we need to remind it + var selectElement = $(element).children("select"); + selectElement.attr("data-hidden-input", newName); + $(element).children("input").val(selectElement.val()); + } + } + + if (isArray && !focusChanged) { + input.focus(); + focusChanged = true; + } + + // if we are adding a dropdown, we should go ahead and make its select + // element is visible + if (isDropdown) { + $(element).children("select").attr("style", ""); + } + + if (isCheckbox) { + $(input).find("input").attr("data-changed", "true"); + } else { + input.attr("data-changed", "true"); + } + } else { + console.log("Unknown table element"); + } + }); + + if (!isArray) { + keyInput.focus(); + } + + input_clone.children('td').each(function () { + if ($(this).attr("name") !== keepField) { + $(this).find("input").val($(this).children('input').attr('data-default')); + } + }); + + if (isArray) { + updateDataChangedForSiblingRows(row, true) + + // the addition of any table row should remove the empty-array-row + row.siblings('.empty-array-row').remove() + } + + badgeForDifferences($(table)) + + row.after(input_clone) +} + +function deleteTableRow($row) { + var $table = $row.closest('table'); + var categoryName = $row.data("category"); + var isArray = $table.data('setting-type') === 'array'; + + $row.empty(); + + if (!isArray) { + if ($row.attr('name')) { + $row.html(""); + } else { + // for rows that didn't have a key, simply remove the row + $row.remove(); + } + } else { + if ($table.find('.' + Settings.DATA_ROW_CLASS + "[data-category='" + categoryName + "']").length <= 1) { + // This is the last row of the category, so delete the header + $table.find('.' + Settings.DATA_CATEGORY_CLASS + "[data-category='" + categoryName + "']").remove(); + } + + if ($table.find('.' + Settings.DATA_ROW_CLASS).length > 1) { + updateDataChangedForSiblingRows($row); + + // this isn't the last row - we can just remove it + $row.remove(); + } else { + // this is the last row, we can't remove it completely since we need to post an empty array + $row + .removeClass(Settings.DATA_ROW_CLASS) + .removeClass(Settings.NEW_ROW_CLASS) + .removeAttr("data-category") + .addClass('empty-array-row') + .html(""); + } + } + + // we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated + badgeForDifferences($table); +} + +function addTableCategory($categoryInputRow) { + var $input = $categoryInputRow.find("input").first(); + var categoryValue = $input.prop("value"); + if (!categoryValue || $categoryInputRow.closest("table").find("tr[data-category='" + categoryValue + "']").length !== 0) { + $categoryInputRow.addClass("has-warning"); + + setTimeout(function () { + $categoryInputRow.removeClass("has-warning"); + }, 400); + + return; + } + + var $rowInput = $categoryInputRow.next(".inputs").clone(); + if (!$rowInput) { + console.error("Error cloning inputs"); + } + + var canAddRows = $categoryInputRow.data("can-add-rows"); + var message = $categoryInputRow.data("message"); + var categoryKey = $categoryInputRow.data("key"); + var width = 0; + $categoryInputRow + .children("td") + .each(function () { + width += $(this).prop("colSpan") || 1; + }); + + $input + .prop("value", "") + .focus(); + + $rowInput.find("td[name='" + categoryKey + "'] > input").first() + .prop("value", categoryValue); + $rowInput + .attr("data-category", categoryValue) + .addClass(Settings.NEW_ROW_CLASS); + + var $newCategoryRow = $(makeTableCategoryHeader(categoryKey, categoryValue, width, true, " - " + message)); + $newCategoryRow.addClass(Settings.NEW_ROW_CLASS); + + $categoryInputRow + .before($newCategoryRow) + .before($rowInput); + + if (canAddRows) { + $rowInput.removeAttr("hidden"); + } else { + addTableRow($rowInput); + } +} + +function deleteTableCategory($categoryHeaderRow) { + var categoryName = $categoryHeaderRow.data("category"); + + $categoryHeaderRow + .closest("table") + .find("tr[data-category='" + categoryName + "']") + .each(function () { + if ($(this).hasClass(Settings.DATA_ROW_CLASS)) { + deleteTableRow($(this)); + } else { + $(this).remove(); + } + }); +} + +function toggleTableCategory($categoryHeaderRow) { + var $icon = $categoryHeaderRow.find("." + Settings.TOGGLE_CATEGORY_SPAN_CLASS).first(); + var categoryName = $categoryHeaderRow.data("category"); + var wasExpanded = $icon.hasClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS); + if (wasExpanded) { + $icon + .addClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS) + .removeClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS); + } else { + $icon + .addClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS) + .removeClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS); + } + $categoryHeaderRow + .closest("table") + .find("tr[data-category='" + categoryName + "']") + .toggleClass("contracted", wasExpanded); +} + +function moveTableRow(row, move_up) { + var table = $(row).closest('table') + var isArray = table.data('setting-type') === 'array' + if (!isArray) { + return; + } + + if (move_up) { + var prev_row = row.prev() + if (prev_row.hasClass(Settings.DATA_ROW_CLASS)) { + prev_row.before(row) + } + } else { + var next_row = row.next() + if (next_row.hasClass(Settings.DATA_ROW_CLASS)) { + next_row.after(row) + } + } + + // we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated + badgeForDifferences($(table)) +} + +function updateDataChangedForSiblingRows(row, forceTrue) { + // anytime a new row is added to an array we need to set data-changed for all sibling row inputs to true + // unless it matches the inital set of values + + if (!forceTrue) { + // figure out which group this row is in + var panelParentID = row.closest('.panel').attr('id') + // get the short name for the setting from the table + var tableShortName = row.closest('table').data('short-name') + + // get a JSON representation of that section + var panelSettingJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID][tableShortName] + if (Settings.initialValues[panelParentID]) { + var initialPanelSettingJSON = Settings.initialValues[panelParentID][tableShortName] + } else { + var initialPanelSettingJSON = {}; + } + + // if they are equal, we don't need data-changed + isTrue = !_.isEqual(panelSettingJSON, initialPanelSettingJSON) + } else { + isTrue = true + } + + row.siblings('.' + Settings.DATA_ROW_CLASS).each(function(){ + var hiddenInput = $(this).find('td.' + Settings.DATA_COL_CLASS + ' input') + if (isTrue) { + hiddenInput.attr('data-changed', isTrue) + } else { + hiddenInput.removeAttr('data-changed') + } + }) +} + +function cleanupFormValues(node) { + if (node.type && node.type === 'checkbox') { + return { name: node.name, value: node.checked ? true : false }; + } else { + return false; + } +} + +function showErrorMessage(title, message) { + swal(title, message) +} diff --git a/domain-server/resources/web/settings/js/bootstrap-switch.min.js b/domain-server/resources/web/js/bootstrap-switch.min.js similarity index 100% rename from domain-server/resources/web/settings/js/bootstrap-switch.min.js rename to domain-server/resources/web/js/bootstrap-switch.min.js diff --git a/domain-server/resources/web/js/domain-server.js b/domain-server/resources/web/js/domain-server.js index ad1509b038..aa658bce3f 100644 --- a/domain-server/resources/web/js/domain-server.js +++ b/domain-server/resources/web/js/domain-server.js @@ -23,16 +23,22 @@ function showRestartModal() { }, 1000); } +function settingsGroupAnchor(base, html_id) { + return base + "#" + html_id + "_group" +} + $(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'); - $('body').on('click', '#restart-server', function(e) { + }).parent().addClass('active'); + + $('body').on('click', '#restart-server', function(e) { swal( { title: "Are you sure?", text: "This will restart your domain server, causing your domain to be briefly offline.", @@ -45,4 +51,35 @@ $(document).ready(function(){ }); return false; }); + + var $contentDropdown = $('#content-settings-nav-dropdown'); + var $settingsDropdown = $('#domain-settings-nav-dropdown'); + + // for pages that have the settings dropdowns + if ($contentDropdown.length && $settingsDropdown.length) { + // make a JSON request to get the dropdown menus for content and settings + // we don't error handle here because the top level menu is still clickable and usables if this fails + $.getJSON('/settings-menu-groups.json', function(data){ + function makeGroupDropdownElement(group, base) { + var html_id = group.html_id ? group.html_id : group.name; + return "
  • " + group.label + "
  • "; + } + + $.each(data.content_settings, function(index, group){ + if (index > 0) { + $contentDropdown.append(""); + } + + $contentDropdown.append(makeGroupDropdownElement(group, "/content/")); + }); + + $.each(data.domain_settings, function(index, group){ + if (index > 0) { + $settingsDropdown.append(""); + } + + $settingsDropdown.append(makeGroupDropdownElement(group, "/settings/")); + }); + }); + } }); diff --git a/domain-server/resources/web/settings/js/form2js.min.js b/domain-server/resources/web/js/form2js.min.js similarity index 100% rename from domain-server/resources/web/settings/js/form2js.min.js rename to domain-server/resources/web/js/form2js.min.js diff --git a/domain-server/resources/web/js/shared.js b/domain-server/resources/web/js/shared.js index 00f699fa4e..e1870a2fa8 100644 --- a/domain-server/resources/web/js/shared.js +++ b/domain-server/resources/web/js/shared.js @@ -1,6 +1,4 @@ -var Settings = { - showAdvanced: false, - ADVANCED_CLASS: 'advanced-setting', +Object.assign(Settings, { DEPRECATED_CLASS: 'deprecated-setting', TRIGGER_CHANGE_CLASS: 'trigger-change', DATA_ROW_CLASS: 'value-row', @@ -41,7 +39,7 @@ var Settings = { FORM_ID: 'settings-form', INVALID_ROW_CLASS: 'invalid-input', DATA_ROW_INDEX: 'data-row-index' -}; +}); var URLs = { // STABLE METAVERSE_URL: https://metaverse.highfidelity.com diff --git a/domain-server/resources/web/js/underscore-min.js b/domain-server/resources/web/js/underscore-min.js index f01025b7bc..3c3eec027b 100644 --- a/domain-server/resources/web/js/underscore-min.js +++ b/domain-server/resources/web/js/underscore-min.js @@ -3,4 +3,3 @@ // (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Underscore may be freely distributed under the MIT license. (function(){function n(n){function t(t,r,e,u,i,o){for(;i>=0&&o>i;i+=n){var a=u?u[i]:i;e=r(e,t[a],a,t)}return e}return function(r,e,u,i){e=b(e,i,4);var o=!k(r)&&m.keys(r),a=(o||r).length,c=n>0?0:a-1;return arguments.length<3&&(u=r[o?o[c]:c],c+=n),t(r,e,u,o,c,a)}}function t(n){return function(t,r,e){r=x(r,e);for(var u=O(t),i=n>0?0:u-1;i>=0&&u>i;i+=n)if(r(t[i],i,t))return i;return-1}}function r(n,t,r){return function(e,u,i){var o=0,a=O(e);if("number"==typeof i)n>0?o=i>=0?i:Math.max(i+a,o):a=i>=0?Math.min(i+1,a):i+a+1;else if(r&&i&&a)return i=r(e,u),e[i]===u?i:-1;if(u!==u)return i=t(l.call(e,o,a),m.isNaN),i>=0?i+o:-1;for(i=n>0?o:a-1;i>=0&&a>i;i+=n)if(e[i]===u)return i;return-1}}function e(n,t){var r=I.length,e=n.constructor,u=m.isFunction(e)&&e.prototype||a,i="constructor";for(m.has(n,i)&&!m.contains(t,i)&&t.push(i);r--;)i=I[r],i in n&&n[i]!==u[i]&&!m.contains(t,i)&&t.push(i)}var u=this,i=u._,o=Array.prototype,a=Object.prototype,c=Function.prototype,f=o.push,l=o.slice,s=a.toString,p=a.hasOwnProperty,h=Array.isArray,v=Object.keys,g=c.bind,y=Object.create,d=function(){},m=function(n){return n instanceof m?n:this instanceof m?void(this._wrapped=n):new m(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=m),exports._=m):u._=m,m.VERSION="1.8.3";var b=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}},x=function(n,t,r){return null==n?m.identity:m.isFunction(n)?b(n,t,r):m.isObject(n)?m.matcher(n):m.property(n)};m.iteratee=function(n,t){return x(n,t,1/0)};var _=function(n,t){return function(r){var e=arguments.length;if(2>e||null==r)return r;for(var u=1;e>u;u++)for(var i=arguments[u],o=n(i),a=o.length,c=0;a>c;c++){var f=o[c];t&&r[f]!==void 0||(r[f]=i[f])}return r}},j=function(n){if(!m.isObject(n))return{};if(y)return y(n);d.prototype=n;var t=new d;return d.prototype=null,t},w=function(n){return function(t){return null==t?void 0:t[n]}},A=Math.pow(2,53)-1,O=w("length"),k=function(n){var t=O(n);return"number"==typeof t&&t>=0&&A>=t};m.each=m.forEach=function(n,t,r){t=b(t,r);var e,u;if(k(n))for(e=0,u=n.length;u>e;e++)t(n[e],e,n);else{var i=m.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},m.map=m.collect=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=Array(u),o=0;u>o;o++){var a=e?e[o]:o;i[o]=t(n[a],a,n)}return i},m.reduce=m.foldl=m.inject=n(1),m.reduceRight=m.foldr=n(-1),m.find=m.detect=function(n,t,r){var e;return e=k(n)?m.findIndex(n,t,r):m.findKey(n,t,r),e!==void 0&&e!==-1?n[e]:void 0},m.filter=m.select=function(n,t,r){var e=[];return t=x(t,r),m.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e},m.reject=function(n,t,r){return m.filter(n,m.negate(x(t)),r)},m.every=m.all=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(!t(n[o],o,n))return!1}return!0},m.some=m.any=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(t(n[o],o,n))return!0}return!1},m.contains=m.includes=m.include=function(n,t,r,e){return k(n)||(n=m.values(n)),("number"!=typeof r||e)&&(r=0),m.indexOf(n,t,r)>=0},m.invoke=function(n,t){var r=l.call(arguments,2),e=m.isFunction(t);return m.map(n,function(n){var u=e?t:n[t];return null==u?u:u.apply(n,r)})},m.pluck=function(n,t){return m.map(n,m.property(t))},m.where=function(n,t){return m.filter(n,m.matcher(t))},m.findWhere=function(n,t){return m.find(n,m.matcher(t))},m.max=function(n,t,r){var e,u,i=-1/0,o=-1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],e>i&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(u>o||u===-1/0&&i===-1/0)&&(i=n,o=u)});return i},m.min=function(n,t,r){var e,u,i=1/0,o=1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],i>e&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(o>u||1/0===u&&1/0===i)&&(i=n,o=u)});return i},m.shuffle=function(n){for(var t,r=k(n)?n:m.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=m.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},m.sample=function(n,t,r){return null==t||r?(k(n)||(n=m.values(n)),n[m.random(n.length-1)]):m.shuffle(n).slice(0,Math.max(0,t))},m.sortBy=function(n,t,r){return t=x(t,r),m.pluck(m.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=x(r,e),m.each(t,function(e,i){var o=r(e,i,t);n(u,e,o)}),u}};m.groupBy=F(function(n,t,r){m.has(n,r)?n[r].push(t):n[r]=[t]}),m.indexBy=F(function(n,t,r){n[r]=t}),m.countBy=F(function(n,t,r){m.has(n,r)?n[r]++:n[r]=1}),m.toArray=function(n){return n?m.isArray(n)?l.call(n):k(n)?m.map(n,m.identity):m.values(n):[]},m.size=function(n){return null==n?0:k(n)?n.length:m.keys(n).length},m.partition=function(n,t,r){t=x(t,r);var e=[],u=[];return m.each(n,function(n,r,i){(t(n,r,i)?e:u).push(n)}),[e,u]},m.first=m.head=m.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:m.initial(n,n.length-t)},m.initial=function(n,t,r){return l.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},m.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:m.rest(n,Math.max(0,n.length-t))},m.rest=m.tail=m.drop=function(n,t,r){return l.call(n,null==t||r?1:t)},m.compact=function(n){return m.filter(n,m.identity)};var S=function(n,t,r,e){for(var u=[],i=0,o=e||0,a=O(n);a>o;o++){var c=n[o];if(k(c)&&(m.isArray(c)||m.isArguments(c))){t||(c=S(c,t,r));var f=0,l=c.length;for(u.length+=l;l>f;)u[i++]=c[f++]}else r||(u[i++]=c)}return u};m.flatten=function(n,t){return S(n,t,!1)},m.without=function(n){return m.difference(n,l.call(arguments,1))},m.uniq=m.unique=function(n,t,r,e){m.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=x(r,e));for(var u=[],i=[],o=0,a=O(n);a>o;o++){var c=n[o],f=r?r(c,o,n):c;t?(o&&i===f||u.push(c),i=f):r?m.contains(i,f)||(i.push(f),u.push(c)):m.contains(u,c)||u.push(c)}return u},m.union=function(){return m.uniq(S(arguments,!0,!0))},m.intersection=function(n){for(var t=[],r=arguments.length,e=0,u=O(n);u>e;e++){var i=n[e];if(!m.contains(t,i)){for(var o=1;r>o&&m.contains(arguments[o],i);o++);o===r&&t.push(i)}}return t},m.difference=function(n){var t=S(arguments,!0,!0,1);return m.filter(n,function(n){return!m.contains(t,n)})},m.zip=function(){return m.unzip(arguments)},m.unzip=function(n){for(var t=n&&m.max(n,O).length||0,r=Array(t),e=0;t>e;e++)r[e]=m.pluck(n,e);return r},m.object=function(n,t){for(var r={},e=0,u=O(n);u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},m.findIndex=t(1),m.findLastIndex=t(-1),m.sortedIndex=function(n,t,r,e){r=x(r,e,1);for(var u=r(t),i=0,o=O(n);o>i;){var a=Math.floor((i+o)/2);r(n[a])i;i++,n+=r)u[i]=n;return u};var E=function(n,t,r,e,u){if(!(e instanceof t))return n.apply(r,u);var i=j(n.prototype),o=n.apply(i,u);return m.isObject(o)?o:i};m.bind=function(n,t){if(g&&n.bind===g)return g.apply(n,l.call(arguments,1));if(!m.isFunction(n))throw new TypeError("Bind must be called on a function");var r=l.call(arguments,2),e=function(){return E(n,e,t,this,r.concat(l.call(arguments)))};return e},m.partial=function(n){var t=l.call(arguments,1),r=function(){for(var e=0,u=t.length,i=Array(u),o=0;u>o;o++)i[o]=t[o]===m?arguments[e++]:t[o];for(;e=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=m.bind(n[r],n);return n},m.memoize=function(n,t){var r=function(e){var u=r.cache,i=""+(t?t.apply(this,arguments):e);return m.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},m.delay=function(n,t){var r=l.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},m.defer=m.partial(m.delay,m,1),m.throttle=function(n,t,r){var e,u,i,o=null,a=0;r||(r={});var c=function(){a=r.leading===!1?0:m.now(),o=null,i=n.apply(e,u),o||(e=u=null)};return function(){var f=m.now();a||r.leading!==!1||(a=f);var l=t-(f-a);return e=this,u=arguments,0>=l||l>t?(o&&(clearTimeout(o),o=null),a=f,i=n.apply(e,u),o||(e=u=null)):o||r.trailing===!1||(o=setTimeout(c,l)),i}},m.debounce=function(n,t,r){var e,u,i,o,a,c=function(){var f=m.now()-o;t>f&&f>=0?e=setTimeout(c,t-f):(e=null,r||(a=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,o=m.now();var f=r&&!e;return e||(e=setTimeout(c,t)),f&&(a=n.apply(i,u),i=u=null),a}},m.wrap=function(n,t){return m.partial(t,n)},m.negate=function(n){return function(){return!n.apply(this,arguments)}},m.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},m.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},m.before=function(n,t){var r;return function(){return--n>0&&(r=t.apply(this,arguments)),1>=n&&(t=null),r}},m.once=m.partial(m.before,2);var M=!{toString:null}.propertyIsEnumerable("toString"),I=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];m.keys=function(n){if(!m.isObject(n))return[];if(v)return v(n);var t=[];for(var r in n)m.has(n,r)&&t.push(r);return M&&e(n,t),t},m.allKeys=function(n){if(!m.isObject(n))return[];var t=[];for(var r in n)t.push(r);return M&&e(n,t),t},m.values=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},m.mapObject=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=u.length,o={},a=0;i>a;a++)e=u[a],o[e]=t(n[e],e,n);return o},m.pairs=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},m.invert=function(n){for(var t={},r=m.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},m.functions=m.methods=function(n){var t=[];for(var r in n)m.isFunction(n[r])&&t.push(r);return t.sort()},m.extend=_(m.allKeys),m.extendOwn=m.assign=_(m.keys),m.findKey=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=0,o=u.length;o>i;i++)if(e=u[i],t(n[e],e,n))return e},m.pick=function(n,t,r){var e,u,i={},o=n;if(null==o)return i;m.isFunction(t)?(u=m.allKeys(o),e=b(t,r)):(u=S(arguments,!1,!1,1),e=function(n,t,r){return t in r},o=Object(o));for(var a=0,c=u.length;c>a;a++){var f=u[a],l=o[f];e(l,f,o)&&(i[f]=l)}return i},m.omit=function(n,t,r){if(m.isFunction(t))t=m.negate(t);else{var e=m.map(S(arguments,!1,!1,1),String);t=function(n,t){return!m.contains(e,t)}}return m.pick(n,t,r)},m.defaults=_(m.allKeys,!0),m.create=function(n,t){var r=j(n);return t&&m.extendOwn(r,t),r},m.clone=function(n){return m.isObject(n)?m.isArray(n)?n.slice():m.extend({},n):n},m.tap=function(n,t){return t(n),n},m.isMatch=function(n,t){var r=m.keys(t),e=r.length;if(null==n)return!e;for(var u=Object(n),i=0;e>i;i++){var o=r[i];if(t[o]!==u[o]||!(o in u))return!1}return!0};var N=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof m&&(n=n._wrapped),t instanceof m&&(t=t._wrapped);var u=s.call(n);if(u!==s.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}var i="[object Array]"===u;if(!i){if("object"!=typeof n||"object"!=typeof t)return!1;var o=n.constructor,a=t.constructor;if(o!==a&&!(m.isFunction(o)&&o instanceof o&&m.isFunction(a)&&a instanceof a)&&"constructor"in n&&"constructor"in t)return!1}r=r||[],e=e||[];for(var c=r.length;c--;)if(r[c]===n)return e[c]===t;if(r.push(n),e.push(t),i){if(c=n.length,c!==t.length)return!1;for(;c--;)if(!N(n[c],t[c],r,e))return!1}else{var f,l=m.keys(n);if(c=l.length,m.keys(t).length!==c)return!1;for(;c--;)if(f=l[c],!m.has(t,f)||!N(n[f],t[f],r,e))return!1}return r.pop(),e.pop(),!0};m.isEqual=function(n,t){return N(n,t)},m.isEmpty=function(n){return null==n?!0:k(n)&&(m.isArray(n)||m.isString(n)||m.isArguments(n))?0===n.length:0===m.keys(n).length},m.isElement=function(n){return!(!n||1!==n.nodeType)},m.isArray=h||function(n){return"[object Array]"===s.call(n)},m.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},m.each(["Arguments","Function","String","Number","Date","RegExp","Error"],function(n){m["is"+n]=function(t){return s.call(t)==="[object "+n+"]"}}),m.isArguments(arguments)||(m.isArguments=function(n){return m.has(n,"callee")}),"function"!=typeof/./&&"object"!=typeof Int8Array&&(m.isFunction=function(n){return"function"==typeof n||!1}),m.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},m.isNaN=function(n){return m.isNumber(n)&&n!==+n},m.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===s.call(n)},m.isNull=function(n){return null===n},m.isUndefined=function(n){return n===void 0},m.has=function(n,t){return null!=n&&p.call(n,t)},m.noConflict=function(){return u._=i,this},m.identity=function(n){return n},m.constant=function(n){return function(){return n}},m.noop=function(){},m.property=w,m.propertyOf=function(n){return null==n?function(){}:function(t){return n[t]}},m.matcher=m.matches=function(n){return n=m.extendOwn({},n),function(t){return m.isMatch(t,n)}},m.times=function(n,t,r){var e=Array(Math.max(0,n));t=b(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},m.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},m.now=Date.now||function(){return(new Date).getTime()};var B={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},T=m.invert(B),R=function(n){var t=function(t){return n[t]},r="(?:"+m.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};m.escape=R(B),m.unescape=R(T),m.result=function(n,t,r){var e=null==n?void 0:n[t];return e===void 0&&(e=r),m.isFunction(e)?e.call(n):e};var q=0;m.uniqueId=function(n){var t=++q+"";return n?n+t:t},m.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var K=/(.)^/,z={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\u2028|\u2029/g,L=function(n){return"\\"+z[n]};m.template=function(n,t,r){!t&&r&&(t=r),t=m.defaults({},t,m.templateSettings);var e=RegExp([(t.escape||K).source,(t.interpolate||K).source,(t.evaluate||K).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,o,a){return i+=n.slice(u,a).replace(D,L),u=a+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":o&&(i+="';\n"+o+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var o=new Function(t.variable||"obj","_",i)}catch(a){throw a.source=i,a}var c=function(n){return o.call(this,n,m)},f=t.variable||"obj";return c.source="function("+f+"){\n"+i+"}",c},m.chain=function(n){var t=m(n);return t._chain=!0,t};var P=function(n,t){return n._chain?m(t).chain():t};m.mixin=function(n){m.each(m.functions(n),function(t){var r=m[t]=n[t];m.prototype[t]=function(){var n=[this._wrapped];return f.apply(n,arguments),P(this,r.apply(m,n))}})},m.mixin(m),m.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=o[n];m.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],P(this,r)}}),m.each(["concat","join","slice"],function(n){var t=o[n];m.prototype[n]=function(){return P(this,t.apply(this._wrapped,arguments))}}),m.prototype.value=function(){return this._wrapped},m.prototype.valueOf=m.prototype.toJSON=m.prototype.value,m.prototype.toString=function(){return""+this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return m})}).call(this); -//# sourceMappingURL=underscore-min.map \ No newline at end of file diff --git a/domain-server/resources/web/settings/index.shtml b/domain-server/resources/web/settings/index.shtml index d36330375a..d71692523a 100644 --- a/domain-server/resources/web/settings/index.shtml +++ b/domain-server/resources/web/settings/index.shtml @@ -1,104 +1,21 @@ -
    -
    -
    - -
    -
    + -
    -
    -
    - - - - - - - -
    -
    - -
    -
    - - - -
    - - -
    -
    -
    -
    - - -
    + - - - + - - - - + + + + + diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 3faeff4482..9a31b766a6 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -1,1262 +1,20 @@ -var DomainInfo = null; - -var viewHelpers = { - getFormGroup: function(keypath, setting, values, isAdvanced) { - form_group = "
    "; - setting_value = _(values).valueForKeyPath(keypath); - - if (_.isUndefined(setting_value) || _.isNull(setting_value)) { - if (_.has(setting, 'default')) { - setting_value = setting.default; - } else { - setting_value = ""; - } - } - - label_class = 'control-label'; - - function common_attrs(extra_classes) { - extra_classes = (!_.isUndefined(extra_classes) ? extra_classes : ""); - return " class='" + (setting.type !== 'checkbox' ? 'form-control' : '') - + " " + Settings.TRIGGER_CHANGE_CLASS + " " + extra_classes + "' data-short-name='" - + setting.name + "' name='" + keypath + "' " - + "id='" + (!_.isUndefined(setting.html_id) ? setting.html_id : keypath) + "'"; - } - - if (setting.type === 'checkbox') { - if (setting.label) { - form_group += "" - } - - form_group += "
    " - form_group += "" - - if (setting.help) { - form_group += "" + setting.help + ""; - } - - form_group += "
    " - } else { - input_type = _.has(setting, 'type') ? setting.type : "text" - - if (setting.label) { - form_group += ""; - } - - if (input_type === 'table') { - form_group += makeTable(setting, keypath, setting_value) - } else { - if (input_type === 'select') { - form_group += "" - - form_group += "" - } else if (input_type === 'button') { - // Is this a button that should link to something directly? - // If so, we use an anchor tag instead of a button tag - - if (setting.href) { - form_group += "" - + setting.button_label + ""; - } else { - form_group += ""; - } - - } else { - - if (input_type == 'integer') { - input_type = "text" - } - - form_group += "" - } - - form_group += "" + setting.help + "" - } - } - - form_group += "
    " - return form_group - } -} - -var qs = (function(a) { - if (a == "") return {}; - var b = {}; - for (var i = 0; i < a.length; ++i) - { - var p=a[i].split('=', 2); - if (p.length == 1) { - b[p[0]] = ""; - } else { - b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " ")); - } - } - return b; -})(window.location.search.substr(1).split('&')); - $(document).ready(function(){ - /* - * Clamped-width. - * Usage: - *
    This long content will force clamped width
    - * - * Author: LV - */ - - $.ajaxSetup({ - timeout: 20000, - }); - - $('[data-clampedwidth]').each(function () { - var elem = $(this); - var parentPanel = elem.data('clampedwidth'); - var resizeFn = function () { - var sideBarNavWidth = $(parentPanel).width() - parseInt(elem.css('paddingLeft')) - parseInt(elem.css('paddingRight')) - parseInt(elem.css('marginLeft')) - parseInt(elem.css('marginRight')) - parseInt(elem.css('borderLeftWidth')) - parseInt(elem.css('borderRightWidth')); - elem.css('width', sideBarNavWidth); - }; - - resizeFn(); - $(window).resize(resizeFn); - }); - - $('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_ROW_BUTTON_CLASS, function(){ - addTableRow($(this).closest('tr')); - }); - - $('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_ROW_BUTTON_CLASS, function(){ - deleteTableRow($(this).closest('tr')); - }); - - $('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_CATEGORY_BUTTON_CLASS, function(){ - addTableCategory($(this).closest('tr')); - }); - - $('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_CATEGORY_BUTTON_CLASS, function(){ - deleteTableCategory($(this).closest('tr')); - }); - - $('#' + Settings.FORM_ID).on('click', '.' + Settings.TOGGLE_CATEGORY_COLUMN_CLASS, function(){ - toggleTableCategory($(this).closest('tr')); - }); - - $('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_UP_BUTTON_CLASS, function(){ - moveTableRow($(this).closest('tr'), true); - }); - - $('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_DOWN_BUTTON_CLASS, function(){ - moveTableRow($(this).closest('tr'), false); - }); - - $('#' + Settings.FORM_ID).on('keyup', function(e){ - var $target = $(e.target); - if (e.keyCode == 13) { - if ($target.is('table input')) { - // capture enter in table input - // if we have a sibling next to us that has an input, jump to it, otherwise check if we have a glyphicon for add to click - sibling = $target.parent('td').next(); - - if (sibling.hasClass(Settings.DATA_COL_CLASS)) { - // set focus to next input - sibling.find('input').focus(); - } else { - - // jump over the re-order row, if that's what we're on - if (sibling.hasClass(Settings.REORDER_BUTTONS_CLASS)) { - sibling = sibling.next(); - } - - // for tables with categories we add the entry and setup the new row on enter - if (sibling.find("." + Settings.ADD_CATEGORY_BUTTON_CLASS).length) { - sibling.find("." + Settings.ADD_CATEGORY_BUTTON_CLASS).click(); - - // set focus to the first input in the new row - $target.closest('table').find('tr.inputs input:first').focus(); - } - - var tableRows = sibling.parent(); - var tableBody = tableRows.parent(); - - // if theres no more siblings, we should jump to a new row - if (sibling.next().length == 0 && tableRows.nextAll().length == 1) { - tableBody.find("." + Settings.ADD_ROW_BUTTON_CLASS).click(); - } - } - - } else if ($target.is('input')) { - $target.change().blur(); - } - - e.preventDefault(); - } - }); - - $('#' + Settings.FORM_ID).on('keypress', function(e){ - if (e.keyCode == 13) { - - e.preventDefault(); - } - }); - - $('#' + Settings.FORM_ID).on('change', '.' + Settings.TRIGGER_CHANGE_CLASS , function(){ - // this input was changed, add the changed data attribute to it - $(this).attr('data-changed', true); - - badgeSidebarForDifferences($(this)); - }); - - $('#' + Settings.FORM_ID).on('switchChange.bootstrapSwitch', 'input.toggle-checkbox', function(){ - // this checkbox was changed, add the changed data attribute to it - $(this).attr('data-changed', true); - - badgeSidebarForDifferences($(this)); - }); - - // Bootstrap switch in table - $('#' + Settings.FORM_ID).on('change', 'input.table-checkbox', function () { - // Bootstrap switches in table: set the changed data attribute for all rows in table. - var row = $(this).closest('tr'); - if (row.hasClass("value-row")) { // Don't set attribute on input row switches prior to it being added to table. - row.find('td.' + Settings.DATA_COL_CLASS + ' input').attr('data-changed', true); - updateDataChangedForSiblingRows(row, true); - badgeSidebarForDifferences($(this)); - } - }); - - $('#' + Settings.FORM_ID).on('change', 'input.table-time', function() { - // Bootstrap switches in table: set the changed data attribute for all rows in table. - var row = $(this).closest('tr'); - if (row.hasClass("value-row")) { // Don't set attribute on input row switches prior to it being added to table. - row.find('td.' + Settings.DATA_COL_CLASS + ' input').attr('data-changed', true); - updateDataChangedForSiblingRows(row, true); - badgeSidebarForDifferences($(this)); - } - }); - - $('.advanced-toggle').click(function(){ - Settings.showAdvanced = !Settings.showAdvanced - var advancedSelector = $('.' + Settings.ADVANCED_CLASS) - - if (Settings.showAdvanced) { - advancedSelector.show(); - $(this).html("Hide advanced") - } else { - advancedSelector.hide(); - $(this).html("Show advanced") - } - - $(this).blur(); - }) - - $('#' + Settings.FORM_ID).on('click', '#' + Settings.CREATE_DOMAIN_ID_BTN_ID, function(){ - $(this).blur(); - showDomainCreationAlert(false); - }) - - $('#' + Settings.FORM_ID).on('click', '#' + Settings.CHOOSE_DOMAIN_ID_BTN_ID, function(){ - $(this).blur(); - chooseFromHighFidelityDomains($(this)) - }); - - $('#' + Settings.FORM_ID).on('click', '#' + Settings.GET_TEMPORARY_NAME_BTN_ID, function(){ - $(this).blur(); - createTemporaryDomain(); - }); - - - $('#' + Settings.FORM_ID).on('change', 'select', function(){ - $("input[name='" + $(this).attr('data-hidden-input') + "']").val($(this).val()).change() - }); - - $('#' + Settings.FORM_ID).on('click', '#' + Settings.DISCONNECT_ACCOUNT_BTN_ID, function(e){ - $(this).blur(); - disonnectHighFidelityAccount(); - e.preventDefault(); - }); - - $('#' + Settings.FORM_ID).on('click', '#' + Settings.CONNECT_ACCOUNT_BTN_ID, function(e){ - $(this).blur(); - prepareAccessTokenPrompt(function(accessToken) { - // we have an access token - set the access token input with this and save settings - $(Settings.ACCESS_TOKEN_SELECTOR).val(accessToken).change(); - saveSettings(); - }); - }); - - var panelsSource = $('#panels-template').html() - Settings.panelsTemplate = _.template(panelsSource) - - var sidebarTemplate = $('#list-group-template').html() - Settings.sidebarTemplate = _.template(sidebarTemplate) - - var navbarHeight = $('.navbar').outerHeight(true); - - $('#setup-sidebar').affix({ - offset: { - top: 1, - bottom: navbarHeight - } - }); - - reloadSettings(function(success){ - if (success) { - handleAction(); - } else { - swal({ - title: '', - type: 'error', - text: Strings.LOADING_SETTINGS_ERROR - }); - } - $('body').scrollspy({ - target: '#setup-sidebar', - offset: navbarHeight - }); - }); -}); - -function getShareName(callback) { - getDomainFromAPI(function(data){ - // check if we have owner_places (for a real domain) or a name (for a temporary domain) - if (data && data.status == "success") { - var shareName; - if (data.domain.default_place_name) { - shareName = data.domain.default_place_name; - } else if (data.domain.name) { - shareName = data.domain.name; - } else if (data.domain.network_address) { - shareName = data.domain.network_address; - if (data.domain.network_port !== 40102) { - shareName += ':' + data.domain.network_port; - } - } - - callback(true, shareName); - } else { - callback(false); - } - }) -} - -function handleAction() { - // check if we were passed an action to handle - var action = qs["action"]; - - if (action == "share") { - // figure out if we already have a stored domain ID - if (Settings.data.values.metaverse.id.length > 0) { - // we need to ask the API what a shareable name for this domain is - getShareName(function(success, shareName){ - if (success) { - var shareLink = "hifi://" + shareName; - - console.log(shareLink); - - // show a dialog with a copiable share URL - swal({ - title: "Share", - type: "input", - inputPlaceholder: shareLink, - inputValue: shareLink, - text: "Copy this URL to invite friends to your domain.", - closeOnConfirm: true - }); - - $('.sweet-alert input').select(); - - } else { - // show an error alert - swal({ - title: '', - type: 'error', - text: "There was a problem retreiving domain information from High Fidelity API.", - confirmButtonText: 'Try again', - showCancelButton: true, - closeOnConfirm: false - }, function(isConfirm){ - if (isConfirm) { - // they want to try getting domain share info again - showSpinnerAlert("Requesting domain information...") - handleAction(); - } else { - swal.close(); - } - }); - } - }); - } else { - // no domain ID present, just show the share dialog - createTemporaryDomain(); - } - } -} - -function dynamicButton(button_id, text) { - return $(""); -} - -function postSettings(jsonSettings) { - // POST the form JSON to the domain-server settings.json endpoint so the settings are saved - $.ajax('/settings.json', { - data: JSON.stringify(jsonSettings), - contentType: 'application/json', - type: 'POST' - }).done(function(data){ - if (data.status == "success") { - if ($(".save-button").html() === SAVE_BUTTON_LABEL_RESTART) { - showRestartModal(); - } else { - location.reload(true); - } - } else { - showErrorMessage("Error", SETTINGS_ERROR_MESSAGE) - reloadSettings(); - } - }).fail(function(){ - showErrorMessage("Error", SETTINGS_ERROR_MESSAGE) - reloadSettings(); - }); -} - -function accessTokenIsSet() { - return Settings.data.values.metaverse.access_token.length > 0; -} - -function setupHFAccountButton() { - - var hasAccessToken = accessTokenIsSet(); - var el; - - if (hasAccessToken) { - el = "

    "; - el += ""; - el += ""; - el += "

    "; - el = $(el); - } else { - // setup an object for the settings we want our button to have - var buttonSetting = { - type: 'button', - name: 'connected_account', - label: 'Connected Account', - } - buttonSetting.help = ""; - buttonSetting.classes = "btn-primary"; - buttonSetting.button_label = "Connect High Fidelity Account"; - buttonSetting.html_id = Settings.CONNECT_ACCOUNT_BTN_ID; - - buttonSetting.href = URLs.METAVERSE_URL + "/user/tokens/new?for_domain_server=true"; - - // since we do not have an access token we change hide domain ID and auto networking settings - // without an access token niether of them can do anything - $("[data-keypath='metaverse.id']").hide(); - - // use the existing getFormGroup helper to ask for a button - el = viewHelpers.getFormGroup('', buttonSetting, Settings.data.values); - } - - // add the button group to the top of the metaverse panel - $('#metaverse .panel-body').prepend(el); -} - -function disonnectHighFidelityAccount() { - // the user clicked on the disconnect account btn - give them a sweet alert to make sure this is what they want to do - swal({ - title: "Are you sure?", - text: "This will remove your domain-server OAuth access token." - + "

    This could cause your domain to appear offline and no longer be reachable via any place names.", - type: "warning", - html: true, - showCancelButton: true - }, function(){ - // we need to post to settings to clear the access-token - $(Settings.ACCESS_TOKEN_SELECTOR).val('').change(); - // reset the domain id to get a new temporary name - $(Settings.DOMAIN_ID_SELECTOR).val('').change(); - saveSettings(); - }); -} - -function showSpinnerAlert(title) { - swal({ - title: title, - text: '
    ', - html: true, - showConfirmButton: false, - allowEscapeKey: false - }); -} - -function showDomainCreationAlert(justConnected) { - swal({ - title: 'Create new domain ID', - type: 'input', - text: 'Enter a label this machine.

    This will help you identify which domain ID belongs to which machine.

    ', - showCancelButton: true, - confirmButtonText: "Create", - closeOnConfirm: false, - html: true - }, function(inputValue){ - if (inputValue === false) { - swal.close(); - - // user cancelled domain ID creation - if we're supposed to save after cancel then save here - if (justConnected) { - saveSettings(); - } - } else { - // we're going to change the alert to a new one with a spinner while we create this domain - showSpinnerAlert('Creating domain ID'); - createNewDomainID(inputValue, justConnected); - } - }); -} - -function createNewDomainID(label, justConnected) { - // get the JSON object ready that we'll use to create a new domain - var domainJSON = { - "label": label - } - - $.post("/api/domains", domainJSON, function(data){ - // we successfully created a domain ID, set it on that field - var domainID = data.domain.id; - console.log("Setting domain id to ", data, domainID); - $(Settings.DOMAIN_ID_SELECTOR).val(domainID).change(); - - if (justConnected) { - var successText = Strings.CREATE_DOMAIN_SUCCESS_JUST_CONNECTED - } else { - var successText = Strings.CREATE_DOMAIN_SUCCESS; - } - - successText += "

    Click the button below to save your new settings and restart your domain-server."; - - // show a sweet alert to say we are all finished up and that we need to save - swal({ - title: 'Success!', - type: 'success', - text: successText, - html: true, - confirmButtonText: 'Save' - }, function(){ - saveSettings(); - }); - }, 'json').fail(function(){ - - var errorText = "There was a problem creating your new domain ID. Do you want to try again or"; - - if (justConnected) { - errorText += " just save your new access token?

    You can always create a new domain ID later."; - } else { - errorText += " cancel?" - } - - // we failed to create the new domain ID, show a sweet-alert that lets them try again or cancel - swal({ - title: '', - type: 'error', - text: errorText, - html: true, - confirmButtonText: 'Try again', - showCancelButton: true, - closeOnConfirm: false - }, function(isConfirm){ - if (isConfirm) { - // they want to try creating a domain ID again - showDomainCreationAlert(justConnected); - } else { - // they want to cancel - if (justConnected) { - // since they just connected we need to save the access token here - saveSettings(); - } - } - }); - }); -} - -function createDomainSpinner() { - var spinner = ''; - return spinner; -} - -function createDomainLoadingError(message) { - var errorEl = $(""); - errorEl.append(message + " "); - - var retryLink = $("Please click here to try again."); - retryLink.click(function(ev) { - ev.preventDefault(); - reloadDomainInfo(); - }); - errorEl.append(retryLink); - - return errorEl; -} - -function parseJSONResponse(xhr) { - try { - return JSON.parse(xhr.responseText); - } catch (e) { - } - return null; -} - -function showOrHideLabel() { - var type = getCurrentDomainIDType(); - var shouldShow = accessTokenIsSet() && (type === DOMAIN_ID_TYPE_FULL || type === DOMAIN_ID_TYPE_UNKNOWN); - $(".panel#label").toggle(shouldShow); - $("li a[href='#label']").parent().toggle(shouldShow); - return shouldShow; -} - -function setupDomainLabelSetting() { - showOrHideLabel(); - - var html = "
    " - html += " Edit"; - html += ""; - html += "
    "; - - html = $(html); - - html.find('a').click(function(ev) { - ev.preventDefault(); - - var label = DomainInfo.label === null ? "" : DomainInfo.label; - var modal_body = "
    "; - modal_body += ""; - modal_body += ""; - modal_body += "
    "; - modal_body += "
    "; - - var dialog = bootbox.dialog({ - title: 'Edit Label', - message: modal_body, - closeButton: false, - onEscape: true, - buttons: [ - { - label: 'Cancel', - className: 'edit-label-cancel-btn', - callback: function() { - dialog.modal('hide'); - } - }, - { - label: 'Save', - className: 'edit-label-save-btn btn btn-primary', - callback: function() { - var data = { - label: $('#domain-label-input').val() - }; - - $('.edit-label-cancel-btn').attr('disabled', 'disabled'); - $('.edit-label-save-btn').attr('disabled', 'disabled'); - $('.edit-label-save-btn').html("Saving..."); - - $('.error-message').hide(); - - $.ajax({ - url: '/api/domains', - type: 'PUT', - data: data, - success: function(xhr) { - dialog.modal('hide'); - reloadDomainInfo(); - }, - error: function(xhr) { - var data = parseJSONResponse(xhr); - console.log(data, data.status, data.data); - if (data.status === "fail") { - for (var key in data.data) { - var errorMsg = data.data[key]; - var errorEl = $('.error-message[data-property="' + key + '"'); - errorEl.html(errorMsg); - errorEl.show(); - } - } - $('.edit-label-cancel-btn').removeAttr('disabled'); - $('.edit-label-save-btn').removeAttr('disabled'); - $('.edit-label-save-btn').html("Save"); - } - }); - return false; - } - } - ], - callback: function(result) { - console.log("result: ", result); - } - }); - }); - - var spinner = createDomainSpinner(); - var errorEl = createDomainLoadingError("Error loading label."); - - html.append(spinner); - html.append(errorEl); - - $('div#label .panel-body').append(html); -} - -function showOrHideAutomaticNetworking() { - var type = getCurrentDomainIDType(); - if (!accessTokenIsSet() || (type !== DOMAIN_ID_TYPE_FULL && type !== DOMAIN_ID_TYPE_UNKNOWN)) { - $("[data-keypath='metaverse.automatic_networking']").hide(); - return false; - } - $("[data-keypath='metaverse.automatic_networking']").show(); - return true; -} - -function setupDomainNetworkingSettings() { - if (!showOrHideAutomaticNetworking()) { - return; - } - - var autoNetworkingSetting = Settings.data.values.metaverse.automatic_networking; - if (autoNetworkingSetting === 'full') { - return; - } - - var includeAddress = autoNetworkingSetting === 'disabled'; - - if (includeAddress) { - var label = "Network Address:Port"; - } else { - var label = "Network Port"; - } - - var lowerName = name.toLowerCase(); - var form = '
    '; - form += ''; - form += ' Edit'; - form += ''; - form += '
    This defines how nodes will connect to your domain. You can read more about automatic networking here.
    '; - form += '
    '; - - form = $(form); - - form.find('#edit-network-address-port').click(function(ev) { - ev.preventDefault(); - - var address = DomainInfo.network_address === null ? '' : DomainInfo.network_address; - var port = DomainInfo.network_port === null ? '' : DomainInfo.network_port; - var modal_body = "
    "; - if (includeAddress) { - modal_body += ""; - modal_body += ""; - modal_body += "
    "; - } - modal_body += ""; - modal_body += ""; - modal_body += "
    "; - modal_body += "
    "; - - var dialog = bootbox.dialog({ - title: 'Edit Network', - message: modal_body, - closeButton: false, - onEscape: true, - buttons: [ - { - label: 'Cancel', - className: 'edit-network-cancel-btn', - callback: function() { - dialog.modal('hide'); - } - }, - { - label: 'Save', - className: 'edit-network-save-btn btn btn-primary', - callback: function() { - var data = { - network_port: $('#network-port-input').val() - }; - if (includeAddress) { - data.network_address = $('#network-address-input').val(); - } - - $('.edit-network-cancel-btn').attr('disabled', 'disabled'); - $('.edit-network-save-btn').attr('disabled', 'disabled'); - $('.edit-network-save-btn').html("Saving..."); - - console.log('data', data); - - $('.error-message').hide(); - - $.ajax({ - url: '/api/domains', - type: 'PUT', - data: data, - success: function(xhr) { - console.log(xhr, parseJSONResponse(xhr)); - dialog.modal('hide'); - reloadDomainInfo(); - }, - error:function(xhr) { - var data = parseJSONResponse(xhr); - console.log(data, data.status, data.data); - if (data.status === "fail") { - for (var key in data.data) { - var errorMsg = data.data[key]; - console.log(key, errorMsg); - var errorEl = $('.error-message[data-property="' + key + '"'); - console.log(errorEl); - errorEl.html(errorMsg); - errorEl.show(); - } - } - $('.edit-network-cancel-btn').removeAttr('disabled'); - $('.edit-network-save-btn').removeAttr('disabled'); - $('.edit-network-save-btn').html("Save"); - } - }); - return false; - } - } - ], - callback: function(result) { - console.log("result: ", result); - } - }); - }); - - var spinner = createDomainSpinner(); - - var errorMessage = '' - if (includeAddress) { - errorMessage = "We were unable to load the network address and port."; - } else { - errorMessage = "We were unable to load the network port." - } - var errorEl = createDomainLoadingError(errorMessage); - - var autoNetworkingEl = $('div[data-keypath="metaverse.automatic_networking"]'); - autoNetworkingEl.after(spinner); - autoNetworkingEl.after(errorEl); - autoNetworkingEl.after(form); -} - - -function setupPlacesTable() { - // create a dummy table using our view helper - var placesTableSetting = { - type: 'table', - name: 'places', - label: 'Places', - html_id: Settings.PLACES_TABLE_ID, - help: "The following places currently point to this domain.
    To point places to this domain, " - + " go to the My Places " - + "page in your High Fidelity Metaverse account.", - read_only: true, - can_add_new_rows: false, - columns: [ + var qs = (function(a) { + if (a == "") return {}; + var b = {}; + for (var i = 0; i < a.length; ++i) { - "name": "name", - "label": "Name" - }, - { - "name": "path", - "label": "Viewpoint or Path" - }, - { - "name": "remove", - "label": "", - "class": "buttons" - } - ] - } - - // get a table for the places - var placesTableGroup = viewHelpers.getFormGroup('', placesTableSetting, Settings.data.values); - - // append the places table in the right place - $('#places_paths .panel-body').prepend(placesTableGroup); - //$('#' + Settings.PLACES_TABLE_ID).append(""); - - var spinner = createDomainSpinner(); - $('#' + Settings.PLACES_TABLE_ID).after($(spinner)); - - var errorEl = createDomainLoadingError("There was an error retreiving your places."); - $("#" + Settings.PLACES_TABLE_ID).after(errorEl); - - // do we have a domain ID? - if (Settings.data.values.metaverse.id.length == 0) { - // we don't have a domain ID - add a button to offer the user a chance to get a temporary one - var temporaryPlaceButton = dynamicButton(Settings.GET_TEMPORARY_NAME_BTN_ID, 'Get a temporary place name'); - $('#' + Settings.PLACES_TABLE_ID).after(temporaryPlaceButton); - } - if (accessTokenIsSet()) { - appendAddButtonToPlacesTable(); - } -} - -function placeTableRow(name, path, isTemporary, placeID) { - var name_link = "" + (isTemporary ? name + " (temporary)" : name) + ""; - - function placeEditClicked() { - editHighFidelityPlace(placeID, name, path); - } - - function placeDeleteClicked() { - var el = $(this); - var confirmString = Strings.REMOVE_PLACE_TITLE.replace("{{place}}", name); - var dialog = bootbox.dialog({ - message: confirmString, - closeButton: false, - onEscape: true, - buttons: [ - { - label: Strings.REMOVE_PLACE_CANCEL_BUTTON, - className: "delete-place-cancel-btn", - callback: function() { - dialog.modal('hide'); - } - }, - { - label: Strings.REMOVE_PLACE_DELETE_BUTTON, - className: "delete-place-confirm-btn btn btn-danger", - callback: function() { - $('.delete-place-cancel-btn').attr('disabled', 'disabled'); - $('.delete-place-confirm-btn').attr('disabled', 'disabled'); - $('.delete-place-confirm-btn').html(Strings.REMOVE_PLACE_DELETE_BUTTON_PENDING); - sendUpdatePlaceRequest( - placeID, - '', - null, - true, - function() { - reloadDomainInfo(); - dialog.modal('hide'); - }, function() { - $('.delete-place-cancel-btn').removeAttr('disabled'); - $('.delete-place-confirm-btn').removeAttr('disabled'); - $('.delete-place-confirm-btn').html(Strings.REMOVE_PLACE_DELETE_BUTTON); - bootbox.alert(Strings.REMOVE_PLACE_ERROR); - }); - return false; - } - }, - ] - }); - } - - if (isTemporary) { - var editLink = ""; - var deleteColumn = ""; - } else { - var editLink = " Edit"; - var deleteColumn = ""; - } - - var row = $("" + name_link + "" + path + editLink + "" + deleteColumn + ""); - row.find(".place-edit").click(placeEditClicked); - row.find(".place-delete").click(placeDeleteClicked); - - return row; -} - -function placeTableRowForPlaceObject(place) { - var placePathOrIndex = (place.path ? place.path : "/"); - return placeTableRow(place.name, placePathOrIndex, false, place.id); -} - -function reloadDomainInfo() { - $('#' + Settings.PLACES_TABLE_ID + " tbody tr").not('.headers').remove(); - - $('.domain-loading-show').show(); - $('.domain-loading-hide').hide(); - $('.domain-loading-error').hide(); - $('.loading-domain-info-spinner').show(); - $('#' + Settings.PLACES_TABLE_ID).append("Hello"); - - getDomainFromAPI(function(data){ - $('.loading-domain-info-spinner').hide(); - $('.domain-loading-show').hide(); - - // check if we have owner_places (for a real domain) or a name (for a temporary domain) - if (data.status == "success") { - $('.domain-loading-hide').show(); - if (data.domain.owner_places) { - // add a table row for each of these names - _.each(data.domain.owner_places, function(place){ - $('#' + Settings.PLACES_TABLE_ID + " tbody").append(placeTableRowForPlaceObject(place)); - }); - } else if (data.domain.name) { - // add a table row for this temporary domain name - $('#' + Settings.PLACES_TABLE_ID + " tbody").append(placeTableRow(data.domain.name, '/', true)); - } - - // Update label - if (showOrHideLabel()) { - var label = data.domain.label; - label = label === null ? '' : label; - $('#network-label').val(label); - } - - // Update automatic networking - if (showOrHideAutomaticNetworking()) { - var autoNetworkingSetting = Settings.data.values.metaverse.automatic_networking; - var address = data.domain.network_address === null ? "" : data.domain.network_address; - var port = data.domain.network_port === null ? "" : data.domain.network_port; - if (autoNetworkingSetting === 'disabled') { - $('#network-address-port input').val(address + ":" + port); - } else if (autoNetworkingSetting === 'ip') { - $('#network-address-port input').val(port); - } - } - - if (accessTokenIsSet()) { - appendAddButtonToPlacesTable(); - } - - } else { - $('.domain-loading-error').show(); - } - }) -} - -function appendDomainIDButtons() { - var domainIDInput = $(Settings.DOMAIN_ID_SELECTOR); - - var createButton = dynamicButton(Settings.CREATE_DOMAIN_ID_BTN_ID, Strings.CREATE_DOMAIN_BUTTON); - createButton.css('margin-top', '10px'); - var chooseButton = dynamicButton(Settings.CHOOSE_DOMAIN_ID_BTN_ID, Strings.CHOOSE_DOMAIN_BUTTON); - chooseButton.css('margin', '10px 0px 0px 10px'); - - domainIDInput.after(chooseButton); - domainIDInput.after(createButton); -} - -function editHighFidelityPlace(placeID, name, path) { - var dialog; - - var modal_body = "
    "; - modal_body += ""; - modal_body += "
    "; - - var modal_buttons = [ - { - label: Strings.EDIT_PLACE_CANCEL_BUTTON, - className: "edit-place-cancel-button", - callback: function() { - dialog.modal('hide'); - } - }, - { - label: Strings.EDIT_PLACE_CONFIRM_BUTTON, - className: 'edit-place-save-button btn btn-primary', - callback: function() { - var placePath = $('#place-path-input').val(); - - if (path == placePath) { - return true; - } - - $('.edit-place-cancel-button').attr('disabled', 'disabled'); - $('.edit-place-save-button').attr('disabled', 'disabled'); - $('.edit-place-save-button').html(Strings.EDIT_PLACE_BUTTON_PENDING); - - sendUpdatePlaceRequest( - placeID, - placePath, - null, - false, - function() { - dialog.modal('hide') - reloadDomainInfo(); - }, - function() { - $('.edit-place-cancel-button').removeAttr('disabled'); - $('.edit-place-save-button').removeAttr('disabled'); - $('.edit-place-save-button').html(Strings.EDIT_PLACE_CONFIRM_BUTTON); - } - ); - - return false; - } - } - ]; - - dialog = bootbox.dialog({ - title: Strings.EDIT_PLACE_TITLE, - closeButton: false, - onEscape: true, - message: modal_body, - buttons: modal_buttons - }) -} - -function appendAddButtonToPlacesTable() { - var addRow = $(" "); - addRow.find(".place-add").click(function(ev) { - ev.preventDefault(); - chooseFromHighFidelityPlaces(Settings.initialValues.metaverse.access_token, null, function(placeName, newDomainID) { - if (newDomainID) { - Settings.data.values.metaverse.id = newDomainID; - var domainIDEl = $("[data-keypath='metaverse.id']"); - domainIDEl.val(newDomainID); - Settings.initialValues.metaverse.id = newDomainID; - badgeSidebarForDifferences(domainIDEl); - } - reloadDomainInfo(); - }); - }); - $('#' + Settings.PLACES_TABLE_ID + " tbody").append(addRow); -} - -function chooseFromHighFidelityDomains(clickedButton) { - // setup the modal to help user pick their domain - if (Settings.initialValues.metaverse.access_token) { - - // add a spinner to the choose button - clickedButton.html("Loading domains..."); - clickedButton.attr('disabled', 'disabled'); - - // get a list of user domains from data-web - $.ajax({ - url: "/api/domains", - dataType: 'json', - jsonp: false, - success: function(data){ - - var modal_buttons = { - cancel: { - label: 'Cancel', - className: 'btn-default' - } - } - - if (data.data.domains.length) { - // setup a select box for the returned domains - modal_body = "

    Choose the High Fidelity domain you want this domain-server to represent.
    This will set your domain ID on the settings page.

    "; - domain_select = $(""); - _.each(data.data.domains, function(domain){ - var domainString = ""; - - if (domain.label) { - domainString += '"' + domain.label+ '" - '; - } - - domainString += domain.id; - - domain_select.append(""); - }) - modal_body += "" + domain_select[0].outerHTML - modal_buttons["success"] = { - label: 'Choose domain', - callback: function() { - domainID = $('#domain-name-select').val() - // set the domain ID on the form - $(Settings.DOMAIN_ID_SELECTOR).val(domainID).change(); - } - } + var p=a[i].split('=', 2); + if (p.length == 1) { + b[p[0]] = ""; } else { - modal_buttons["success"] = { - label: 'Create new domain', - callback: function() { - window.open(URLs.METAVERSE_URL + "/user/domains", '_blank'); - } - } - modal_body = "

    You do not have any domains in your High Fidelity account." + - "

    Go to your domains page to create a new one. Once your domain is created re-open this dialog to select it.

    " + b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " ")); } - - bootbox.dialog({ - title: "Choose matching domain", - onEscape: true, - message: modal_body, - buttons: modal_buttons - }) - }, - error: function() { - bootbox.alert("Failed to retrieve your domains from the High Fidelity Metaverse"); - }, - complete: function() { - // remove the spinner from the choose button - clickedButton.html("Choose from my domains") - clickedButton.removeAttr('disabled') } - }); - - } else { - bootbox.alert({ - message: "You must have an access token to query your High Fidelity domains.

    " + - "Please follow the instructions on the settings page to add an access token.", - title: "Access token required" - }) - } - } - -function createTemporaryDomain() { - swal({ - title: 'Create temporary place name', - text: "This will create a temporary place name and domain ID" - + " so other users can easily connect to your domain.

    " - + "In order to make your domain reachable, this will also enable full automatic networking.", - showCancelButton: true, - confirmButtonText: 'Create', - closeOnConfirm: false, - html: true - }, function(isConfirm){ - if (isConfirm) { - showSpinnerAlert('Creating temporary place name'); - - // make a get request to get a temporary domain - $.post(URLs.METAVERSE_URL + '/api/v1/domains/temporary', function(data){ - if (data.status == "success") { - var domain = data.data.domain; - - // we should have a new domain ID - set it on the domain ID value - $(Settings.DOMAIN_ID_SELECTOR).val(domain.id).change(); - - // we also need to make sure auto networking is set to full - $('[data-hidden-input="metaverse.automatic_networking"]').val("full").change(); - - swal({ - type: 'success', - title: 'Success!', - text: "We have created a temporary name and domain ID for you.

    " - + "Your temporary place name is " + domain.name + ".

    " - + "Press the button below to save your new settings and restart your domain-server.", - confirmButtonText: 'Save', - html: true - }, function(){ - saveSettings(); - }); - } - }); - } - }); -} - -function reloadSettings(callback) { - $.getJSON('/settings.json', function(data){ - _.extend(data, viewHelpers) - - $('.nav-stacked').html(Settings.sidebarTemplate(data)) - $('#panels').html(Settings.panelsTemplate(data)) - - Settings.data = data; - Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true); + return b; + })(window.location.search.substr(1).split('&')); + Settings.afterReloadActions = function() { // append the domain selection modal appendDomainIDButtons(); @@ -1291,117 +49,10 @@ function reloadSettings(callback) { } } - // setup any bootstrap switches - $('.toggle-checkbox').bootstrapSwitch(); - - $('[data-toggle="tooltip"]').tooltip(); - - // call the callback now that settings are loaded - callback(true); - }).fail(function() { - // call the failure object since settings load faild - callback(false) - }); -} - -function validateInputs() { - // check if any new values are bad - var tables = $('table'); - - var inputsValid = true; - - var tables = $('table'); - - // clear any current invalid rows - $('tr.' + Settings.INVALID_ROW_CLASS).removeClass(Settings.INVALID_ROW_CLASS); - - function markParentRowInvalid(rowChild) { - $(rowChild).closest('tr').addClass(Settings.INVALID_ROW_CLASS); + handleAction(); } - _.each(tables, function(table) { - // validate keys specificially for spaces and equality to an existing key - var newKeys = $(table).find('tr.' + Settings.NEW_ROW_CLASS + ' td.key'); - - var keyWithSpaces = false; - var empty = false; - var duplicateKey = false; - - _.each(newKeys, function(keyCell) { - var keyVal = $(keyCell).children('input').val(); - - if (keyVal.indexOf(' ') !== -1) { - keyWithSpaces = true; - markParentRowInvalid(keyCell); - return; - } - - // make sure the key isn't empty - if (keyVal.length === 0) { - empty = true - - markParentRowInvalid(input); - return; - } - - // make sure we don't have duplicate keys in the table - var otherKeys = $(table).find('td.key').not(keyCell); - _.each(otherKeys, function(otherKeyCell) { - var keyInput = $(otherKeyCell).children('input'); - - if (keyInput.length) { - if ($(keyInput).val() == keyVal) { - duplicateKey = true; - } - } else if ($(otherKeyCell).html() == keyVal) { - duplicateKey = true; - } - - if (duplicateKey) { - markParentRowInvalid(keyCell); - return; - } - }); - - }); - - if (keyWithSpaces) { - showErrorMessage("Error", "Key contains spaces"); - inputsValid = false; - return - } - - if (empty) { - showErrorMessage("Error", "Empty field(s)"); - inputsValid = false; - return - } - - if (duplicateKey) { - showErrorMessage("Error", "Two keys cannot be identical"); - inputsValid = false; - return; - } - }); - - return inputsValid; -} - -var SETTINGS_ERROR_MESSAGE = "There was a problem saving domain settings. Please try again!"; - -function saveSettings() { - - if (validateInputs()) { - // POST the form JSON to the domain-server settings.json endpoint so the settings are saved - var canPost = true; - - // disable any inputs not changed - $("input:not([data-changed])").each(function () { - $(this).prop('disabled', true); - }); - - // grab a JSON representation of the form via form2js - var formJSON = form2js('settings-form', ".", false, cleanupFormValues, true); + Settings.handlePostSettings = function(formJSON) { // check if we've set the basic http password if (formJSON["security"]) { @@ -1424,727 +75,945 @@ function saveSettings() { delete formJSON["security"]["verify_http_password"]; } else { bootbox.alert({ "message": "Passwords must match!", "title": "Password Error" }); - canPost = false; + return false; } } } - console.log("----- SAVING ------"); + console.log("----- handlePostSettings() called ------"); console.log(formJSON); - // re-enable all inputs - $("input").each(function () { - $(this).prop('disabled', false); - }); + if (formJSON["security"]) { + var username = formJSON["security"]["http_username"]; - // remove focus from the button - $(this).blur(); + var password = formJSON["security"]["http_password"]; - if (canPost) { - if (formJSON["security"]) { - var username = formJSON["security"]["http_username"]; + if ((password == sha256_digest("")) && (username == undefined || (username && username.length != 0))) { + swal({ + title: "Are you sure?", + text: "You have entered a blank password with a non-blank username. Are you sure you want to require a blank password?", + type: "warning", + showCancelButton: true, + confirmButtonColor: "#5cb85c", + confirmButtonText: "Yes!", + closeOnConfirm: true + }, + function () { + formJSON["security"]["http_password"] = ""; - var password = formJSON["security"]["http_password"]; + postSettings(formJSON); + }); - if ((password == sha256_digest("")) && (username == undefined || (username && username.length != 0))) { - swal({ - title: "Are you sure?", - text: "You have entered a blank password with a non-blank username. Are you sure you want to require a blank password?", - type: "warning", - showCancelButton: true, - confirmButtonColor: "#5cb85c", - confirmButtonText: "Yes!", - closeOnConfirm: true - }, - function () { - formJSON["security"]["http_password"] = ""; - postSettings(formJSON); - }); - return; - } + return; } - // POST the form JSON to the domain-server settings.json endpoint so the settings are saved - postSettings(formJSON); } - } -} -$('body').on('click', '.save-button', function(e){ - saveSettings(); - return false; -}); + postSettings(formJSON); + }; -function makeTable(setting, keypath, setting_value) { - var isArray = !_.has(setting, 'key'); - var categoryKey = setting.categorize_by_key; - var isCategorized = !!categoryKey && isArray; - - if (!isArray && setting.can_order) { - setting.can_order = false; - } - - var html = ""; - - if (setting.help) { - html += "" + setting.help + "" - } - - var nonDeletableRowKey = setting["non-deletable-row-key"]; - var nonDeletableRowValues = setting["non-deletable-row-values"]; - - html += ""; - - if (setting.caption) { - html += "" - } - - // Column groups - if (setting.groups) { - html += "" - _.each(setting.groups, function (group) { - html += "" - }) - if (!setting.read_only) { - if (setting.can_order) { - html += ""; - } - html += "" - } - html += "" - } - - // Column names - html += "" - - if (setting.numbered === true) { - html += "" // Row number - } - - if (setting.key) { - html += "" // Key - } - - var numVisibleColumns = 0; - _.each(setting.columns, function(col) { - if (!col.hidden) numVisibleColumns++; - html += "" // Data + $('#' + Settings.FORM_ID).on('click', '#' + Settings.CREATE_DOMAIN_ID_BTN_ID, function(){ + $(this).blur(); + showDomainCreationAlert(false); }) - if (!setting.read_only) { - if (setting.can_order) { - numVisibleColumns++; - html += ""; - } - numVisibleColumns++; - html += ""; + $('#' + Settings.FORM_ID).on('click', '#' + Settings.CHOOSE_DOMAIN_ID_BTN_ID, function(){ + $(this).blur(); + chooseFromHighFidelityDomains($(this)) + }); + + $('#' + Settings.FORM_ID).on('click', '#' + Settings.GET_TEMPORARY_NAME_BTN_ID, function(){ + $(this).blur(); + createTemporaryDomain(); + }); + + $('#' + Settings.FORM_ID).on('click', '#' + Settings.DISCONNECT_ACCOUNT_BTN_ID, function(e){ + $(this).blur(); + disonnectHighFidelityAccount(); + e.preventDefault(); + }); + + $('#' + Settings.FORM_ID).on('click', '#' + Settings.CONNECT_ACCOUNT_BTN_ID, function(e){ + $(this).blur(); + prepareAccessTokenPrompt(function(accessToken) { + // we have an access token - set the access token input with this and save settings + $(Settings.ACCESS_TOKEN_SELECTOR).val(accessToken).change(); + saveSettings(); + }); + }); + + function accessTokenIsSet() { + return Settings.data.values.metaverse.access_token.length > 0; } - // populate rows in the table from existing values - var row_num = 1; - - if (keypath.length > 0 && _.size(setting_value) > 0) { - var rowIsObject = setting.columns.length > 1; - - _.each(setting_value, function(row, rowIndexOrName) { - var categoryPair = {}; - var categoryValue = ""; - if (isCategorized) { - categoryValue = rowIsObject ? row[categoryKey] : row; - categoryPair[categoryKey] = categoryValue; - if (_.findIndex(setting_value, categoryPair) === rowIndexOrName) { - html += makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, setting.can_add_new_categories, ""); + function getShareName(callback) { + getDomainFromAPI(function(data){ + // check if we have owner_places (for a real domain) or a name (for a temporary domain) + if (data && data.status == "success") { + var shareName; + if (data.domain.default_place_name) { + shareName = data.domain.default_place_name; + } else if (data.domain.name) { + shareName = data.domain.name; + } else if (data.domain.network_address) { + shareName = data.domain.network_address; + if (data.domain.network_port !== 40102) { + shareName += ':' + data.domain.network_port; + } } + + callback(true, shareName); + } else { + callback(false); + } + }) + } + + function handleAction() { + // check if we were passed an action to handle + var action = qs["action"]; + + if (action == "share") { + // figure out if we already have a stored domain ID + if (Settings.data.values.metaverse.id.length > 0) { + // we need to ask the API what a shareable name for this domain is + getShareName(function(success, shareName){ + if (success) { + var shareLink = "hifi://" + shareName; + + console.log(shareLink); + + // show a dialog with a copiable share URL + swal({ + title: "Share", + type: "input", + inputPlaceholder: shareLink, + inputValue: shareLink, + text: "Copy this URL to invite friends to your domain.", + closeOnConfirm: true + }); + + $('.sweet-alert input').select(); + + } else { + // show an error alert + swal({ + title: '', + type: 'error', + text: "There was a problem retreiving domain information from High Fidelity API.", + confirmButtonText: 'Try again', + showCancelButton: true, + closeOnConfirm: false + }, function(isConfirm){ + if (isConfirm) { + // they want to try getting domain share info again + showSpinnerAlert("Requesting domain information...") + handleAction(); + } else { + swal.close(); + } + }); + } + }); + } else { + // no domain ID present, just show the share dialog + createTemporaryDomain(); + } + } + } + + function setupHFAccountButton() { + + var hasAccessToken = accessTokenIsSet(); + var el; + + if (hasAccessToken) { + el = "

    "; + el += ""; + el += ""; + el += "

    "; + el = $(el); + } else { + // setup an object for the settings we want our button to have + var buttonSetting = { + type: 'button', + name: 'connected_account', + label: 'Connected Account', + } + buttonSetting.help = ""; + buttonSetting.classes = "btn-primary"; + buttonSetting.button_label = "Connect High Fidelity Account"; + buttonSetting.html_id = Settings.CONNECT_ACCOUNT_BTN_ID; + + buttonSetting.href = URLs.METAVERSE_URL + "/user/tokens/new?for_domain_server=true"; + + // since we do not have an access token we change hide domain ID and auto networking settings + // without an access token niether of them can do anything + $("[data-keypath='metaverse.id']").hide(); + + // use the existing getFormGroup helper to ask for a button + el = viewHelpers.getFormGroup('', buttonSetting, Settings.data.values); + } + + // add the button group to the top of the metaverse panel + $('#metaverse .panel-body').prepend(el); + } + + function disonnectHighFidelityAccount() { + // the user clicked on the disconnect account btn - give them a sweet alert to make sure this is what they want to do + swal({ + title: "Are you sure?", + text: "This will remove your domain-server OAuth access token." + + "

    This could cause your domain to appear offline and no longer be reachable via any place names.", + type: "warning", + html: true, + showCancelButton: true + }, function(){ + // we need to post to settings to clear the access-token + $(Settings.ACCESS_TOKEN_SELECTOR).val('').change(); + // reset the domain id to get a new temporary name + $(Settings.DOMAIN_ID_SELECTOR).val('').change(); + saveSettings(); + }); + } + + function showDomainCreationAlert(justConnected) { + swal({ + title: 'Create new domain ID', + type: 'input', + text: 'Enter a label this machine.

    This will help you identify which domain ID belongs to which machine.

    ', + showCancelButton: true, + confirmButtonText: "Create", + closeOnConfirm: false, + html: true + }, function(inputValue){ + if (inputValue === false) { + swal.close(); + + // user cancelled domain ID creation - if we're supposed to save after cancel then save here + if (justConnected) { + saveSettings(); + } + } else { + // we're going to change the alert to a new one with a spinner while we create this domain + showSpinnerAlert('Creating domain ID'); + createNewDomainID(inputValue, justConnected); + } + }); + } + + function createNewDomainID(label, justConnected) { + // get the JSON object ready that we'll use to create a new domain + var domainJSON = { + "label": label + } + + $.post("/api/domains", domainJSON, function(data){ + // we successfully created a domain ID, set it on that field + var domainID = data.domain.id; + console.log("Setting domain id to ", data, domainID); + $(Settings.DOMAIN_ID_SELECTOR).val(domainID).change(); + + if (justConnected) { + var successText = Strings.CREATE_DOMAIN_SUCCESS_JUST_CONNECTED + } else { + var successText = Strings.CREATE_DOMAIN_SUCCESS; } - html += ""; + successText += "

    Click the button below to save your new settings and restart your domain-server."; - if (setting.numbered === true) { - html += "" + // show a sweet alert to say we are all finished up and that we need to save + swal({ + title: 'Success!', + type: 'success', + text: successText, + html: true, + confirmButtonText: 'Save' + }, function(){ + saveSettings(); + }); + }, 'json').fail(function(){ + + var errorText = "There was a problem creating your new domain ID. Do you want to try again or"; + + if (justConnected) { + errorText += " just save your new access token?

    You can always create a new domain ID later."; + } else { + errorText += " cancel?" } - if (setting.key) { - html += "" - } - - var isNonDeletableRow = !setting.can_add_new_rows; - - _.each(setting.columns, function(col) { - - var colValue, colName; - if (isArray) { - colValue = rowIsObject ? row[col.name] : row; - colName = keypath + "[" + rowIndexOrName + "]" + (rowIsObject ? "." + col.name : ""); + // we failed to create the new domain ID, show a sweet-alert that lets them try again or cancel + swal({ + title: '', + type: 'error', + text: errorText, + html: true, + confirmButtonText: 'Try again', + showCancelButton: true, + closeOnConfirm: false + }, function(isConfirm){ + if (isConfirm) { + // they want to try creating a domain ID again + showDomainCreationAlert(justConnected); } else { - colValue = row[col.name]; - colName = keypath + "." + rowIndexOrName + "." + col.name; + // they want to cancel + if (justConnected) { + // since they just connected we need to save the access token here + saveSettings(); + } + } + }); + }); + } + + function createDomainSpinner() { + var spinner = ''; + return spinner; + } + + function createDomainLoadingError(message) { + var errorEl = $(""); + errorEl.append(message + " "); + + var retryLink = $("Please click here to try again."); + retryLink.click(function(ev) { + ev.preventDefault(); + reloadDomainInfo(); + }); + errorEl.append(retryLink); + + return errorEl; + } + + function showOrHideLabel() { + var type = getCurrentDomainIDType(); + var shouldShow = accessTokenIsSet() && (type === DOMAIN_ID_TYPE_FULL || type === DOMAIN_ID_TYPE_UNKNOWN); + $(".panel#label").toggle(shouldShow); + $("li a[href='#label']").parent().toggle(shouldShow); + return shouldShow; + } + + function setupDomainLabelSetting() { + showOrHideLabel(); + + var html = "
    " + html += " Edit"; + html += ""; + html += "
    "; + + html = $(html); + + html.find('a').click(function(ev) { + ev.preventDefault(); + + var label = DomainInfo.label === null ? "" : DomainInfo.label; + var modal_body = "
    "; + modal_body += ""; + modal_body += ""; + modal_body += "
    "; + modal_body += "
    "; + + var dialog = bootbox.dialog({ + title: 'Edit Label', + message: modal_body, + closeButton: false, + onEscape: true, + buttons: [ + { + label: 'Cancel', + className: 'edit-label-cancel-btn', + callback: function() { + dialog.modal('hide'); + } + }, + { + label: 'Save', + className: 'edit-label-save-btn btn btn-primary', + callback: function() { + var data = { + label: $('#domain-label-input').val() + }; + + $('.edit-label-cancel-btn').attr('disabled', 'disabled'); + $('.edit-label-save-btn').attr('disabled', 'disabled'); + $('.edit-label-save-btn').html("Saving..."); + + $('.error-message').hide(); + + $.ajax({ + url: '/api/domains', + type: 'PUT', + data: data, + success: function(xhr) { + dialog.modal('hide'); + reloadDomainInfo(); + }, + error: function(xhr) { + var data = parseJSONResponse(xhr); + console.log(data, data.status, data.data); + if (data.status === "fail") { + for (var key in data.data) { + var errorMsg = data.data[key]; + var errorEl = $('.error-message[data-property="' + key + '"'); + errorEl.html(errorMsg); + errorEl.show(); + } + } + $('.edit-label-cancel-btn').removeAttr('disabled'); + $('.edit-label-save-btn').removeAttr('disabled'); + $('.edit-label-save-btn').html("Save"); + } + }); + return false; + } + } + ], + callback: function(result) { + console.log("result: ", result); + } + }); + }); + + var spinner = createDomainSpinner(); + var errorEl = createDomainLoadingError("Error loading label."); + + html.append(spinner); + html.append(errorEl); + + $('div#label .panel-body').append(html); + } + + function showOrHideAutomaticNetworking() { + var type = getCurrentDomainIDType(); + if (!accessTokenIsSet() || (type !== DOMAIN_ID_TYPE_FULL && type !== DOMAIN_ID_TYPE_UNKNOWN)) { + $("[data-keypath='metaverse.automatic_networking']").hide(); + return false; + } + $("[data-keypath='metaverse.automatic_networking']").show(); + return true; + } + + function setupDomainNetworkingSettings() { + if (!showOrHideAutomaticNetworking()) { + return; + } + + var autoNetworkingSetting = Settings.data.values.metaverse.automatic_networking; + if (autoNetworkingSetting === 'full') { + return; + } + + var includeAddress = autoNetworkingSetting === 'disabled'; + + if (includeAddress) { + var label = "Network Address:Port"; + } else { + var label = "Network Port"; + } + + var lowerName = name.toLowerCase(); + var form = '
    '; + form += ''; + form += ' Edit'; + form += ''; + form += '
    This defines how nodes will connect to your domain. You can read more about automatic networking here.
    '; + form += '
    '; + + form = $(form); + + form.find('#edit-network-address-port').click(function(ev) { + ev.preventDefault(); + + var address = DomainInfo.network_address === null ? '' : DomainInfo.network_address; + var port = DomainInfo.network_port === null ? '' : DomainInfo.network_port; + var modal_body = "
    "; + if (includeAddress) { + modal_body += ""; + modal_body += ""; + modal_body += "
    "; + } + modal_body += ""; + modal_body += ""; + modal_body += "
    "; + modal_body += "
    "; + + var dialog = bootbox.dialog({ + title: 'Edit Network', + message: modal_body, + closeButton: false, + onEscape: true, + buttons: [ + { + label: 'Cancel', + className: 'edit-network-cancel-btn', + callback: function() { + dialog.modal('hide'); + } + }, + { + label: 'Save', + className: 'edit-network-save-btn btn btn-primary', + callback: function() { + var data = { + network_port: $('#network-port-input').val() + }; + if (includeAddress) { + data.network_address = $('#network-address-input').val(); + } + + $('.edit-network-cancel-btn').attr('disabled', 'disabled'); + $('.edit-network-save-btn').attr('disabled', 'disabled'); + $('.edit-network-save-btn').html("Saving..."); + + console.log('data', data); + + $('.error-message').hide(); + + $.ajax({ + url: '/api/domains', + type: 'PUT', + data: data, + success: function(xhr) { + console.log(xhr, parseJSONResponse(xhr)); + dialog.modal('hide'); + reloadDomainInfo(); + }, + error:function(xhr) { + var data = parseJSONResponse(xhr); + console.log(data, data.status, data.data); + if (data.status === "fail") { + for (var key in data.data) { + var errorMsg = data.data[key]; + console.log(key, errorMsg); + var errorEl = $('.error-message[data-property="' + key + '"'); + console.log(errorEl); + errorEl.html(errorMsg); + errorEl.show(); + } + } + $('.edit-network-cancel-btn').removeAttr('disabled'); + $('.edit-network-save-btn').removeAttr('disabled'); + $('.edit-network-save-btn').html("Save"); + } + }); + return false; + } + } + ], + callback: function(result) { + console.log("result: ", result); + } + }); + }); + + var spinner = createDomainSpinner(); + + var errorMessage = '' + if (includeAddress) { + errorMessage = "We were unable to load the network address and port."; + } else { + errorMessage = "We were unable to load the network port." + } + var errorEl = createDomainLoadingError(errorMessage); + + var autoNetworkingEl = $('div[data-keypath="metaverse.automatic_networking"]'); + autoNetworkingEl.after(spinner); + autoNetworkingEl.after(errorEl); + autoNetworkingEl.after(form); + } + + + function setupPlacesTable() { + // create a dummy table using our view helper + var placesTableSetting = { + type: 'table', + name: 'places', + label: 'Places', + html_id: Settings.PLACES_TABLE_ID, + help: "The following places currently point to this domain.
    To point places to this domain, " + + " go to the My Places " + + "page in your High Fidelity Metaverse account.", + read_only: true, + can_add_new_rows: false, + columns: [ + { + "name": "name", + "label": "Name" + }, + { + "name": "path", + "label": "Viewpoint or Path" + }, + { + "name": "remove", + "label": "", + "class": "buttons" + } + ] + } + + // get a table for the places + var placesTableGroup = viewHelpers.getFormGroup('', placesTableSetting, Settings.data.values); + + // append the places table in the right place + $('#places_paths .panel-body').prepend(placesTableGroup); + //$('#' + Settings.PLACES_TABLE_ID).append(""); + + var spinner = createDomainSpinner(); + $('#' + Settings.PLACES_TABLE_ID).after($(spinner)); + + var errorEl = createDomainLoadingError("There was an error retreiving your places."); + $("#" + Settings.PLACES_TABLE_ID).after(errorEl); + + // do we have a domain ID? + if (Settings.data.values.metaverse.id.length == 0) { + // we don't have a domain ID - add a button to offer the user a chance to get a temporary one + var temporaryPlaceButton = dynamicButton(Settings.GET_TEMPORARY_NAME_BTN_ID, 'Get a temporary place name'); + $('#' + Settings.PLACES_TABLE_ID).after(temporaryPlaceButton); + } + if (accessTokenIsSet()) { + appendAddButtonToPlacesTable(); + } + } + + function placeTableRow(name, path, isTemporary, placeID) { + var name_link = "" + (isTemporary ? name + " (temporary)" : name) + ""; + + function placeEditClicked() { + editHighFidelityPlace(placeID, name, path); + } + + function placeDeleteClicked() { + var el = $(this); + var confirmString = Strings.REMOVE_PLACE_TITLE.replace("{{place}}", name); + var dialog = bootbox.dialog({ + message: confirmString, + closeButton: false, + onEscape: true, + buttons: [ + { + label: Strings.REMOVE_PLACE_CANCEL_BUTTON, + className: "delete-place-cancel-btn", + callback: function() { + dialog.modal('hide'); + } + }, + { + label: Strings.REMOVE_PLACE_DELETE_BUTTON, + className: "delete-place-confirm-btn btn btn-danger", + callback: function() { + $('.delete-place-cancel-btn').attr('disabled', 'disabled'); + $('.delete-place-confirm-btn').attr('disabled', 'disabled'); + $('.delete-place-confirm-btn').html(Strings.REMOVE_PLACE_DELETE_BUTTON_PENDING); + sendUpdatePlaceRequest( + placeID, + '', + null, + true, + function() { + reloadDomainInfo(); + dialog.modal('hide'); + }, function() { + $('.delete-place-cancel-btn').removeAttr('disabled'); + $('.delete-place-confirm-btn').removeAttr('disabled'); + $('.delete-place-confirm-btn').html(Strings.REMOVE_PLACE_DELETE_BUTTON); + bootbox.alert(Strings.REMOVE_PLACE_ERROR); + }); + return false; + } + }, + ] + }); + } + + if (isTemporary) { + var editLink = ""; + var deleteColumn = ""; + } else { + var editLink = " Edit"; + var deleteColumn = ""; + } + + var row = $("" + deleteColumn + ""); + row.find(".place-edit").click(placeEditClicked); + row.find(".place-delete").click(placeDeleteClicked); + + return row; + } + + function placeTableRowForPlaceObject(place) { + var placePathOrIndex = (place.path ? place.path : "/"); + return placeTableRow(place.name, placePathOrIndex, false, place.id); + } + + function reloadDomainInfo() { + $('#' + Settings.PLACES_TABLE_ID + " tbody tr").not('.headers').remove(); + + $('.domain-loading-show').show(); + $('.domain-loading-hide').hide(); + $('.domain-loading-error').hide(); + $('.loading-domain-info-spinner').show(); + $('#' + Settings.PLACES_TABLE_ID).append("Hello"); + + getDomainFromAPI(function(data){ + $('.loading-domain-info-spinner').hide(); + $('.domain-loading-show').hide(); + + // check if we have owner_places (for a real domain) or a name (for a temporary domain) + if (data.status == "success") { + $('.domain-loading-hide').show(); + if (data.domain.owner_places) { + // add a table row for each of these names + _.each(data.domain.owner_places, function(place){ + $('#' + Settings.PLACES_TABLE_ID + " tbody").append(placeTableRowForPlaceObject(place)); + }); + } else if (data.domain.name) { + // add a table row for this temporary domain name + $('#' + Settings.PLACES_TABLE_ID + " tbody").append(placeTableRow(data.domain.name, '/', true)); } - isNonDeletableRow = isNonDeletableRow - || (nonDeletableRowKey === col.name && nonDeletableRowValues.indexOf(colValue) !== -1); - - if (isArray && col.type === "checkbox" && col.editable) { - html += - ""; - } else if (isArray && col.type === "time" && col.editable) { - html += - ""; - } else { - // Use a hidden input so that the values are posted. - html += - ""; + // Update label + if (showOrHideLabel()) { + var label = data.domain.label; + label = label === null ? '' : label; + $('#network-label').val(label); } + // Update automatic networking + if (showOrHideAutomaticNetworking()) { + var autoNetworkingSetting = Settings.data.values.metaverse.automatic_networking; + var address = data.domain.network_address === null ? "" : data.domain.network_address; + var port = data.domain.network_port === null ? "" : data.domain.network_port; + if (autoNetworkingSetting === 'disabled') { + $('#network-address-port input').val(address + ":" + port); + } else if (autoNetworkingSetting === 'ip') { + $('#network-address-port input').val(port); + } + } + + if (accessTokenIsSet()) { + appendAddButtonToPlacesTable(); + } + + } else { + $('.domain-loading-error').show(); + } + }) + } + + function appendDomainIDButtons() { + var domainIDInput = $(Settings.DOMAIN_ID_SELECTOR); + + var createButton = dynamicButton(Settings.CREATE_DOMAIN_ID_BTN_ID, Strings.CREATE_DOMAIN_BUTTON); + createButton.css('margin-top', '10px'); + var chooseButton = dynamicButton(Settings.CHOOSE_DOMAIN_ID_BTN_ID, Strings.CHOOSE_DOMAIN_BUTTON); + chooseButton.css('margin', '10px 0px 0px 10px'); + + domainIDInput.after(chooseButton); + domainIDInput.after(createButton); + } + + function editHighFidelityPlace(placeID, name, path) { + var dialog; + + var modal_body = "
    "; + modal_body += ""; + modal_body += "
    "; + + var modal_buttons = [ + { + label: Strings.EDIT_PLACE_CANCEL_BUTTON, + className: "edit-place-cancel-button", + callback: function() { + dialog.modal('hide'); + } + }, + { + label: Strings.EDIT_PLACE_CONFIRM_BUTTON, + className: 'edit-place-save-button btn btn-primary', + callback: function() { + var placePath = $('#place-path-input').val(); + + if (path == placePath) { + return true; + } + + $('.edit-place-cancel-button').attr('disabled', 'disabled'); + $('.edit-place-save-button').attr('disabled', 'disabled'); + $('.edit-place-save-button').html(Strings.EDIT_PLACE_BUTTON_PENDING); + + sendUpdatePlaceRequest( + placeID, + placePath, + null, + false, + function() { + dialog.modal('hide') + reloadDomainInfo(); + }, + function() { + $('.edit-place-cancel-button').removeAttr('disabled'); + $('.edit-place-save-button').removeAttr('disabled'); + $('.edit-place-save-button').html(Strings.EDIT_PLACE_CONFIRM_BUTTON); + } + ); + + return false; + } + } + ]; + + dialog = bootbox.dialog({ + title: Strings.EDIT_PLACE_TITLE, + closeButton: false, + onEscape: true, + message: modal_body, + buttons: modal_buttons + }) + } + + function appendAddButtonToPlacesTable() { + var addRow = $(""); + addRow.find(".place-add").click(function(ev) { + ev.preventDefault(); + chooseFromHighFidelityPlaces(Settings.initialValues.metaverse.access_token, null, function(placeName, newDomainID) { + if (newDomainID) { + Settings.data.values.metaverse.id = newDomainID; + var domainIDEl = $("[data-keypath='metaverse.id']"); + domainIDEl.val(newDomainID); + Settings.initialValues.metaverse.id = newDomainID; + badgeForDifferences(domainIDEl); + } + reloadDomainInfo(); + }); + }); + $('#' + Settings.PLACES_TABLE_ID + " tbody").append(addRow); + } + + function chooseFromHighFidelityDomains(clickedButton) { + // setup the modal to help user pick their domain + if (Settings.initialValues.metaverse.access_token) { + + // add a spinner to the choose button + clickedButton.html("Loading domains..."); + clickedButton.attr('disabled', 'disabled'); + + // get a list of user domains from data-web + $.ajax({ + url: "/api/domains", + dataType: 'json', + jsonp: false, + success: function(data){ + + var modal_buttons = { + cancel: { + label: 'Cancel', + className: 'btn-default' + } + } + + if (data.data.domains.length) { + // setup a select box for the returned domains + modal_body = "

    Choose the High Fidelity domain you want this domain-server to represent.
    This will set your domain ID on the settings page.

    "; + domain_select = $(""); + _.each(data.data.domains, function(domain){ + var domainString = ""; + + if (domain.label) { + domainString += '"' + domain.label+ '" - '; + } + + domainString += domain.id; + + domain_select.append(""); + }) + modal_body += "" + domain_select[0].outerHTML + modal_buttons["success"] = { + label: 'Choose domain', + callback: function() { + domainID = $('#domain-name-select').val() + // set the domain ID on the form + $(Settings.DOMAIN_ID_SELECTOR).val(domainID).change(); + } + } + } else { + modal_buttons["success"] = { + label: 'Create new domain', + callback: function() { + window.open(URLs.METAVERSE_URL + "/user/domains", '_blank'); + } + } + modal_body = "

    You do not have any domains in your High Fidelity account." + + "

    Go to your domains page to create a new one. Once your domain is created re-open this dialog to select it.

    " + } + + bootbox.dialog({ + title: "Choose matching domain", + onEscape: true, + message: modal_body, + buttons: modal_buttons + }) + }, + error: function() { + bootbox.alert("Failed to retrieve your domains from the High Fidelity Metaverse"); + }, + complete: function() { + // remove the spinner from the choose button + clickedButton.html("Choose from my domains") + clickedButton.removeAttr('disabled') + } }); - if (!setting.read_only) { - if (setting.can_order) { - html += "" - } - if (isNonDeletableRow) { - html += ""; - } else { - html += ""; - } - } - - html += "" - - if (isCategorized && setting.can_add_new_rows && _.findLastIndex(setting_value, categoryPair) === rowIndexOrName) { - html += makeTableInputs(setting, categoryPair, categoryValue); - } - - row_num++ - }); - } - - // populate inputs in the table for new values - if (!setting.read_only) { - if (setting.can_add_new_categories) { - html += makeTableCategoryInput(setting, numVisibleColumns); - } - - if (setting.can_add_new_rows || setting.can_add_new_categories) { - html += makeTableHiddenInputs(setting, {}, ""); - } - } - html += "
    " + setting.caption + "
    " + group.label + "
    #" + setting.key.label + "" + col.label + "
    " + row_num + "" + rowIndexOrName + "
    " + name_link + "" + path + editLink + "
    " + - "" + - "" + - "" + - "" + - colValue + - "" + - "
    " - + "
    " - - return html; -} - -function makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, canRemove, message) { - var html = - "" + - "" + - "" + - "" + categoryValue + "" + - "" + - ((canRemove) ? ( - "" + - "" + - "" - ) : ( - "" - )) + - ""; - return html; -} - -function makeTableHiddenInputs(setting, initialValues, categoryValue) { - var html = ""; - - if (setting.numbered === true) { - html += ""; - } - - if (setting.key) { - html += "\ - \ - " - } - - _.each(setting.columns, function(col) { - var defaultValue = _.has(initialValues, col.name) ? initialValues[col.name] : col.default; - if (col.type === "checkbox") { - html += - "" + - "" + - ""; - } else if (col.type === "select") { - html += "" - html += ""; - html += ""; - } else { - html += - "" + - "" + - ""; - } - }) - - if (setting.can_order) { - html += "" - } - html += "" - html += "" - - return html -} - -function makeTableCategoryInput(setting, numVisibleColumns) { - var canAddRows = setting.can_add_new_rows; - var categoryKey = setting.categorize_by_key; - var placeholder = setting.new_category_placeholder || ""; - var message = setting.new_category_message || ""; - var html = - "" + - "" + - "" + - "" + - "" + - "" + - "" + - ""; - return html; -} - -function getDescriptionForKey(key) { - for (var i in Settings.data.descriptions) { - if (Settings.data.descriptions[i].name === key) { - return Settings.data.descriptions[i]; - } - } -} - -var SAVE_BUTTON_LABEL_SAVE = "Save"; -var SAVE_BUTTON_LABEL_RESTART = "Save and restart"; -var reasonsForRestart = []; -var numChangesBySection = {}; - -function badgeSidebarForDifferences(changedElement) { - // figure out which group this input is in - var panelParentID = changedElement.closest('.panel').attr('id'); - - // if the panel contains non-grouped settings, the initial value is Settings.initialValues - var isGrouped = $('#' + panelParentID).hasClass('grouped'); - - if (isGrouped) { - var initialPanelJSON = Settings.initialValues[panelParentID] - ? Settings.initialValues[panelParentID] - : {}; - - // get a JSON representation of that section - var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID]; - } else { - var initialPanelJSON = Settings.initialValues; - - // get a JSON representation of that section - var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true); - } - - var badgeValue = 0 - var description = getDescriptionForKey(panelParentID); - - // badge for any settings we have that are not the same or are not present in initialValues - for (var setting in panelJSON) { - if ((!_.has(initialPanelJSON, setting) && panelJSON[setting] !== "") || - (!_.isEqual(panelJSON[setting], initialPanelJSON[setting]) - && (panelJSON[setting] !== "" || _.has(initialPanelJSON, setting)))) { - badgeValue += 1; - - // add a reason to restart - if (description && description.restart != false) { - reasonsForRestart.push(setting); - } - } else { - // remove a reason to restart - if (description && description.restart != false) { - reasonsForRestart = $.grep(reasonsForRestart, function(v) { return v != setting; }); - } - } - } - - // update the list-group-item badge to have the new value - if (badgeValue == 0) { - badgeValue = "" - } - - numChangesBySection[panelParentID] = badgeValue; - - var hasChanges = badgeValue > 0; - - if (!hasChanges) { - for (var key in numChangesBySection) { - if (numChangesBySection[key] > 0) { - hasChanges = true; - break; - } - } - } - - $(".save-button").prop("disabled", !hasChanges); - $(".save-button").html(reasonsForRestart.length > 0 ? SAVE_BUTTON_LABEL_RESTART : SAVE_BUTTON_LABEL_SAVE); - $("a[href='#" + panelParentID + "'] .badge").html(badgeValue); -} - -function addTableRow(row) { - var table = row.parents('table'); - var isArray = table.data('setting-type') === 'array'; - var keepField = row.data("keep-field"); - - var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS); - - var input_clone = row.clone(); - - if (!isArray) { - // show the key input - var keyInput = row.children(".key").children("input"); - } - - // Change input row to data row - var table = row.parents("table"); - var setting_name = table.attr("name"); - row.addClass(Settings.DATA_ROW_CLASS + " " + Settings.NEW_ROW_CLASS); - - // if this is an array, add the row index (which is the index of the last row + 1) - // as a data attribute to the row - var row_index = 0; - if (isArray) { - var previous_row = row.siblings('.' + Settings.DATA_ROW_CLASS + ':last'); - - if (previous_row.length > 0) { - row_index = parseInt(previous_row.attr(Settings.DATA_ROW_INDEX), 10) + 1; - } else { - row_index = 0; - } - - row.attr(Settings.DATA_ROW_INDEX, row_index); - } - - var focusChanged = false; - - _.each(row.children(), function(element) { - if ($(element).hasClass("numbered")) { - // Index row - var numbers = columns.children(".numbered") - if (numbers.length > 0) { - $(element).html(parseInt(numbers.last().text()) + 1) } else { - $(element).html(1) + bootbox.alert({ + message: "You must have an access token to query your High Fidelity domains.

    " + + "Please follow the instructions on the settings page to add an access token.", + title: "Access token required" + }) } - } else if ($(element).hasClass(Settings.REORDER_BUTTONS_CLASS)) { - $(element).html("") - } else if ($(element).hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) { - // Change buttons - var anchor = $(element).children("a") - anchor.removeClass(Settings.ADD_ROW_SPAN_CLASSES) - anchor.addClass(Settings.DEL_ROW_SPAN_CLASSES) - } else if ($(element).hasClass("key")) { - var input = $(element).children("input") - input.show(); - } else if ($(element).hasClass(Settings.DATA_COL_CLASS)) { - // show inputs - var input = $(element).find("input"); - input.show(); + } - var isCheckbox = input.hasClass("table-checkbox"); - var isDropdown = input.hasClass("table-dropdown"); + function createTemporaryDomain() { + swal({ + title: 'Create temporary place name', + text: "This will create a temporary place name and domain ID" + + " so other users can easily connect to your domain.

    " + + "In order to make your domain reachable, this will also enable full automatic networking.", + showCancelButton: true, + confirmButtonText: 'Create', + closeOnConfirm: false, + html: true + }, function(isConfirm){ + if (isConfirm) { + showSpinnerAlert('Creating temporary place name'); - if (isArray) { - var key = $(element).attr('name'); + // make a get request to get a temporary domain + $.post(URLs.METAVERSE_URL + '/api/v1/domains/temporary', function(data){ + if (data.status == "success") { + var domain = data.data.domain; - // are there multiple columns or just one? - // with multiple we have an array of Objects, with one we have an array of whatever the value type is - var num_columns = row.children('.' + Settings.DATA_COL_CLASS).length - var newName = setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : ""); + // we should have a new domain ID - set it on the domain ID value + $(Settings.DOMAIN_ID_SELECTOR).val(domain.id).change(); - input.attr("name", newName); + // we also need to make sure auto networking is set to full + $('[data-hidden-input="metaverse.automatic_networking"]').val("full").change(); - if (isDropdown) { - // default values for hidden inputs inside child selects gets cleared so we need to remind it - var selectElement = $(element).children("select"); - selectElement.attr("data-hidden-input", newName); - $(element).children("input").val(selectElement.val()); - } - } else { - // because the name of the setting in question requires the key - // setup a hook to change the HTML name of the element whenever the key changes - var colName = $(element).attr("name"); - keyInput.on('change', function(){ - input.attr("name", setting_name + "." + $(this).val() + "." + colName); + swal({ + type: 'success', + title: 'Success!', + text: "We have created a temporary name and domain ID for you.

    " + + "Your temporary place name is " + domain.name + ".

    " + + "Press the button below to save your new settings and restart your domain-server.", + confirmButtonText: 'Save', + html: true + }, function(){ + saveSettings(); + }); + } }); } - - if (!focusChanged) { - input.focus(); - focusChanged = true; - } - - // if we are adding a dropdown, we should go ahead and make its select - // element is visible - if (isDropdown) { - $(element).children("select").attr("style", ""); - } - - if (isCheckbox) { - $(input).find("input").attr("data-changed", "true"); - } else { - input.attr("data-changed", "true"); - } - } else { - console.log("Unknown table element"); - } - }); - - input_clone.children('td').each(function () { - if ($(this).attr("name") !== keepField) { - $(this).find("input").val($(this).children('input').attr('data-default')); - } - }); - - if (isArray) { - updateDataChangedForSiblingRows(row, true) - - // the addition of any table row should remove the empty-array-row - row.siblings('.empty-array-row').remove() - } - - badgeSidebarForDifferences($(table)) - - row.after(input_clone) -} - -function deleteTableRow($row) { - var $table = $row.closest('table'); - var categoryName = $row.data("category"); - var isArray = $table.data('setting-type') === 'array'; - - $row.empty(); - - if (!isArray) { - if ($row.attr('name')) { - $row.html(""); - } else { - // for rows that didn't have a key, simply remove the row - $row.remove(); - } - } else { - if ($table.find('.' + Settings.DATA_ROW_CLASS + "[data-category='" + categoryName + "']").length <= 1) { - // This is the last row of the category, so delete the header - $table.find('.' + Settings.DATA_CATEGORY_CLASS + "[data-category='" + categoryName + "']").remove(); - } - - if ($table.find('.' + Settings.DATA_ROW_CLASS).length > 1) { - updateDataChangedForSiblingRows($row); - - // this isn't the last row - we can just remove it - $row.remove(); - } else { - // this is the last row, we can't remove it completely since we need to post an empty array - $row - .removeClass(Settings.DATA_ROW_CLASS) - .removeClass(Settings.NEW_ROW_CLASS) - .removeAttr("data-category") - .addClass('empty-array-row') - .html(""); - } - } - - // we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated - badgeSidebarForDifferences($table); -} - -function addTableCategory($categoryInputRow) { - var $input = $categoryInputRow.find("input").first(); - var categoryValue = $input.prop("value"); - if (!categoryValue || $categoryInputRow.closest("table").find("tr[data-category='" + categoryValue + "']").length !== 0) { - $categoryInputRow.addClass("has-warning"); - - setTimeout(function () { - $categoryInputRow.removeClass("has-warning"); - }, 400); - - return; - } - - var $rowInput = $categoryInputRow.next(".inputs").clone(); - if (!$rowInput) { - console.error("Error cloning inputs"); - } - - var canAddRows = $categoryInputRow.data("can-add-rows"); - var message = $categoryInputRow.data("message"); - var categoryKey = $categoryInputRow.data("key"); - var width = 0; - $categoryInputRow - .children("td") - .each(function () { - width += $(this).prop("colSpan") || 1; }); - - $input - .prop("value", "") - .focus(); - - $rowInput.find("td[name='" + categoryKey + "'] > input").first() - .prop("value", categoryValue); - $rowInput - .attr("data-category", categoryValue) - .addClass(Settings.NEW_ROW_CLASS); - - var $newCategoryRow = $(makeTableCategoryHeader(categoryKey, categoryValue, width, true, " - " + message)); - $newCategoryRow.addClass(Settings.NEW_ROW_CLASS); - - $categoryInputRow - .before($newCategoryRow) - .before($rowInput); - - if (canAddRows) { - $rowInput.removeAttr("hidden"); - } else { - addTableRow($rowInput); - } -} - -function deleteTableCategory($categoryHeaderRow) { - var categoryName = $categoryHeaderRow.data("category"); - - $categoryHeaderRow - .closest("table") - .find("tr[data-category='" + categoryName + "']") - .each(function () { - if ($(this).hasClass(Settings.DATA_ROW_CLASS)) { - deleteTableRow($(this)); - } else { - $(this).remove(); - } - }); -} - -function toggleTableCategory($categoryHeaderRow) { - var $icon = $categoryHeaderRow.find("." + Settings.TOGGLE_CATEGORY_SPAN_CLASS).first(); - var categoryName = $categoryHeaderRow.data("category"); - var wasExpanded = $icon.hasClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS); - if (wasExpanded) { - $icon - .addClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS) - .removeClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS); - } else { - $icon - .addClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS) - .removeClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS); - } - $categoryHeaderRow - .closest("table") - .find("tr[data-category='" + categoryName + "']") - .toggleClass("contracted", wasExpanded); -} - -function moveTableRow(row, move_up) { - var table = $(row).closest('table') - var isArray = table.data('setting-type') === 'array' - if (!isArray) { - return; } - if (move_up) { - var prev_row = row.prev() - if (prev_row.hasClass(Settings.DATA_ROW_CLASS)) { - prev_row.before(row) - } - } else { - var next_row = row.next() - if (next_row.hasClass(Settings.DATA_ROW_CLASS)) { - next_row.after(row) - } - } - - // we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated - badgeSidebarForDifferences($(table)) -} - -function updateDataChangedForSiblingRows(row, forceTrue) { - // anytime a new row is added to an array we need to set data-changed for all sibling row inputs to true - // unless it matches the inital set of values - - if (!forceTrue) { - // figure out which group this row is in - var panelParentID = row.closest('.panel').attr('id') - // get the short name for the setting from the table - var tableShortName = row.closest('table').data('short-name') - - // get a JSON representation of that section - var panelSettingJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID][tableShortName] - if (Settings.initialValues[panelParentID]) { - var initialPanelSettingJSON = Settings.initialValues[panelParentID][tableShortName] - } else { - var initialPanelSettingJSON = {}; - } - - // if they are equal, we don't need data-changed - isTrue = !_.isEqual(panelSettingJSON, initialPanelSettingJSON) - } else { - isTrue = true - } - - row.siblings('.' + Settings.DATA_ROW_CLASS).each(function(){ - var hiddenInput = $(this).find('td.' + Settings.DATA_COL_CLASS + ' input') - if (isTrue) { - hiddenInput.attr('data-changed', isTrue) - } else { - hiddenInput.removeAttr('data-changed') - } - }) -} - -function cleanupFormValues(node) { - if (node.type && node.type === 'checkbox') { - return { name: node.name, value: node.checked ? true : false }; - } else { - return false; - } -} - -function showErrorMessage(title, message) { - swal(title, message) -} +}); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 65053b7366..68a36195d9 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -501,7 +501,7 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) { // store the new domain ID and auto network setting immediately QString newSettingsJSON = QString("{\"metaverse\": { \"id\": \"%1\", \"automatic_networking\": \"full\"}}").arg(id); auto settingsDocument = QJsonDocument::fromJson(newSettingsJSON.toUtf8()); - _settingsManager.recurseJSONObjectAndOverwriteSettings(settingsDocument.object()); + _settingsManager.recurseJSONObjectAndOverwriteSettings(settingsDocument.object(), DomainSettings); // store the new ID and auto networking setting on disk _settingsManager.persistToFile(); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 05227d35b7..52754babb3 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -39,8 +39,10 @@ const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings const QString DESCRIPTION_SETTINGS_KEY = "settings"; const QString SETTING_DEFAULT_KEY = "default"; const QString DESCRIPTION_NAME_KEY = "name"; +const QString DESCRIPTION_GROUP_LABEL_KEY = "label"; const QString SETTING_DESCRIPTION_TYPE_KEY = "type"; const QString DESCRIPTION_COLUMNS_KEY = "columns"; +const QString CONTENT_SETTING_FLAG_KEY = "content_setting"; const QString SETTINGS_VIEWPOINT_KEY = "viewpoint"; @@ -63,6 +65,8 @@ DomainServerSettingsManager::DomainServerSettingsManager() { if (descriptionObject.contains(DESCRIPTION_SETTINGS_KEY)) { _descriptionArray = descriptionDocument.object()[DESCRIPTION_SETTINGS_KEY].toArray(); + splitSettingsDescription(); + return; } } @@ -78,6 +82,91 @@ DomainServerSettingsManager::DomainServerSettingsManager() { Q_ARG(int, MISSING_SETTINGS_DESC_ERROR_CODE)); } +void DomainServerSettingsManager::splitSettingsDescription() { + // construct separate description arrays for domain settings and content settings + // since they are displayed on different pages + + // along the way we also construct one object that holds the groups separated by domain settings + // and content settings, so that the DS can setup dropdown menus below "Content" and "Settings" + // headers to jump directly to a settings group on the page of either + QJsonArray domainSettingsMenuGroups; + QJsonArray contentSettingsMenuGroups; + + foreach(const QJsonValue& group, _descriptionArray) { + QJsonObject groupObject = group.toObject(); + + static const QString HIDDEN_GROUP_KEY = "hidden"; + bool groupHidden = groupObject.contains(HIDDEN_GROUP_KEY) && groupObject[HIDDEN_GROUP_KEY].toBool(); + + QJsonArray domainSettingArray; + QJsonArray contentSettingArray; + + foreach(const QJsonValue& settingDescription, groupObject[DESCRIPTION_SETTINGS_KEY].toArray()) { + QJsonObject settingDescriptionObject = settingDescription.toObject(); + + bool isContentSetting = settingDescriptionObject.contains(CONTENT_SETTING_FLAG_KEY) + && settingDescriptionObject[CONTENT_SETTING_FLAG_KEY].toBool(); + + if (isContentSetting) { + // push the setting description to the pending content setting array + contentSettingArray.push_back(settingDescriptionObject); + } else { + // push the setting description to the pending domain setting array + domainSettingArray.push_back(settingDescriptionObject); + } + } + + if (!domainSettingArray.isEmpty() || !contentSettingArray.isEmpty()) { + + // we know for sure we'll have something to add to our settings menu groups + // so setup that object for the group now, as long as the group isn't hidden alltogether + QJsonObject settingsDropdownGroup; + + if (!groupHidden) { + settingsDropdownGroup[DESCRIPTION_NAME_KEY] = groupObject[DESCRIPTION_NAME_KEY]; + settingsDropdownGroup[DESCRIPTION_GROUP_LABEL_KEY] = groupObject[DESCRIPTION_GROUP_LABEL_KEY]; + + static const QString DESCRIPTION_GROUP_HTML_ID_KEY = "html_id"; + if (groupObject.contains(DESCRIPTION_GROUP_HTML_ID_KEY)) { + settingsDropdownGroup[DESCRIPTION_GROUP_HTML_ID_KEY] = groupObject[DESCRIPTION_GROUP_HTML_ID_KEY]; + } + } + + if (!domainSettingArray.isEmpty()) { + // we have some domain settings from this group, add the group with the filtered settings + QJsonObject filteredGroupObject = groupObject; + filteredGroupObject[DESCRIPTION_SETTINGS_KEY] = domainSettingArray; + _domainSettingsDescription.push_back(filteredGroupObject); + + // if the group isn't hidden, add its information to the domain settings menu groups + if (!groupHidden) { + domainSettingsMenuGroups.push_back(settingsDropdownGroup); + } + } + + if (!contentSettingArray.isEmpty()) { + // we have some content settings from this group, add the group with the filtered settings + QJsonObject filteredGroupObject = groupObject; + filteredGroupObject[DESCRIPTION_SETTINGS_KEY] = contentSettingArray; + _contentSettingsDescription.push_back(filteredGroupObject); + + // if the group isn't hidden, add its information to the content settings menu groups + if (!groupHidden) { + contentSettingsMenuGroups.push_back(settingsDropdownGroup); + } + } + } + } + + // populate the settings menu groups with what we've collected + + static const QString SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY = "domain_settings"; + static const QString SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY = "content_settings"; + + _settingsMenuGroups[SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY] = domainSettingsMenuGroups; + _settingsMenuGroups[SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY] = contentSettingsMenuGroups; +} + void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer message) { Assignment::Type type; message->readPrimitive(&type); @@ -986,48 +1075,72 @@ QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QStrin } bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection *connection, const QUrl &url) { - if (connection->requestOperation() == QNetworkAccessManager::PostOperation && url.path() == SETTINGS_PATH_JSON) { - // this is a POST operation to change one or more settings - QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent()); - QJsonObject postedObject = postedDocument.object(); + if (connection->requestOperation() == QNetworkAccessManager::PostOperation) { + if (url.path() == SETTINGS_PATH_JSON || url.path() == CONTENT_SETTINGS_PATH_JSON) { + // this is a POST operation to change one or more settings + QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent()); + QJsonObject postedObject = postedDocument.object(); - // we recurse one level deep below each group for the appropriate setting - bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject); + SettingsType endpointType = url.path() == SETTINGS_PATH_JSON ? DomainSettings : ContentSettings; - // store whatever the current _settingsMap is to file - persistToFile(); + // we recurse one level deep below each group for the appropriate setting + bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject, endpointType); - // return success to the caller - QString jsonSuccess = "{\"status\": \"success\"}"; - connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json"); + // store whatever the current _settingsMap is to file + persistToFile(); - // defer a restart to the domain-server, this gives our HTTPConnection enough time to respond - if (restartRequired) { - const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000; - QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart())); - } else { - unpackPermissions(); - apiRefreshGroupInformation(); - emit updateNodePermissions(); - emit settingsUpdated(); + // return success to the caller + QString jsonSuccess = "{\"status\": \"success\"}"; + connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json"); + + // defer a restart to the domain-server, this gives our HTTPConnection enough time to respond + if (restartRequired) { + const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000; + QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart())); + } else { + unpackPermissions(); + apiRefreshGroupInformation(); + emit updateNodePermissions(); + emit settingsUpdated(); + } + + return true; } + } else if (connection->requestOperation() == QNetworkAccessManager::GetOperation) { + static const QString SETTINGS_MENU_GROUPS_PATH = "/settings-menu-groups.json"; - return true; - } else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH_JSON) { - // setup a JSON Object with descriptions and non-omitted settings - const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions"; - const QString SETTINGS_RESPONSE_VALUE_KEY = "values"; + if (url.path() == SETTINGS_PATH_JSON || url.path() == CONTENT_SETTINGS_PATH_JSON) { - QJsonObject rootObject; - rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = _descriptionArray; - rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true); - connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json"); + // setup a JSON Object with descriptions and non-omitted settings + const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions"; + const QString SETTINGS_RESPONSE_VALUE_KEY = "values"; + + QJsonObject rootObject; + + bool forDomainSettings = (url.path() == SETTINGS_PATH_JSON); + bool forContentSettings = (url.path() == CONTENT_SETTINGS_PATH_JSON);; + + rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = forDomainSettings + ? _domainSettingsDescription : _contentSettingsDescription; + + // grab a domain settings object for all types, filtered for the right class of settings + rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true, forDomainSettings, forContentSettings); + + connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json"); + + return true; + } else if (url.path() == SETTINGS_MENU_GROUPS_PATH) { + connection->respond(HTTPConnection::StatusCode200, QJsonDocument(_settingsMenuGroups).toJson(), "application/json"); + + return true; + } } return false; } -QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& typeValue, bool isAuthenticated) { +QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& typeValue, bool isAuthenticated, + bool includeDomainSettings, bool includeContentSettings) { QJsonObject responseObject; if (!typeValue.isEmpty() || isAuthenticated) { @@ -1036,8 +1149,15 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty const QString AFFECTED_TYPES_JSON_KEY = "assignment-types"; + QJsonArray& filteredDescriptionArray = _descriptionArray; + if (includeDomainSettings && !includeContentSettings) { + filteredDescriptionArray = _domainSettingsDescription; + } else if (includeContentSettings && !includeDomainSettings) { + filteredDescriptionArray = _contentSettingsDescription; + } + // enumerate the groups in the description object to find which settings to pass - foreach(const QJsonValue& groupValue, _descriptionArray) { + foreach(const QJsonValue& groupValue, filteredDescriptionArray) { QJsonObject groupObject = groupValue.toObject(); QString groupKey = groupObject[DESCRIPTION_NAME_KEY].toString(); QJsonArray groupSettingsArray = groupObject[DESCRIPTION_SETTINGS_KEY].toArray(); @@ -1045,10 +1165,13 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty QJsonObject groupResponseObject; foreach(const QJsonValue& settingValue, groupSettingsArray) { + const QString VALUE_HIDDEN_FLAG_KEY = "value-hidden"; QJsonObject settingObject = settingValue.toObject(); + // consider this setting as long as it isn't hidden + // and we've been asked to include this type (domain setting or content setting) if (!settingObject[VALUE_HIDDEN_FLAG_KEY].toBool()) { QJsonArray affectedTypesArray = settingObject[AFFECTED_TYPES_JSON_KEY].toArray(); if (affectedTypesArray.isEmpty()) { @@ -1212,7 +1335,8 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson return QJsonObject(); } -bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) { +bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, + SettingsType settingsType) { static const QString SECURITY_ROOT_KEY = "security"; static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist"; static const QString BROADCASTING_KEY = "broadcasting"; @@ -1222,6 +1346,8 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ auto& settingsVariant = _configMap.getConfig(); bool needRestart = false; + auto& filteredDescriptionArray = settingsType == DomainSettings ? _domainSettingsDescription : _contentSettingsDescription; + // Iterate on the setting groups foreach(const QString& rootKey, postedObject.keys()) { const QJsonValue& rootValue = postedObject[rootKey]; @@ -1236,7 +1362,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ QJsonObject groupDescriptionObject; // we need to check the description array to see if this is a root setting or a group setting - foreach(const QJsonValue& groupValue, _descriptionArray) { + foreach(const QJsonValue& groupValue, filteredDescriptionArray) { if (groupValue.toObject()[DESCRIPTION_NAME_KEY] == rootKey) { // we matched a group - keep this since we'll use it below to update the settings groupDescriptionObject = groupValue.toObject(); @@ -1269,6 +1395,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ if (!matchingDescriptionObject.isEmpty()) { updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject); + if (rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY && rootKey != SETTINGS_PATHS_KEY && rootKey != WIZARD_KEY) { needRestart = true; @@ -1286,6 +1413,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ if (!matchingDescriptionObject.isEmpty()) { const QJsonValue& settingValue = rootValue.toObject()[settingKey]; updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject); + if ((rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY && rootKey != DESCRIPTION_ROOT_KEY && rootKey != WIZARD_KEY) || settingKey == AC_SUBNET_WHITELIST_KEY) { diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index d2f6d1e526..5e13c9f28a 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -13,6 +13,7 @@ #define hifi_DomainServerSettingsManager_h #include +#include #include #include @@ -28,6 +29,7 @@ const QString SETTINGS_PATHS_KEY = "paths"; const QString SETTINGS_PATH = "/settings"; const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json"; +const QString CONTENT_SETTINGS_PATH_JSON = "/content-settings.json"; const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions"; const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions"; const QString IP_PERMISSIONS_KEYPATH = "security.ip_permissions"; @@ -38,6 +40,10 @@ const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens"; using GroupByUUIDKey = QPair; // groupID, rankID +enum SettingsType { + DomainSettings, + ContentSettings +}; class DomainServerSettingsManager : public QObject { Q_OBJECT @@ -123,8 +129,10 @@ private slots: private: QStringList _argumentList; - QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false); - bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject); + QJsonArray filteredDescriptionArray(bool isContentSettings); + QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false, + bool includeDomainSettings = true, bool includeContentSettings = true); + bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, SettingsType settingsType); void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap, const QJsonObject& settingDescription); @@ -132,8 +140,15 @@ private: void sortPermissions(); void persistToFile(); + void splitSettingsDescription(); + double _descriptionVersion; + QJsonArray _descriptionArray; + QJsonArray _domainSettingsDescription; + QJsonArray _contentSettingsDescription; + QJsonObject _settingsMenuGroups; + HifiConfigVariantMap _configMap; friend class DomainServer; diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index bd256578d8..fd127a2e92 100644 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -79,7 +79,7 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, QHash redirectHeader; redirectHeader.insert(QByteArray("Location"), redirectLocation.toUtf8()); - connection->respond(HTTPConnection::StatusCode301, "", HTTPConnection::DefaultContentType, redirectHeader); + connection->respond(HTTPConnection::StatusCode302, "", HTTPConnection::DefaultContentType, redirectHeader); } // if the last thing is a trailing slash then we want to look for index file From b0967dfc3a188fb799f0c82fd42d41f3e7272a39 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 30 Jan 2018 16:38:32 -0800 Subject: [PATCH 230/569] move some more settings to content, leave places in domain settings --- domain-server/resources/describe-settings.json | 7 +++++-- domain-server/resources/web/js/base-settings.js | 8 ++++++-- domain-server/resources/web/js/domain-server.js | 7 +++++++ domain-server/resources/web/settings/js/settings.js | 11 +++++++++-- domain-server/src/DomainServerSettingsManager.cpp | 7 ++++++- 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 705d110542..b9a4246895 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -55,8 +55,8 @@ ] }, { - "label": "Places / Paths", - "html_id": "places_paths", + "label": "Paths", + "html_id": "paths", "restart": false, "settings": [ { @@ -64,6 +64,7 @@ "label": "Paths", "help": "Clients can enter a path to reach an exact viewpoint in your domain.
    Add rows to the table below to map a path to a viewpoint.
    The index path ( / ) is where clients will enter if they do not enter an explicit path.", "type": "table", + "content_setting": true, "can_add_new_rows": true, "key": { "name": "path", @@ -1081,6 +1082,7 @@ "name": "attenuation_per_doubling_in_distance", "label": "Default Domain Attenuation", "help": "Factor between 0 and 1.0 (0: No attenuation, 1.0: extreme attenuation)", + "content_setting": true, "placeholder": "0.5", "default": "0.5", "advanced": false @@ -1313,6 +1315,7 @@ "name": "entityEditFilter", "label": "Filter Entity Edits", "help": "Check all entity edits against this filter function.", + "content_setting": true, "placeholder": "url whose content is like: function filter(properties) { return properties; }", "default": "", "advanced": true diff --git a/domain-server/resources/web/js/base-settings.js b/domain-server/resources/web/js/base-settings.js index 5e32463d61..aad342da63 100644 --- a/domain-server/resources/web/js/base-settings.js +++ b/domain-server/resources/web/js/base-settings.js @@ -94,9 +94,13 @@ var viewHelpers = { function reloadSettings(callback) { $.getJSON(Settings.endpoint, function(data){ - _.extend(data, viewHelpers) + _.extend(data, viewHelpers); - $('#panels').html(Settings.panelsTemplate(data)) + for (var spliceIndex in Settings.extraGroups) { + data.descriptions.splice(spliceIndex, 0, Settings.extraGroups[spliceIndex]); + } + + $('#panels').html(Settings.panelsTemplate(data)); Settings.data = data; Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true); diff --git a/domain-server/resources/web/js/domain-server.js b/domain-server/resources/web/js/domain-server.js index aa658bce3f..a7c8c3a4d1 100644 --- a/domain-server/resources/web/js/domain-server.js +++ b/domain-server/resources/web/js/domain-server.js @@ -79,6 +79,13 @@ $(document).ready(function(){ } $settingsDropdown.append(makeGroupDropdownElement(group, "/settings/")); + + // for domain settings, we add a dummy "Places" group that we fill + // via the API - add it to the dropdown menu in the right spot + if (index == 1) { + $settingsDropdown.append(""); + $settingsDropdown.append(makeGroupDropdownElement({ html_id: 'places', label: 'Places' }, "/settings/")); + } }); }); } diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 9a31b766a6..d10d1304c3 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -14,6 +14,14 @@ $(document).ready(function(){ return b; })(window.location.search.substr(1).split('&')); + // define extra groups to add to description, with their splice index + Settings.extraGroups = { + 1: { + html_id: 'places', + label: 'Places' + } + } + Settings.afterReloadActions = function() { // append the domain selection modal appendDomainIDButtons(); @@ -657,8 +665,7 @@ $(document).ready(function(){ var placesTableGroup = viewHelpers.getFormGroup('', placesTableSetting, Settings.data.values); // append the places table in the right place - $('#places_paths .panel-body').prepend(placesTableGroup); - //$('#' + Settings.PLACES_TABLE_ID).append(""); + $('#places .panel-body').prepend(placesTableGroup); var spinner = createDomainSpinner(); $('#' + Settings.PLACES_TABLE_ID).after($(spinner)); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 52754babb3..f91c5af06f 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -123,7 +123,10 @@ void DomainServerSettingsManager::splitSettingsDescription() { QJsonObject settingsDropdownGroup; if (!groupHidden) { - settingsDropdownGroup[DESCRIPTION_NAME_KEY] = groupObject[DESCRIPTION_NAME_KEY]; + if (groupObject.contains(DESCRIPTION_NAME_KEY)) { + settingsDropdownGroup[DESCRIPTION_NAME_KEY] = groupObject[DESCRIPTION_NAME_KEY]; + } + settingsDropdownGroup[DESCRIPTION_GROUP_LABEL_KEY] = groupObject[DESCRIPTION_GROUP_LABEL_KEY]; static const QString DESCRIPTION_GROUP_HTML_ID_KEY = "html_id"; @@ -1383,7 +1386,9 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ foreach(const QJsonValue& groupValue, _descriptionArray) { // find groups with root values (they don't have a group name) QJsonObject groupObject = groupValue.toObject(); + if (!groupObject.contains(DESCRIPTION_NAME_KEY)) { + // this is a group with root values - check if our setting is in here matchingDescriptionObject = settingDescriptionFromGroup(groupObject, rootKey); From 8c924ea1065f6f7e3eea80b2f92b13c6e3573710 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 30 Jan 2018 17:13:03 -0800 Subject: [PATCH 231/569] update badge colour --- domain-server/resources/web/css/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 22de75a778..5155ab2330 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -414,6 +414,7 @@ ul.nav li.dropdown ul.dropdown-menu .divider { .badge { margin-left: 5px; + background-color: #00B4EF !important; } .panel-title { From 2c2a6d5c603342099c8edddc5fed026d1ac1b3f5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 30 Jan 2018 17:24:56 -0800 Subject: [PATCH 232/569] add the empty label group to domain extra groups --- domain-server/resources/describe-settings.json | 6 ------ domain-server/resources/web/settings/js/settings.js | 6 +++++- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index b9a4246895..bd351eb173 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1,12 +1,6 @@ { "version": 2.1, "settings": [ - { - "name": "label", - "label": "Label", - "settings": [ - ] - }, { "name": "metaverse", "label": "Metaverse / Networking", diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index d10d1304c3..39d776af8b 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -16,7 +16,11 @@ $(document).ready(function(){ // define extra groups to add to description, with their splice index Settings.extraGroups = { - 1: { + 0: { + html_id: 'label', + label: 'Label' + }, + 2: { html_id: 'places', label: 'Places' } From 6e93eb5dfa5b28da904c840ba7070323d9f513f7 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 30 Jan 2018 17:27:52 -0800 Subject: [PATCH 233/569] update SVG icon for HMD with new colour --- domain-server/resources/web/images/hmd-w-eyes.svg | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/domain-server/resources/web/images/hmd-w-eyes.svg b/domain-server/resources/web/images/hmd-w-eyes.svg index c100de2f4e..0e9081c10c 100644 --- a/domain-server/resources/web/images/hmd-w-eyes.svg +++ b/domain-server/resources/web/images/hmd-w-eyes.svg @@ -2,7 +2,10 @@ - + .st0{fill:#666666;} + +
    - <% _.each(settings, function(setting) { %> + <% _.each(split_settings[0], function(setting) { %> <% keypath = isGrouped ? group.name + "." + setting.name : setting.name %> <%= getFormGroup(keypath, setting, values, false) %> <% }); %> + + <% if (split_settings[1].length > 0) { %> + +
    + <% _.each(split_settings[1], function(setting) { %> + <% keypath = isGrouped ? group.name + "." + setting.name : setting.name %> + <%= getFormGroup(keypath, setting, values, true) %> + <% }); %> +
    + <% } %>
    <% } %> diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 5155ab2330..8090c9f450 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -426,3 +426,7 @@ ul.nav li.dropdown ul.dropdown-menu .divider { position: relative; top: -1px; } + +.advanced-settings-section { + margin-top: 20px; +} From bb2df1eb37f30fda8a8f71be18a1b941836ebfca Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 31 Jan 2018 15:27:29 -0800 Subject: [PATCH 236/569] rename entity server section to remove settings --- domain-server/resources/describe-settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index bd351eb173..cf3321f831 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1268,7 +1268,7 @@ }, { "name": "entity_server_settings", - "label": "Entity Server Settings", + "label": "Entity Server", "assignment-types": [ 6 ], From 0ace92798d66c95987396f1b7c2d4d8989e3a42f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 31 Jan 2018 17:18:49 -0800 Subject: [PATCH 237/569] repair 2.1 settings migration code for avatar height --- domain-server/src/DomainServerSettingsManager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index f91c5af06f..874e543a08 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -406,14 +406,14 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList QVariant* avatarMinScale = _configMap.valueForKeyPath(AVATAR_MIN_SCALE_KEYPATH); if (avatarMinScale) { - float scale = avatarMinScale->toFloat(); - _configMap.valueForKeyPath(AVATAR_MIN_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT); + auto newMinScaleVariant = _configMap.valueForKeyPath(AVATAR_MIN_HEIGHT_KEYPATH, true); + *newMinScaleVariant = avatarMinScale->toFloat() * DEFAULT_AVATAR_HEIGHT; } QVariant* avatarMaxScale = _configMap.valueForKeyPath(AVATAR_MAX_SCALE_KEYPATH); if (avatarMaxScale) { - float scale = avatarMaxScale->toFloat(); - _configMap.valueForKeyPath(AVATAR_MAX_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT); + auto newMaxScaleVariant = _configMap.valueForKeyPath(AVATAR_MAX_HEIGHT_KEYPATH, true); + *newMaxScaleVariant = avatarMaxScale->toFloat() * DEFAULT_AVATAR_HEIGHT; } } From 2f6b079d80dc618bb8208d644c2d9dadb79069f9 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 31 Jan 2018 17:19:07 -0800 Subject: [PATCH 238/569] quick re-organization of settings groups --- .../resources/describe-settings.json | 234 +++++++++--------- .../resources/web/js/domain-server.js | 2 +- domain-server/resources/web/js/shared.js | 4 + 3 files changed, 122 insertions(+), 118 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index cf3321f831..103c045516 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -951,99 +951,6 @@ } ] }, - { - "name": "asset_server", - "label": "Asset Server (ATP)", - "assignment-types": [ 3 ], - "settings": [ - { - "name": "enabled", - "type": "checkbox", - "label": "Enabled", - "help": "Assigns an asset-server in your domain to serve files to clients via the ATP protocol (over UDP)", - "default": true, - "advanced": true - }, - { - "name": "assets_path", - "type": "string", - "label": "Assets Path", - "help": "The path to the directory assets are stored in.
    If this path is relative, it will be relative to the application data directory.
    If you change this path you will need to manually copy any existing assets from the previous directory.", - "default": "", - "advanced": true - }, - { - "name": "assets_filesize_limit", - "type": "int", - "label": "File Size Limit", - "help": "The file size limit of an asset that can be imported into the asset server in MBytes. 0 (default) means no limit on file size.", - "default": 0, - "advanced": true - } - ] - }, - { - "name": "entity_script_server", - "label": "Entity Script Server (ESS)", - "assignment-types": [ 5 ], - "settings": [ - { - "name": "entity_pps_per_script", - "label": "Entity PPS per script", - "help": "The number of packets per second (PPS) that can be sent to the entity server for each server entity script. This contributes to a total overall amount.
    Example: 1000 PPS with 5 entites gives a total PPS of 5000 that is shared among the entity scripts. A single could use 4000 PPS, leaving 1000 for the rest, for example.", - "default": 900, - "type": "int", - "advanced": true - }, - { - "name": "max_total_entity_pps", - "label": "Maximum Total Entity PPS", - "help": "The maximum total packets per seconds (PPS) that can be sent to the entity server.
    Example: 5 scripts @ 1000 PPS per script = 5000 total PPS. A maximum total PPS of 4000 would cap this 5000 total PPS to 4000.", - "default": 9000, - "type": "int", - "advanced": true - } - ] - }, - { - "name": "avatars", - "label": "Avatars", - "assignment-types": [ 1, 2 ], - "settings": [ - { - "name": "min_avatar_height", - "type": "double", - "label": "Minimum Avatar Height (meters)", - "help": "Limits the height of avatars in your domain. Must be at least 0.009.", - "placeholder": 0.4, - "default": 0.4 - }, - { - "name": "max_avatar_height", - "type": "double", - "label": "Maximum Avatar Height (meters)", - "help": "Limits the scale of avatars in your domain. Cannot be greater than 1755.", - "placeholder": 5.2, - "default": 5.2 - }, - { - "name": "avatar_whitelist", - "label": "Avatars Allowed from:", - "help": "Comma separated list of URLs (with optional paths) that avatar .fst files are allowed from. If someone attempts to use an avatar with a different domain, it will be rejected and the replacement avatar will be used. If left blank, any domain is allowed.", - "placeholder": "", - "default": "", - "advanced": true - }, - { - "name": "replacement_avatar", - "label": "Replacement Avatar for disallowed avatars", - "help": "A URL for an avatar .fst to be used when someone tries to use an avatar that is not allowed. If left blank, the generic default avatar is used.", - "placeholder": "", - "default": "", - "advanced": true - } - ] - }, { "name": "audio_threading", "label": "Audio Threading", @@ -1266,9 +1173,82 @@ } ] }, + { + "name": "avatars", + "label": "Avatars", + "assignment-types": [ 1, 2 ], + "settings": [ + { + "name": "min_avatar_height", + "type": "double", + "label": "Minimum Avatar Height (meters)", + "help": "Limits the height of avatars in your domain. Must be at least 0.009.", + "placeholder": 0.4, + "default": 0.4 + }, + { + "name": "max_avatar_height", + "type": "double", + "label": "Maximum Avatar Height (meters)", + "help": "Limits the scale of avatars in your domain. Cannot be greater than 1755.", + "placeholder": 5.2, + "default": 5.2 + }, + { + "name": "avatar_whitelist", + "label": "Avatars Allowed from:", + "help": "Comma separated list of URLs (with optional paths) that avatar .fst files are allowed from. If someone attempts to use an avatar with a different domain, it will be rejected and the replacement avatar will be used. If left blank, any domain is allowed.", + "placeholder": "", + "default": "", + "advanced": true + }, + { + "name": "replacement_avatar", + "label": "Replacement Avatar for disallowed avatars", + "help": "A URL for an avatar .fst to be used when someone tries to use an avatar that is not allowed. If left blank, the generic default avatar is used.", + "placeholder": "", + "default": "", + "advanced": true + } + ] + }, + { + "name": "avatar_mixer", + "label": "Avatar Mixer", + "assignment-types": [ + 1 + ], + "settings": [ + { + "name": "max_node_send_bandwidth", + "type": "double", + "label": "Per-Node Bandwidth", + "help": "Desired maximum send bandwidth (in Megabits per second) to each node", + "placeholder": 5.0, + "default": 5.0, + "advanced": true + }, + { + "name": "auto_threads", + "label": "Automatically determine thread count", + "type": "checkbox", + "help": "Allow system to determine number of threads (recommended)", + "default": false, + "advanced": true + }, + { + "name": "num_threads", + "label": "Number of Threads", + "help": "Threads to spin up for avatar mixing (if not automatically set)", + "placeholder": "1", + "default": "1", + "advanced": true + } + ] + }, { "name": "entity_server_settings", - "label": "Entity Server", + "label": "Entities", "assignment-types": [ 6 ], @@ -1504,35 +1484,55 @@ ] }, { - "name": "avatar_mixer", - "label": "Avatar Mixer", - "assignment-types": [ - 1 - ], + "name": "asset_server", + "label": "Asset Server (ATP)", + "assignment-types": [ 3 ], "settings": [ { - "name": "max_node_send_bandwidth", - "type": "double", - "label": "Per-Node Bandwidth", - "help": "Desired maximum send bandwidth (in Megabits per second) to each node", - "placeholder": 5.0, - "default": 5.0, - "advanced": true - }, - { - "name": "auto_threads", - "label": "Automatically determine thread count", + "name": "enabled", "type": "checkbox", - "help": "Allow system to determine number of threads (recommended)", - "default": false, + "label": "Enabled", + "help": "Assigns an asset-server in your domain to serve files to clients via the ATP protocol (over UDP)", + "default": true, "advanced": true }, { - "name": "num_threads", - "label": "Number of Threads", - "help": "Threads to spin up for avatar mixing (if not automatically set)", - "placeholder": "1", - "default": "1", + "name": "assets_path", + "type": "string", + "label": "Assets Path", + "help": "The path to the directory assets are stored in.
    If this path is relative, it will be relative to the application data directory.
    If you change this path you will need to manually copy any existing assets from the previous directory.", + "default": "", + "advanced": true + }, + { + "name": "assets_filesize_limit", + "type": "int", + "label": "File Size Limit", + "help": "The file size limit of an asset that can be imported into the asset server in MBytes. 0 (default) means no limit on file size.", + "default": 0, + "advanced": true + } + ] + }, + { + "name": "entity_script_server", + "label": "Entity Script Server (ESS)", + "assignment-types": [ 5 ], + "settings": [ + { + "name": "entity_pps_per_script", + "label": "Entity PPS per script", + "help": "The number of packets per second (PPS) that can be sent to the entity server for each server entity script. This contributes to a total overall amount.
    Example: 1000 PPS with 5 entites gives a total PPS of 5000 that is shared among the entity scripts. A single could use 4000 PPS, leaving 1000 for the rest, for example.", + "default": 900, + "type": "int", + "advanced": true + }, + { + "name": "max_total_entity_pps", + "label": "Maximum Total Entity PPS", + "help": "The maximum total packets per seconds (PPS) that can be sent to the entity server.
    Example: 5 scripts @ 1000 PPS per script = 5000 total PPS. A maximum total PPS of 4000 would cap this 5000 total PPS to 4000.", + "default": 9000, + "type": "int", "advanced": true } ] diff --git a/domain-server/resources/web/js/domain-server.js b/domain-server/resources/web/js/domain-server.js index a7c8c3a4d1..ae5a452ec9 100644 --- a/domain-server/resources/web/js/domain-server.js +++ b/domain-server/resources/web/js/domain-server.js @@ -82,7 +82,7 @@ $(document).ready(function(){ // for domain settings, we add a dummy "Places" group that we fill // via the API - add it to the dropdown menu in the right spot - if (index == 1) { + if (index == 0) { $settingsDropdown.append(""); $settingsDropdown.append(makeGroupDropdownElement({ html_id: 'places', label: 'Places' }, "/settings/")); } diff --git a/domain-server/resources/web/js/shared.js b/domain-server/resources/web/js/shared.js index e1870a2fa8..e7fc77b707 100644 --- a/domain-server/resources/web/js/shared.js +++ b/domain-server/resources/web/js/shared.js @@ -1,3 +1,7 @@ +if (typeof Settings === "undefined") { + Settings = {}; +} + Object.assign(Settings, { DEPRECATED_CLASS: 'deprecated-setting', TRIGGER_CHANGE_CLASS: 'trigger-change', From 11fe279f6f976a878a3140875ea25132e725ede7 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 1 Feb 2018 15:47:12 -0800 Subject: [PATCH 239/569] add domain settings backup and restore to web interface --- .../resources/web/content/js/content.js | 10 - domain-server/resources/web/css/style.css | 4 + domain-server/resources/web/header.html | 2 +- .../resources/web/js/base-settings.js | 10 + .../resources/web/js/domain-server.js | 7 +- domain-server/resources/web/js/shared.js | 8 +- .../resources/web/settings/js/settings.js | 86 ++++++- .../src/DomainServerSettingsManager.cpp | 227 ++++++++++++++++-- .../src/DomainServerSettingsManager.h | 7 +- 9 files changed, 316 insertions(+), 45 deletions(-) diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js index 7b2ed37b15..e448952c65 100644 --- a/domain-server/resources/web/content/js/content.js +++ b/domain-server/resources/web/content/js/content.js @@ -2,16 +2,6 @@ $(document).ready(function(){ Settings.afterReloadActions = function() {}; - function showSpinnerAlert(title) { - swal({ - title: title, - text: '
    ', - html: true, - showConfirmButton: false, - allowEscapeKey: false - }); - } - var frm = $('#upload-form'); frm.submit(function (ev) { $.ajax({ diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 8090c9f450..ed19d46fb5 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -430,3 +430,7 @@ ul.nav li.dropdown ul.dropdown-menu .divider { .advanced-settings-section { margin-top: 20px; } + +#restore-settings-button { + margin-top: 10px; +} diff --git a/domain-server/resources/web/header.html b/domain-server/resources/web/header.html index aff82d557e..a9a477c7fb 100644 --- a/domain-server/resources/web/header.html +++ b/domain-server/resources/web/header.html @@ -67,7 +67,7 @@
    diff --git a/domain-server/resources/web/js/base-settings.js b/domain-server/resources/web/js/base-settings.js index f6069a3c40..3f410d4e2c 100644 --- a/domain-server/resources/web/js/base-settings.js +++ b/domain-server/resources/web/js/base-settings.js @@ -155,7 +155,10 @@ function postSettings(jsonSettings) { $(document).ready(function(){ - $('.save-button.navbar-btn').show(); + $(document).on('click', '.save-button', function(e){ + saveSettings(); + e.preventDefault(); + }); $.ajaxSetup({ timeout: 20000, @@ -254,7 +257,7 @@ $(document).ready(function(){ } }); - $('#' + Settings.FORM_ID).on('change keyup paste', '.' + Settings.TRIGGER_CHANGE_CLASS , function(){ + $('#' + Settings.FORM_ID).on('change keyup paste', '.' + Settings.TRIGGER_CHANGE_CLASS , function(e){ // this input was changed, add the changed data attribute to it $(this).attr('data-changed', true); @@ -438,11 +441,6 @@ function saveSettings() { } } -$('body').on('click', '.save-button', function(e){ - saveSettings(); - return false; -}); - function makeTable(setting, keypath, setting_value) { var isArray = !_.has(setting, 'key'); var categoryKey = setting.categorize_by_key; @@ -788,8 +786,8 @@ function badgeForDifferences(changedElement) { } } - $(".save-button").prop("disabled", !hasChanges); - $(".save-button").html(reasonsForRestart.length > 0 ? SAVE_BUTTON_LABEL_RESTART : SAVE_BUTTON_LABEL_SAVE); + $('.save-button').prop("disabled", !hasChanges); + $('.save-button-text').html(reasonsForRestart.length > 0 ? SAVE_BUTTON_LABEL_RESTART : SAVE_BUTTON_LABEL_SAVE); // add the badge to the navbar item and the panel header $("a[href='" + settingsGroupAnchor(Settings.path, panelParentID) + "'] .badge").html(badgeValue); @@ -832,7 +830,7 @@ function addTableRow(row) { var keyInput = row.children(".key").children("input"); // whenever the keyInput changes, re-badge for differences - keyInput.on('change keyup paste', function(){ + keyInput.on('change keyup paste', function(e){ // update siblings in the row to have the correct name var currentKey = $(this).val(); @@ -844,7 +842,7 @@ function addTableRow(row) { } else { input.removeAttr("name"); } - }) + }); badgeForDifferences($(this)); }); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 401b322502..131670d221 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -1181,7 +1181,7 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection // create a timestamped filename for the backup const QString DATETIME_FORMAT { "yyyy-MM-dd_HH-mm-ss" }; - auto backupFilename = "ds-settings-" + QDateTime::currentDateTime().toString(DATETIME_FORMAT) + ".json"; + auto backupFilename = "domain-settings_" + QDateTime::currentDateTime().toString(DATETIME_FORMAT) + ".json"; downloadHeaders.insert("Content-Disposition", QString("attachment; filename=\"%1\"").arg(backupFilename).toLocal8Bit()); From 9f2015ba469737c9d2e59f4e646d306050a4d739 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 5 Feb 2018 16:58:18 -0800 Subject: [PATCH 241/569] check correct element to determine if restart is required --- domain-server/resources/web/js/base-settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/resources/web/js/base-settings.js b/domain-server/resources/web/js/base-settings.js index 3f410d4e2c..17f06f3ad1 100644 --- a/domain-server/resources/web/js/base-settings.js +++ b/domain-server/resources/web/js/base-settings.js @@ -138,7 +138,7 @@ function postSettings(jsonSettings) { type: 'POST' }).done(function(data){ if (data.status == "success") { - if ($(".save-button").html() === SAVE_BUTTON_LABEL_RESTART) { + if ($(".save-button-text").html() === SAVE_BUTTON_LABEL_RESTART) { showRestartModal(); } else { location.reload(true); From 3516a8939a23f821f3d1dfb407e1e964fad807b4 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 6 Feb 2018 16:44:51 -0800 Subject: [PATCH 242/569] flag settings in description as being excluded from backups --- .../resources/describe-settings.json | 9 ++++--- .../src/DomainServerSettingsManager.cpp | 24 ++++++++++++------- .../src/DomainServerSettingsManager.h | 2 +- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 103c045516..93d703c8b3 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -9,7 +9,8 @@ "name": "access_token", "label": "Access Token", "help": "This is your OAuth access token to connect this domain-server with your High Fidelity account.
    It can be generated by clicking the 'Connect Account' button above.
    You can also go to the My Security page of your account and generate a token with the 'domains' scope and paste it here.", - "advanced": true + "advanced": true, + "backup": false }, { "name": "id", @@ -159,7 +160,8 @@ { "name": "http_username", "label": "HTTP Username", - "help": "Username used for basic HTTP authentication." + "help": "Username used for basic HTTP authentication.", + "backup": false }, { "name": "http_password", @@ -167,7 +169,8 @@ "type": "password", "help": "Password used for basic HTTP authentication. Leave this alone if you do not want to change it.", "password_placeholder": "******", - "value-hidden": true + "value-hidden": true, + "backup": false }, { "name": "verify_http_password", diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 131670d221..b8d4a07238 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -40,6 +40,7 @@ const QString DESCRIPTION_SETTINGS_KEY = "settings"; const QString SETTING_DEFAULT_KEY = "default"; const QString DESCRIPTION_NAME_KEY = "name"; const QString DESCRIPTION_GROUP_LABEL_KEY = "label"; +const QString DESCRIPTION_BACKUP_FLAG_KEY = "backup"; const QString SETTING_DESCRIPTION_TYPE_KEY = "type"; const QString DESCRIPTION_COLUMNS_KEY = "columns"; const QString CONTENT_SETTING_FLAG_KEY = "content_setting"; @@ -1173,7 +1174,7 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection } else if (url.path() == SETTINGS_BACKUP_PATH) { // grab the settings backup as an authenticated user // for the domain settings type only, excluding hidden and default values - auto currentDomainSettingsJSON = settingsResponseObjectForType("", true, true, false, false); + auto currentDomainSettingsJSON = settingsResponseObjectForType("", true, true, false, false, true); // setup headers that tell the client to download the file wth a special name Headers downloadHeaders; @@ -1240,13 +1241,15 @@ bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settings } foreach(const QJsonValue& descriptionSettingValue, descriptionGroupSettings) { - const QString VALUE_HIDDEN_FLAG_KEY = "value-hidden"; QJsonObject descriptionSettingObject = descriptionSettingValue.toObject(); - // we'll override this setting with the default or what is in the restore as long as it isn't hidden - if (!descriptionSettingObject[VALUE_HIDDEN_FLAG_KEY].toBool()) { + // we'll override this setting with the default or what is in the restore as long as + // it isn't specifically excluded from backups + bool isBackedUpSetting = !descriptionSettingObject.contains(DESCRIPTION_BACKUP_FLAG_KEY) + || descriptionSettingObject[DESCRIPTION_BACKUP_FLAG_KEY].toBool(); + if (isBackedUpSetting) { QString settingName = descriptionSettingObject[DESCRIPTION_NAME_KEY].toString(); // check if we have a matching setting for this in the restore @@ -1315,7 +1318,7 @@ bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settings QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QString& typeValue, bool isAuthenticated, bool includeDomainSettings, bool includeContentSettings, - bool includeDefaults) { + bool includeDefaults, bool isForBackup) { QJsonObject responseObject; if (!typeValue.isEmpty() || isAuthenticated) { @@ -1347,7 +1350,11 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt QJsonObject settingObject = settingValue.toObject(); // consider this setting as long as it isn't hidden - if (!settingObject[VALUE_HIDDEN_FLAG_KEY].toBool()) { + // and either this isn't for a backup or it's a value included in backups + bool includedInBackups = !settingObject.contains(DESCRIPTION_BACKUP_FLAG_KEY) + || settingObject[DESCRIPTION_BACKUP_FLAG_KEY].toBool(); + + if (!settingObject[VALUE_HIDDEN_FLAG_KEY].toBool() && (!isForBackup || includedInBackups)) { QJsonArray affectedTypesArray = settingObject[AFFECTED_TYPES_JSON_KEY].toArray(); if (affectedTypesArray.isEmpty()) { affectedTypesArray = groupObject[AFFECTED_TYPES_JSON_KEY].toArray(); @@ -1370,8 +1377,8 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt variantValue = _configMap.value(settingName); } - // final check for inclusion, either we include default values - // or we don't but this isn't a default value + // final check for inclusion + // either we include default values or we don't but this isn't a default value if (includeDefaults || !variantValue.isNull()) { QJsonValue result; @@ -1407,7 +1414,6 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt } } - return responseObject; } diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 00707c33e3..9b2427b344 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -132,7 +132,7 @@ private: QJsonArray filteredDescriptionArray(bool isContentSettings); QJsonObject settingsResponseObjectForType(const QString& typeValue, bool isAuthenticated = false, bool includeDomainSettings = true, bool includeContentSettings = true, - bool includeDefaults = true); + bool includeDefaults = true, bool isForBackup = false); bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, SettingsType settingsType); void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap, From 37b8fa2c0c84af04bb259d969c9a89c3d5c708ff Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 7 Feb 2018 15:22:33 -0800 Subject: [PATCH 243/569] put back default values in settings response --- domain-server/resources/web/js/shared.js | 7 ++++--- domain-server/src/DomainServerSettingsManager.cpp | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/domain-server/resources/web/js/shared.js b/domain-server/resources/web/js/shared.js index f5346ce024..69721ee924 100644 --- a/domain-server/resources/web/js/shared.js +++ b/domain-server/resources/web/js/shared.js @@ -164,11 +164,12 @@ function getDomainFromAPI(callback) { if (callback === undefined) { callback = function() {}; } - - var domainID = Settings.data.values.metaverse.id; - if (domainID === null || domainID === undefined || domainID === '') { + + if (!domainIDIsSet()) { callback({ status: 'fail' }); return null; + } else { + var domainID = Settings.data.values.metaverse.id; } pendingDomainRequest = $.ajax({ diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index b8d4a07238..8febbd5769 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -1162,7 +1162,7 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection // and exclude default values rootObject[SETTINGS_RESPONSE_VALUE_KEY] = settingsResponseObjectForType("", true, forDomainSettings, forContentSettings, - false); + true); connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json"); From e14f46101b47c55a4bd540060655ae8e9cadeed8 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 14 Feb 2018 13:49:43 -0800 Subject: [PATCH 244/569] fix tablet rotation when switching into and out of create mode --- scripts/system/libraries/WebTablet.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index 05b4963280..a28de5abc2 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -47,7 +47,7 @@ function calcSpawnInfo(hand, landscape) { var headPos = (HMD.active && Camera.mode === "first person") ? HMD.position : Camera.position; var headRot = (HMD.active && Camera.mode === "first person") ? HMD.orientation : Camera.orientation; - var forward = Quat.getForward(headRot); + var forward = Quat.getForward(Quat.cancelOutRollAndPitch(headRot)); var FORWARD_OFFSET = 0.5 * MyAvatar.sensorToWorldScale; finalPosition = Vec3.sum(headPos, Vec3.multiply(FORWARD_OFFSET, forward)); var orientation = Quat.lookAt({x: 0, y: 0, z: 0}, forward, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.UNIT_Y)); @@ -269,8 +269,9 @@ WebTablet.prototype.setLandscape = function(newLandscapeValue) { } this.landscape = newLandscapeValue; + var cameraOrientation = Quat.cancelOutRollAndPitch(Camera.orientation); Overlays.editOverlay(this.tabletEntityID, - { rotation: Quat.multiply(Camera.orientation, this.landscape ? ROT_LANDSCAPE : ROT_Y_180) }); + { rotation: Quat.multiply(cameraOrientation, this.landscape ? ROT_LANDSCAPE : ROT_Y_180) }); var tabletWidth = getTabletWidthFromSettings() * MyAvatar.sensorToWorldScale; var tabletScaleFactor = tabletWidth / TABLET_NATURAL_DIMENSIONS.x; @@ -278,7 +279,7 @@ WebTablet.prototype.setLandscape = function(newLandscapeValue) { var screenWidth = 0.82 * tabletWidth; var screenHeight = 0.81 * tabletHeight; Overlays.editOverlay(this.webOverlayID, { - rotation: Quat.multiply(Camera.orientation, ROT_LANDSCAPE_WINDOW), + rotation: Quat.multiply(cameraOrientation, ROT_LANDSCAPE_WINDOW), dimensions: {x: this.landscape ? screenHeight : screenWidth, y: this.landscape ? screenWidth : screenHeight, z: 0.1} }); }; From d466c079a4c4a89738ae9d045bcb946953861e7c Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Wed, 14 Feb 2018 17:00:52 -0500 Subject: [PATCH 245/569] Simple fix for 801 Uncaught TypeError (details below). entityProperties.js was throwing out: 801 Uncaught TypeError: Cannot read property 'split' of undefined This moves properties.modelURL related checks behind the property.type validation check to avoid calling the function on an undefined member if the object doesn't have that key defined. Changes Committed: modified: scripts/system/html/js/entityProperties.js --- scripts/system/html/js/entityProperties.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 35235634e9..b27c852974 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -798,10 +798,13 @@ function loaded() { // HTML workaround since image is not yet a separate entity type var IMAGE_MODEL_NAME = 'default-image-model.fbx'; - var urlParts = properties.modelURL.split('/') - var propsFilename = urlParts[urlParts.length - 1]; - if (properties.type === "Model" && propsFilename === IMAGE_MODEL_NAME) { - properties.type = "Image"; + if (properties.type === "Model") { + var urlParts = properties.modelURL.split('/'); + var propsFilename = urlParts[urlParts.length - 1]; + + if (propsFilename === IMAGE_MODEL_NAME) { + properties.type = "Image"; + } } // Create class name for css ruleset filtering From 4d4b42848b2b12adb53954cac675391978ee2b71 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 14 Feb 2018 16:31:28 -0800 Subject: [PATCH 246/569] add alert for moved content settings --- domain-server/resources/web/base-settings.html | 6 ------ domain-server/resources/web/settings/index.shtml | 8 ++++++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/domain-server/resources/web/base-settings.html b/domain-server/resources/web/base-settings.html index 2f0fcbe73b..da5561d03d 100644 --- a/domain-server/resources/web/base-settings.html +++ b/domain-server/resources/web/base-settings.html @@ -1,10 +1,4 @@
    -
    -
    - -
    -
    -
    diff --git a/domain-server/resources/web/settings/index.shtml b/domain-server/resources/web/settings/index.shtml index d71692523a..bdc02c5dcc 100644 --- a/domain-server/resources/web/settings/index.shtml +++ b/domain-server/resources/web/settings/index.shtml @@ -8,6 +8,14 @@ }; +
    +
    +
    +
    Your domain content settings are now available in Content
    +
    +
    +
    + From 5a8b8d4ac84068c47d5a06d66632cb3fd21aaa4c Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 14 Feb 2018 16:44:02 -0800 Subject: [PATCH 247/569] Lots of changes --- .../qml/hifi/commerce/checkout/Checkout.qml | 132 +++++++++++++----- .../InspectionCertificate.qml | 1 + .../hifi/commerce/purchases/PurchasedItem.qml | 2 + interface/src/commerce/Ledger.cpp | 5 +- 4 files changed, 105 insertions(+), 35 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index bd3f706004..981d5c5b9a 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -40,8 +40,9 @@ Rectangle { property bool isCertified; property string itemType; property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar"]; - property var buttonTextNormal: ["REZ IT", "WEAR IT", "SWAP CONTENT SET", "INSTALL IT", "WEAR IT"]; - property var buttonTextClicked: ["REZZED", "WORN", "SWAPPED", "INSTALLED", "WORN"] + property var buttonTextNormal: ["REZ IT", "WEAR IT", "REPLACE CONTENT SET", "INSTALL IT", "WEAR IT"]; + property var buttonTextClicked: ["REZZED", "WORN", "CONTENT SET REPLACED!", "INSTALLED", "WORN"] + property var buttonGlyph: [hifi.glyphs.wand, hifi.glyphs.hat, hifi.glyphs.globe, hifi.glyphs.install, hifi.glyphs.avatar]; property bool shouldBuyWithControlledFailure: false; property bool debugCheckoutSuccess: false; property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified(); @@ -102,7 +103,7 @@ Rectangle { } else { root.balanceReceived = true; root.balanceAfterPurchase = result.data.balance - root.itemPrice; - root.setBuyText(); + root.refreshBuyUI(); } } @@ -117,7 +118,7 @@ Rectangle { console.log("WARNING - Received 'Already Owned' status about different Marketplace ID!"); root.alreadyOwned = false; } - root.setBuyText(); + root.refreshBuyUI(); } } } @@ -131,11 +132,11 @@ Rectangle { onItemHrefChanged: { if (root.itemHref.indexOf(".fst") > -1) { root.itemType = "avatar"; - } else if (root.itemHref.endsWith('.json.gz')) { + } else if (root.itemHref.indexOf('.json.gz') > -1) { root.itemType = "contentSet"; - } else if (root.itemHref.endsWith('.json')) { + } else if (root.itemHref.indexOf('.json') > -1) { root.itemType = "entity"; // "wearable" type handled later - } else if (root.itemHref.endsWith('.js')) { + } else if (root.itemHref.indexOf('.js') > -1) { root.itemType = "app"; } else { console.log("WARNING - Item type is UNKNOWN!"); @@ -304,6 +305,31 @@ Rectangle { anchors.left: parent.left; anchors.right: parent.right; + Rectangle { + id: loading; + z: 997; + visible: !root.ownershipStatusReceived || !root.balanceReceived; + anchors.fill: parent; + color: Qt.rgba(0.0, 0.0, 0.0, 0.7); + + // This object is always used in a popup. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup/section. + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + propagateComposedEvents: false; + } + + AnimatedImage { + source: "../common/images/loader.gif" + width: 96; + height: width; + anchors.verticalCenter: parent.verticalCenter; + anchors.horizontalCenter: parent.horizontalCenter; + } + } + RalewayRegular { id: confirmPurchaseText; anchors.top: parent.top; @@ -437,7 +463,7 @@ Rectangle { Rectangle { id: buyTextContainer; visible: buyText.text !== ""; - anchors.top: cancelPurchaseButton.bottom; + anchors.top: parent.top; anchors.topMargin: 16; anchors.left: parent.left; anchors.right: parent.right; @@ -480,10 +506,23 @@ Rectangle { // Alignment horizontalAlignment: Text.AlignLeft; verticalAlignment: Text.AlignVCenter; + } + } - onLinkActivated: { - sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName}); - } + // "View in My Purchases" button + HifiControlsUit.Button { + id: viewInMyPurchasesButton; + visible: false; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.light; + anchors.top: buyTextContainer.visible ? buyTextContainer.bottom : checkoutActionButtonsContainer.top; + anchors.topMargin: 16; + height: 40; + anchors.left: parent.left; + anchors.right: parent.right; + text: "VIEW THIS ITEM IN MY PURCHASES"; + onClicked: { + sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName}); } } @@ -491,21 +530,38 @@ Rectangle { HifiControlsUit.Button { id: buyButton; enabled: (root.balanceAfterPurchase >= 0 && ownershipStatusReceived && balanceReceived) || (!root.isCertified); - color: hifi.buttons.blue; + color: viewInMyPurchasesButton.visible ? hifi.buttons.white : hifi.buttons.blue; colorScheme: hifi.colorSchemes.light; - anchors.top: checkoutActionButtonsContainer.top; + anchors.top: viewInMyPurchasesButton.visible ? viewInMyPurchasesButton.bottom : + (buyTextContainer.visible ? buyTextContainer.bottom : checkoutActionButtonsContainer.top); anchors.topMargin: 16; height: 40; anchors.left: parent.left; anchors.right: parent.right; - text: ((root.isCertified) ? ((ownershipStatusReceived && balanceReceived) ? "Confirm Purchase" : "--") : "Get Item"); + text: ((root.isCertified) ? ((ownershipStatusReceived && balanceReceived) ? + (viewInMyPurchasesButton.visible ? "Buy It Again" : "Confirm Purchase") : "--") : "Get Item"); onClicked: { if (root.isCertified) { - buyButton.enabled = false; if (!root.shouldBuyWithControlledFailure) { - Commerce.buy(itemId, itemPrice); + if (root.itemType === "contentSet" && !Entities.canReplaceContent()) { + lightboxPopup.titleText = "Purchase Content Set"; + lightboxPopup.bodyText = "You will not be able to replace this domain's content with " + root.itemName + + " until the server owner gives you 'Replace Content' permissions.

    Are you sure you want to purchase this content set?"; + lightboxPopup.button1text = "CANCEL"; + lightboxPopup.button1method = "root.visible = false;" + lightboxPopup.button2text = "CONFIRM"; + lightboxPopup.button2method = "Commerce.buy('" + root.itemId + "', " + root.itemPrice + ");" + + "root.visible = false; buyButton.enabled = false; loading.visible = true;"; + lightboxPopup.visible = true; + } else { + buyButton.enabled = false; + loading.visible = true; + Commerce.buy(root.itemId, root.itemPrice); + } } else { - Commerce.buy(itemId, itemPrice, true); + buyButton.enabled = false; + loading.visible = true; + Commerce.buy(root.itemId, root.itemPrice, true); } } else { if (urlHandler.canHandleUrl(itemHref)) { @@ -618,8 +674,10 @@ Rectangle { // "Rez" button HifiControlsUit.Button { id: rezNowButton; - enabled: root.canRezCertifiedItems || root.itemType === "wearable"; - buttonGlyph: hifi.glyphs.lightning; + enabled: (root.itemType === "entity" && root.canRezCertifiedItems) || + (root.itemType === "contentSet" && Entities.canReplaceContent()) || + root.itemType === "wearable"; + buttonGlyph: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)]; color: hifi.buttons.red; colorScheme: hifi.colorSchemes.light; anchors.top: completeText2.bottom; @@ -631,11 +689,17 @@ Rectangle { onClicked: { if (root.itemType === "contentSet") { lightboxPopup.titleText = "Replace Content"; - lightboxPopup.bodyText = "Rezzing this content set will replace the existing environment and all of the items in this domain."; + lightboxPopup.bodyText = "Rezzing this content set will replace the existing environment and all of the items in this domain. " + + "If you want to save the state of the content in this domain, create a backup before proceeding.

    " + + "For more information about backing up and restoring content, " + + "" + + "click here to open info on your desktop browser."; lightboxPopup.button1text = "CANCEL"; lightboxPopup.button1method = "root.visible = false;" lightboxPopup.button2text = "CONFIRM"; - lightboxPopup.button2method = "sendToScript({method: 'checkout_rezClicked', itemHref: " + root.itemHref + ", itemType: " + root.itemType + "});"; + lightboxPopup.button2method = "Commerce.replaceContentSet('" + root.itemId + "', '" + root.itemHref + "');" + + "root.visible = false;rezzedNotifContainer.visible = true; rezzedNotifContainerTimer.start();" + + "UserActivityLogger.commerceEntityRezzed('" + root.itemId + "', 'checkout', '" + root.itemType + "');"; lightboxPopup.visible = true; } else { sendToScript({method: 'checkout_rezClicked', itemHref: root.itemHref, itemType: root.itemType}); @@ -919,7 +983,7 @@ Rectangle { itemHref = message.params.itemHref; referrer = message.params.referrer; itemAuthor = message.params.itemAuthor; - setBuyText(); + refreshBuyUI(); break; default: console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); @@ -927,13 +991,13 @@ Rectangle { } signal sendToScript(var message); - function setBuyText() { + function refreshBuyUI() { if (root.isCertified) { if (root.ownershipStatusReceived && root.balanceReceived) { if (root.balanceAfterPurchase < 0) { if (root.alreadyOwned) { - buyText.text = "Your Wallet does not have sufficient funds to purchase this item again.
    " + - '
    View the copy you own in My Purchases'; + buyText.text = "Your Wallet does not have sufficient funds to purchase this item again."; + viewInMyPurchasesButton.visible = true; } else { buyText.text = "Your Wallet does not have sufficient funds to purchase this item."; } @@ -943,15 +1007,19 @@ Rectangle { buyGlyph.size = 54; } else { if (root.alreadyOwned) { - buyText.text = 'You already own this item.
    Purchasing it will buy another copy.
    View this item in My Purchases
    '; - buyTextContainer.color = "#FFD6AD"; - buyTextContainer.border.color = "#FAC07D"; - buyGlyph.text = hifi.glyphs.alert; - buyGlyph.size = 46; + viewInMyPurchasesButton.visible = true; } else { buyText.text = ""; } + + if (root.itemType === "contentSet" && !Entities.canReplaceContent()) { + buyText.text = "The domain owner must enable 'Replace Content' permissions for you in this " + + "domain's server settings before you can replace this domain's content with " + root.itemName + ""; + buyTextContainer.color = "#FFC3CD"; + buyTextContainer.border.color = "#F3808F"; + buyGlyph.text = hifi.glyphs.alert; + buyGlyph.size = 54; + } } } else { buyText.text = ""; @@ -973,7 +1041,7 @@ Rectangle { } root.balanceReceived = false; root.ownershipStatusReceived = false; - Commerce.inventory(); + Commerce.alreadyOwned(root.itemId); Commerce.balance(); } diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index f493747c5e..a622349d00 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -208,6 +208,7 @@ Rectangle { // able to click on a button/mouseArea underneath the popup/section. MouseArea { anchors.fill: parent; + hoverEnabled: true; propagateComposedEvents: false; } diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 9db847eada..d19a16e74e 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -209,6 +209,8 @@ Item { "You do not have 'Replace Content' permissions in this domain. Learn more"; } else if (root.itemType === "entity") { "You do not have 'Rez Certified' permissions in this domain. Learn more"; + } else { + "" } } size: 16; diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index f319881035..dff441f840 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -49,6 +49,7 @@ Handler(balance) Handler(inventory) Handler(transferHfcToNode) Handler(transferHfcToUsername) +Handler(alreadyOwned) void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request) { auto accountManager = DependencyManager::get(); @@ -337,13 +338,11 @@ void Ledger::transferHfcToUsername(const QString& hfc_key, const QString& userna signedSend("transaction", transactionString, hfc_key, "transfer_hfc_to_user", "transferHfcToUsernameSuccess", "transferHfcToUsernameFailure"); } -void Ledger::alreadyOwnedSuccess(QNetworkReply& reply) { apiResponse("alreadyOwned", reply); } -void Ledger::alreadyOwnedFailure(QNetworkReply& reply) { failResponse("alreadyOwned", reply); } void Ledger::alreadyOwned(const QString& marketplaceId) { auto wallet = DependencyManager::get(); QString endpoint = "already_owned"; QJsonObject request; request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); request["marketplace_item_id"] = marketplaceId; - send(endpoint, "alreadyOwnedSuccess", "alreadyOwnedFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::None, request); + send(endpoint, "alreadyOwnedSuccess", "alreadyOwnedFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request); } From 324eefc9146d0933ceb819145b2d206dafa38288 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 14 Feb 2018 12:56:34 -0800 Subject: [PATCH 248/569] remove fresnel, add unlit, fix overlays, cleanup --- .../qml/hifi/tablet/NewMaterialDialog.qml | 4 +- interface/src/Application.cpp | 16 +- interface/src/ui/overlays/ModelOverlay.cpp | 14 +- interface/src/ui/overlays/ModelOverlay.h | 4 +- interface/src/ui/overlays/Overlay.cpp | 8 +- interface/src/ui/overlays/Overlay.h | 4 +- .../src/avatars-renderer/Avatar.cpp | 12 +- .../src/avatars-renderer/Avatar.h | 4 +- libraries/avatars/src/AvatarData.h | 4 +- .../src/RenderableEntityItem.cpp | 8 +- .../src/RenderableEntityItem.h | 4 +- .../src/RenderableMaterialEntityItem.cpp | 14 +- .../src/RenderableMaterialEntityItem.h | 6 +- .../src/RenderableModelEntityItem.cpp | 12 +- .../src/RenderableModelEntityItem.h | 4 +- libraries/entities/src/EntityItem.cpp | 12 +- libraries/entities/src/EntityItem.h | 8 +- .../entities/src/EntityItemProperties.cpp | 120 +++++++-------- libraries/entities/src/EntityItemProperties.h | 12 +- libraries/entities/src/EntityPropertyFlags.h | 10 +- libraries/entities/src/EntityTree.cpp | 31 ++-- libraries/entities/src/EntityTree.h | 8 +- libraries/entities/src/MaterialEntityItem.cpp | 145 ++++++++---------- libraries/entities/src/MaterialEntityItem.h | 58 +++---- libraries/graphics/src/graphics/Material.h | 4 + .../src/model-networking/MaterialCache.cpp | 61 ++++++-- .../src/model-networking/MaterialCache.h | 17 +- libraries/octree/src/OctreePacketData.h | 4 +- libraries/render-utils/src/Model.cpp | 24 +-- libraries/render-utils/src/Model.h | 6 +- libraries/shared/src/MaterialMappingMode.cpp | 24 +++ .../{MaterialMode.h => MaterialMappingMode.h} | 12 +- libraries/shared/src/MaterialMode.cpp | 24 --- scripts/developer/tests/toolbarTest.js | 2 +- scripts/system/edit.js | 6 +- scripts/system/html/entityProperties.html | 12 +- scripts/system/html/js/entityProperties.js | 78 +++++----- 37 files changed, 417 insertions(+), 379 deletions(-) create mode 100644 libraries/shared/src/MaterialMappingMode.cpp rename libraries/shared/src/{MaterialMode.h => MaterialMappingMode.h} (54%) delete mode 100644 libraries/shared/src/MaterialMode.cpp diff --git a/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml b/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml index d0bbd79a95..226c17e8e2 100644 --- a/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml +++ b/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml @@ -114,7 +114,7 @@ Rectangle { } ComboBox { - id: materialMode + id: materialMappingMode property var materialArray: ["UV space material", "3D projected material"] @@ -142,7 +142,7 @@ Rectangle { method: "newMaterialDialogAdd", params: { textInput: materialURL.text, - //comboBox: materialMode.currentIndex + //comboBox: materialMappingMode.currentIndex } }); } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 717efa6e5a..70b94443ea 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1593,37 +1593,37 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo } }); - EntityTree::setAddMaterialToAvatarOperator([](const QUuid& avatarID, graphics::MaterialPointer material, const QString& parentMaterialID) { + EntityTree::setAddMaterialToAvatarOperator([](const QUuid& avatarID, graphics::MaterialPointer material, const QString& parentMaterialName) { auto avatarManager = DependencyManager::get(); auto avatar = avatarManager->getAvatarBySessionID(avatarID); if (avatar) { - avatar->addMaterial(material, parentMaterialID); + avatar->addMaterial(material, parentMaterialName); return true; } return false; }); - EntityTree::setRemoveMaterialFromAvatarOperator([](const QUuid& avatarID, graphics::MaterialPointer material, const QString& parentMaterialID) { + EntityTree::setRemoveMaterialFromAvatarOperator([](const QUuid& avatarID, graphics::MaterialPointer material, const QString& parentMaterialName) { auto avatarManager = DependencyManager::get(); auto avatar = avatarManager->getAvatarBySessionID(avatarID); if (avatar) { - avatar->removeMaterial(material, parentMaterialID); + avatar->removeMaterial(material, parentMaterialName); return true; } return false; }); - EntityTree::setAddMaterialToOverlayOperator([&](const QUuid& overlayID, graphics::MaterialPointer material, const QString& parentMaterialID) { + EntityTree::setAddMaterialToOverlayOperator([&](const QUuid& overlayID, graphics::MaterialPointer material, const QString& parentMaterialName) { auto overlay = _overlays.getOverlay(overlayID); if (overlay) { - overlay->addMaterial(material, parentMaterialID); + overlay->addMaterial(material, parentMaterialName); return true; } return false; }); - EntityTree::setRemoveMaterialFromOverlayOperator([&](const QUuid& overlayID, graphics::MaterialPointer material, const QString& parentMaterialID) { + EntityTree::setRemoveMaterialFromOverlayOperator([&](const QUuid& overlayID, graphics::MaterialPointer material, const QString& parentMaterialName) { auto overlay = _overlays.getOverlay(overlayID); if (overlay) { - overlay->removeMaterial(material, parentMaterialID); + overlay->removeMaterial(material, parentMaterialName); return true; } return false; diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index ab7f55d6e7..e4ee889307 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -83,6 +83,7 @@ void ModelOverlay::update(float deltatime) { auto modelOverlay = static_cast(&data); modelOverlay->setSubRenderItemIDs(newRenderItemIDs); }); + processMaterials(); } if (_visibleDirty) { _visibleDirty = false; @@ -108,6 +109,7 @@ void ModelOverlay::update(float deltatime) { bool ModelOverlay::addToScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) { Volume3DOverlay::addToScene(overlay, scene, transaction); _model->addToScene(scene, transaction); + processMaterials(); return true; } @@ -633,17 +635,17 @@ uint32_t ModelOverlay::fetchMetaSubItems(render::ItemIDs& subItems) const { return 0; } -void ModelOverlay::addMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) { - Overlay::addMaterial(material, parentMaterialID); +void ModelOverlay::addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { + Overlay::addMaterial(material, parentMaterialName); if (_model && _model->fetchRenderItemIDs().size() > 0) { - _model->addMaterial(material, parentMaterialID); + _model->addMaterial(material, parentMaterialName); } } -void ModelOverlay::removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) { - Overlay::removeMaterial(material, parentMaterialID); +void ModelOverlay::removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { + Overlay::removeMaterial(material, parentMaterialName); if (_model && _model->fetchRenderItemIDs().size() > 0) { - _model->removeMaterial(material, parentMaterialID); + _model->removeMaterial(material, parentMaterialName); } } diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 6761b1508c..5735854b3b 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -59,8 +59,8 @@ public: void setDrawInFront(bool drawInFront) override; void setDrawHUDLayer(bool drawHUDLayer) override; - void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) override; - void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) override; + void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) override; + void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) override; protected: Transform evalRenderTransform() override; diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index 04d6c2fe4d..766177e3a6 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -236,12 +236,12 @@ QVector qVectorOverlayIDFromScriptValue(const QScriptValue& array) { return newVector; } -void Overlay::addMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) { +void Overlay::addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { std::lock_guard lock(_materialsLock); - _materials[parentMaterialID].push(material); + _materials[parentMaterialName].push(material); } -void Overlay::removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) { +void Overlay::removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { std::lock_guard lock(_materialsLock); - _materials[parentMaterialID].remove(material); + _materials[parentMaterialName].remove(material); } \ No newline at end of file diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index bd9f7942fd..ff45acddcf 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -91,8 +91,8 @@ public: unsigned int getStackOrder() const { return _stackOrder; } void setStackOrder(unsigned int stackOrder) { _stackOrder = stackOrder; } - virtual void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialID); - virtual void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialID); + virtual void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName); + virtual void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName); protected: float updatePulse(); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 3276a8fe89..796e73fcbc 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1763,19 +1763,19 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const { } } -void Avatar::addMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) { +void Avatar::addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { std::lock_guard lock(_materialsLock); - _materials[parentMaterialID].push(material); + _materials[parentMaterialName].push(material); if (_skeletonModel && _skeletonModel->fetchRenderItemIDs().size() > 0) { - _skeletonModel->addMaterial(material, parentMaterialID); + _skeletonModel->addMaterial(material, parentMaterialName); } } -void Avatar::removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) { +void Avatar::removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { std::lock_guard lock(_materialsLock); - _materials[parentMaterialID].remove(material); + _materials[parentMaterialName].remove(material); if (_skeletonModel && _skeletonModel->fetchRenderItemIDs().size() > 0) { - _skeletonModel->removeMaterial(material, parentMaterialID); + _skeletonModel->removeMaterial(material, parentMaterialName); } } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 49aa664a9f..14283a9188 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -272,8 +272,8 @@ public: virtual void setAvatarEntityDataChanged(bool value) override; - void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) override; - void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) override; + void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) override; + void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) override; public slots: diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 832585a483..48f7e19146 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -696,8 +696,8 @@ public: bool getIsReplicated() const { return _isReplicated; } - virtual void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) {} - virtual void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) {} + virtual void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) {} + virtual void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) {} signals: void displayNameChanged(); diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index 81d7dceb0d..9ccf58c04c 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -404,12 +404,12 @@ void EntityRenderer::onRemoveFromScene(const EntityItemPointer& entity) { QObject::disconnect(this, &EntityRenderer::requestRenderUpdate, this, nullptr); } -void EntityRenderer::addMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) { +void EntityRenderer::addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { std::lock_guard lock(_materialsLock); - _materials[parentMaterialID].push(material); + _materials[parentMaterialName].push(material); } -void EntityRenderer::removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) { +void EntityRenderer::removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { std::lock_guard lock(_materialsLock); - _materials[parentMaterialID].remove(material); + _materials[parentMaterialName].remove(material); } \ No newline at end of file diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 6247a97c21..4fc50ccc9a 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -102,8 +102,8 @@ protected: } public slots: - virtual void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialID); - virtual void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialID); + virtual void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName); + virtual void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName); signals: void requestRenderUpdate(); diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp index 529aa03124..70d0234732 100644 --- a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp @@ -18,7 +18,7 @@ bool MaterialEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityP if (entity->getParentID() != _parentID || entity->getClientOnly() != _clientOnly || entity->getOwningAvatarID() != _owningAvatarID) { return true; } - if (entity->getMaterialPos() != _materialPos || entity->getMaterialScale() != _materialScale || entity->getMaterialRot() != _materialRot) { + if (entity->getMaterialMappingPos() != _materialMappingPos || entity->getMaterialMappingScale() != _materialMappingScale || entity->getMaterialMappingRot() != _materialMappingRot) { return true; } return false; @@ -30,9 +30,9 @@ void MaterialEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& _parentID = entity->getParentID(); _clientOnly = entity->getClientOnly(); _owningAvatarID = entity->getOwningAvatarID(); - _materialPos = entity->getMaterialPos(); - _materialScale = entity->getMaterialScale(); - _materialRot = entity->getMaterialRot(); + _materialMappingPos = entity->getMaterialMappingPos(); + _materialMappingScale = entity->getMaterialMappingScale(); + _materialMappingRot = entity->getMaterialMappingRot(); _renderTransform = getModelTransform(); const float MATERIAL_ENTITY_SCALE = 0.5f; _renderTransform.postScale(MATERIAL_ENTITY_SCALE); @@ -238,9 +238,9 @@ void MaterialEntityRenderer::doRender(RenderArgs* args) { parentID = _clientOnly ? _owningAvatarID : _parentID; renderTransform = _renderTransform; drawMaterial = _drawMaterial; - textureTransform.setTranslation(glm::vec3(_materialPos, 0)); - textureTransform.setRotation(glm::vec3(0, 0, glm::radians(_materialRot))); - textureTransform.setScale(glm::vec3(_materialScale, 1)); + textureTransform.setTranslation(glm::vec3(_materialMappingPos, 0)); + textureTransform.setRotation(glm::vec3(0, 0, glm::radians(_materialMappingRot))); + textureTransform.setScale(glm::vec3(_materialMappingScale, 1)); }); if (!parentID.isNull() || !drawMaterial) { return; diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.h b/libraries/entities-renderer/src/RenderableMaterialEntityItem.h index 4af21d84f6..fef1a41138 100644 --- a/libraries/entities-renderer/src/RenderableMaterialEntityItem.h +++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.h @@ -34,9 +34,9 @@ private: QUuid _parentID; bool _clientOnly; QUuid _owningAvatarID; - glm::vec2 _materialPos; - glm::vec2 _materialScale; - float _materialRot; + glm::vec2 _materialMappingPos; + glm::vec2 _materialMappingScale; + float _materialMappingRot; Transform _renderTransform; std::shared_ptr _drawMaterial; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index e28ba797d5..3205d68513 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1467,17 +1467,17 @@ void ModelEntityRenderer::mapJoints(const TypedEntityPointer& entity, const QStr } } -void ModelEntityRenderer::addMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) { - Parent::addMaterial(material, parentMaterialID); +void ModelEntityRenderer::addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { + Parent::addMaterial(material, parentMaterialName); if (_model && _model->fetchRenderItemIDs().size() > 0) { - _model->addMaterial(material, parentMaterialID); + _model->addMaterial(material, parentMaterialName); } } -void ModelEntityRenderer::removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) { - Parent::removeMaterial(material, parentMaterialID); +void ModelEntityRenderer::removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { + Parent::removeMaterial(material, parentMaterialName); if (_model && _model->fetchRenderItemIDs().size() > 0) { - _model->removeMaterial(material, parentMaterialID); + _model->removeMaterial(material, parentMaterialName); } } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 3951deaa2d..9d9b98ba98 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -144,8 +144,8 @@ public: ModelEntityRenderer(const EntityItemPointer& entity); public slots: - void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) override; - void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) override; + void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) override; + void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) override; protected: virtual void removeFromScene(const ScenePointer& scene, Transaction& transaction) override; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 3ea81e5a36..3bb1406252 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -2940,16 +2940,16 @@ void EntityItem::preDelete() { } } -void EntityItem::addMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) { +void EntityItem::addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { std::lock_guard lock(_materialsLock); - _materials[parentMaterialID].push(material); - emit addMaterialToRenderItem(material, parentMaterialID); + _materials[parentMaterialName].push(material); + emit addMaterialToRenderItem(material, parentMaterialName); } -void EntityItem::removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) { +void EntityItem::removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { std::lock_guard lock(_materialsLock); - _materials[parentMaterialID].remove(material); - emit removeMaterialFromRenderItem(material, parentMaterialID); + _materials[parentMaterialName].remove(material); + emit removeMaterialFromRenderItem(material, parentMaterialName); } std::unordered_map EntityItem::getMaterials() { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 97d2f32a04..203b513a76 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -482,14 +482,14 @@ public: virtual void preDelete(); virtual void postParentFixup() {} - void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialID); - void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialID); + void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName); + void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName); std::unordered_map getMaterials(); signals: void requestRenderUpdate(); - void addMaterialToRenderItem(graphics::MaterialPointer material, const QString& parentMaterialID); - void removeMaterialFromRenderItem(graphics::MaterialPointer material, const QString& parentMaterialID); + void addMaterialToRenderItem(graphics::MaterialPointer material, const QString& parentMaterialName); + void removeMaterialFromRenderItem(graphics::MaterialPointer material, const QString& parentMaterialName); protected: QHash _changeHandlers; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 450d0727f9..a2724c4cbf 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -115,15 +115,15 @@ void buildStringToShapeTypeLookup() { addShapeType(SHAPE_TYPE_STATIC_MESH); } -QHash stringToMaterialModeLookup; +QHash stringToMaterialMappingModeLookup; -void addMaterialMode(MaterialMode mode) { - stringToMaterialModeLookup[MaterialModeHelpers::getNameForMaterialMode(mode)] = mode; +void addMaterialMappingMode(MaterialMappingMode mode) { + stringToMaterialMappingModeLookup[MaterialMappingModeHelpers::getNameForMaterialMappingMode(mode)] = mode; } -void buildStringToMaterialModeLookup() { - addMaterialMode(UV); - addMaterialMode(PROJECTED); +void buildStringToMaterialMappingModeLookup() { + addMaterialMappingMode(UV); + addMaterialMappingMode(PROJECTED); } QString getCollisionGroupAsString(uint8_t group) { @@ -270,18 +270,18 @@ void EntityItemProperties::setSkyboxModeFromString(const QString& skyboxMode) { } } -QString EntityItemProperties::getMaterialModeAsString() const { - return MaterialModeHelpers::getNameForMaterialMode(_materialMode); +QString EntityItemProperties::getMaterialMappingModeAsString() const { + return MaterialMappingModeHelpers::getNameForMaterialMappingMode(_materialMappingMode); } -void EntityItemProperties::setMaterialModeFromString(const QString& materialMode) { - if (stringToMaterialModeLookup.empty()) { - buildStringToMaterialModeLookup(); +void EntityItemProperties::setMaterialMappingModeFromString(const QString& materialMappingMode) { + if (stringToMaterialMappingModeLookup.empty()) { + buildStringToMaterialMappingModeLookup(); } - auto materialModeItr = stringToMaterialModeLookup.find(materialMode.toLower()); - if (materialModeItr != stringToMaterialModeLookup.end()) { - _materialMode = materialModeItr.value(); - _materialModeChanged = true; + auto materialMappingModeItr = stringToMaterialMappingModeLookup.find(materialMappingMode.toLower()); + if (materialMappingModeItr != stringToMaterialMappingModeLookup.end()) { + _materialMappingMode = materialMappingModeItr.value(); + _materialMappingModeChanged = true; } } @@ -356,12 +356,12 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_RADIUS_START, radiusStart); CHECK_PROPERTY_CHANGE(PROP_RADIUS_FINISH, radiusFinish); CHECK_PROPERTY_CHANGE(PROP_MATERIAL_URL, materialURL); - CHECK_PROPERTY_CHANGE(PROP_MATERIAL_TYPE, materialMode); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_MODE, materialMappingMode); CHECK_PROPERTY_CHANGE(PROP_MATERIAL_PRIORITY, priority); - CHECK_PROPERTY_CHANGE(PROP_PARENT_MATERIAL_ID, parentMaterialID); - CHECK_PROPERTY_CHANGE(PROP_MATERIAL_POS, materialPos); - CHECK_PROPERTY_CHANGE(PROP_MATERIAL_SCALE, materialScale); - CHECK_PROPERTY_CHANGE(PROP_MATERIAL_ROT, materialRot); + CHECK_PROPERTY_CHANGE(PROP_PARENT_MATERIAL_NAME, parentMaterialName); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_POS, materialMappingPos); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_SCALE, materialMappingScale); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_ROT, materialMappingRot); // Certifiable Properties CHECK_PROPERTY_CHANGE(PROP_ITEM_NAME, itemName); @@ -661,12 +661,12 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool // Materials if (_type == EntityTypes::Material) { COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_URL, materialURL); - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_MATERIAL_TYPE, materialMode, getMaterialModeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_MATERIAL_MAPPING_MODE, materialMappingMode, getMaterialMappingModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_PRIORITY, priority); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_MATERIAL_ID, parentMaterialID); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_POS, materialPos); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_SCALE, materialScale); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_ROT, materialRot); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_MATERIAL_NAME, parentMaterialName); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_MAPPING_POS, materialMappingPos); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_MAPPING_SCALE, materialMappingScale); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_MAPPING_ROT, materialMappingRot); } if (!skipDefaults && !strictSemantics) { @@ -803,12 +803,12 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(radiusFinish, float, setRadiusFinish); COPY_PROPERTY_FROM_QSCRIPTVALUE(relayParentJoints, bool, setRelayParentJoints); COPY_PROPERTY_FROM_QSCRIPTVALUE(materialURL, QString, setMaterialURL); - COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(materialMode, MaterialMode); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(materialMappingMode, MaterialMappingMode); COPY_PROPERTY_FROM_QSCRIPTVALUE(priority, quint16, setPriority); - COPY_PROPERTY_FROM_QSCRIPTVALUE(parentMaterialID, QString, setParentMaterialID); - COPY_PROPERTY_FROM_QSCRIPTVALUE(materialPos, glmVec2, setMaterialPos); - COPY_PROPERTY_FROM_QSCRIPTVALUE(materialScale, glmVec2, setMaterialScale); - COPY_PROPERTY_FROM_QSCRIPTVALUE(materialRot, float, setMaterialRot); + COPY_PROPERTY_FROM_QSCRIPTVALUE(parentMaterialName, QString, setParentMaterialName); + COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingPos, glmVec2, setMaterialMappingPos); + COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingScale, glmVec2, setMaterialMappingScale); + COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingRot, float, setMaterialMappingRot); // Certifiable Properties COPY_PROPERTY_FROM_QSCRIPTVALUE(itemName, QString, setItemName); @@ -1164,12 +1164,12 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_RADIUS_FINISH, RadiusFinish, radiusFinish, float); ADD_PROPERTY_TO_MAP(PROP_MATERIAL_URL, MaterialURL, materialURL, QString); - ADD_PROPERTY_TO_MAP(PROP_MATERIAL_TYPE, MaterialMode, materialMode, MaterialMode); + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_MODE, MaterialMappingMode, materialMappingMode, MaterialMappingMode); ADD_PROPERTY_TO_MAP(PROP_MATERIAL_PRIORITY, Priority, priority, quint16); - ADD_PROPERTY_TO_MAP(PROP_PARENT_MATERIAL_ID, ParentMaterialID, parentMaterialID, QString); - ADD_PROPERTY_TO_MAP(PROP_MATERIAL_POS, MaterialPos, materialPos, glmVec2); - ADD_PROPERTY_TO_MAP(PROP_MATERIAL_SCALE, MaterialScale, materialScale, glmVec2); - ADD_PROPERTY_TO_MAP(PROP_MATERIAL_ROT, MaterialRot, materialRot, float); + ADD_PROPERTY_TO_MAP(PROP_PARENT_MATERIAL_NAME, ParentMaterialName, parentMaterialName, QString); + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_POS, MaterialMappingPos, materialMappingPos, glmVec2); + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_SCALE, MaterialMappingScale, materialMappingScale, glmVec2); + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_ROT, MaterialMappingRot, materialMappingRot, float); // Certifiable Properties ADD_PROPERTY_TO_MAP(PROP_ITEM_NAME, ItemName, itemName, QString); @@ -1557,12 +1557,12 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy // Materials if (properties.getType() == EntityTypes::Material) { APPEND_ENTITY_PROPERTY(PROP_MATERIAL_URL, properties.getMaterialURL()); - APPEND_ENTITY_PROPERTY(PROP_MATERIAL_TYPE, (uint32_t)properties.getMaterialMode()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_MODE, (uint32_t)properties.getMaterialMappingMode()); APPEND_ENTITY_PROPERTY(PROP_MATERIAL_PRIORITY, properties.getPriority()); - APPEND_ENTITY_PROPERTY(PROP_PARENT_MATERIAL_ID, properties.getParentMaterialID()); - APPEND_ENTITY_PROPERTY(PROP_MATERIAL_POS, properties.getMaterialPos()); - APPEND_ENTITY_PROPERTY(PROP_MATERIAL_SCALE, properties.getMaterialScale()); - APPEND_ENTITY_PROPERTY(PROP_MATERIAL_ROT, properties.getMaterialRot()); + APPEND_ENTITY_PROPERTY(PROP_PARENT_MATERIAL_NAME, properties.getParentMaterialName()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_POS, properties.getMaterialMappingPos()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_SCALE, properties.getMaterialMappingScale()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_ROT, properties.getMaterialMappingRot()); } APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName()); @@ -1924,12 +1924,12 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int // Materials if (properties.getType() == EntityTypes::Material) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_URL, QString, setMaterialURL); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_TYPE, MaterialMode, setMaterialMode); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_MODE, MaterialMappingMode, setMaterialMappingMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_PRIORITY, quint16, setPriority); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARENT_MATERIAL_ID, QString, setParentMaterialID); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_POS, glmVec2, setMaterialPos); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_SCALE, glmVec2, setMaterialScale); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_ROT, float, setMaterialRot); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARENT_MATERIAL_NAME, QString, setParentMaterialName); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_POS, glmVec2, setMaterialMappingPos); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_SCALE, glmVec2, setMaterialMappingScale); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_ROT, float, setMaterialMappingRot); } READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName); @@ -2106,12 +2106,12 @@ void EntityItemProperties::markAllChanged() { //_alphaFinishChanged = true; _materialURLChanged = true; - _materialModeChanged = true; + _materialMappingModeChanged = true; _priorityChanged = true; - _parentMaterialIDChanged = true; - _materialPosChanged = true; - _materialScaleChanged = true; - _materialRotChanged = true; + _parentMaterialNameChanged = true; + _materialMappingPosChanged = true; + _materialMappingScaleChanged = true; + _materialMappingRotChanged = true; // Certifiable Properties _itemNameChanged = true; @@ -2439,23 +2439,23 @@ QList EntityItemProperties::listChangedProperties() { if (materialURLChanged()) { out += "materialURL"; } - if (materialModeChanged()) { - out += "materialMode"; + if (materialMappingModeChanged()) { + out += "materialMappingMode"; } if (priorityChanged()) { out += "priority"; } - if (parentMaterialIDChanged()) { - out += "parentMaterialID"; + if (parentMaterialNameChanged()) { + out += "parentMaterialName"; } - if (materialPosChanged()) { - out += "materialPos"; + if (materialMappingPosChanged()) { + out += "materialMappingPos"; } - if (materialScaleChanged()) { - out += "materialScale"; + if (materialMappingScaleChanged()) { + out += "materialMappingScale"; } - if (materialRotChanged()) { - out += "materialRot"; + if (materialMappingRotChanged()) { + out += "materialMappingRot"; } // Certifiable Properties diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index daa2272e36..36ca47291c 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -44,7 +44,7 @@ #include "TextEntityItem.h" #include "ZoneEntityItem.h" -#include "MaterialMode.h" +#include "MaterialMappingMode.h" const quint64 UNKNOWN_CREATED_TIME = 0; @@ -223,12 +223,12 @@ public: DEFINE_PROPERTY_REF(PROP_SHAPE, Shape, shape, QString, "Sphere"); DEFINE_PROPERTY_REF(PROP_MATERIAL_URL, MaterialURL, materialURL, QString, ""); - DEFINE_PROPERTY_REF_ENUM(PROP_MATERIAL_TYPE, MaterialMode, materialMode, MaterialMode, UV); + DEFINE_PROPERTY_REF_ENUM(PROP_MATERIAL_MAPPING_MODE, MaterialMappingMode, materialMappingMode, MaterialMappingMode, UV); DEFINE_PROPERTY_REF(PROP_MATERIAL_PRIORITY, Priority, priority, quint16, 0); - DEFINE_PROPERTY_REF(PROP_PARENT_MATERIAL_ID, ParentMaterialID, parentMaterialID, QString, "0"); - DEFINE_PROPERTY_REF(PROP_MATERIAL_POS, MaterialPos, materialPos, glmVec2, glm::vec2(0, 0)); - DEFINE_PROPERTY_REF(PROP_MATERIAL_SCALE, MaterialScale, materialScale, glmVec2, glm::vec2(1, 1)); - DEFINE_PROPERTY_REF(PROP_MATERIAL_ROT, MaterialRot, materialRot, float, 0); + DEFINE_PROPERTY_REF(PROP_PARENT_MATERIAL_NAME, ParentMaterialName, parentMaterialName, QString, "0"); + DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_POS, MaterialMappingPos, materialMappingPos, glmVec2, glm::vec2(0, 0)); + DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_SCALE, MaterialMappingScale, materialMappingScale, glmVec2, glm::vec2(1, 1)); + DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_ROT, MaterialMappingRot, materialMappingRot, float, 0); // Certifiable Properties - related to Proof of Purchase certificates DEFINE_PROPERTY_REF(PROP_ITEM_NAME, ItemName, itemName, QString, ENTITY_ITEM_DEFAULT_ITEM_NAME); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 6ed2186760..b65d5d1a3f 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -228,12 +228,12 @@ enum EntityPropertyList { PROP_LOCAL_DIMENSIONS, // only used to convert values to and from scripts PROP_MATERIAL_URL, - PROP_MATERIAL_TYPE, + PROP_MATERIAL_MAPPING_MODE, PROP_MATERIAL_PRIORITY, - PROP_PARENT_MATERIAL_ID, - PROP_MATERIAL_POS, - PROP_MATERIAL_SCALE, - PROP_MATERIAL_ROT, + PROP_PARENT_MATERIAL_NAME, + PROP_MATERIAL_MAPPING_POS, + PROP_MATERIAL_MAPPING_SCALE, + PROP_MATERIAL_MAPPING_ROT, //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index d8a4d2799e..8abd5efc13 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1740,18 +1740,15 @@ void EntityTree::fixupNeedsParentFixups() { }); entity->locationChanged(true); entity->postParentFixup(); - } else if (_avatarIDs.contains(entity->getParentID())) { - if (getIsServer()) { - // this is a child of an avatar, which the entity server will never have - // a SpatiallyNestable object for. Add it to a list for cleanup when the avatar leaves. - if (!_childrenOfAvatars.contains(entity->getParentID())) { - _childrenOfAvatars[entity->getParentID()] = QSet(); - } - _childrenOfAvatars[entity->getParentID()] += entity->getEntityItemID(); + } else if (getIsServer() || _avatarIDs.contains(entity->getParentID())) { + // this is a child of an avatar, which the entity server will never have + // a SpatiallyNestable object for. Add it to a list for cleanup when the avatar leaves. + if (!_childrenOfAvatars.contains(entity->getParentID())) { + _childrenOfAvatars[entity->getParentID()] = QSet(); } + _childrenOfAvatars[entity->getParentID()] += entity->getEntityItemID(); doMove = true; iter.remove(); // and pull it out of the list - entity->postParentFixup(); } if (queryAACubeSuccess && doMove) { @@ -2394,30 +2391,30 @@ std::function Ent std::function EntityTree::_addMaterialToOverlayOperator = nullptr; std::function EntityTree::_removeMaterialFromOverlayOperator = nullptr; -bool EntityTree::addMaterialToAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const QString& parentMaterialID) { +bool EntityTree::addMaterialToAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const QString& parentMaterialName) { if (_addMaterialToAvatarOperator) { - return _addMaterialToAvatarOperator(avatarID, material, parentMaterialID); + return _addMaterialToAvatarOperator(avatarID, material, parentMaterialName); } return false; } -bool EntityTree::removeMaterialFromAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const QString& parentMaterialID) { +bool EntityTree::removeMaterialFromAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const QString& parentMaterialName) { if (_removeMaterialFromAvatarOperator) { - return _removeMaterialFromAvatarOperator(avatarID, material, parentMaterialID); + return _removeMaterialFromAvatarOperator(avatarID, material, parentMaterialName); } return false; } -bool EntityTree::addMaterialToOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const QString& parentMaterialID) { +bool EntityTree::addMaterialToOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const QString& parentMaterialName) { if (_addMaterialToOverlayOperator) { - return _addMaterialToOverlayOperator(overlayID, material, parentMaterialID); + return _addMaterialToOverlayOperator(overlayID, material, parentMaterialName); } return false; } -bool EntityTree::removeMaterialFromOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const QString& parentMaterialID) { +bool EntityTree::removeMaterialFromOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const QString& parentMaterialName) { if (_removeMaterialFromOverlayOperator) { - return _removeMaterialFromOverlayOperator(overlayID, material, parentMaterialID); + return _removeMaterialFromOverlayOperator(overlayID, material, parentMaterialName); } return false; } \ No newline at end of file diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index c37b6c8a81..568d552d8d 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -282,13 +282,13 @@ public: static void setAddMaterialToAvatarOperator(std::function addMaterialToAvatarOperator) { _addMaterialToAvatarOperator = addMaterialToAvatarOperator; } static void setRemoveMaterialFromAvatarOperator(std::function removeMaterialFromAvatarOperator) { _removeMaterialFromAvatarOperator = removeMaterialFromAvatarOperator; } - static bool addMaterialToAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const QString& parentMaterialID); - static bool removeMaterialFromAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const QString& parentMaterialID); + static bool addMaterialToAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const QString& parentMaterialName); + static bool removeMaterialFromAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const QString& parentMaterialName); static void setAddMaterialToOverlayOperator(std::function addMaterialToOverlayOperator) { _addMaterialToOverlayOperator = addMaterialToOverlayOperator; } static void setRemoveMaterialFromOverlayOperator(std::function removeMaterialFromOverlayOperator) { _removeMaterialFromOverlayOperator = removeMaterialFromOverlayOperator; } - static bool addMaterialToOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const QString& parentMaterialID); - static bool removeMaterialFromOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const QString& parentMaterialID); + static bool addMaterialToOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const QString& parentMaterialName); + static bool removeMaterialFromOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const QString& parentMaterialName); signals: void deletingEntity(const EntityItemID& entityID); diff --git a/libraries/entities/src/MaterialEntityItem.cpp b/libraries/entities/src/MaterialEntityItem.cpp index fcca2d9803..91333e6e80 100644 --- a/libraries/entities/src/MaterialEntityItem.cpp +++ b/libraries/entities/src/MaterialEntityItem.cpp @@ -30,12 +30,12 @@ MaterialEntityItem::MaterialEntityItem(const EntityItemID& entityItemID) : Entit EntityItemProperties MaterialEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialURL, getMaterialURL); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMode, getMaterialMode); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMappingMode, getMaterialMappingMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(priority, getPriority); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentMaterialID, getParentMaterialID); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialPos, getMaterialPos); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialScale, getMaterialScale); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialRot, getMaterialRot); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentMaterialName, getParentMaterialName); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMappingPos, getMaterialMappingPos); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMappingScale, getMaterialMappingScale); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMappingRot, getMaterialMappingRot); return properties; } @@ -43,12 +43,12 @@ bool MaterialEntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialURL, setMaterialURL); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMode, setMaterialMode); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingMode, setMaterialMappingMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(priority, setPriority); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentMaterialID, setParentMaterialID); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialPos, setMaterialPos); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialScale, setMaterialScale); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialRot, setMaterialRot); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentMaterialName, setParentMaterialName); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingPos, setMaterialMappingPos); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingScale, setMaterialMappingScale); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingRot, setMaterialMappingRot); if (somethingChanged) { bool wantDebug = false; @@ -72,12 +72,12 @@ int MaterialEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* da const unsigned char* dataAt = data; READ_ENTITY_PROPERTY(PROP_MATERIAL_URL, QString, setMaterialURL); - READ_ENTITY_PROPERTY(PROP_MATERIAL_TYPE, MaterialMode, setMaterialMode); + READ_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_MODE, MaterialMappingMode, setMaterialMappingMode); READ_ENTITY_PROPERTY(PROP_MATERIAL_PRIORITY, quint16, setPriority); - READ_ENTITY_PROPERTY(PROP_PARENT_MATERIAL_ID, QString, setParentMaterialID); - READ_ENTITY_PROPERTY(PROP_MATERIAL_POS, glm::vec2, setMaterialPos); - READ_ENTITY_PROPERTY(PROP_MATERIAL_SCALE, glm::vec2, setMaterialScale); - READ_ENTITY_PROPERTY(PROP_MATERIAL_ROT, float, setMaterialRot); + READ_ENTITY_PROPERTY(PROP_PARENT_MATERIAL_NAME, QString, setParentMaterialName); + READ_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_POS, glm::vec2, setMaterialMappingPos); + READ_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_SCALE, glm::vec2, setMaterialMappingScale); + READ_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_ROT, float, setMaterialMappingRot); return bytesRead; } @@ -87,12 +87,12 @@ int MaterialEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* da EntityPropertyFlags MaterialEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_MATERIAL_URL; - requestedProperties += PROP_MATERIAL_TYPE; + requestedProperties += PROP_MATERIAL_MAPPING_MODE; requestedProperties += PROP_MATERIAL_PRIORITY; - requestedProperties += PROP_PARENT_MATERIAL_ID; - requestedProperties += PROP_MATERIAL_POS; - requestedProperties += PROP_MATERIAL_SCALE; - requestedProperties += PROP_MATERIAL_ROT; + requestedProperties += PROP_PARENT_MATERIAL_NAME; + requestedProperties += PROP_MATERIAL_MAPPING_POS; + requestedProperties += PROP_MATERIAL_MAPPING_SCALE; + requestedProperties += PROP_MATERIAL_MAPPING_ROT; return requestedProperties; } @@ -106,26 +106,26 @@ void MaterialEntityItem::appendSubclassData(OctreePacketData* packetData, Encode bool successPropertyFits = true; APPEND_ENTITY_PROPERTY(PROP_MATERIAL_URL, getMaterialURL()); - APPEND_ENTITY_PROPERTY(PROP_MATERIAL_TYPE, (uint32_t)getMaterialMode()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_MODE, (uint32_t)getMaterialMappingMode()); APPEND_ENTITY_PROPERTY(PROP_MATERIAL_PRIORITY, getPriority()); - APPEND_ENTITY_PROPERTY(PROP_PARENT_MATERIAL_ID, getParentMaterialID()); - APPEND_ENTITY_PROPERTY(PROP_MATERIAL_POS, getMaterialPos()); - APPEND_ENTITY_PROPERTY(PROP_MATERIAL_SCALE, getMaterialScale()); - APPEND_ENTITY_PROPERTY(PROP_MATERIAL_ROT, getMaterialRot()); + APPEND_ENTITY_PROPERTY(PROP_PARENT_MATERIAL_NAME, getParentMaterialName()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_POS, getMaterialMappingPos()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_SCALE, getMaterialMappingScale()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_ROT, getMaterialMappingRot()); } void MaterialEntityItem::debugDump() const { quint64 now = usecTimestampNow(); qCDebug(entities) << " MATERIAL EntityItem id:" << getEntityItemID() << "---------------------------------------------"; qCDebug(entities) << " name:" << _name; - qCDebug(entities) << " material json:" << _materialURL; + qCDebug(entities) << " material url:" << _materialURL; qCDebug(entities) << " current material name:" << _currentMaterialName; - qCDebug(entities) << " material type:" << _materialMode; + qCDebug(entities) << " material mapping mode:" << _materialMappingMode; qCDebug(entities) << " priority:" << _priority; - qCDebug(entities) << " parent material ID:" << _parentMaterialID; - qCDebug(entities) << " material pos:" << _materialPos; - qCDebug(entities) << " material scale:" << _materialRot; - qCDebug(entities) << " material rot:" << _materialScale; + qCDebug(entities) << " parent material name:" << _parentMaterialName; + qCDebug(entities) << " material mapping pos:" << _materialMappingPos; + qCDebug(entities) << " material mapping scale:" << _materialMappingRot; + qCDebug(entities) << " material mapping rot:" << _materialMappingScale; qCDebug(entities) << " position:" << debugTreeVector(getWorldPosition()); qCDebug(entities) << " dimensions:" << debugTreeVector(getScaledDimensions()); qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); @@ -137,8 +137,8 @@ void MaterialEntityItem::setUnscaledDimensions(const glm::vec3& value) { } std::shared_ptr MaterialEntityItem::getMaterial() const { - auto material = _materials.find(_currentMaterialName); - if (material != _materials.end()) { + auto material = _parsedMaterials.networkMaterials.find(_currentMaterialName); + if (material != _parsedMaterials.networkMaterials.end()) { return material.value(); } else { return nullptr; @@ -151,28 +151,14 @@ void MaterialEntityItem::setMaterialURL(const QString& materialURLString, bool u removeMaterial(); _materialURL = materialURLString; - // TODO: if URL ends with ?string, try to set _currentMaterialName = string + if (materialURLString.contains("?")) { + auto split = materialURLString.split("?"); + _currentMaterialName = split.last(); + } if (usingUserData) { - QJsonDocument materialJSON = QJsonDocument::fromJson(getUserData().toUtf8()); - _materials.clear(); - _materialNames.clear(); - if (!materialJSON.isNull()) { - if (materialJSON.isArray()) { - QJsonArray materials = materialJSON.array(); - for (auto material : materials) { - if (!material.isNull() && material.isObject()) { - auto networkMaterial = NetworkMaterialResource::parseJSONMaterial(material.toObject()); - _materials[networkMaterial.first] = networkMaterial.second; - _materialNames.push_back(networkMaterial.first); - } - } - } else if (materialJSON.isObject()) { - auto networkMaterial = NetworkMaterialResource::parseJSONMaterial(materialJSON.object()); - _materials[networkMaterial.first] = networkMaterial.second; - _materialNames.push_back(networkMaterial.first); - } - } + _parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromJson(getUserData().toUtf8())); + // Since our material changed, the current name might not be valid anymore, so we need to update setCurrentMaterialName(_currentMaterialName); applyMaterial(); @@ -180,8 +166,7 @@ void MaterialEntityItem::setMaterialURL(const QString& materialURLString, bool u _networkMaterial = MaterialCache::instance().getMaterial(materialURLString); auto onMaterialRequestFinished = [&](bool success) { if (success) { - _materials[_networkMaterial->name] = _networkMaterial->networkMaterial; - _materialNames.push_back(_networkMaterial->name); + _parsedMaterials = _networkMaterial->parsedMaterials; setCurrentMaterialName(_currentMaterialName); applyMaterial(); @@ -199,11 +184,11 @@ void MaterialEntityItem::setMaterialURL(const QString& materialURLString, bool u } void MaterialEntityItem::setCurrentMaterialName(const QString& currentMaterialName) { - auto material = _materials.find(currentMaterialName); - if (material != _materials.end()) { + auto material = _parsedMaterials.networkMaterials.find(currentMaterialName); + if (material != _parsedMaterials.networkMaterials.end()) { _currentMaterialName = currentMaterialName; - } else if (_materialNames.size() > 0) { - _currentMaterialName = _materialNames[0]; + } else if (_parsedMaterials.names.size() > 0) { + _currentMaterialName = _parsedMaterials.names[0]; } } @@ -217,26 +202,26 @@ void MaterialEntityItem::setUserData(const QString& userData) { } } -void MaterialEntityItem::setMaterialPos(const glm::vec2& materialPos) { - if (_materialPos != materialPos) { +void MaterialEntityItem::setMaterialMappingPos(const glm::vec2& materialMappingPos) { + if (_materialMappingPos != materialMappingPos) { removeMaterial(); - _materialPos = materialPos; + _materialMappingPos = materialMappingPos; applyMaterial(); } } -void MaterialEntityItem::setMaterialScale(const glm::vec2& materialScale) { - if (_materialScale != materialScale) { +void MaterialEntityItem::setMaterialMappingScale(const glm::vec2& materialMappingScale) { + if (_materialMappingScale != materialMappingScale) { removeMaterial(); - _materialScale = materialScale; + _materialMappingScale = materialMappingScale; applyMaterial(); } } -void MaterialEntityItem::setMaterialRot(const float& materialRot) { - if (_materialRot != materialRot) { +void MaterialEntityItem::setMaterialMappingRot(const float& materialMappingRot) { + if (_materialMappingRot != materialMappingRot) { removeMaterial(); - _materialRot = materialRot; + _materialMappingRot = materialMappingRot; applyMaterial(); } } @@ -249,10 +234,10 @@ void MaterialEntityItem::setPriority(quint16 priority) { } } -void MaterialEntityItem::setParentMaterialID(const QString& parentMaterialID) { - if (_parentMaterialID != parentMaterialID) { +void MaterialEntityItem::setParentMaterialName(const QString& parentMaterialName) { + if (_parentMaterialName != parentMaterialName) { removeMaterial(); - _parentMaterialID = parentMaterialID; + _parentMaterialName = parentMaterialName; applyMaterial(); } } @@ -293,16 +278,16 @@ void MaterialEntityItem::removeMaterial() { if (tree) { EntityItemPointer entity = tree->findEntityByEntityItemID(parentID); if (entity) { - entity->removeMaterial(material, getParentMaterialID()); + entity->removeMaterial(material, getParentMaterialName()); return; } } - if (EntityTree::removeMaterialFromAvatar(parentID, material, getParentMaterialID())) { + if (EntityTree::removeMaterialFromAvatar(parentID, material, getParentMaterialName())) { return; } - if (EntityTree::removeMaterialFromOverlay(parentID, material, getParentMaterialID())) { + if (EntityTree::removeMaterialFromOverlay(parentID, material, getParentMaterialName())) { return; } @@ -317,9 +302,9 @@ void MaterialEntityItem::applyMaterial() { return; } Transform textureTransform; - textureTransform.setTranslation(glm::vec3(_materialPos, 0)); - textureTransform.setRotation(glm::vec3(0, 0, glm::radians(_materialRot))); - textureTransform.setScale(glm::vec3(_materialScale, 1)); + textureTransform.setTranslation(glm::vec3(_materialMappingPos, 0)); + textureTransform.setRotation(glm::vec3(0, 0, glm::radians(_materialMappingRot))); + textureTransform.setScale(glm::vec3(_materialMappingScale, 1)); material->setTextureTransforms(textureTransform); material->setPriority(getPriority()); @@ -328,16 +313,16 @@ void MaterialEntityItem::applyMaterial() { if (tree) { EntityItemPointer entity = tree->findEntityByEntityItemID(parentID); if (entity) { - entity->addMaterial(material, getParentMaterialID()); + entity->addMaterial(material, getParentMaterialName()); return; } } - if (EntityTree::addMaterialToAvatar(parentID, material, getParentMaterialID())) { + if (EntityTree::addMaterialToAvatar(parentID, material, getParentMaterialName())) { return; } - if (EntityTree::addMaterialToOverlay(parentID, material, getParentMaterialID())) { + if (EntityTree::addMaterialToOverlay(parentID, material, getParentMaterialName())) { return; } diff --git a/libraries/entities/src/MaterialEntityItem.h b/libraries/entities/src/MaterialEntityItem.h index d015bae681..3c8df190bf 100644 --- a/libraries/entities/src/MaterialEntityItem.h +++ b/libraries/entities/src/MaterialEntityItem.h @@ -11,7 +11,7 @@ #include "EntityItem.h" -#include "MaterialMode.h" +#include "MaterialMappingMode.h" #include #include @@ -58,21 +58,21 @@ public: QString getCurrentMaterialName() const { return _currentMaterialName; } void setCurrentMaterialName(const QString& currentMaterialName); - MaterialMode getMaterialMode() const { return _materialMode; } - void setMaterialMode(MaterialMode mode) { _materialMode = mode; } + MaterialMappingMode getMaterialMappingMode() const { return _materialMappingMode; } + void setMaterialMappingMode(MaterialMappingMode mode) { _materialMappingMode = mode; } quint16 getPriority() const { return _priority; } void setPriority(quint16 priority); - QString getParentMaterialID() const { return _parentMaterialID; } - void setParentMaterialID(const QString& parentMaterialID); + QString getParentMaterialName() const { return _parentMaterialName; } + void setParentMaterialName(const QString& parentMaterialName); - glm::vec2 getMaterialPos() const { return _materialPos; } - void setMaterialPos(const glm::vec2& materialPos); - glm::vec2 getMaterialScale() const { return _materialScale; } - void setMaterialScale(const glm::vec2& materialScale); - float getMaterialRot() const { return _materialRot; } - void setMaterialRot(const float& materialRot); + glm::vec2 getMaterialMappingPos() const { return _materialMappingPos; } + void setMaterialMappingPos(const glm::vec2& materialMappingPos); + glm::vec2 getMaterialMappingScale() const { return _materialMappingScale; } + void setMaterialMappingScale(const glm::vec2& materialMappingScale); + float getMaterialMappingRot() const { return _materialMappingRot; } + void setMaterialMappingRot(const float& materialMappingRot); std::shared_ptr getMaterial() const; @@ -91,33 +91,37 @@ public: private: // URL for this material. Currently, only JSON format is supported. Set to "userData" to use the user data to live edit a material. // The following fields are supported in the JSON: - // strings: - // name (NOT YET USED) - // floats: - // opacity, roughness, metallic, scattering - // colors (arrays of 3 floats 0-1. Optional fourth value in array can be a boolean isSRGB): - // emissive, albedo, fresnel - // urls to textures: - // emissiveMap, albedoMap, metallicMap, roughnessMap, normalMap, occlusionMap, lightmapMap, scatteringMap + // materialVersion: a uint for the version of this network material (currently, only 1 is supported) + // materials, which is either an object or an array of objects, each with the following properties: + // strings: + // name (NOT YET USED), model (NOT YET USED, should use "hifi_pbr") + // floats: + // opacity, roughness, metallic, scattering + // bool: + // unlit + // colors (arrays of 3 floats 0-1. Optional fourth value in array can be a boolean isSRGB): + // emissive, albedo + // urls to textures: + // emissiveMap, albedoMap (set opacityMap = albedoMap for transparency), metallicMap or specularMap, roughnessMap or glossMap, + // normalMap or bumpMap, occlusionMap, lightmapMap (broken, FIXME), scatteringMap (only works if normal mapped) QString _materialURL; // Type of material. "uv" or "projected". NOT YET IMPLEMENTED, only UV is used - MaterialMode _materialMode { UV }; + MaterialMappingMode _materialMappingMode { UV }; // Priority for this material when applying it to its parent. Only the highest priority material will be used. Materials with the same priority are (essentially) randomly sorted. // Base materials that come with models always have priority 0. quint16 _priority { 0 }; // An identifier for choosing a submesh or submeshes within a parent. If in the format "mat::", all submeshes with material name "" will be replaced. Otherwise, - // parentMaterialID will be parsed as an unsigned int (strings not starting with "mat::" will parse to 0), representing the mesh index to modify. - QString _parentMaterialID { "0" }; + // parentMaterialName will be parsed as an unsigned int (strings not starting with "mat::" will parse to 0), representing the mesh index to modify. + QString _parentMaterialName { "0" }; // Offset position in UV-space of top left of material, (0, 0) to (1, 1) - glm::vec2 _materialPos { 0, 0 }; + glm::vec2 _materialMappingPos { 0, 0 }; // How much to scale this material within its parent's UV-space - glm::vec2 _materialScale { 1, 1 }; + glm::vec2 _materialMappingScale { 1, 1 }; // How much to rotate this material within its parent's UV-space (degrees) - float _materialRot { 0 }; + float _materialMappingRot { 0 }; NetworkMaterialResourcePointer _networkMaterial; - QHash> _materials; - std::vector _materialNames; + NetworkMaterialResource::ParsedMaterials _parsedMaterials; QString _currentMaterialName; bool _retryApply { false }; diff --git a/libraries/graphics/src/graphics/Material.h b/libraries/graphics/src/graphics/Material.h index 5abc090be3..4b1e7c82c1 100755 --- a/libraries/graphics/src/graphics/Material.h +++ b/libraries/graphics/src/graphics/Material.h @@ -361,6 +361,8 @@ public: const QString& getName() { return _name; } + void setModel(const QString& model) { _model = model; } + protected: QString _name { "" }; @@ -379,6 +381,8 @@ private: quint16 _priority { 0 }; + QString _model { "hifi_pbr" }; + }; typedef std::shared_ptr< Material > MaterialPointer; diff --git a/libraries/model-networking/src/model-networking/MaterialCache.cpp b/libraries/model-networking/src/model-networking/MaterialCache.cpp index 723448f65a..ef1d071d86 100644 --- a/libraries/model-networking/src/model-networking/MaterialCache.cpp +++ b/libraries/model-networking/src/model-networking/MaterialCache.cpp @@ -15,13 +15,10 @@ NetworkMaterialResource::NetworkMaterialResource(const QUrl& url) : Resource(url) {} void NetworkMaterialResource::downloadFinished(const QByteArray& data) { - if (_url.toString().endsWith(".json")) { - QJsonDocument materialJSON = QJsonDocument::fromJson(data); - if (!materialJSON.isNull() && materialJSON.isObject()) { - auto parsedMaterial = parseJSONMaterial(materialJSON.object()); - name = parsedMaterial.first; - networkMaterial = parsedMaterial.second; - } + parsedMaterials.reset(); + + if (_url.toString().contains(".json")) { + parsedMaterials = parseJSONMaterials(QJsonDocument::fromJson(data)); } // TODO: parse other material types @@ -46,6 +43,39 @@ bool NetworkMaterialResource::parseJSONColor(const QJsonValue& array, glm::vec3& return false; } +NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMaterials(const QJsonDocument& materialJSON) { + ParsedMaterials toReturn; + if (!materialJSON.isNull() && materialJSON.isObject()) { + QJsonObject materialJSONObject = materialJSON.object(); + for (auto& key : materialJSONObject.keys()) { + if (key == "materialVersion") { + auto value = materialJSONObject.value(key); + if (value.isDouble()) { + toReturn.version = (uint)value.toInt(); + } + } else if (key == "materials") { + auto materialsValue = materialJSONObject.value(key); + if (materialsValue.isArray()) { + QJsonArray materials = materialsValue.toArray(); + for (auto material : materials) { + if (!material.isNull() && material.isObject()) { + auto parsedMaterial = parseJSONMaterial(material.toObject()); + toReturn.networkMaterials[parsedMaterial.first] = parsedMaterial.second; + toReturn.names.push_back(parsedMaterial.first); + } + } + } else if (materialsValue.isObject()) { + auto parsedMaterial = parseJSONMaterial(materialsValue.toObject()); + toReturn.networkMaterials[parsedMaterial.first] = parsedMaterial.second; + toReturn.names.push_back(parsedMaterial.first); + } + } + } + } + + return toReturn;; +} + std::pair> NetworkMaterialResource::parseJSONMaterial(const QJsonObject& materialJSON) { QString name = ""; std::shared_ptr material = std::make_shared(); @@ -55,6 +85,11 @@ std::pair> NetworkMaterialResource::pa if (nameJSON.isString()) { name = nameJSON.toString(); } + } else if (key == "model") { + auto modelJSON = materialJSON.value(key); + if (modelJSON.isString()) { + material->setModel(modelJSON.toString()); + } } else if (key == "emissive") { glm::vec3 color; bool isSRGB; @@ -67,6 +102,11 @@ std::pair> NetworkMaterialResource::pa if (value.isDouble()) { material->setOpacity(value.toDouble()); } + } else if (key == "unlit") { + auto value = materialJSON.value(key); + if (value.isBool()) { + material->setUnlit(value.toBool()); + } } else if (key == "albedo") { glm::vec3 color; bool isSRGB; @@ -79,13 +119,6 @@ std::pair> NetworkMaterialResource::pa if (value.isDouble()) { material->setRoughness(value.toDouble()); } - } else if (key == "fresnel") { - glm::vec3 color; - bool isSRGB; - bool valid = parseJSONColor(materialJSON.value(key), color, isSRGB); - if (valid) { - material->setFresnel(color, isSRGB); - } } else if (key == "metallic") { auto value = materialJSON.value(key); if (value.isDouble()) { diff --git a/libraries/model-networking/src/model-networking/MaterialCache.h b/libraries/model-networking/src/model-networking/MaterialCache.h index 1b0f1c577c..3188fd0094 100644 --- a/libraries/model-networking/src/model-networking/MaterialCache.h +++ b/libraries/model-networking/src/model-networking/MaterialCache.h @@ -22,9 +22,22 @@ public: virtual void downloadFinished(const QByteArray& data) override; - QString name { "" }; - std::shared_ptr networkMaterial { nullptr }; + typedef struct ParsedMaterials { + uint version { 1 }; + std::vector names; + QHash> networkMaterials; + void reset() { + version = 1; + names.clear(); + networkMaterials.clear(); + } + + } ParsedMaterials; + + ParsedMaterials parsedMaterials; + + static ParsedMaterials parseJSONMaterials(const QJsonDocument& materialJSON); static std::pair> parseJSONMaterial(const QJsonObject& materialJSON); private: diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index 85100ec177..8d8f599fea 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -33,7 +33,7 @@ #include #include -#include "MaterialMode.h" +#include "MaterialMappingMode.h" #include "OctreeConstants.h" #include "OctreeElement.h" @@ -263,7 +263,7 @@ public: static int unpackDataFromBytes(const unsigned char* dataBytes, rgbColor& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, glm::quat& result) { int bytes = unpackOrientationQuatFromBytes(dataBytes, result); return bytes; } static int unpackDataFromBytes(const unsigned char* dataBytes, ShapeType& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } - static int unpackDataFromBytes(const unsigned char* dataBytes, MaterialMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } + static int unpackDataFromBytes(const unsigned char* dataBytes, MaterialMappingMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, QString& result); static int unpackDataFromBytes(const unsigned char* dataBytes, QUuid& result); static int unpackDataFromBytes(const unsigned char* dataBytes, xColor& result); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 84687a46d0..15cab065f3 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1527,29 +1527,29 @@ bool Model::isRenderable() const { return !_meshStates.empty() || (isLoaded() && _renderGeometry->getMeshes().empty()); } -std::vector Model::getMeshIDsFromMaterialID(QString parentMaterialID) { - // try to find all meshes with materials that match parentMaterialID as a string - // if none, return parentMaterialID as a uint +std::vector Model::getMeshIDsFromMaterialID(QString parentMaterialName) { + // try to find all meshes with materials that match parentMaterialName as a string + // if none, return parentMaterialName as a uint std::vector toReturn; const QString MATERIAL_NAME_PREFIX = "mat::"; - if (parentMaterialID.startsWith(MATERIAL_NAME_PREFIX)) { - parentMaterialID.replace(0, MATERIAL_NAME_PREFIX.size(), QString("")); - for (int i = 0; i < _modelMeshMaterialNames.size(); i++) { - if (_modelMeshMaterialNames[i] == parentMaterialID) { + if (parentMaterialName.startsWith(MATERIAL_NAME_PREFIX)) { + parentMaterialName.replace(0, MATERIAL_NAME_PREFIX.size(), QString("")); + for (unsigned int i = 0; i < (unsigned int)_modelMeshMaterialNames.size(); i++) { + if (_modelMeshMaterialNames[i] == parentMaterialName) { toReturn.push_back(i); } } } if (toReturn.empty()) { - toReturn.push_back(parentMaterialID.toUInt()); + toReturn.push_back(parentMaterialName.toUInt()); } return toReturn; } -void Model::addMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) { - std::vector shapeIDs = getMeshIDsFromMaterialID(parentMaterialID); +void Model::addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { + std::vector shapeIDs = getMeshIDsFromMaterialID(parentMaterialName); render::Transaction transaction; for (auto shapeID : shapeIDs) { if (shapeID < _modelMeshRenderItemIDs.size()) { @@ -1573,8 +1573,8 @@ void Model::addMaterial(graphics::MaterialPointer material, const QString& paren AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction); } -void Model::removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialID) { - std::vector shapeIDs = getMeshIDsFromMaterialID(parentMaterialID); +void Model::removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { + std::vector shapeIDs = getMeshIDsFromMaterialID(parentMaterialName); render::Transaction transaction; for (auto shapeID : shapeIDs) { if (shapeID < _modelMeshRenderItemIDs.size()) { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 67bc646473..6b59b62046 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -318,8 +318,8 @@ public: void scaleToFit(); - void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialID); - void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialID); + void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName); + void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName); public slots: void loadURLFinished(bool success); @@ -473,7 +473,7 @@ private: void calculateTextureInfo(); - std::vector getMeshIDsFromMaterialID(QString parentMaterialID); + std::vector getMeshIDsFromMaterialID(QString parentMaterialName); }; Q_DECLARE_METATYPE(ModelPointer) diff --git a/libraries/shared/src/MaterialMappingMode.cpp b/libraries/shared/src/MaterialMappingMode.cpp new file mode 100644 index 0000000000..1ddad178a2 --- /dev/null +++ b/libraries/shared/src/MaterialMappingMode.cpp @@ -0,0 +1,24 @@ +// +// Created by Sam Gondelman on 1/17/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "MaterialMappingMode.h" + +const char* materialMappingModeNames[] = { + "uv", + "projected" +}; + +static const size_t MATERIAL_MODE_NAMES = (sizeof(materialMappingModeNames) / sizeof((materialMappingModeNames)[0])); + +QString MaterialMappingModeHelpers::getNameForMaterialMappingMode(MaterialMappingMode mode) { + if (((int)mode <= 0) || ((int)mode >= (int)MATERIAL_MODE_NAMES)) { + mode = (MaterialMappingMode)0; + } + + return materialMappingModeNames[(int)mode]; +} \ No newline at end of file diff --git a/libraries/shared/src/MaterialMode.h b/libraries/shared/src/MaterialMappingMode.h similarity index 54% rename from libraries/shared/src/MaterialMode.h rename to libraries/shared/src/MaterialMappingMode.h index a933693f75..848c2aa28c 100644 --- a/libraries/shared/src/MaterialMode.h +++ b/libraries/shared/src/MaterialMappingMode.h @@ -6,20 +6,20 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef hifi_MaterialMode_h -#define hifi_MaterialMode_h +#ifndef hifi_MaterialMappingMode_h +#define hifi_MaterialMappingMode_h #include "QString" -enum MaterialMode { +enum MaterialMappingMode { UV = 0, PROJECTED }; -class MaterialModeHelpers { +class MaterialMappingModeHelpers { public: - static QString getNameForMaterialMode(MaterialMode mode); + static QString getNameForMaterialMappingMode(MaterialMappingMode mode); }; -#endif // hifi_MaterialMode_h +#endif // hifi_MaterialMappingMode_h diff --git a/libraries/shared/src/MaterialMode.cpp b/libraries/shared/src/MaterialMode.cpp deleted file mode 100644 index cb656a7bf7..0000000000 --- a/libraries/shared/src/MaterialMode.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// -// Created by Sam Gondelman on 1/17/2018 -// Copyright 2018 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "MaterialMode.h" - -const char* materialModeNames[] = { - "uv", - "projected" -}; - -static const size_t MATERIAL_MODE_NAMES = (sizeof(materialModeNames) / sizeof((materialModeNames)[0])); - -QString MaterialModeHelpers::getNameForMaterialMode(MaterialMode mode) { - if (((int)mode <= 0) || ((int)mode >= (int)MATERIAL_MODE_NAMES)) { - mode = (MaterialMode)0; - } - - return materialModeNames[(int)mode]; -} \ No newline at end of file diff --git a/scripts/developer/tests/toolbarTest.js b/scripts/developer/tests/toolbarTest.js index dfc97ebe39..b713445927 100644 --- a/scripts/developer/tests/toolbarTest.js +++ b/scripts/developer/tests/toolbarTest.js @@ -92,7 +92,7 @@ var toolBar = (function() { newMaterialButton = toolBar.addButton({ objectName: "newMaterialButton", - imageURL: toolIconUrl + "model-01.svg", + imageURL: toolIconUrl + "material-01.svg", alpha: 0.9, visible: false }); diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 15d4759a8a..0167b55810 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -400,10 +400,10 @@ var toolBar = (function () { function handleNewMaterialDialogResult(result) { if (result) { var materialURL = result.textInput; - //var materialMode; + //var materialMappingMode; //switch (result.comboBox) { // case MATERIAL_MODE_PROJECTED: - // materialMode = "projected"; + // materialMappingMode = "projected"; // break; // default: // shapeType = "uv"; @@ -414,7 +414,7 @@ var toolBar = (function () { createNewEntity({ type: "Material", materialURL: materialURL, - //materialMode: materialMode, + //materialMappingMode: materialMappingMode, priority: DEFAULT_LAYERED_MATERIAL_PRIORITY }); } diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 2ca0107d19..c53a2fa5bd 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -805,7 +805,7 @@ -

    +
    From 12f4b8dbb19321c74c7b967b569627b87b8c23aa Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Wed, 14 Feb 2018 19:12:24 -0800 Subject: [PATCH 252/569] Corrected event listener for canCastShadow - still bad. --- scripts/system/html/js/entityProperties.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 9502e9f4d4..8868159848 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1392,12 +1392,8 @@ function loaded() { elShape.addEventListener('change', createEmitTextPropertyUpdateFunction('shape')); - if (properties.type === "Model" || - properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { - - elCanCastShadow.addEventListener('change', createEmitTextPropertyUpdateFunction('canCastShadow')); - } - + elCanCastShadow.addEventListener('change', createEmitCheckedPropertyUpdateFunction('canCastShadow')); + elImageURL.addEventListener('change', createImageURLUpdateFunction('textures')); elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl')); From 26e7a85a955db374b526f8a117585ff6c47ccbd2 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Wed, 14 Feb 2018 21:15:29 -0800 Subject: [PATCH 253/569] Fixed possible crash. --- libraries/render-utils/src/RenderShadowTask.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index e34f550def..24a14a697c 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -386,6 +386,10 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon assert(lightStage); // Exit if current keylight does not cast shadows + if (!lightStage->getCurrentKeyLight()) { + return; + } + bool castShadows = lightStage->getCurrentKeyLight()->getCastShadows(); if (!castShadows) { output.edit0() = ItemFilter::Builder::nothing(); From adb02d69f9c50721d9e186870ae8ca6cf201a413 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Wed, 14 Feb 2018 21:45:56 -0800 Subject: [PATCH 254/569] WIP -adding canCastShadow flag. --- libraries/entities/src/EntityItemProperties.cpp | 5 +++++ libraries/entities/src/EntityItemProperties.h | 3 ++- .../entities/src/EntityItemPropertiesDefaults.h | 3 ++- libraries/entities/src/ModelEntityItem.h | 2 +- libraries/entities/src/ShapeEntityItem.h | 2 +- scripts/system/html/js/entityProperties.js | 16 ++++++++-------- 6 files changed, 19 insertions(+), 12 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 79c36180d6..3c3c0742da 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -878,6 +878,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(angularVelocity); COPY_PROPERTY_IF_CHANGED(angularDamping); COPY_PROPERTY_IF_CHANGED(visible); + COPY_PROPERTY_IF_CHANGED(canCastShadow); COPY_PROPERTY_IF_CHANGED(color); COPY_PROPERTY_IF_CHANGED(colorSpread); COPY_PROPERTY_IF_CHANGED(colorStart); @@ -1050,6 +1051,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue std::call_once(initMap, [](){ ADD_PROPERTY_TO_MAP(PROP_VISIBLE, Visible, visible, bool); + ADD_PROPERTY_TO_MAP(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool); ADD_PROPERTY_TO_MAP(PROP_POSITION, Position, position, glm::vec3); ADD_PROPERTY_TO_MAP(PROP_DIMENSIONS, Dimensions, dimensions, glm::vec3); ADD_PROPERTY_TO_MAP(PROP_ROTATION, Rotation, rotation, glm::quat); @@ -2172,6 +2174,9 @@ QList EntityItemProperties::listChangedProperties() { if (visibleChanged()) { out += "visible"; } + if (canCastShadowChanged()) { + out += "canCastShadow"; + } if (rotationChanged()) { out += "rotation"; } diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index ec10910092..dcec1a1f81 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -128,7 +128,7 @@ public: DEFINE_PROPERTY_REF(PROP_SCRIPT, Script, script, QString, ENTITY_ITEM_DEFAULT_SCRIPT); DEFINE_PROPERTY(PROP_SCRIPT_TIMESTAMP, ScriptTimestamp, scriptTimestamp, quint64, ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP); DEFINE_PROPERTY_REF(PROP_COLLISION_SOUND_URL, CollisionSoundURL, collisionSoundURL, QString, ENTITY_ITEM_DEFAULT_COLLISION_SOUND_URL); - DEFINE_PROPERTY(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool, ENTITY_ITEM_DEFAULT_CAST_SHADOW); + DEFINE_PROPERTY(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool, ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW); DEFINE_PROPERTY_REF(PROP_COLOR, Color, color, xColor, particle::DEFAULT_COLOR); DEFINE_PROPERTY_REF(PROP_COLOR_SPREAD, ColorSpread, colorSpread, xColor, particle::DEFAULT_COLOR_SPREAD); DEFINE_PROPERTY_REF(PROP_COLOR_START, ColorStart, colorStart, xColor, particle::DEFAULT_COLOR); @@ -415,6 +415,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, Velocity, velocity, "in meters"); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Name, name, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Visible, visible, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, CanCastShadow, canCastShadow, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Rotation, rotation, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Density, density, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Gravity, gravity, ""); diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index f85d55dc3a..efbf45ce8d 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -46,7 +46,8 @@ const quint32 ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION = 0; const float ENTITY_ITEM_DEFAULT_ALPHA = 1.0f; const float ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA = 1.0f; const bool ENTITY_ITEM_DEFAULT_VISIBLE = true; -const bool ENTITY_ITEM_DEFAULT_CAST_SHADOW { true }; +const bool ENTITY_ITEM_DEFAULT_CAST_SHADOWS { true }; +const bool ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW { false }; const QString ENTITY_ITEM_DEFAULT_SCRIPT = QString(""); const quint64 ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP = 0; diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 49f20c48e2..791eebb7d9 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -174,7 +174,7 @@ protected: ShapeType _shapeType = SHAPE_TYPE_NONE; - bool _canCastShadow{ ENTITY_ITEM_DEFAULT_CAST_SHADOW }; + bool _canCastShadow{ ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW }; private: uint64_t _lastAnimated{ 0 }; diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index 4a08936bce..4bc008f761 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -115,7 +115,7 @@ protected: //! ellipsoids. ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_ELLIPSOID }; - bool _canCastShadow { ENTITY_ITEM_DEFAULT_CAST_SHADOW }; + bool _canCastShadow { ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW }; }; #endif // hifi_ShapeEntityItem_h diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 8868159848..fca43c4665 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -802,11 +802,11 @@ function loaded() { // HTML workaround since image is not yet a separate entity type var IMAGE_MODEL_NAME = 'default-image-model.fbx'; - var urlParts = properties.modelURL.split('/') - var propsFilename = urlParts[urlParts.length - 1]; - if (properties.type === "Model" && propsFilename === IMAGE_MODEL_NAME) { - properties.type = "Image"; - } +//// var urlParts = properties.modelURL.split('/') +//// var propsFilename = urlParts[urlParts.length - 1]; +//// if (properties.type === "Model" && propsFilename === IMAGE_MODEL_NAME) { +//// properties.type = "Image"; +//// } // Create class name for css ruleset filtering elPropertiesList.className = properties.type + 'Menu'; @@ -983,11 +983,11 @@ function loaded() { properties.color.green + "," + properties.color.blue + ")"; } - if (properties.type === "Model" || - properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { + //if (properties.type === "Model" || + // properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { elCanCastShadow = properties.canCastShadow; - } + //} if (properties.type === "Model") { elModelURL.value = properties.modelURL; From 7e99570824418cc1a57b72cad44ddd8c386a1bff Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Thu, 15 Feb 2018 07:54:55 -0800 Subject: [PATCH 255/569] Fixed Ubuntu warnings. --- libraries/entities/src/ModelEntityItem.cpp | 1 - libraries/graphics/src/graphics/Light.cpp | 2 +- libraries/graphics/src/graphics/Light.h | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 7c14d8a4a0..b1edd47a67 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -619,7 +619,6 @@ void ModelEntityItem::setColor(const rgbColor& value) { }); } -#pragma optimize("", off) void ModelEntityItem::setColor(const xColor& value) { withWriteLock([&] { _color[RED_INDEX] = value.red; diff --git a/libraries/graphics/src/graphics/Light.cpp b/libraries/graphics/src/graphics/Light.cpp index 50601299dd..76d8a6030a 100755 --- a/libraries/graphics/src/graphics/Light.cpp +++ b/libraries/graphics/src/graphics/Light.cpp @@ -69,7 +69,7 @@ void Light::setCastShadows(const bool castShadows) { _castShadows = castShadows; } -const bool Light::getCastShadows() const { +bool Light::getCastShadows() const { return _castShadows; } diff --git a/libraries/graphics/src/graphics/Light.h b/libraries/graphics/src/graphics/Light.h index ebe22d5593..bb9fb3e5b9 100755 --- a/libraries/graphics/src/graphics/Light.h +++ b/libraries/graphics/src/graphics/Light.h @@ -104,7 +104,7 @@ public: const Vec3& getDirection() const; void setCastShadows(const bool castShadows); - const bool getCastShadows() const; + bool getCastShadows() const; void setOrientation(const Quat& orientation); const glm::quat& getOrientation() const { return _transform.getRotation(); } From cb9327e03020b7cd6965c73cf7db2177eb46e7d9 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 10 Jan 2018 13:09:22 -0800 Subject: [PATCH 256/569] Add entity file sync and domain content backups --- .clang-format | 10 +- assignment-client/CMakeLists.txt | 1 + assignment-client/src/Agent.cpp | 1 - .../src/entities/EntityServer.cpp | 2 - assignment-client/src/entities/EntityServer.h | 13 +- assignment-client/src/octree/OctreeServer.cpp | 201 ++++++------ assignment-client/src/octree/OctreeServer.h | 21 +- .../src/scripts/EntityScriptServer.cpp | 2 +- domain-server/CMakeLists.txt | 12 +- .../src/DomainContentBackupManager.cpp | 303 ++++++++++++++++++ .../src/DomainContentBackupManager.h | 88 +++++ domain-server/src/DomainServer.cpp | 213 +++++++++++- domain-server/src/DomainServer.h | 28 +- interface/src/Application.cpp | 8 +- interface/src/ui/DomainConnectionModel.cpp | 2 +- libraries/entities/src/EntityTree.cpp | 10 + libraries/image/CMakeLists.txt | 3 +- libraries/networking/src/LimitedNodeList.cpp | 1 + libraries/networking/src/ThreadedAssignment.h | 6 +- libraries/networking/src/udt/PacketHeaders.h | 7 + libraries/networking/src/udt/Socket.cpp | 4 +- libraries/octree/CMakeLists.txt | 1 + libraries/octree/src/Octree.cpp | 54 +++- libraries/octree/src/Octree.h | 14 +- libraries/octree/src/OctreePersistThread.cpp | 102 +++--- libraries/octree/src/OctreePersistThread.h | 11 +- libraries/octree/src/OctreeUtils.cpp | 77 +++++ libraries/octree/src/OctreeUtils.h | 23 ++ libraries/shared/src/SharedUtil.cpp | 6 +- libraries/shared/src/SharedUtil.h | 1 + 30 files changed, 1026 insertions(+), 199 deletions(-) create mode 100644 domain-server/src/DomainContentBackupManager.cpp create mode 100644 domain-server/src/DomainContentBackupManager.h diff --git a/.clang-format b/.clang-format index f000a27017..507b1eb232 100644 --- a/.clang-format +++ b/.clang-format @@ -1,12 +1,12 @@ Language: Cpp Standard: Cpp11 -BasedOnStyle: "Chromium" +BasedOnStyle: "Chromium" ColumnLimit: 128 IndentWidth: 4 UseTab: Never BreakBeforeBraces: Custom -BraceWrapping: +BraceWrapping: AfterEnum: true AfterClass: false AfterControlStatement: false @@ -21,11 +21,11 @@ BraceWrapping: AccessModifierOffset: -4 -AllowShortFunctionsOnASingleLine: InlineOnly -BreakConstructorInitializers: BeforeColon +AllowShortFunctionsOnASingleLine: InlineOnly +BreakConstructorInitializers: BeforeColon BreakConstructorInitializersBeforeComma: true IndentCaseLabels: true -ReflowComments: false +ReflowComments: false Cpp11BracedListStyle: false ContinuationIndentWidth: 4 ConstructorInitializerAllOnOneLineOrOnePerLine: false diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index c73e8e1d34..3de4c5fd3f 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -6,6 +6,7 @@ setup_hifi_project(Core Gui Network Script Quick WebSockets) if (APPLE) set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks") endif () +set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "/testing/") setup_memory_debugger() diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index a42b78a6fa..10b8d44545 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -340,7 +340,6 @@ void Agent::scriptRequestFinished() { request->deleteLater(); } - void Agent::executeScript() { _scriptEngine = scriptEngineFactory(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload); diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index f72832f902..e394884dc2 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -116,7 +116,6 @@ void EntityServer::beforeRun() { void EntityServer::entityCreated(const EntityItem& newEntity, const SharedNodePointer& senderNode) { } - // EntityServer will use the "special packets" to send list of recently deleted entities bool EntityServer::hasSpecialPacketsToSend(const SharedNodePointer& node) { bool shouldSendDeletedEntities = false; @@ -277,7 +276,6 @@ int EntityServer::sendSpecialPackets(const SharedNodePointer& node, OctreeQueryN return totalBytes; } - void EntityServer::pruneDeletedEntities() { EntityTreePointer tree = std::static_pointer_cast(_tree); if (tree->hasAnyDeletedEntities()) { diff --git a/assignment-client/src/entities/EntityServer.h b/assignment-client/src/entities/EntityServer.h index 05404b28c8..4d3f1ee89f 100644 --- a/assignment-client/src/entities/EntityServer.h +++ b/assignment-client/src/entities/EntityServer.h @@ -30,7 +30,6 @@ struct ViewerSendingStats { class SimpleEntitySimulation; using SimpleEntitySimulationPointer = std::shared_ptr; - class EntityServer : public OctreeServer, public NewlyCreatedEntityHook { Q_OBJECT public: @@ -38,7 +37,7 @@ public: ~EntityServer(); // Subclasses must implement these methods - virtual std::unique_ptr createOctreeQueryNode() override ; + virtual std::unique_ptr createOctreeQueryNode() override; virtual char getMyNodeType() const override { return NodeType::EntityServer; } virtual PacketType getMyQueryMessageType() const override { return PacketType::EntityQuery; } virtual const char* getMyServerName() const override { return MODEL_SERVER_NAME; } @@ -82,12 +81,12 @@ private: QReadWriteLock _viewerSendingStatsLock; QMap> _viewerSendingStats; - static const int DEFAULT_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = 45 * 60 * 1000; // 45m - static const int DEFAULT_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = 60 * 60 * 1000; // 1h - int _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = DEFAULT_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS; // 45m - int _MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = DEFAULT_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS; // 1h + static const int DEFAULT_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = 45 * 60 * 1000; // 45m + static const int DEFAULT_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = 60 * 60 * 1000; // 1h + int _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = DEFAULT_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS; // 45m + int _MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = DEFAULT_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS; // 1h QTimer _dynamicDomainVerificationTimer; void startDynamicDomainVerification(); }; -#endif // hifi_EntityServer_h +#endif // hifi_EntityServer_h diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 42494ea7ee..e78f9f108b 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -33,6 +33,10 @@ #include #include +#include + +Q_LOGGING_CATEGORY(octree_server, "hifi.octree-server") + int OctreeServer::_clientCount = 0; const int MOVING_AVERAGE_SAMPLE_COUNTS = 1000; @@ -84,6 +88,8 @@ int OctreeServer::_longProcessWait = 0; int OctreeServer::_shortProcessWait = 0; int OctreeServer::_noProcessWait = 0; +static const QString PERSIST_FILE_DOWNLOAD_PATH = "/models.json.gz"; + void OctreeServer::resetSendingStats() { _averageLoopTime.reset(); @@ -202,7 +208,6 @@ void OctreeServer::trackPacketSendingTime(float time) { } } - void OctreeServer::trackProcessWaitTime(float time) { const float MAX_SHORT_TIME = 10.0f; const float MAX_LONG_TIME = 100.0f; @@ -283,8 +288,6 @@ void OctreeServer::initHTTPManager(int port) { _httpManager = new HTTPManager(QHostAddress::AnyIPv4, port, documentRoot, this, this); } -const QString PERSIST_FILE_DOWNLOAD_PATH = "/models.json.gz"; - bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) { #ifdef FORCE_CRASH @@ -922,87 +925,6 @@ void OctreeServer::handleOctreeDataNackPacket(QSharedPointer me } } -void OctreeServer::handleOctreeFileReplacement(QSharedPointer message) { - if (!_isFinished && !_isShuttingDown) { - // these messages are only allowed to come from the domain server, so make sure that is the case - auto nodeList = DependencyManager::get(); - if (message->getSenderSockAddr() == nodeList->getDomainHandler().getSockAddr()) { - // it's far cleaner to load up the new content upon server startup - // so here we just store a special file at our persist path - // and then force a stop of the server so that it can pick it up when it relaunches - if (!_persistAbsoluteFilePath.isEmpty()) { - replaceContentFromMessageData(message->getMessage()); - } else { - qDebug() << "Cannot perform octree file replacement since current persist file path is not yet known"; - } - } else { - qDebug() << "Received an octree file replacement that was not from our domain server - refusing to process"; - } - } -} - -// Message->getMessage() contains a QByteArray representation of the URL to download from -void OctreeServer::handleOctreeFileReplacementFromURL(QSharedPointer message) { - qInfo() << "Received request to replace content from a url"; - if (!_isFinished && !_isShuttingDown) { - // This call comes from Interface, so we skip our domain server check - // but confirm that we have permissions to replace content sets - if (DependencyManager::get()->getThisNodeCanReplaceContent()) { - if (!_persistAbsoluteFilePath.isEmpty()) { - // Convert message data into our URL - QString url(message->getMessage()); - QUrl modelsURL = QUrl(url, QUrl::StrictMode); - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest request(modelsURL); - QNetworkReply* reply = networkAccessManager.get(request); - connect(reply, &QNetworkReply::finished, [this, reply, modelsURL]() { - QNetworkReply::NetworkError networkError = reply->error(); - if (networkError == QNetworkReply::NoError) { - QByteArray contents = reply->readAll(); - replaceContentFromMessageData(contents); - } else { - qDebug() << "Error downloading JSON from specified file"; - } - }); - } else { - qDebug() << "Cannot perform octree file replacement since current persist file path is not yet known"; - } - } - } -} - -void OctreeServer::replaceContentFromMessageData(QByteArray content) { - //Assume we have compressed data - auto compressedOctree = content; - QByteArray jsonOctree; - - bool wasCompressed = gunzip(compressedOctree, jsonOctree); - if (!wasCompressed) { - // the source was not compressed, assume we were sent regular JSON data - jsonOctree = compressedOctree; - } - // check the JSON data to verify it is an object - if (QJsonDocument::fromJson(jsonOctree).isObject()) { - if (!wasCompressed) { - // source was not compressed, we compress it before we write it locally - gzip(jsonOctree, compressedOctree); - } - // write the compressed octree data to a special file - auto replacementFilePath = _persistAbsoluteFilePath.append(OctreePersistThread::REPLACEMENT_FILE_EXTENSION); - QFile replacementFile(replacementFilePath); - if (replacementFile.open(QIODevice::WriteOnly) && replacementFile.write(compressedOctree) != -1) { - // we've now written our replacement file, time to take the server down so it can - // process it when it comes back up - qInfo() << "Wrote octree replacement file to" << replacementFilePath << "- stopping server"; - setFinished(true); - } else { - qWarning() << "Could not write replacement octree data to file - refusing to process"; - } - } else { - qDebug() << "Received replacement octree file that is invalid - refusing to process"; - } -} - bool OctreeServer::readOptionBool(const QString& optionName, const QJsonObject& settingsSectionObject, bool& result) { result = false; // assume it doesn't exist bool optionAvailable = false; @@ -1119,7 +1041,19 @@ void OctreeServer::readConfiguration() { _persistFilePath = getMyDefaultPersistFilename(); } + // If persist filename does not exist, let's see if there is one beside the application binary + // If there is, let's copy it over to our target persist directory + QDir persistPath { _persistFilePath }; + _persistAbsoluteFilePath = persistPath.absolutePath(); + + if (persistPath.isRelative()) { + // if the domain settings passed us a relative path, make an absolute path that is relative to the + // default data directory + _persistAbsoluteFilePath = QDir(PathUtils::getAppDataFilePath("entities/")).absoluteFilePath(_persistFilePath); + } + qDebug() << "persistFilePath=" << _persistFilePath; + qDebug() << "persisAbsoluteFilePath=" << _persistAbsoluteFilePath; _persistAsFileType = "json.gz"; @@ -1200,20 +1134,90 @@ void OctreeServer::run() { } void OctreeServer::domainSettingsRequestComplete() { + if (_state != OctreeServerState::WaitingForDomainSettings) { + qCWarning(octree_server) << "Received domain settings after they have already been received"; + return; + } + + auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); + packetReceiver.registerListener(getMyQueryMessageType(), this, "handleOctreeQueryPacket"); + packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket"); + + packetReceiver.registerListener(PacketType::OctreeDataFileReply, this, "handleOctreeDataFileReply"); + + qDebug(octree_server) << "Received domain settings"; + + readConfiguration(); + + _state = OctreeServerState::WaitingForOctreeDataNegotation; + + auto nodeList = DependencyManager::get(); + const DomainHandler& domainHandler = nodeList->getDomainHandler(); + + auto packet = NLPacket::create(PacketType::OctreeDataFileRequest, -1, true, false); + + OctreeUtils::RawOctreeData data; + qCDebug(octree_server) << "Reading octree data from" << _persistAbsoluteFilePath; + if (OctreeUtils::readOctreeDataInfoFromFile(_persistAbsoluteFilePath, &data)) { + qCDebug(octree_server) << "Current octree data: ID(" << data.id << ") DataVersion(" << data.version << ")"; + packet->writePrimitive(true); + auto id = data.id.toRfc4122(); + packet->write(id); + packet->writePrimitive(data.version); + } else { + qCWarning(octree_server) << "No octree data found"; + packet->writePrimitive(false); + } + + qCDebug(octree_server) << "Sending request for octree data to DS"; + nodeList->sendPacket(std::move(packet), domainHandler.getSockAddr()); +} + +void OctreeServer::handleOctreeDataFileReply(QSharedPointer message) { + bool includesNewData; + message->readPrimitive(&includesNewData); + QByteArray replaceData; + if (includesNewData) { + replaceData = message->readAll(); + qDebug() << "Got reply to octree data file request, new data sent"; + } else { + qDebug() << "Got reply to octree data file request, current entity data is sufficient"; + + OctreeUtils::RawOctreeData data; + qCDebug(octree_server) << "Reading octree data from" << _persistAbsoluteFilePath; + if (OctreeUtils::readOctreeDataInfoFromFile(_persistAbsoluteFilePath, &data)) { + if (data.id.isNull()) { + qCDebug(octree_server) << "Current octree data has a null id, updating"; + data.id = QUuid::createUuid(); + data.version = 0; + + QFile file(_persistAbsoluteFilePath); + if (file.open(QIODevice::WriteOnly)) { + auto entityData = data.toByteArray(); + file.write(entityData); + file.close(); + } else { + qCDebug(octree_server) << "Failed to update octree data"; + } + } + } + } + beginRunning(replaceData); +} + +void OctreeServer::beginRunning(QByteArray replaceData) { + if (_state == OctreeServerState::Running) { + qCWarning(octree_server) << "Server is already running"; + return; + } + + _state = OctreeServerState::Running; auto nodeList = DependencyManager::get(); // we need to ask the DS about agents so we can ping/reply with them nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer }); - auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerListener(getMyQueryMessageType(), this, "handleOctreeQueryPacket"); - packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket"); - packetReceiver.registerListener(PacketType::OctreeFileReplacement, this, "handleOctreeFileReplacement"); - packetReceiver.registerListener(PacketType::OctreeFileReplacementFromUrl, this, "handleOctreeFileReplacementFromURL"); - - readConfiguration(); - beforeRun(); // after payload has been processed connect(nodeList.data(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(nodeAdded(SharedNodePointer))); @@ -1233,17 +1237,6 @@ void OctreeServer::domainSettingsRequestComplete() { // if we want Persistence, set up the local file and persist thread if (_wantPersist) { - // If persist filename does not exist, let's see if there is one beside the application binary - // If there is, let's copy it over to our target persist directory - QDir persistPath { _persistFilePath }; - _persistAbsoluteFilePath = persistPath.absolutePath(); - - if (persistPath.isRelative()) { - // if the domain settings passed us a relative path, make an absolute path that is relative to the - // default data directory - _persistAbsoluteFilePath = QDir(PathUtils::getAppDataFilePath("entities/")).absoluteFilePath(_persistFilePath); - } - static const QString ENTITY_PERSIST_EXTENSION = ".json.gz"; // force the persist file to end with .json.gz @@ -1328,7 +1321,7 @@ void OctreeServer::domainSettingsRequestComplete() { // now set up PersistThread _persistThread = new OctreePersistThread(_tree, _persistAbsoluteFilePath, _backupDirectoryPath, _persistInterval, - _wantBackup, _settings, _debugTimestampNow, _persistAsFileType); + _wantBackup, _settings, _debugTimestampNow, _persistAsFileType, replaceData); _persistThread->initialize(true); } diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index 0eba914064..6f77920ee0 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -27,8 +27,18 @@ #include "OctreeServerConsts.h" #include "OctreeInboundPacketProcessor.h" +#include + +Q_DECLARE_LOGGING_CATEGORY(octree_server) + const int DEFAULT_PACKETS_PER_INTERVAL = 2000; // some 120,000 packets per second total +enum class OctreeServerState { + WaitingForDomainSettings, + WaitingForOctreeDataNegotation, + Running +}; + /// Handles assignments of type OctreeServer - sending octrees to various clients. class OctreeServer : public ThreadedAssignment, public HTTPRequestHandler { Q_OBJECT @@ -36,6 +46,8 @@ public: OctreeServer(ReceivedMessage& message); ~OctreeServer(); + OctreeServerState _state { OctreeServerState::WaitingForDomainSettings }; + /// allows setting of run arguments void setArguments(int argc, char** argv); @@ -137,8 +149,9 @@ private slots: void domainSettingsRequestComplete(); void handleOctreeQueryPacket(QSharedPointer message, SharedNodePointer senderNode); void handleOctreeDataNackPacket(QSharedPointer message, SharedNodePointer senderNode); - void handleOctreeFileReplacement(QSharedPointer message); - void handleOctreeFileReplacementFromURL(QSharedPointer message); + //void handleOctreeFileReplacement(QSharedPointer message); + //void handleOctreeFileReplacementFromURL(QSharedPointer message); + void handleOctreeDataFileReply(QSharedPointer message); void removeSendThread(); protected: @@ -159,11 +172,13 @@ protected: QString getFileLoadTime(); QString getConfiguration(); QString getStatusLink(); + + void beginRunning(QByteArray replaceData); UniqueSendThread createSendThread(const SharedNodePointer& node); virtual UniqueSendThread newSendThread(const SharedNodePointer& node); - void replaceContentFromMessageData(QByteArray content); + //void replaceContentFromMessageData(QByteArray content); int _argc; const char** _argv; diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index b4a6b3af93..60cb1e349b 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -178,7 +178,7 @@ void EntityScriptServer::updateEntityPPS() { int numRunningScripts = _entitiesScriptEngine->getNumRunningEntityScripts(); int pps; if (std::numeric_limits::max() / _entityPPSPerScript < numRunningScripts) { - qWarning() << QString("Integer multiplaction would overflow, clamping to maxint: %1 * %2").arg(numRunningScripts).arg(_entityPPSPerScript); + qWarning() << QString("Integer multiplication would overflow, clamping to maxint: %1 * %2").arg(numRunningScripts).arg(_entityPPSPerScript); pps = std::numeric_limits::max(); pps = std::min(_maxEntityPPS, pps); } else { diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index c1e275e4d3..0e958b9537 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -22,7 +22,17 @@ setup_memory_debugger() symlink_or_copy_directory_beside_target(${_SHOULD_SYMLINK_RESOURCES} "${CMAKE_CURRENT_SOURCE_DIR}/resources" "resources") # link the shared hifi libraries -link_hifi_libraries(embedded-webserver networking shared avatars) +link_hifi_libraries(embedded-webserver networking shared avatars octree) + +add_dependency_external_projects(quazip) + +find_package(QuaZip REQUIRED) +target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${QUAZIP_INCLUDE_DIRS}) +target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES}) + +if (WIN32) + add_paths_to_fixup_libs(${QUAZIP_DLL_PATH}) +endif () # find OpenSSL find_package(OpenSSL REQUIRED) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp new file mode 100644 index 0000000000..0eca10f8af --- /dev/null +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -0,0 +1,303 @@ +// +// DomainContentBackupManager.cpp +// libraries/octree/src +// +// Created by Brad Hefta-Gaub on 8/21/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "DomainServer.h" +#include "DomainContentBackupManager.h" +const int DomainContentBackupManager::DEFAULT_PERSIST_INTERVAL = 1000 * 30; // every 30 seconds + +// Backup format looks like: daily_backup-TIMESTAMP.zip +const static QString DATETIME_FORMAT { "yyyy-MM-dd_HH-mm-ss" }; +const static QString DATETIME_FORMAT_RE("\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}"); + +void DomainContentBackupManager::addCreateBackupHandler(CreateBackupHandler handler) { + _backupHandlers.push_back(handler); +} + +DomainContentBackupManager::DomainContentBackupManager(const QString& backupDirectory, + const QJsonObject& settings, + int persistInterval, + bool debugTimestampNow) + : _backupDirectory(backupDirectory), + _persistInterval(persistInterval), + _initialLoadComplete(false), + _loadTimeUSecs(0), + _lastCheck(0), + _debugTimestampNow(debugTimestampNow), + _lastTimeDebug(0) { + parseSettings(settings); +} + +void DomainContentBackupManager::parseSettings(const QJsonObject& settings) { + qDebug() << settings << settings["backups"] << settings["backups"].isArray(); + if (settings["backups"].isArray()) { + const QJsonArray& backupRules = settings["backups"].toArray(); + qCDebug(domain_server) << "BACKUP RULES:"; + + for (const QJsonValue& value : backupRules) { + QJsonObject obj = value.toObject(); + + int interval = 0; + int count = 0; + + QJsonValue intervalVal = obj["backupInterval"]; + if (intervalVal.isString()) { + interval = intervalVal.toString().toInt(); + } else { + interval = intervalVal.toInt(); + } + + QJsonValue countVal = obj["maxBackupVersions"]; + if (countVal.isString()) { + count = countVal.toString().toInt(); + } else { + count = countVal.toInt(); + } + + auto name = obj["Name"].toString(); + auto format = obj["format"].toString(); + format = name.replace(" ", "_").toLower() + "-"; + + qCDebug(domain_server) << " Name:" << name; + qCDebug(domain_server) << " format:" << format; + qCDebug(domain_server) << " interval:" << interval; + qCDebug(domain_server) << " count:" << count; + + BackupRule newRule = { name, interval, format, count, 0 }; + + newRule.lastBackupSeconds = getMostRecentBackupTimeInSecs(format); + + if (newRule.lastBackupSeconds > 0) { + auto now = QDateTime::currentSecsSinceEpoch(); + auto sinceLastBackup = now - newRule.lastBackupSeconds; + qCDebug(domain_server).noquote() << " lastBackup:" << formatSecTime(sinceLastBackup) << "ago"; + } else { + qCDebug(domain_server) << " lastBackup: NEVER"; + } + + _backupRules << newRule; + } + } else { + qCDebug(domain_server) << "BACKUP RULES: NONE"; + } +} + +int64_t DomainContentBackupManager::getMostRecentBackupTimeInSecs(const QString& format) { + int64_t mostRecentBackupInSecs = 0; + + QString mostRecentBackupFileName; + QDateTime mostRecentBackupTime; + + bool recentBackup = getMostRecentBackup(format, mostRecentBackupFileName, mostRecentBackupTime); + + if (recentBackup) { + mostRecentBackupInSecs = mostRecentBackupTime.toSecsSinceEpoch(); + } + + return mostRecentBackupInSecs; +} + +bool DomainContentBackupManager::process() { + if (isStillRunning()) { + constexpr int64_t MSECS_TO_USECS = 1000; + constexpr int64_t USECS_TO_SLEEP = 10 * MSECS_TO_USECS; // every 10ms + std::this_thread::sleep_for(std::chrono::microseconds(USECS_TO_SLEEP)); + + int64_t now = usecTimestampNow(); + int64_t sinceLastSave = now - _lastCheck; + int64_t intervalToCheck = _persistInterval * MSECS_TO_USECS; + + if (sinceLastSave > intervalToCheck) { + _lastCheck = now; + persist(); + } + } + + // if we were asked to debugTimestampNow do that now... + if (_debugTimestampNow) { + + quint64 now = usecTimestampNow(); + quint64 sinceLastDebug = now - _lastTimeDebug; + quint64 DEBUG_TIMESTAMP_INTERVAL = 600000000; // every 10 minutes + + if (sinceLastDebug > DEBUG_TIMESTAMP_INTERVAL) { + _lastTimeDebug = usecTimestampNow(true); // ask for debug output + } + } + + return isStillRunning(); +} + +void DomainContentBackupManager::aboutToFinish() { + qCDebug(domain_server) << "Persist thread about to finish..."; + persist(); +} + +void DomainContentBackupManager::persist() { + QDir backupDir { _backupDirectory }; + backupDir.mkpath("."); + + // create our "lock" file to indicate we're saving. + QString lockFileName = _backupDirectory + "/running.lock"; + + std::ofstream lockFile(qPrintable(lockFileName), std::ios::out | std::ios::binary); + if (lockFile.is_open()) { + backup(); + + lockFile.close(); + remove(qPrintable(lockFileName)); + } +} + +bool DomainContentBackupManager::getMostRecentBackup(const QString& format, + QString& mostRecentBackupFileName, + QDateTime& mostRecentBackupTime) { + QRegExp formatRE { QRegExp::escape(format) + "(" + DATETIME_FORMAT_RE + ")" + "\\.zip" }; + + QStringList filters; + filters << format + "*.zip"; + + bool bestBackupFound = false; + QString bestBackupFile; + QDateTime bestBackupFileTime; + + // Iterate over all of the backup files in the persist location + QDirIterator dirIterator(_backupDirectory, filters, QDir::Files | QDir::NoSymLinks, QDirIterator::NoIteratorFlags); + while (dirIterator.hasNext()) { + dirIterator.next(); + auto fileName = dirIterator.fileInfo().fileName(); + + if (formatRE.exactMatch(fileName)) { + auto datetime = formatRE.cap(1); + auto createdAt = QDateTime::fromString(datetime, DATETIME_FORMAT); + + if (!createdAt.isValid()) { + qDebug() << "Skipping backup with invalid timestamp: " << datetime; + continue; + } + + qDebug() << "Checking " << dirIterator.fileInfo().filePath(); + + // Based on last modified date, track the most recently modified file as the best backup + if (createdAt > bestBackupFileTime) { + bestBackupFound = true; + bestBackupFile = dirIterator.filePath(); + bestBackupFileTime = createdAt; + } + } else { + qDebug() << "NO match: " << fileName << formatRE; + } + } + + // If we found a backup then return the results + if (bestBackupFound) { + mostRecentBackupFileName = bestBackupFile; + mostRecentBackupTime = bestBackupFileTime; + } + return bestBackupFound; +} + +void DomainContentBackupManager::removeOldBackupVersions(const BackupRule& rule) { + QDir backupDir { _backupDirectory }; + if (backupDir.exists() && rule.maxBackupVersions > 0) { + qCDebug(domain_server) << "Rolling old backup versions for rule" << rule.name << "..."; + + auto matchingFiles = + backupDir.entryInfoList({ rule.extensionFormat + "*.zip" }, QDir::Files | QDir::NoSymLinks, QDir::Name); + + int backupsToDelete = matchingFiles.length() - rule.maxBackupVersions; + for (int i = 0; i < backupsToDelete; ++i) { + auto fileInfo = matchingFiles[i].absoluteFilePath(); + QFile backupFile(fileInfo); + if (backupFile.remove()) { + qCDebug(domain_server) << "Removed old backup: " << backupFile.fileName(); + } else { + qCDebug(domain_server) << "Failed to remove old backup: " << backupFile.fileName(); + } + } + + qCDebug(domain_server) << "Done rolling old backup versions..."; + } else { + qCDebug(domain_server) << "Rolling backups for rule" << rule.name << "." + << " Max Rolled Backup Versions less than 1 [" << rule.maxBackupVersions << "]." + << " No need to roll backups..."; + } +} + +void DomainContentBackupManager::backup() { + auto nowDateTime = QDateTime::currentDateTime(); + auto nowSeconds = nowDateTime.toSecsSinceEpoch(); + + for (BackupRule& rule : _backupRules) { + auto secondsSinceLastBackup = nowSeconds - rule.lastBackupSeconds; + + qCDebug(domain_server) << "Checking [" << rule.name << "] - Time since last backup [" << secondsSinceLastBackup + << "] " + << "compared to backup interval [" << rule.intervalSeconds << "]..."; + + if (secondsSinceLastBackup > rule.intervalSeconds) { + qCDebug(domain_server) << "Time since last backup [" << secondsSinceLastBackup << "] for rule [" << rule.name + << "] exceeds backup interval [" << rule.intervalSeconds << "] doing backup now..."; + + auto timestamp = QDateTime::currentDateTime().toString(DATETIME_FORMAT); + auto fileName = "backup-" + rule.extensionFormat + timestamp + ".zip"; + auto zip = new QuaZip(_backupDirectory + "/" + fileName); + zip->open(QuaZip::mdAdd); + + for (auto& handler : _backupHandlers) { + handler(zip); + } + + zip->close(); + + qDebug() << "Created backup: " << fileName; + + removeOldBackupVersions(rule); + + if (rule.maxBackupVersions > 0) { + // Execute backup + auto result = true; + if (result) { + qCDebug(domain_server) << "DONE backing up persist file..."; + rule.lastBackupSeconds = nowSeconds; + } else { + qCDebug(domain_server) << "ERROR in backing up persist file..."; + perror("ERROR in backing up persist file"); + } + } else { + qCDebug(domain_server) << "This backup rule" << rule.name << " has Max Rolled Backup Versions less than 1 [" + << rule.maxBackupVersions << "]." + << " There are no backups to be done..."; + } + } else { + qCDebug(domain_server) << "Backup not needed for this rule [" << rule.name << "]..."; + } + } +} diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h new file mode 100644 index 0000000000..20408fe486 --- /dev/null +++ b/domain-server/src/DomainContentBackupManager.h @@ -0,0 +1,88 @@ +// +// DomainContentBackupManager.h +// libraries/octree/src +// +// Created by Brad Hefta-Gaub on 8/21/13. +// Copyright 2013 High Fidelity, Inc. +// +// +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_DomainContentBackupManager_h +#define hifi_DomainContentBackupManager_h + +#include +#include +#include + +#include +#include + +#include + +using BackupResult = std::vector; +using CreateBackupHandler = std::function; +using RecoverBackupHandler = std::function; + +class DomainContentBackupManager : public GenericThread { + Q_OBJECT +public: + class BackupRule { + public: + QString name; + int intervalSeconds; + QString extensionFormat; + int maxBackupVersions; + qint64 lastBackupSeconds; + }; + + static const int DEFAULT_PERSIST_INTERVAL; + + DomainContentBackupManager(const QString& rootBackupDirectory, + const QJsonObject& settings, + int persistInterval = DEFAULT_PERSIST_INTERVAL, + bool debugTimestampNow = false); + + void addCreateBackupHandler(CreateBackupHandler handler); + bool isInitialLoadComplete() const { return _initialLoadComplete; } + int64_t getLoadElapsedTime() const { return _loadTimeUSecs; } + + void aboutToFinish(); /// call this to inform the persist thread that the owner is about to finish to support final persist + + void replaceData(QByteArray data); + +signals: + void loadCompleted(); + +protected: + /// Implements generic processing behavior for this thread. + bool process() override; + + void persist(); + void backup(); + void removeOldBackupVersions(const BackupRule& rule); + bool getMostRecentBackup(const QString& format, QString& mostRecentBackupFileName, QDateTime& mostRecentBackupTime); + int64_t getMostRecentBackupTimeInSecs(const QString& format); + void parseSettings(const QJsonObject& settings); + +private: + QString _backupDirectory; + std::vector _backupHandlers; + int _persistInterval; + bool _initialLoadComplete; + + int64_t _loadTimeUSecs; + + time_t _lastPersistTime; + int64_t _lastCheck; + bool _wantBackup{ true }; + QVector _backupRules; + + bool _debugTimestampNow; + int64_t _lastTimeDebug; +}; + +#endif // hifi_DomainContentBackupManager_h diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 68a36195d9..e083710d35 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -47,7 +48,14 @@ #include "DomainServerNodeData.h" #include "NodeConnectionData.h" +#include + +#include + +Q_LOGGING_CATEGORY(domain_server, "hifi.domain_server") + const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token"; +const QString DomainServer::REPLACEMENT_FILE_EXTENSION = ".replace"; int const DomainServer::EXIT_CODE_REBOOT = 234923; @@ -280,6 +288,30 @@ DomainServer::DomainServer(int argc, char* argv[]) : qDebug() << "Ignoring subnet in whitelist, invalid ip portion: " << subnet; } } + + qDebug() << "Starting persist thread"; + if (QDir(getEntitiesDirPath()).mkpath(".")) { + qCDebug(domain_server) << "Created entities data directory"; + } + maybeHandleReplacementEntityFile(); + auto entitiesFilePath = getEntitiesFilePath(); + _contentManager.reset(new DomainContentBackupManager(getContentBackupDir(), _settingsManager.responseObjectForType("6")["entity_server_settings"].toObject())); + _contentManager->addCreateBackupHandler([entitiesFilePath](QuaZip* zip) { + qDebug() << "Creating a backup from handler"; + + QFile entitiesFile { entitiesFilePath }; + + if (entitiesFile.open(QIODevice::ReadOnly)) { + QuaZipFile zipFile { zip }; + zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo("models.json.gz", entitiesFilePath)); + zipFile.write(entitiesFile.readAll()); + zipFile.close(); + if (zipFile.getZipError() != UNZ_OK) { + qDebug() << "Failed to write entities file to backup:" << zipFile.getZipError(); + } + } + }); + _contentManager->initialize(true); } void DomainServer::parseCommandLine() { @@ -352,6 +384,11 @@ DomainServer::~DomainServer() { // destroy the LimitedNodeList before the DomainServer QCoreApplication is down DependencyManager::destroy(); + + if (_contentManager) { + _contentManager->aboutToFinish(); + _contentManager->terminating(); + } } void DomainServer::queuedQuit(QString quitMessage, int exitCode) { @@ -691,6 +728,12 @@ void DomainServer::setupNodeListAndAssignments() { packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied, this, "processICEServerHeartbeatDenialPacket"); packetReceiver.registerListener(PacketType::ICEServerHeartbeatACK, this, "processICEServerHeartbeatACK"); + packetReceiver.registerListener(PacketType::OctreeDataFileRequest, this, "processOctreeDataRequestMessage"); + packetReceiver.registerListener(PacketType::OctreeDataPersist, this, "processOctreeDataPersistMessage"); + + packetReceiver.registerListener(PacketType::OctreeFileReplacement, this, "handleOctreeFileReplacementRequest"); + packetReceiver.registerListener(PacketType::OctreeFileReplacementFromUrl, this, "handleOctreeFileReplacementFromURLRequest"); + // set a custom packetVersionMatch as the verify packet operator for the udt::Socket nodeList->setPacketFilterOperator(&DomainServer::isPacketVerified); @@ -1605,6 +1648,7 @@ void DomainServer::sendHeartbeatToIceServer() { qWarning() << "Waiting for keypair generation to complete before sending ICE heartbeat."; if (!limitedNodeList->getSessionUUID().isNull()) { + qDebug() << "generating keypair"; accountManager->generateNewDomainKeypair(limitedNodeList->getSessionUUID()); } else { qWarning() << "Attempting to send ICE server heartbeat with no domain ID. This is not supported"; @@ -1695,10 +1739,88 @@ void DomainServer::sendHeartbeatToIceServer() { } else { qDebug() << "Not sending ice-server heartbeat since there is no selected ice-server."; qDebug() << "Waiting for" << _iceServerAddr << "host lookup response"; - } } +void DomainServer::processOctreeDataPersistMessage(QSharedPointer message) { + qDebug() << "Received octree data persist message"; + auto data = message->readAll(); + auto filePath = getEntitiesFilePath(); + + QFile f(filePath); + if (f.open(QIODevice::WriteOnly)) { + f.write(data); + OctreeUtils::RawOctreeData octreeData; + if (OctreeUtils::readOctreeDataInfoFromData(data, &octreeData)) { + qCDebug(domain_server) << "Wrote new entiteis file" << octreeData.id << octreeData.version; + } else { + qCDebug(domain_server) << "Failed to read new octree data info"; + } + } else { + qCDebug(domain_server) << "Failed to write new entities file"; + } +} + +QString DomainServer::getContentBackupDir() { + return PathUtils::getAppDataFilePath("backup"); +} + +QString DomainServer::getEntitiesDirPath() { + return PathUtils::getAppDataFilePath("entities"); +} + +QString DomainServer::getEntitiesFilePath() { + return PathUtils::getAppDataFilePath("entities/models.json.gz"); +} + +QString DomainServer::getEntitiesReplacementFilePath() { + return getEntitiesFilePath().append(REPLACEMENT_FILE_EXTENSION); +} + +void DomainServer::processOctreeDataRequestMessage(QSharedPointer message) { + qDebug() << "Got request for octree data from " << message->getSenderSockAddr(); + + bool remoteHasExistingData { false }; + QUuid id; + int version; + message->readPrimitive(&remoteHasExistingData); + if (remoteHasExistingData) { + auto idData = message->read(16); + id = QUuid::fromRfc4122(idData); + message->readPrimitive(&version); + qCDebug(domain_server) << "Entity server does have existing data: ID(" << id << ") DataVersion(" << version << ")"; + } else { + qCDebug(domain_server) << "Entity server does not have existing data"; + } + auto entityFilePath = getEntitiesFilePath(); + + //QFile file(entityFilePath); + auto reply = NLPacketList::create(PacketType::OctreeDataFileReply, QByteArray(), true, true); + OctreeUtils::RawOctreeData data; + if (OctreeUtils::readOctreeDataInfoFromFile(entityFilePath, &data)) { + if (data.id == id && data.version <= version) { + qCDebug(domain_server) << "ES has sufficient octree data, not sending data"; + reply->writePrimitive(false); + } else { + qCDebug(domain_server) << "Sending newer octree data to ES"; + QFile file(entityFilePath); + if (file.open(QIODevice::ReadOnly)) { + reply->writePrimitive(true); + reply->write(file.readAll()); + } else { + qCDebug(domain_server) << "Unable to load entity file"; + reply->writePrimitive(false); + } + } + } else { + qCDebug(domain_server) << "Domain server does not have valid octree data"; + reply->writePrimitive(false); + } + + auto nodeList = DependencyManager::get(); + nodeList->sendPacketList(std::move(reply), message->getSenderSockAddr()); +} + void DomainServer::processNodeJSONStatsPacket(QSharedPointer packetList, SharedNodePointer sendingNode) { auto nodeData = static_cast(sendingNode->getLinkedData()); if (nodeData) { @@ -3105,9 +3227,64 @@ void DomainServer::setupGroupCacheRefresh() { } } +void DomainServer::maybeHandleReplacementEntityFile() { + QFile replacementFile(getEntitiesReplacementFilePath()); + if (replacementFile.exists()) { + qCDebug(domain_server) << "Replacing existing entity date with replacement file"; + QFile currentFile(getEntitiesFilePath()); + if (currentFile.exists()) { + if (currentFile.remove()) { + qCDebug(domain_server) << "Removed existing entity file"; + } else { + qCWarning(domain_server) << "Failled to remove existing entity file"; + } + } + if (replacementFile.rename(getEntitiesFilePath())) { + qCDebug(domain_server) << "Successfully updated entities data file with replacement file"; + } else { + qCWarning(domain_server) << "Failed to update entities data file with replacement file"; + } + } +} + void DomainServer::handleOctreeFileReplacement(QByteArray octreeFile) { // enumerate the nodes and find any octree type servers with active sockets + //Assume we have compressed data + auto compressedOctree = octreeFile; + QByteArray jsonOctree; + + bool wasCompressed = gunzip(compressedOctree, jsonOctree); + if (!wasCompressed) { + // the source was not compressed, assume we were sent regular JSON data + jsonOctree = compressedOctree; + } + + OctreeUtils::RawOctreeData data; + if (OctreeUtils::readOctreeDataInfoFromData(jsonOctree, &data)) { + data.id = QUuid::createUuid(); + data.version = 0; + + gzip(data.toByteArray(), compressedOctree); + + // write the compressed octree data to a special file + auto replacementFilePath = getEntitiesReplacementFilePath(); + QFile replacementFile(replacementFilePath); + if (replacementFile.open(QIODevice::WriteOnly) && replacementFile.write(compressedOctree) != -1) { + // we've now written our replacement file, time to take the server down so it can + // process it when it comes back up + qInfo() << "Wrote octree replacement file to" << replacementFilePath << "- stopping server"; + + QMetaObject::invokeMethod(this, "restart", Qt::QueuedConnection); + } else { + qWarning() << "Could not write replacement octree data to file - refusing to process"; + } + } else { + qDebug() << "Received replacement octree file that is invalid - refusing to process"; + } + + + return; auto limitedNodeList = DependencyManager::get(); limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) { return node->getType() == NodeType::EntityServer && node->getActiveSocket(); @@ -3121,3 +3298,37 @@ void DomainServer::handleOctreeFileReplacement(QByteArray octreeFile) { limitedNodeList->sendPacketList(std::move(octreeFilePacketList), *octreeNode); }); } + +void DomainServer::handleOctreeFileReplacementFromURLRequest(QSharedPointer message) { + qInfo() << "Received request to replace content from a url"; + auto node = DependencyManager::get()->findNodeWithAddr(message->getSenderSockAddr()); + if (node) { + qDebug() << "Found node: " << node->getCanReplaceContent(); + } + if (node->getCanReplaceContent()) { + // Convert message data into our URL + QString url(message->getMessage()); + QUrl modelsURL = QUrl(url, QUrl::StrictMode); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest request(modelsURL); + QNetworkReply* reply = networkAccessManager.get(request); + connect(reply, &QNetworkReply::finished, [this, reply, modelsURL]() { + QNetworkReply::NetworkError networkError = reply->error(); + if (networkError == QNetworkReply::NoError) { + handleOctreeFileReplacement(reply->readAll()); + } else { + qDebug() << "Error downloading JSON from specified file: " << modelsURL; + } + }); + } +} + + + + +void DomainServer::handleOctreeFileReplacementRequest(QSharedPointer message) { + auto node = DependencyManager::get()->nodeWithUUID(message->getSourceID()); + if (node->getCanReplaceContent()) { + handleOctreeFileReplacement(message->readAll()); + } +} diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index c7d779b394..ee0350665e 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -32,9 +32,14 @@ #include "DomainServerSettingsManager.h" #include "DomainServerWebSessionData.h" #include "WalletTransaction.h" +#include "DomainContentBackupManager.h" #include "PendingAssignedNodeData.h" +#include + +Q_DECLARE_LOGGING_CATEGORY(domain_server) + typedef QSharedPointer SharedAssignmentPointer; typedef QMultiHash TransactionHash; @@ -65,6 +70,8 @@ public: bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false) override; bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url, bool skipSubHandler = false) override; + static const QString REPLACEMENT_FILE_EXTENSION; + public slots: /// Called by NodeList to inform us a node has been added void nodeAdded(SharedNodePointer node); @@ -84,6 +91,13 @@ private slots: void processICEServerHeartbeatDenialPacket(QSharedPointer message); void processICEServerHeartbeatACK(QSharedPointer message); + void handleOctreeFileReplacementFromURLRequest(QSharedPointer message); + void handleOctreeFileReplacementRequest(QSharedPointer message); + void handleOctreeFileReplacement(QByteArray octreeFile); + + void processOctreeDataRequestMessage(QSharedPointer message); + void processOctreeDataPersistMessage(QSharedPointer message); + void setupPendingAssignmentCredits(); void sendPendingTransactionsToServer(); @@ -91,8 +105,7 @@ private slots: void sendHeartbeatToMetaverse() { sendHeartbeatToMetaverse(QString()); } void sendHeartbeatToIceServer(); - void handleConnectedNode(SharedNodePointer newNode); - + void handleConnectedNode(SharedNodePointer newNode); void handleTempDomainSuccess(QNetworkReply& requestReply); void handleTempDomainError(QNetworkReply& requestReply); @@ -109,8 +122,6 @@ private slots: void handleSuccessfulICEServerAddressUpdate(QNetworkReply& requestReply); void handleFailedICEServerAddressUpdate(QNetworkReply& requestReply); - void handleOctreeFileReplacement(QByteArray octreeFile); - void updateReplicatedNodes(); void updateDownstreamNodes(); void updateUpstreamNodes(); @@ -127,6 +138,13 @@ private: const QUuid& getID(); void parseCommandLine(); + QString getContentBackupDir(); + QString getEntitiesDirPath(); + QString getEntitiesFilePath(); + QString getEntitiesReplacementFilePath(); + + void maybeHandleReplacementEntityFile(); + void setupNodeListAndAssignments(); bool optionallySetupOAuth(); bool optionallyReadX509KeyAndCertificate(); @@ -252,6 +270,8 @@ private: bool _sendICEServerAddressToMetaverseAPIInProgress { false }; bool _sendICEServerAddressToMetaverseAPIRedo { false }; + std::unique_ptr _contentManager { nullptr }; + QHash> _pendingOAuthConnections; QThread _assetClientThread; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5a340f471e..c031c0e8d4 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6240,13 +6240,15 @@ bool Application::askToReplaceDomainContent(const QString& url) { // Given confirmation, send request to domain server to replace content qCDebug(interfaceapp) << "Attempting to replace domain content: " << url; QByteArray urlData(url.toUtf8()); - auto limitedNodeList = DependencyManager::get(); + auto limitedNodeList = DependencyManager::get(); + const auto& domainHandler = limitedNodeList->getDomainHandler(); limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) { return node->getType() == NodeType::EntityServer && node->getActiveSocket(); - }, [&urlData, limitedNodeList](const SharedNodePointer& octreeNode) { + }, [&urlData, limitedNodeList, &domainHandler](const SharedNodePointer& octreeNode) { auto octreeFilePacket = NLPacket::create(PacketType::OctreeFileReplacementFromUrl, urlData.size(), true); octreeFilePacket->write(urlData); - limitedNodeList->sendPacket(std::move(octreeFilePacket), *octreeNode); + qDebug() << "WRiting url data: " << urlData; + limitedNodeList->sendPacket(std::move(octreeFilePacket), domainHandler.getSockAddr()); }); auto addressManager = DependencyManager::get(); addressManager->handleLookupString(DOMAIN_SPAWNING_POINT); diff --git a/interface/src/ui/DomainConnectionModel.cpp b/interface/src/ui/DomainConnectionModel.cpp index b9e4c1348e..83aa18420c 100644 --- a/interface/src/ui/DomainConnectionModel.cpp +++ b/interface/src/ui/DomainConnectionModel.cpp @@ -98,4 +98,4 @@ void DomainConnectionModel::refresh() { //inform view that we want refresh data beginResetModel(); endResetModel(); -} \ No newline at end of file +} diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 60bcc85575..4f96a6d072 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2244,6 +2244,8 @@ bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer if (! entityDescription.contains("Entities")) { entityDescription["Entities"] = QVariantList(); } + entityDescription["DataVersion"] = ++_persistDataVersion; + entityDescription["Id"] = _persistID; QScriptEngine scriptEngine; RecurseOctreeToMapOperator theOperator(entityDescription, element, &scriptEngine, skipDefaultValues, skipThoseWithBadParents, _myAvatar); @@ -2256,6 +2258,14 @@ bool EntityTree::readFromMap(QVariantMap& map) { int contentVersion = map["Version"].toInt(); bool needsConversion = (contentVersion < (int)EntityVersion::ZoneLightInheritModes); + if (map.contains("Id")) { + _persistID = map["Id"].toUuid(); + } + + if (map.contains("DataVersion")) { + _persistDataVersion = map["DataVersion"].toInt(); + } + // map will have a top-level list keyed as "Entities". This will be extracted // and iterated over. Each member of this list is converted to a QVariantMap, then // to a QScriptValue, and then to EntityItemProperties. These properties are used diff --git a/libraries/image/CMakeLists.txt b/libraries/image/CMakeLists.txt index 442fa714b3..e6a1856327 100644 --- a/libraries/image/CMakeLists.txt +++ b/libraries/image/CMakeLists.txt @@ -5,7 +5,8 @@ link_hifi_libraries(shared gpu) if (NOT ANDROID) add_dependency_external_projects(nvtt) find_package(NVTT REQUIRED) + target_include_directories(${TARGET_NAME} PRIVATE ${NVTT_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${NVTT_LIBRARIES}) add_paths_to_fixup_libs(${NVTT_DLL_PATH}) -endif() \ No newline at end of file +endif() diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 2343695914..3516fe948a 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -326,6 +326,7 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe static QMultiMap hashDebugSuppressMap; if (!hashDebugSuppressMap.contains(sourceID, headerType)) { + qCDebug(networking) << packetHeaderHash << expectedHash; qCDebug(networking) << "Packet hash mismatch on" << headerType << "- Sender" << sourceID; hashDebugSuppressMap.insert(sourceID, headerType); diff --git a/libraries/networking/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h index 8b35acaac5..007e41a543 100644 --- a/libraries/networking/src/ThreadedAssignment.h +++ b/libraries/networking/src/ThreadedAssignment.h @@ -18,8 +18,6 @@ #include "Assignment.h" -using DownstreamNodeFoundCallback = std::function; - class ThreadedAssignment : public Assignment { Q_OBJECT public: @@ -47,10 +45,10 @@ protected: QTimer _domainServerTimer; QTimer _statsTimer; int _numQueuedCheckIns { 0 }; - + protected slots: void domainSettingsRequestFailed(); - + private slots: void checkInWithDomainServerOrExit(); }; diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 5757cea496..7cd02608a1 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -126,6 +126,11 @@ public: EntityScriptCallMethod, ChallengeOwnershipRequest, ChallengeOwnershipReply, + + OctreeDataFileRequest, + OctreeDataFileReply, + OctreeDataPersist, + NUM_PACKET_TYPE }; @@ -165,6 +170,8 @@ public: << PacketTypeEnum::Value::DomainConnectionDenied << PacketTypeEnum::Value::DomainServerPathQuery << PacketTypeEnum::Value::DomainServerPathResponse << PacketTypeEnum::Value::DomainServerAddedNode << PacketTypeEnum::Value::DomainServerConnectionToken << PacketTypeEnum::Value::DomainSettingsRequest + << PacketTypeEnum::Value::OctreeDataFileRequest << PacketTypeEnum::Value::OctreeDataFileReply + << PacketTypeEnum::Value::OctreeDataPersist << PacketTypeEnum::Value::OctreeFileReplacementFromUrl << PacketTypeEnum::Value::DomainSettings << PacketTypeEnum::Value::ICEServerPeerInformation << PacketTypeEnum::Value::ICEServerQuery << PacketTypeEnum::Value::ICEServerHeartbeat << PacketTypeEnum::Value::ICEServerHeartbeatACK << PacketTypeEnum::Value::ICEPing diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 55643985c8..019ae07c16 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -328,7 +328,7 @@ void Socket::checkForReadyReadBackup() { void Socket::readPendingDatagrams() { int packetSizeWithHeader = -1; - while ((packetSizeWithHeader = _udpSocket.pendingDatagramSize()) != -1) { + while ((packetSizeWithHeader = _udpSocket.pendingDatagramSize()) > 0) { // we're reading a packet so re-start the readyRead backup timer _readyReadBackupTimer->start(); @@ -517,7 +517,7 @@ void Socket::handleSocketError(QAbstractSocket::SocketError socketError) { static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(SOCKET_REGEX); - qCDebug(networking) << "udt::Socket error - " << socketError; + qCDebug(networking) << "udt::Socket error - " << socketError << _udpSocket.errorString(); } void Socket::handleStateChanged(QAbstractSocket::SocketState socketState) { diff --git a/libraries/octree/CMakeLists.txt b/libraries/octree/CMakeLists.txt index bea036add3..228779dbba 100644 --- a/libraries/octree/CMakeLists.txt +++ b/libraries/octree/CMakeLists.txt @@ -1,3 +1,4 @@ set(TARGET_NAME octree) +include_directories(system /usr/local/Cellar/qt5/5.9.1/include) setup_hifi_library() link_hifi_libraries(shared networking) diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index c63ff2f560..23352a548c 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -1757,6 +1757,19 @@ bool Octree::readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, QVariant asVariant = asDocument.toVariant(); QVariantMap asMap = asVariant.toMap(); bool success = readFromMap(asMap); + /* + if (success) { + if (asMap.contains("DataVersion") && asMap.contains("Id")) { + bool versionOk; + auto dataVersion = asMap["DataVersion"].toLongLong(&versionOk); + if (versionOk) { + auto id = asMap["Id"].toUuid(); + _persistDataVersion = dataVersion; + _persistID = id; + } + } + } + */ delete[] rawData; return success; } @@ -1778,11 +1791,9 @@ bool Octree::writeToFile(const char* fileName, const OctreeElementPointer& eleme return success; } -bool Octree::writeToJSONFile(const char* fileName, const OctreeElementPointer& element, bool doGzip) { +bool Octree::toJSON(QJsonDocument* doc, const OctreeElementPointer& element) { QVariantMap entityDescription; - qCDebug(octree, "Saving JSON SVO to file %s...", fileName); - OctreeElementPointer top; if (element) { top = element; @@ -1802,17 +1813,35 @@ bool Octree::writeToJSONFile(const char* fileName, const OctreeElementPointer& e return false; } - // convert the QVariantMap to JSON - QByteArray jsonData = QJsonDocument::fromVariant(entityDescription).toJson(); - QByteArray jsonDataForFile; + *doc = QJsonDocument::fromVariant(entityDescription); + return true; +} - if (doGzip) { - if (!gzip(jsonData, jsonDataForFile, -1)) { - qCritical("unable to gzip data while saving to json."); - return false; - } +bool Octree::toGzippedJSON(QByteArray* data, const OctreeElementPointer& element) { + QJsonDocument doc; + if (!toJSON(&doc, element)) { + qCritical("Failed to convert Entities to QVariantMap while converting to json."); + return false; + } + + QByteArray jsonData = doc.toJson(); + + if (!gzip(jsonData, *data, -1)) { + qCritical("Unable to gzip data while saving to json."); + return false; } else { - jsonDataForFile = jsonData; + qDebug() <<"Did gzip!"; + } + + return true; +} + +bool Octree::writeToJSONFile(const char* fileName, const OctreeElementPointer& element, bool doGzip) { + qCDebug(octree, "Saving JSON SVO to file %s...", fileName); + + QByteArray jsonDataForFile; + if (!toGzippedJSON(&jsonDataForFile)) { + return false; } QFile persistFile(fileName); @@ -1823,6 +1852,7 @@ bool Octree::writeToJSONFile(const char* fileName, const OctreeElementPointer& e qCritical("Could not write to JSON description of entities."); } + return success; } diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 1648cb0f47..1b9495717b 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -283,8 +283,10 @@ public: void loadOctreeFile(const char* fileName); // Octree exporters - bool writeToFile(const char* filename, const OctreeElementPointer& element = NULL, QString persistAsFileType = "json.gz"); - bool writeToJSONFile(const char* filename, const OctreeElementPointer& element = NULL, bool doGzip = false); + bool toJSON(QJsonDocument* doc, const OctreeElementPointer& element = nullptr); + bool toGzippedJSON(QByteArray* data, const OctreeElementPointer& element = nullptr); + bool writeToFile(const char* filename, const OctreeElementPointer& element = nullptr, QString persistAsFileType = "json.gz"); + bool writeToJSONFile(const char* filename, const OctreeElementPointer& element = nullptr, bool doGzip = false); virtual bool writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues, bool skipThoseWithBadParents) = 0; @@ -326,6 +328,11 @@ public: virtual void dumpTree() { } virtual void pruneTree() { } + void setEntityVersionInfo(QUuid id, int64_t dataVersion) { + _persistID = id; + _persistDataVersion = dataVersion; + } + virtual void resetEditStats() { } virtual quint64 getAverageDecodeTime() const { return 0; } virtual quint64 getAverageLookupTime() const { return 0; } @@ -359,6 +366,9 @@ protected: OctreeElementPointer _rootElement = nullptr; + QUuid _persistID { QUuid::createUuid() }; + int _persistDataVersion { 0 }; + bool _isDirty; bool _shouldReaverage; bool _stopImport; diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index ea6bd28fc4..9c9a4d40db 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -31,18 +31,19 @@ #include "OctreeLogging.h" #include "OctreePersistThread.h" +#include "OctreeUtils.h" const int OctreePersistThread::DEFAULT_PERSIST_INTERVAL = 1000 * 30; // every 30 seconds -const QString OctreePersistThread::REPLACEMENT_FILE_EXTENSION = ".replace"; OctreePersistThread::OctreePersistThread(OctreePointer tree, const QString& filename, const QString& backupDirectory, int persistInterval, bool wantBackup, const QJsonObject& settings, bool debugTimestampNow, - QString persistAsFileType) : + QString persistAsFileType, const QByteArray& replacementData) : _tree(tree), _filename(filename), _backupDirectory(backupDirectory), _persistInterval(persistInterval), _initialLoadComplete(false), + _replacementData(replacementData), _loadTimeUSecs(0), _lastCheck(0), _wantBackup(wantBackup), @@ -52,6 +53,7 @@ OctreePersistThread::OctreePersistThread(OctreePointer tree, const QString& file { parseSettings(settings); + // in case the persist filename has an extension that doesn't match the file type QString sansExt = fileNameWithoutExtension(_filename, PERSIST_EXTENSIONS); _filename = sansExt + "." + _persistAsFileType; @@ -132,51 +134,56 @@ quint64 OctreePersistThread::getMostRecentBackupTimeInUsecs(const QString& forma return mostRecentBackupInUsecs; } -void OctreePersistThread::possiblyReplaceContent() { - // before we load the normal file, check if there's a pending replacement file - auto replacementFileName = _filename + REPLACEMENT_FILE_EXTENSION; +void OctreePersistThread::replaceData(QByteArray data) { + backupCurrentFile(); - QFile replacementFile { replacementFileName }; - if (replacementFile.exists()) { - // we have a replacement file to process - qDebug() << "Replacing models file with" << replacementFileName; - - // first take the current models file and move it to a different filename, appended with the timestamp - QFile currentFile { _filename }; - if (currentFile.exists()) { - static const QString FILENAME_TIMESTAMP_FORMAT = "yyyyMMdd-hhmmss"; - auto backupFileName = _filename + ".backup." + QDateTime::currentDateTime().toString(FILENAME_TIMESTAMP_FORMAT); - - if (currentFile.rename(backupFileName)) { - qDebug() << "Moved previous models file to" << backupFileName; - } else { - qWarning() << "Could not backup previous models file to" << backupFileName << "- removing replacement models file"; - - if (!replacementFile.remove()) { - qWarning() << "Could not remove replacement models file from" << replacementFileName - << "- replacement will be re-attempted on next server restart"; - return; - } - } - } - - // rename the replacement file to match what the persist thread is just about to read - if (!replacementFile.rename(_filename)) { - qWarning() << "Could not replace models file with" << replacementFileName << "- starting with empty models file"; - } + QFile currentFile { _filename }; + if (currentFile.open(QIODevice::WriteOnly)) { + currentFile.write(data); + qDebug() << "Wrote replacement data"; + } else { + qWarning() << "Failed to write replacement data"; } } +// Return true if current file is backed up successfully or doesn't exist. +bool OctreePersistThread::backupCurrentFile() { + // first take the current models file and move it to a different filename, appended with the timestamp + QFile currentFile { _filename }; + if (currentFile.exists()) { + static const QString FILENAME_TIMESTAMP_FORMAT = "yyyyMMdd-hhmmss"; + auto backupFileName = _filename + ".backup." + QDateTime::currentDateTime().toString(FILENAME_TIMESTAMP_FORMAT); + + if (currentFile.rename(backupFileName)) { + qDebug() << "Moved previous models file to" << backupFileName; + return true; + } else { + qWarning() << "Could not backup previous models file to" << backupFileName << "- removing replacement models file"; + return false; + } + } + return true; +} bool OctreePersistThread::process() { if (!_initialLoadComplete) { - possiblyReplaceContent(); - quint64 loadStarted = usecTimestampNow(); qCDebug(octree) << "loading Octrees from file: " << _filename << "..."; - bool persistantFileRead; + if (_replacementData.isNull()) { + sendLatestEntityDataToDS(); + } else { + replaceData(_replacementData); + _replacementData.clear(); + } + + OctreeUtils::RawOctreeData data; + if (OctreeUtils::readOctreeDataInfoFromFile(_filename, &data)) { + _tree->setEntityVersionInfo(data.id, data.version); + } + + bool persistentFileRead; _tree->withWriteLock([&] { PerformanceWarning warn(true, "Loading Octree File", true); @@ -199,7 +206,7 @@ bool OctreePersistThread::process() { qCDebug(octree) << "Loading Octree... lock file removed:" << lockFileName; } - persistantFileRead = _tree->readFromFile(qPrintable(_filename.toLocal8Bit())); + persistentFileRead = _tree->readFromFile(qPrintable(_filename.toLocal8Bit())); _tree->pruneTree(); }); @@ -207,7 +214,7 @@ bool OctreePersistThread::process() { _loadTimeUSecs = loadDone - loadStarted; _tree->clearDirtyBit(); // the tree is clean since we just loaded it - qCDebug(octree, "DONE loading Octrees from file... fileRead=%s", debug::valueOf(persistantFileRead)); + qCDebug(octree, "DONE loading Octrees from file... fileRead=%s", debug::valueOf(persistentFileRead)); unsigned long nodeCount = OctreeElement::getNodeCount(); unsigned long internalNodeCount = OctreeElement::getInternalNodeCount(); @@ -272,7 +279,6 @@ bool OctreePersistThread::process() { return isStillRunning(); // keep running till they terminate us } - void OctreePersistThread::aboutToFinish() { qCDebug(octree) << "Persist thread about to finish..."; persist(); @@ -319,6 +325,23 @@ void OctreePersistThread::persist() { remove(qPrintable(lockFileName)); qCDebug(octree) << "saving Octree lock file removed:" << lockFileName; } + + sendLatestEntityDataToDS(); + } +} + +void OctreePersistThread::sendLatestEntityDataToDS() { + qDebug() << "Sending latest entity data to DS"; + auto nodeList = DependencyManager::get(); + const DomainHandler& domainHandler = nodeList->getDomainHandler(); + + QByteArray data; + if (_tree->toGzippedJSON(&data)) { + auto message = NLPacketList::create(PacketType::OctreeDataPersist, QByteArray(), true, true); + message->write(data); + nodeList->sendPacketList(std::move(message), domainHandler.getSockAddr()); + } else { + qCWarning(octree) << "Failed to persist octree to DS"; } } @@ -453,7 +476,6 @@ void OctreePersistThread::rollOldBackupVersions(const BackupRule& rule) { } } - void OctreePersistThread::backup() { qCDebug(octree) << "backup operation wantBackup:" << _wantBackup; if (_wantBackup) { diff --git a/libraries/octree/src/OctreePersistThread.h b/libraries/octree/src/OctreePersistThread.h index 2441223467..3fdad2c3f7 100644 --- a/libraries/octree/src/OctreePersistThread.h +++ b/libraries/octree/src/OctreePersistThread.h @@ -18,7 +18,6 @@ #include #include "Octree.h" -/// Generalized threaded processor for handling received inbound packets. class OctreePersistThread : public GenericThread { Q_OBJECT public: @@ -32,11 +31,11 @@ public: }; static const int DEFAULT_PERSIST_INTERVAL; - static const QString REPLACEMENT_FILE_EXTENSION; OctreePersistThread(OctreePointer tree, const QString& filename, const QString& backupDirectory, int persistInterval = DEFAULT_PERSIST_INTERVAL, bool wantBackup = false, - const QJsonObject& settings = QJsonObject(), bool debugTimestampNow = false, QString persistAsFileType="json.gz"); + const QJsonObject& settings = QJsonObject(), bool debugTimestampNow = false, + QString persistAsFileType="json.gz", const QByteArray& replacementData = QByteArray()); bool isInitialLoadComplete() const { return _initialLoadComplete; } quint64 getLoadElapsedTime() const { return _loadTimeUSecs; } @@ -61,7 +60,10 @@ protected: bool getMostRecentBackup(const QString& format, QString& mostRecentBackupFileName, QDateTime& mostRecentBackupTime); quint64 getMostRecentBackupTimeInUsecs(const QString& format); void parseSettings(const QJsonObject& settings); - void possiblyReplaceContent(); + bool backupCurrentFile(); + + void replaceData(QByteArray data); + void sendLatestEntityDataToDS(); private: OctreePointer _tree; @@ -69,6 +71,7 @@ private: QString _backupDirectory; int _persistInterval; bool _initialLoadComplete; + QByteArray _replacementData; quint64 _loadTimeUSecs; diff --git a/libraries/octree/src/OctreeUtils.cpp b/libraries/octree/src/OctreeUtils.cpp index ca15324d4e..d8925a10ca 100644 --- a/libraries/octree/src/OctreeUtils.cpp +++ b/libraries/octree/src/OctreeUtils.cpp @@ -16,7 +16,11 @@ #include #include +#include +#include +#include +#include float calculateRenderAccuracy(const glm::vec3& position, const AABox& bounds, @@ -75,3 +79,76 @@ float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust const float smallestSize = 0.01f; return (smallestSize * MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT) / boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale); } + +bool OctreeUtils::readOctreeFile(QString path, QJsonDocument* doc) { + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Cannot open json file for reading: " << path; + return false; + } + + QByteArray data = file.readAll(); + QByteArray jsonData; + + if (path.endsWith(".json.gz")) { + if (!gunzip(data, jsonData)) { + qCritical() << "json File not in gzip format: " << path; + return false; + } + } else { + jsonData = data; + } + + *doc = QJsonDocument::fromJson(jsonData); + return !doc->isNull(); +} + +bool readOctreeDataInfoFromJSON(QJsonObject root, OctreeUtils::RawOctreeData* octreeData) { + if (root.contains("Id") && root.contains("DataVersion")) { + octreeData->id = root["Id"].toVariant().toUuid(); + octreeData->version = root["DataVersion"].toInt(); + } + if (root.contains("Entities")) { + octreeData->octreeData = root["Entities"].toArray(); + } + return true; +} + +bool OctreeUtils::readOctreeDataInfoFromData(QByteArray data, OctreeUtils::RawOctreeData* octreeData) { + QByteArray jsonData; + if (gunzip(data, jsonData)) { + data = jsonData; + } + + auto doc = QJsonDocument::fromJson(data); + if (doc.isNull()) { + return false; + } + + auto root = doc.object(); + return readOctreeDataInfoFromJSON(root, octreeData); +} + +bool OctreeUtils::readOctreeDataInfoFromFile(QString path, OctreeUtils::RawOctreeData* octreeData) { + QJsonDocument doc; + if (!OctreeUtils::readOctreeFile(path, &doc)) { + return false; + } + + auto root = doc.object(); + return readOctreeDataInfoFromJSON(root, octreeData); +} + +QByteArray OctreeUtils::RawOctreeData::toByteArray() { + QJsonObject obj { + { "DataVersion", QJsonValue((qint64)version) }, + { "Id", QJsonValue(id.toString()) }, + { "Version", QJsonValue(5) }, + { "Entities", octreeData } + }; + + QJsonDocument doc; + doc.setObject(obj); + + return doc.toJson(); +} diff --git a/libraries/octree/src/OctreeUtils.h b/libraries/octree/src/OctreeUtils.h index 0f87bb6f68..e5c7b617cd 100644 --- a/libraries/octree/src/OctreeUtils.h +++ b/libraries/octree/src/OctreeUtils.h @@ -14,7 +14,30 @@ #include "OctreeConstants.h" +#include +#include + class AABox; +class QJsonDocument; + +namespace OctreeUtils { + +// RawOctreeData is an intermediate format between JSON and a fully deserialized Octree. +class RawOctreeData { +public: + QUuid id { QUuid() }; + int64_t version { -1 }; + + QJsonArray octreeData; + + QByteArray toByteArray(); +}; + +bool readOctreeFile(QString path, QJsonDocument* doc); +bool readOctreeDataInfoFromData(QByteArray data, RawOctreeData* octreeData); +bool readOctreeDataInfoFromFile(QString path, RawOctreeData* octreeData); + +} /// renderAccuracy represents a floating point "visibility" of an object based on it's view from the camera. At a simple /// level it returns 0.0f for things that are so small for the current settings that they could not be visible. diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 8e5c30711c..f46d0768c1 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -105,7 +105,7 @@ void usecTimestampNowForceClockSkew(qint64 clockSkew) { ::usecTimestampNowAdjust = clockSkew; } -static qint64 TIME_REFERENCE = 0; // in usec +static std::atomic TIME_REFERENCE { 0 }; // in usec static std::once_flag usecTimestampNowIsInitialized; static QElapsedTimer timestampTimer; @@ -771,6 +771,10 @@ QString formatUsecTime(double usecs) { return formatUsecTime(usecs); } +QString formatSecTime(qint64 secs) { + return formatUsecTime(secs * 1000000); +} + QString formatSecondsElapsed(float seconds) { QString result; diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 5a1e48d9c0..7f9fb026f8 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -216,6 +216,7 @@ QString formatUsecTime(float usecs); QString formatUsecTime(double usecs); QString formatUsecTime(quint64 usecs); QString formatUsecTime(qint64 usecs); +QString formatSecTime(qint64 secs); QString formatSecondsElapsed(float seconds); bool similarStrings(const QString& stringA, const QString& stringB); From fc8e7a0841a875a2b7886784aa407547834326da Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 6 Feb 2018 14:33:01 -0800 Subject: [PATCH 257/569] Add target_zlib to DS CMakeLists.txt --- domain-server/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index 0e958b9537..a578be5ff6 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -24,6 +24,8 @@ symlink_or_copy_directory_beside_target(${_SHOULD_SYMLINK_RESOURCES} "${CMAKE_CU # link the shared hifi libraries link_hifi_libraries(embedded-webserver networking shared avatars octree) +target_zlib() + add_dependency_external_projects(quazip) find_package(QuaZip REQUIRED) From ff5be2d690c9cbe3e5bd313daad68976f091d83f Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 7 Feb 2018 09:27:51 -0800 Subject: [PATCH 258/569] Fix entity data ID sometimes being reset --- domain-server/src/DomainServer.cpp | 5 ++++- libraries/octree/src/OctreePersistThread.cpp | 11 +++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index e083710d35..edb3fe77dd 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1802,7 +1802,7 @@ void DomainServer::processOctreeDataRequestMessage(QSharedPointerwritePrimitive(false); } else { - qCDebug(domain_server) << "Sending newer octree data to ES"; + qCDebug(domain_server) << "Sending newer octree data to ES: ID(" << data.id << ") DataVersion(" << data.version << ")"; QFile file(entityFilePath); if (file.open(QIODevice::ReadOnly)) { reply->writePrimitive(true); @@ -3312,6 +3312,9 @@ void DomainServer::handleOctreeFileReplacementFromURLRequest(QSharedPointererror(); if (networkError == QNetworkReply::NoError) { diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index 9c9a4d40db..d51bd540bc 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -171,15 +171,13 @@ bool OctreePersistThread::process() { quint64 loadStarted = usecTimestampNow(); qCDebug(octree) << "loading Octrees from file: " << _filename << "..."; - if (_replacementData.isNull()) { - sendLatestEntityDataToDS(); - } else { + if (!_replacementData.isNull()) { replaceData(_replacementData); - _replacementData.clear(); } OctreeUtils::RawOctreeData data; if (OctreeUtils::readOctreeDataInfoFromFile(_filename, &data)) { + qDebug() << "Setting entity version info to: " << data.id << data.version; _tree->setEntityVersionInfo(data.id, data.version); } @@ -244,6 +242,11 @@ bool OctreePersistThread::process() { // want an uninitialized value for this, so we set it to the current time (startup of the server) time(&_lastPersistTime); + if (_replacementData.isNull()) { + sendLatestEntityDataToDS(); + } + _replacementData.clear(); + emit loadCompleted(); } From 1b7b4eee50064fbeaf062fe810225993dc417fed Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 12 Feb 2018 11:46:45 -0800 Subject: [PATCH 259/569] Fix entity data not being gzipped when adding id+version --- assignment-client/src/octree/OctreeServer.cpp | 2 +- libraries/octree/src/OctreeUtils.cpp | 12 ++++++++++++ libraries/octree/src/OctreeUtils.h | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index e78f9f108b..6704786c36 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -1193,7 +1193,7 @@ void OctreeServer::handleOctreeDataFileReply(QSharedPointer mes QFile file(_persistAbsoluteFilePath); if (file.open(QIODevice::WriteOnly)) { - auto entityData = data.toByteArray(); + auto entityData = data.toGzippedByteArray(); file.write(entityData); file.close(); } else { diff --git a/libraries/octree/src/OctreeUtils.cpp b/libraries/octree/src/OctreeUtils.cpp index d8925a10ca..e068e83b23 100644 --- a/libraries/octree/src/OctreeUtils.cpp +++ b/libraries/octree/src/OctreeUtils.cpp @@ -152,3 +152,15 @@ QByteArray OctreeUtils::RawOctreeData::toByteArray() { return doc.toJson(); } + +QByteArray OctreeUtils::RawOctreeData::toGzippedByteArray() { + auto data = toByteArray(); + QByteArray gzData; + + if (!gzip(data, gzData, -1)) { + qCritical("Unable to gzip data while converting json."); + return QByteArray(); + } + + return gzData; +} \ No newline at end of file diff --git a/libraries/octree/src/OctreeUtils.h b/libraries/octree/src/OctreeUtils.h index e5c7b617cd..18b0d27883 100644 --- a/libraries/octree/src/OctreeUtils.h +++ b/libraries/octree/src/OctreeUtils.h @@ -31,6 +31,7 @@ public: QJsonArray octreeData; QByteArray toByteArray(); + QByteArray toGzippedByteArray(); }; bool readOctreeFile(QString path, QJsonDocument* doc); From 2a667fcd60271c6a55147968aec8e0bae70d1bfe Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 13 Feb 2018 11:36:22 -0800 Subject: [PATCH 260/569] Cleanup entity -> ds persist --- assignment-client/CMakeLists.txt | 1 - assignment-client/src/octree/OctreeServer.cpp | 19 +++++--- assignment-client/src/octree/OctreeServer.h | 2 - .../src/DomainContentBackupManager.cpp | 18 +------ domain-server/src/DomainServer.cpp | 48 +++++++------------ interface/src/Application.cpp | 1 - libraries/entities/src/EntityTree.cpp | 2 +- libraries/octree/CMakeLists.txt | 1 - libraries/octree/src/Octree.cpp | 13 ----- libraries/octree/src/Octree.h | 2 + libraries/octree/src/OctreePersistThread.cpp | 1 + libraries/octree/src/OctreeUtils.cpp | 10 ++++ libraries/octree/src/OctreeUtils.h | 6 ++- 13 files changed, 49 insertions(+), 75 deletions(-) diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index 3de4c5fd3f..c73e8e1d34 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -6,7 +6,6 @@ setup_hifi_project(Core Gui Network Script Quick WebSockets) if (APPLE) set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks") endif () -set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "/testing/") setup_memory_debugger() diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 6704786c36..05d070606a 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -1044,12 +1044,13 @@ void OctreeServer::readConfiguration() { // If persist filename does not exist, let's see if there is one beside the application binary // If there is, let's copy it over to our target persist directory QDir persistPath { _persistFilePath }; - _persistAbsoluteFilePath = persistPath.absolutePath(); if (persistPath.isRelative()) { // if the domain settings passed us a relative path, make an absolute path that is relative to the // default data directory _persistAbsoluteFilePath = QDir(PathUtils::getAppDataFilePath("entities/")).absoluteFilePath(_persistFilePath); + } else { + _persistAbsoluteFilePath = persistPath.absolutePath(); } qDebug() << "persistFilePath=" << _persistFilePath; @@ -1174,6 +1175,11 @@ void OctreeServer::domainSettingsRequestComplete() { } void OctreeServer::handleOctreeDataFileReply(QSharedPointer message) { + if (_state != OctreeServerState::WaitingForOctreeDataNegotation) { + qCWarning(octree_server) << "Server received ocree data file reply but is not currently negotiating."; + return; + } + bool includesNewData; message->readPrimitive(&includesNewData); QByteArray replaceData; @@ -1188,8 +1194,7 @@ void OctreeServer::handleOctreeDataFileReply(QSharedPointer mes if (OctreeUtils::readOctreeDataInfoFromFile(_persistAbsoluteFilePath, &data)) { if (data.id.isNull()) { qCDebug(octree_server) << "Current octree data has a null id, updating"; - data.id = QUuid::createUuid(); - data.version = 0; + data.resetIdAndVersion(); QFile file(_persistAbsoluteFilePath); if (file.open(QIODevice::WriteOnly)) { @@ -1202,17 +1207,17 @@ void OctreeServer::handleOctreeDataFileReply(QSharedPointer mes } } } + + _state = OctreeServerState::Running; beginRunning(replaceData); } void OctreeServer::beginRunning(QByteArray replaceData) { - if (_state == OctreeServerState::Running) { - qCWarning(octree_server) << "Server is already running"; + if (_state != OctreeServerState::Running) { + qCWarning(octree_server) << "Server is not running"; return; } - _state = OctreeServerState::Running; - auto nodeList = DependencyManager::get(); // we need to ask the DS about agents so we can ping/reply with them diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index 6f77920ee0..eab71647e3 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -149,8 +149,6 @@ private slots: void domainSettingsRequestComplete(); void handleOctreeQueryPacket(QSharedPointer message, SharedNodePointer senderNode); void handleOctreeDataNackPacket(QSharedPointer message, SharedNodePointer senderNode); - //void handleOctreeFileReplacement(QSharedPointer message); - //void handleOctreeFileReplacementFromURL(QSharedPointer message); void handleOctreeDataFileReply(QSharedPointer message); void removeSendThread(); diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 0eca10f8af..4f544d7011 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -279,23 +279,9 @@ void DomainContentBackupManager::backup() { qDebug() << "Created backup: " << fileName; - removeOldBackupVersions(rule); + rule.lastBackupSeconds = nowSeconds; - if (rule.maxBackupVersions > 0) { - // Execute backup - auto result = true; - if (result) { - qCDebug(domain_server) << "DONE backing up persist file..."; - rule.lastBackupSeconds = nowSeconds; - } else { - qCDebug(domain_server) << "ERROR in backing up persist file..."; - perror("ERROR in backing up persist file"); - } - } else { - qCDebug(domain_server) << "This backup rule" << rule.name << " has Max Rolled Backup Versions less than 1 [" - << rule.maxBackupVersions << "]." - << " There are no backups to be done..."; - } + removeOldBackupVersions(rule); } else { qCDebug(domain_server) << "Backup not needed for this rule [" << rule.name << "]..."; } diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index edb3fe77dd..8f0e26375e 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -289,7 +289,6 @@ DomainServer::DomainServer(int argc, char* argv[]) : } } - qDebug() << "Starting persist thread"; if (QDir(getEntitiesDirPath()).mkpath(".")) { qCDebug(domain_server) << "Created entities data directory"; } @@ -1785,7 +1784,8 @@ void DomainServer::processOctreeDataRequestMessage(QSharedPointerreadPrimitive(&remoteHasExistingData); if (remoteHasExistingData) { - auto idData = message->read(16); + constexpr size_t UUID_SIZE_BYTES = 16; + auto idData = message->read(UUID_SIZE_BYTES); id = QUuid::fromRfc4122(idData); message->readPrimitive(&version); qCDebug(domain_server) << "Entity server does have existing data: ID(" << id << ") DataVersion(" << version << ")"; @@ -1794,7 +1794,6 @@ void DomainServer::processOctreeDataRequestMessage(QSharedPointer(); - limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) { - return node->getType() == NodeType::EntityServer && node->getActiveSocket(); - }, [&octreeFile, limitedNodeList](const SharedNodePointer& octreeNode) { - // setup a packet to send to this octree server with the new octree file data - auto octreeFilePacketList = NLPacketList::create(PacketType::OctreeFileReplacement, QByteArray(), true, true); - octreeFilePacketList->write(octreeFile); - - qDebug() << "Sending an octree file replacement of" << octreeFile.size() << "bytes to" << octreeNode; - - limitedNodeList->sendPacketList(std::move(octreeFilePacketList), *octreeNode); - }); } void DomainServer::handleOctreeFileReplacementFromURLRequest(QSharedPointer message) { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c031c0e8d4..bc44bb4cf0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6247,7 +6247,6 @@ bool Application::askToReplaceDomainContent(const QString& url) { }, [&urlData, limitedNodeList, &domainHandler](const SharedNodePointer& octreeNode) { auto octreeFilePacket = NLPacket::create(PacketType::OctreeFileReplacementFromUrl, urlData.size(), true); octreeFilePacket->write(urlData); - qDebug() << "WRiting url data: " << urlData; limitedNodeList->sendPacket(std::move(octreeFilePacket), domainHandler.getSockAddr()); }); auto addressManager = DependencyManager::get(); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 4f96a6d072..f632bcf140 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2244,7 +2244,7 @@ bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer if (! entityDescription.contains("Entities")) { entityDescription["Entities"] = QVariantList(); } - entityDescription["DataVersion"] = ++_persistDataVersion; + entityDescription["DataVersion"] = _persistDataVersion; entityDescription["Id"] = _persistID; QScriptEngine scriptEngine; RecurseOctreeToMapOperator theOperator(entityDescription, element, &scriptEngine, skipDefaultValues, diff --git a/libraries/octree/CMakeLists.txt b/libraries/octree/CMakeLists.txt index 228779dbba..bea036add3 100644 --- a/libraries/octree/CMakeLists.txt +++ b/libraries/octree/CMakeLists.txt @@ -1,4 +1,3 @@ set(TARGET_NAME octree) -include_directories(system /usr/local/Cellar/qt5/5.9.1/include) setup_hifi_library() link_hifi_libraries(shared networking) diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 23352a548c..d62cbad765 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -1757,19 +1757,6 @@ bool Octree::readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, QVariant asVariant = asDocument.toVariant(); QVariantMap asMap = asVariant.toMap(); bool success = readFromMap(asMap); - /* - if (success) { - if (asMap.contains("DataVersion") && asMap.contains("Id")) { - bool versionOk; - auto dataVersion = asMap["DataVersion"].toLongLong(&versionOk); - if (versionOk) { - auto id = asMap["Id"].toUuid(); - _persistDataVersion = dataVersion; - _persistID = id; - } - } - } - */ delete[] rawData; return success; } diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 1b9495717b..8954e53f8b 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -341,6 +341,8 @@ public: virtual quint64 getAverageLoggingTime() const { return 0; } virtual quint64 getAverageFilterTime() const { return 0; } + void incrementPersistDataVersion() { _persistDataVersion++; } + signals: void importSize(float x, float y, float z); void importProgress(int progress); diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index d51bd540bc..a669b7d3bb 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -311,6 +311,7 @@ void OctreePersistThread::persist() { backup(); // handle backup if requested qCDebug(octree) << "persist operation DONE with backup..."; + _tree->incrementPersistDataVersion(); // create our "lock" file to indicate we're saving. QString lockFileName = _filename + ".lock"; diff --git a/libraries/octree/src/OctreeUtils.cpp b/libraries/octree/src/OctreeUtils.cpp index e068e83b23..85ea3beb86 100644 --- a/libraries/octree/src/OctreeUtils.cpp +++ b/libraries/octree/src/OctreeUtils.cpp @@ -80,6 +80,9 @@ float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust return (smallestSize * MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT) / boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale); } +// Reads octree file and parses it into a QJsonDocument. Handles both gzipped and non-gzipped files. +// Returns true if the file was successfully opened and parsed, otherwise false. +// Example failures: file does not exist, gzipped file cannot be unzipped, invalid JSON. bool OctreeUtils::readOctreeFile(QString path, QJsonDocument* doc) { QFile file(path); if (!file.open(QIODevice::ReadOnly)) { @@ -129,6 +132,8 @@ bool OctreeUtils::readOctreeDataInfoFromData(QByteArray data, OctreeUtils::RawOc return readOctreeDataInfoFromJSON(root, octreeData); } +// Reads octree file and parses it into a RawOctreeData object. +// Returns false if readOctreeFile fails. bool OctreeUtils::readOctreeDataInfoFromFile(QString path, OctreeUtils::RawOctreeData* octreeData) { QJsonDocument doc; if (!OctreeUtils::readOctreeFile(path, &doc)) { @@ -163,4 +168,9 @@ QByteArray OctreeUtils::RawOctreeData::toGzippedByteArray() { } return gzData; +} + +void OctreeUtils::RawOctreeData::resetIdAndVersion() { + id = QUuid::createUuid(); + version = OctreeUtils::INITIAL_VERSION; } \ No newline at end of file diff --git a/libraries/octree/src/OctreeUtils.h b/libraries/octree/src/OctreeUtils.h index 18b0d27883..6fb0e62bcb 100644 --- a/libraries/octree/src/OctreeUtils.h +++ b/libraries/octree/src/OctreeUtils.h @@ -22,14 +22,18 @@ class QJsonDocument; namespace OctreeUtils { +using Version = int64_t; +constexpr Version INITIAL_VERSION = 0; + // RawOctreeData is an intermediate format between JSON and a fully deserialized Octree. class RawOctreeData { public: QUuid id { QUuid() }; - int64_t version { -1 }; + Version version { -1 }; QJsonArray octreeData; + void resetIdAndVersion(); QByteArray toByteArray(); QByteArray toGzippedByteArray(); }; From 0bbbff95cd2bd33bd6a0cad70ce351d9e14454c6 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 14 Feb 2018 11:38:18 -0800 Subject: [PATCH 261/569] Fix replacement octree data not working --- assignment-client/src/octree/OctreeServer.h | 2 -- domain-server/src/DomainServer.cpp | 22 ++++++++++++++------- libraries/octree/src/OctreeUtils.cpp | 8 ++------ 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index eab71647e3..e7efc731f2 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -176,8 +176,6 @@ protected: UniqueSendThread createSendThread(const SharedNodePointer& node); virtual UniqueSendThread newSendThread(const SharedNodePointer& node); - //void replaceContentFromMessageData(QByteArray content); - int _argc; const char** _argv; char** _parsedArgV; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 8f0e26375e..3eb1f21da0 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -3234,15 +3234,23 @@ void DomainServer::maybeHandleReplacementEntityFile() { } else { qCDebug(domain_server) << "Replacing existing entity date with replacement file"; - data.resetIdAndVersion(); - auto gzippedData = data.toGzippedByteArray(); - - QFile currentFile(getEntitiesFilePath()); - if (!currentFile.open(QIODevice::WriteOnly)) { + QFile replacementFile(replacementFilePath); + if (!replacementFile.remove()) { + // If we can't remove the replacement file, we are at risk of getting into a state where + // we continually replace the primary entity file with the replacement entity file. qCWarning(domain_server) - << "Failed to update entities data file with replacement file, unable to open entities file for writing"; + << "Unable to remove replacement file, bailing"; } else { - currentFile.write(gzippedData); + data.resetIdAndVersion(); + auto gzippedData = data.toGzippedByteArray(); + + QFile currentFile(getEntitiesFilePath()); + if (!currentFile.open(QIODevice::WriteOnly)) { + qCWarning(domain_server) + << "Failed to update entities data file with replacement file, unable to open entities file for writing"; + } else { + currentFile.write(gzippedData); + } } } } diff --git a/libraries/octree/src/OctreeUtils.cpp b/libraries/octree/src/OctreeUtils.cpp index 85ea3beb86..739c2661b3 100644 --- a/libraries/octree/src/OctreeUtils.cpp +++ b/libraries/octree/src/OctreeUtils.cpp @@ -93,12 +93,7 @@ bool OctreeUtils::readOctreeFile(QString path, QJsonDocument* doc) { QByteArray data = file.readAll(); QByteArray jsonData; - if (path.endsWith(".json.gz")) { - if (!gunzip(data, jsonData)) { - qCritical() << "json File not in gzip format: " << path; - return false; - } - } else { + if (!gunzip(data, jsonData)) { jsonData = data; } @@ -173,4 +168,5 @@ QByteArray OctreeUtils::RawOctreeData::toGzippedByteArray() { void OctreeUtils::RawOctreeData::resetIdAndVersion() { id = QUuid::createUuid(); version = OctreeUtils::INITIAL_VERSION; + qDebug() << "Reset octree data to: " << id << version; } \ No newline at end of file From 11b7fb89a903f044356b275f45cfeda21e883665 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 6 Feb 2018 15:37:48 -0800 Subject: [PATCH 262/569] Integrate new backup systems --- domain-server/src/BackupHandler.h | 110 ++++++++++++++++++ domain-server/src/BackupSupervisor.cpp | 94 +++++++++------ domain-server/src/BackupSupervisor.h | 66 ++++++++++- .../src/DomainContentBackupManager.cpp | 15 ++- .../src/DomainContentBackupManager.h | 17 +-- domain-server/src/DomainServer.cpp | 20 +--- domain-server/src/DomainServer.h | 2 + 7 files changed, 251 insertions(+), 73 deletions(-) create mode 100644 domain-server/src/BackupHandler.h diff --git a/domain-server/src/BackupHandler.h b/domain-server/src/BackupHandler.h new file mode 100644 index 0000000000..c8e90025f8 --- /dev/null +++ b/domain-server/src/BackupHandler.h @@ -0,0 +1,110 @@ +// +// BackupHandler.h +// assignment-client +// +// Created by Clement Brisset on 2/5/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_BackupHandler_h +#define hifi_BackupHandler_h + +#include + +#include + +#include + +class BackupHandler { +public: + template + BackupHandler(T x) : _self(std::make_shared>(std::move(x))) {} + + void loadBackup(const QuaZip& zip) { + _self->loadBackup(zip); + } + void createBackup(QuaZip& zip) const { + _self->createBackup(zip); + } + void recoverBackup(const QuaZip& zip) const { + _self->recoverBackup(zip); + } + void deleteBackup(const QuaZip& zip) { + _self->deleteBackup(zip); + } + void consolidateBackup(QuaZip& zip) const { + _self->consolidateBackup(zip); + } + +private: + struct Concept { + virtual ~Concept() = default; + + virtual void loadBackup(const QuaZip& zip) = 0; + virtual void createBackup(QuaZip& zip) const = 0; + virtual void recoverBackup(const QuaZip& zip) const = 0; + virtual void deleteBackup(const QuaZip& zip) = 0; + virtual void consolidateBackup(QuaZip& zip) const = 0; + }; + + template + struct Model : Concept { + Model(T x) : data(std::move(x)) {} + + void loadBackup(const QuaZip& zip) { + data.loadBackup(zip); + } + void createBackup(QuaZip& zip) const { + data.createBackup(zip); + } + void recoverBackup(const QuaZip& zip) const { + data.recoverBackup(zip); + } + void deleteBackup(const QuaZip& zip) { + data.deleteBackup(zip); + } + void consolidateBackup(QuaZip& zip) const { + data.consolidateBackup(zip); + } + + T data; + }; + + std::shared_ptr _self; +}; + +#include +class EntitiesBackupHandler { +public: + EntitiesBackupHandler(QString entitiesFilePath) : _entitiesFilePath(entitiesFilePath) {} + + void loadBackup(const QuaZip& zip) {} + + void createBackup(QuaZip& zip) const { + qDebug() << "Creating a backup from handler"; + + QFile entitiesFile { _entitiesFilePath }; + + if (entitiesFile.open(QIODevice::ReadOnly)) { + QuaZipFile zipFile { &zip }; + zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo("models.json.gz", _entitiesFilePath)); + zipFile.write(entitiesFile.readAll()); + zipFile.close(); + if (zipFile.getZipError() != UNZ_OK) { + qDebug() << "testCreate(): outFile.close(): " << zipFile.getZipError(); + } + } + } + + void recoverBackup(const QuaZip& zip) const {} + void deleteBackup(const QuaZip& zip) {} + void consolidateBackup(QuaZip& zip) const {} + +private: + QString _entitiesFilePath; +}; + +#endif /* hifi_BackupHandler_h */ diff --git a/domain-server/src/BackupSupervisor.cpp b/domain-server/src/BackupSupervisor.cpp index 03ad5de558..95fb1c6a6d 100644 --- a/domain-server/src/BackupSupervisor.cpp +++ b/domain-server/src/BackupSupervisor.cpp @@ -40,6 +40,39 @@ BackupSupervisor::BackupSupervisor() { } loadAllBackups(); + + static constexpr int MAPPINGS_REFRESH_INTERVAL = 30 * 1000; + _mappingsRefreshTimer.setInterval(MAPPINGS_REFRESH_INTERVAL); + _mappingsRefreshTimer.setTimerType(Qt::CoarseTimer); + _mappingsRefreshTimer.setSingleShot(false); + QObject::connect(&_mappingsRefreshTimer, &QTimer::timeout, this, &BackupSupervisor::refreshMappings); + _mappingsRefreshTimer.start(); +} + +void BackupSupervisor::refreshMappings() { + auto assetClient = DependencyManager::get(); + auto request = assetClient->createGetAllMappingsRequest(); + + QObject::connect(request, &GetAllMappingsRequest::finished, this, [this](GetAllMappingsRequest* request) { + if (request->getError() == MappingRequest::NoError) { + const auto& mappings = request->getMappings(); + + qDebug() << "Refreshed" << mappings.size() << "asset mappings!"; + + _currentMappings.clear(); + for (const auto& mapping : mappings) { + _currentMappings.insert({ mapping.first, mapping.second.hash }); + } + _lastMappingsRefresh = usecTimestampNow(); + } else { + qCritical() << "Could not refresh asset server mappings."; + qCritical() << " Error:" << request->getErrorString(); + } + + request->deleteLater(); + }); + + request->start(); } void BackupSupervisor::loadAllBackups() { @@ -138,35 +171,26 @@ void BackupSupervisor::backupAssetServer() { return; } - auto assetClient = DependencyManager::get(); - auto request = assetClient->createGetAllMappingsRequest(); + if (_lastMappingsRefresh == 0) { + qWarning() << "Current mappings not yet loaded, "; + return; + } - connect(request, &GetAllMappingsRequest::finished, this, [this](GetAllMappingsRequest* request) { - qDebug() << "Got" << request->getMappings().size() << "mappings!"; - - if (request->getError() != MappingRequest::NoError) { - qCritical() << "Could not complete backup."; - qCritical() << " Error:" << request->getErrorString(); - finishBackup(); - request->deleteLater(); - return; - } - - if (!writeBackupFile(request->getMappings())) { - finishBackup(); - request->deleteLater(); - return; - } - - assert(!_backups.empty()); - const auto& mappings = _backups.back().mappings; - backupMissingFiles(mappings); - - request->deleteLater(); - }); + static constexpr quint64 MAX_REFRESH_TIME = 15 * 60 * 1000 * 1000; + if (usecTimestampNow() - _lastMappingsRefresh > MAX_REFRESH_TIME) { + qWarning() << "Backing up asset mappings that appear old."; + } startBackup(); - request->start(); + + if (!writeBackupFile(_currentMappings)) { + finishBackup(); + return; + } + + assert(!_backups.empty()); + const auto& mappings = _backups.back().mappings; + backupMissingFiles(mappings); } void BackupSupervisor::backupMissingFiles(const AssetUtils::Mappings& mappings) { @@ -193,7 +217,7 @@ void BackupSupervisor::backupNextMissingFile() { auto assetClient = DependencyManager::get(); auto assetRequest = assetClient->createRequest(hash); - connect(assetRequest, &AssetRequest::finished, this, [this](AssetRequest* request) { + QObject::connect(assetRequest, &AssetRequest::finished, this, [this](AssetRequest* request) { if (request->getError() == AssetRequest::NoError) { qDebug() << "Got" << request->getHash(); @@ -213,7 +237,7 @@ void BackupSupervisor::backupNextMissingFile() { assetRequest->start(); } -bool BackupSupervisor::writeBackupFile(const AssetUtils::AssetMappings& mappings) { +bool BackupSupervisor::writeBackupFile(const AssetUtils::Mappings& mappings) { auto filename = MAPPINGS_PREFIX + QDateTime::currentDateTimeUtc().toString(Qt::ISODate) + ".json"; QFile file { PathUtils::getAppDataPath() + BACKUPS_DIR + filename }; if (!file.open(QFile::WriteOnly)) { @@ -224,9 +248,9 @@ bool BackupSupervisor::writeBackupFile(const AssetUtils::AssetMappings& mappings AssetServerBackup backup; QJsonObject jsonObject; for (auto& mapping : mappings) { - backup.mappings[mapping.first] = mapping.second.hash; - _assetsInBackups.insert(mapping.second.hash); - jsonObject.insert(mapping.first, mapping.second.hash); + backup.mappings[mapping.first] = mapping.second; + _assetsInBackups.insert(mapping.second); + jsonObject.insert(mapping.first, mapping.second); } QJsonDocument document(jsonObject); @@ -262,7 +286,7 @@ void BackupSupervisor::restoreAssetServer(int backupIndex) { auto assetClient = DependencyManager::get(); auto request = assetClient->createGetAllMappingsRequest(); - connect(request, &GetAllMappingsRequest::finished, this, [this, backupIndex](GetAllMappingsRequest* request) { + QObject::connect(request, &GetAllMappingsRequest::finished, this, [this, backupIndex](GetAllMappingsRequest* request) { if (request->getError() == MappingRequest::NoError) { const auto& newMappings = _backups.at(backupIndex).mappings; computeServerStateDifference(request->getMappings(), newMappings); @@ -332,7 +356,7 @@ void BackupSupervisor::restoreNextAsset() { auto assetClient = DependencyManager::get(); auto request = assetClient->createUpload(assetFilename); - connect(request, &AssetUpload::finished, this, [this](AssetUpload* request) { + QObject::connect(request, &AssetUpload::finished, this, [this](AssetUpload* request) { if (request->getError() != AssetUpload::NoError) { qCritical() << "Failed to restore asset:" << request->getFilename(); qCritical() << " Error:" << request->getErrorString(); @@ -350,7 +374,7 @@ void BackupSupervisor::updateMappings() { auto assetClient = DependencyManager::get(); for (const auto& mapping : _mappingsLeftToSet) { auto request = assetClient->createSetMappingRequest(mapping.first, mapping.second); - connect(request, &SetMappingRequest::finished, this, [this](SetMappingRequest* request) { + QObject::connect(request, &SetMappingRequest::finished, this, [this](SetMappingRequest* request) { if (request->getError() != MappingRequest::NoError) { qCritical() << "Failed to set mapping:" << request->getPath(); qCritical() << " Error:" << request->getErrorString(); @@ -369,7 +393,7 @@ void BackupSupervisor::updateMappings() { _mappingsLeftToSet.clear(); auto request = assetClient->createDeleteMappingsRequest(_mappingsLeftToDelete); - connect(request, &DeleteMappingsRequest::finished, this, [this](DeleteMappingsRequest* request) { + QObject::connect(request, &DeleteMappingsRequest::finished, this, [this](DeleteMappingsRequest* request) { if (request->getError() != MappingRequest::NoError) { qCritical() << "Failed to delete mappings"; qCritical() << " Error:" << request->getErrorString(); diff --git a/domain-server/src/BackupSupervisor.h b/domain-server/src/BackupSupervisor.h index 067abdc25c..dd293c7fd5 100644 --- a/domain-server/src/BackupSupervisor.h +++ b/domain-server/src/BackupSupervisor.h @@ -16,11 +16,17 @@ #include #include +#include +#include +#include #include +#include #include +class QuaZip; + struct AssetServerBackup { std::string filePath; AssetUtils::Mappings mappings; @@ -42,7 +48,12 @@ public: bool backupInProgress() const { return _backupInProgress; } bool restoreInProgress() const { return _restoreInProgress; } + AssetUtils::Mappings getCurrentMappings() const { return _currentMappings; } + quint64 getLastRefreshTimestamp() const { return _lastMappingsRefresh; } + private: + void refreshMappings(); + void loadAllBackups(); bool loadBackup(const QString& backupFile); @@ -50,7 +61,7 @@ private: void finishBackup() { _backupInProgress = false; } void backupMissingFiles(const AssetUtils::Mappings& mappings); void backupNextMissingFile(); - bool writeBackupFile(const AssetUtils::AssetMappings& mappings); + bool writeBackupFile(const AssetUtils::Mappings& mappings); bool writeAssetFile(const AssetUtils::AssetHash& hash, const QByteArray& data); void startRestore() { _restoreInProgress = true; } @@ -64,6 +75,10 @@ private: QString _backupsDirectory; QString _assetsDirectory; + + quint64 _lastMappingsRefresh { 0 }; + AssetUtils::Mappings _currentMappings; + // Internal storage for backups on disk bool _allBackupsLoadedSuccessfully { false }; std::vector _backups; @@ -80,6 +95,55 @@ private: std::vector> _mappingsLeftToSet; AssetUtils::AssetPathList _mappingsLeftToDelete; int _mappingRequestsInFlight { 0 }; + + QTimer _mappingsRefreshTimer; +}; + + +#include +class AssetsBackupHandler { +public: + AssetsBackupHandler(BackupSupervisor* backupSupervisor) : _backupSupervisor(backupSupervisor) {} + + void loadBackup(const QuaZip& zip) {} + + void createBackup(QuaZip& zip) const { + quint64 lastRefreshTimestamp = _backupSupervisor->getLastRefreshTimestamp(); + AssetUtils::Mappings mappings = _backupSupervisor->getCurrentMappings(); + + if (lastRefreshTimestamp == 0) { + qWarning() << "Current mappings not yet loaded, "; + return; + } + + static constexpr quint64 MAX_REFRESH_TIME = 15 * 60 * 1000 * 1000; + if (usecTimestampNow() - lastRefreshTimestamp > MAX_REFRESH_TIME) { + qWarning() << "Backing up asset mappings that appear old."; + } + + QJsonObject jsonObject; + for (const auto& mapping : mappings) { + jsonObject.insert(mapping.first, mapping.second); + } + QJsonDocument document(jsonObject); + + QuaZipFile zipFile { &zip }; + if (!zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo("mappings.json"))) { + qDebug() << "testCreate(): outFile.open()"; + } + zipFile.write(document.toJson()); + zipFile.close(); + if (zipFile.getZipError() != UNZ_OK) { + qDebug() << "testCreate(): outFile.close(): " << zipFile.getZipError(); + } + } + + void recoverBackup(const QuaZip& zip) const {} + void deleteBackup(const QuaZip& zip) {} + void consolidateBackup(QuaZip& zip) const {} + +private: + BackupSupervisor* _backupSupervisor; }; #endif /* hifi_BackupSupervisor_h */ diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 4f544d7011..39ae63bc16 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -37,8 +37,8 @@ const int DomainContentBackupManager::DEFAULT_PERSIST_INTERVAL = 1000 * 30; // const static QString DATETIME_FORMAT { "yyyy-MM-dd_HH-mm-ss" }; const static QString DATETIME_FORMAT_RE("\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}"); -void DomainContentBackupManager::addCreateBackupHandler(CreateBackupHandler handler) { - _backupHandlers.push_back(handler); +void DomainContentBackupManager::addBackupHandler(BackupHandler handler) { + _backupHandlers.push_back(std::move(handler)); } DomainContentBackupManager::DomainContentBackupManager(const QString& backupDirectory, @@ -48,7 +48,6 @@ DomainContentBackupManager::DomainContentBackupManager(const QString& backupDire : _backupDirectory(backupDirectory), _persistInterval(persistInterval), _initialLoadComplete(false), - _loadTimeUSecs(0), _lastCheck(0), _debugTimestampNow(debugTimestampNow), _lastTimeDebug(0) { @@ -268,14 +267,14 @@ void DomainContentBackupManager::backup() { auto timestamp = QDateTime::currentDateTime().toString(DATETIME_FORMAT); auto fileName = "backup-" + rule.extensionFormat + timestamp + ".zip"; - auto zip = new QuaZip(_backupDirectory + "/" + fileName); - zip->open(QuaZip::mdAdd); + QuaZip zip(_backupDirectory + "/" + fileName); + zip.open(QuaZip::mdAdd); - for (auto& handler : _backupHandlers) { - handler(zip); + for (const auto& handler : _backupHandlers) { + handler.createBackup(zip); } - zip->close(); + zip.close(); qDebug() << "Created backup: " << fileName; diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h index 20408fe486..67fc51f8f3 100644 --- a/domain-server/src/DomainContentBackupManager.h +++ b/domain-server/src/DomainContentBackupManager.h @@ -15,17 +15,11 @@ #define hifi_DomainContentBackupManager_h #include -#include #include -#include -#include +#include -#include - -using BackupResult = std::vector; -using CreateBackupHandler = std::function; -using RecoverBackupHandler = std::function; +#include "BackupHandler.h" class DomainContentBackupManager : public GenericThread { Q_OBJECT @@ -46,9 +40,8 @@ public: int persistInterval = DEFAULT_PERSIST_INTERVAL, bool debugTimestampNow = false); - void addCreateBackupHandler(CreateBackupHandler handler); + void addBackupHandler(BackupHandler handler); bool isInitialLoadComplete() const { return _initialLoadComplete; } - int64_t getLoadElapsedTime() const { return _loadTimeUSecs; } void aboutToFinish(); /// call this to inform the persist thread that the owner is about to finish to support final persist @@ -70,12 +63,10 @@ protected: private: QString _backupDirectory; - std::vector _backupHandlers; + std::vector _backupHandlers; int _persistInterval; bool _initialLoadComplete; - int64_t _loadTimeUSecs; - time_t _lastPersistTime; int64_t _lastCheck; bool _wantBackup{ true }; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 3eb1f21da0..ed14bf3bdc 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -45,6 +45,7 @@ #include #include +#include "BackupSupervisor.h" #include "DomainServerNodeData.h" #include "NodeConnectionData.h" @@ -293,23 +294,10 @@ DomainServer::DomainServer(int argc, char* argv[]) : qCDebug(domain_server) << "Created entities data directory"; } maybeHandleReplacementEntityFile(); - auto entitiesFilePath = getEntitiesFilePath(); + _contentManager.reset(new DomainContentBackupManager(getContentBackupDir(), _settingsManager.responseObjectForType("6")["entity_server_settings"].toObject())); - _contentManager->addCreateBackupHandler([entitiesFilePath](QuaZip* zip) { - qDebug() << "Creating a backup from handler"; - - QFile entitiesFile { entitiesFilePath }; - - if (entitiesFile.open(QIODevice::ReadOnly)) { - QuaZipFile zipFile { zip }; - zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo("models.json.gz", entitiesFilePath)); - zipFile.write(entitiesFile.readAll()); - zipFile.close(); - if (zipFile.getZipError() != UNZ_OK) { - qDebug() << "Failed to write entities file to backup:" << zipFile.getZipError(); - } - } - }); + _contentManager->addBackupHandler(EntitiesBackupHandler(getEntitiesFilePath())); + _contentManager->addBackupHandler(AssetsBackupHandler(&_backupSupervisor)); _contentManager->initialize(true); } diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index ee0350665e..645327225b 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -275,6 +275,8 @@ private: QHash> _pendingOAuthConnections; QThread _assetClientThread; + + BackupSupervisor _backupSupervisor; }; From a6447da64c6fcc0b950564c2fac16cfbbefb611a Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 12 Feb 2018 16:11:42 -0800 Subject: [PATCH 263/569] More Asset Backup work --- domain-server/src/BackupHandler.h | 55 +- domain-server/src/BackupSupervisor.cpp | 489 ++++++++++-------- domain-server/src/BackupSupervisor.h | 96 +--- .../src/DomainContentBackupManager.cpp | 60 ++- .../src/DomainContentBackupManager.h | 20 +- domain-server/src/DomainServer.cpp | 4 +- domain-server/src/DomainServer.h | 2 - 7 files changed, 359 insertions(+), 367 deletions(-) diff --git a/domain-server/src/BackupHandler.h b/domain-server/src/BackupHandler.h index c8e90025f8..5c859165b7 100644 --- a/domain-server/src/BackupHandler.h +++ b/domain-server/src/BackupHandler.h @@ -21,21 +21,21 @@ class BackupHandler { public: template - BackupHandler(T x) : _self(std::make_shared>(std::move(x))) {} + BackupHandler(T* x) : _self(new Model(x)) {} - void loadBackup(const QuaZip& zip) { + void loadBackup(QuaZip& zip) { _self->loadBackup(zip); } - void createBackup(QuaZip& zip) const { + void createBackup(QuaZip& zip) { _self->createBackup(zip); } - void recoverBackup(const QuaZip& zip) const { + void recoverBackup(QuaZip& zip) { _self->recoverBackup(zip); } - void deleteBackup(const QuaZip& zip) { + void deleteBackup(QuaZip& zip) { _self->deleteBackup(zip); } - void consolidateBackup(QuaZip& zip) const { + void consolidateBackup(QuaZip& zip) { _self->consolidateBackup(zip); } @@ -43,37 +43,37 @@ private: struct Concept { virtual ~Concept() = default; - virtual void loadBackup(const QuaZip& zip) = 0; - virtual void createBackup(QuaZip& zip) const = 0; - virtual void recoverBackup(const QuaZip& zip) const = 0; - virtual void deleteBackup(const QuaZip& zip) = 0; - virtual void consolidateBackup(QuaZip& zip) const = 0; + virtual void loadBackup(QuaZip& zip) = 0; + virtual void createBackup(QuaZip& zip) = 0; + virtual void recoverBackup(QuaZip& zip) = 0; + virtual void deleteBackup(QuaZip& zip) = 0; + virtual void consolidateBackup(QuaZip& zip) = 0; }; template struct Model : Concept { - Model(T x) : data(std::move(x)) {} + Model(T* x) : data(x) {} - void loadBackup(const QuaZip& zip) { - data.loadBackup(zip); + void loadBackup(QuaZip& zip) { + data->loadBackup(zip); } - void createBackup(QuaZip& zip) const { - data.createBackup(zip); + void createBackup(QuaZip& zip) { + data->createBackup(zip); } - void recoverBackup(const QuaZip& zip) const { - data.recoverBackup(zip); + void recoverBackup(QuaZip& zip) { + data->recoverBackup(zip); } - void deleteBackup(const QuaZip& zip) { - data.deleteBackup(zip); + void deleteBackup(QuaZip& zip) { + data->deleteBackup(zip); } - void consolidateBackup(QuaZip& zip) const { - data.consolidateBackup(zip); + void consolidateBackup(QuaZip& zip) { + data->consolidateBackup(zip); } - T data; + std::unique_ptr data; }; - std::shared_ptr _self; + std::unique_ptr _self; }; #include @@ -81,12 +81,13 @@ class EntitiesBackupHandler { public: EntitiesBackupHandler(QString entitiesFilePath) : _entitiesFilePath(entitiesFilePath) {} - void loadBackup(const QuaZip& zip) {} + void loadBackup(QuaZip& zip) {} void createBackup(QuaZip& zip) const { qDebug() << "Creating a backup from handler"; QFile entitiesFile { _entitiesFilePath }; + qDebug() << entitiesFile.size(); if (entitiesFile.open(QIODevice::ReadOnly)) { QuaZipFile zipFile { &zip }; @@ -99,8 +100,8 @@ public: } } - void recoverBackup(const QuaZip& zip) const {} - void deleteBackup(const QuaZip& zip) {} + void recoverBackup(QuaZip& zip) const {} + void deleteBackup(QuaZip& zip) {} void consolidateBackup(QuaZip& zip) const {} private: diff --git a/domain-server/src/BackupSupervisor.cpp b/domain-server/src/BackupSupervisor.cpp index 95fb1c6a6d..e0f0378fd4 100644 --- a/domain-server/src/BackupSupervisor.cpp +++ b/domain-server/src/BackupSupervisor.cpp @@ -13,6 +13,9 @@ #include #include +#include + +#include #include #include @@ -20,33 +23,231 @@ #include #include -const QString BACKUPS_DIR = "backups/"; -const QString ASSETS_DIR = "files/"; -const QString MAPPINGS_PREFIX = "mappings-"; +const QString ASSETS_DIR = "/assets/"; +const QString MAPPINGS_FILE = "mappings.json"; using namespace std; -BackupSupervisor::BackupSupervisor() { - _backupsDirectory = PathUtils::getAppDataPath() + BACKUPS_DIR; - QDir backupDir { _backupsDirectory }; - if (!backupDir.exists()) { - backupDir.mkpath("."); - } +Q_DECLARE_LOGGING_CATEGORY(backup_supervisor) +Q_LOGGING_CATEGORY(backup_supervisor, "hifi.backup-supervisor"); - _assetsDirectory = PathUtils::getAppDataPath() + BACKUPS_DIR + ASSETS_DIR; +BackupSupervisor::BackupSupervisor(const QString& backupDirectory) { + _assetsDirectory = backupDirectory + ASSETS_DIR; QDir assetsDir { _assetsDirectory }; if (!assetsDir.exists()) { assetsDir.mkpath("."); } - loadAllBackups(); + refreshAssetsOnDisk(); - static constexpr int MAPPINGS_REFRESH_INTERVAL = 30 * 1000; - _mappingsRefreshTimer.setInterval(MAPPINGS_REFRESH_INTERVAL); _mappingsRefreshTimer.setTimerType(Qt::CoarseTimer); - _mappingsRefreshTimer.setSingleShot(false); + _mappingsRefreshTimer.setSingleShot(true); QObject::connect(&_mappingsRefreshTimer, &QTimer::timeout, this, &BackupSupervisor::refreshMappings); - _mappingsRefreshTimer.start(); + + auto nodeList = DependencyManager::get(); + QObject::connect(nodeList.data(), &LimitedNodeList::nodeAdded, this, [this](SharedNodePointer node) { + if (node->getType() == NodeType::AssetServer) { + // Give the Asset Server some time to bootup. + static constexpr int ASSET_SERVER_BOOTUP_MARGIN = 1 * 1000; + _mappingsRefreshTimer.start(ASSET_SERVER_BOOTUP_MARGIN); + } + }); +} + + +void BackupSupervisor::refreshAssetsOnDisk() { + QDir assetsDir { _assetsDirectory }; + auto assetNames = assetsDir.entryList(QDir::Files); + + // store all valid hashes + copy_if(begin(assetNames), end(assetNames), + inserter(_assetsOnDisk, begin(_assetsOnDisk)), + AssetUtils::isValidHash); + +} + +void BackupSupervisor::refreshAssetsInBackups() { + _assetsInBackups.clear(); + for (const auto& backup : _backups) { + for (const auto& mapping : backup.mappings) { + _assetsInBackups.insert(mapping.second); + } + } +} + +void BackupSupervisor::checkForMissingAssets() { + vector missingAssets; + set_difference(begin(_assetsInBackups), end(_assetsInBackups), + begin(_assetsOnDisk), end(_assetsOnDisk), + back_inserter(missingAssets)); + if (missingAssets.size() > 0) { + qCWarning(backup_supervisor) << "Found" << missingAssets.size() << "assets missing."; + } +} + +void BackupSupervisor::checkForAssetsToDelete() { + vector deprecatedAssets; + set_difference(begin(_assetsOnDisk), end(_assetsOnDisk), + begin(_assetsInBackups), end(_assetsInBackups), + back_inserter(deprecatedAssets)); + + if (deprecatedAssets.size() > 0) { + qCDebug(backup_supervisor) << "Found" << deprecatedAssets.size() << "assets to delete."; + if (_allBackupsLoadedSuccessfully) { + for (const auto& hash : deprecatedAssets) { + QFile::remove(_assetsDirectory + hash); + } + } else { + qCWarning(backup_supervisor) << "Some backups did not load properly, aborting deleting for safety."; + } + } +} + +void BackupSupervisor::loadBackup(QuaZip& zip) { + _backups.push_back({ zip.getZipName().toStdString(), {}, false }); + auto& backup = _backups.back(); + + if (!zip.setCurrentFile(MAPPINGS_FILE)) { + qCCritical(backup_supervisor) << "Failed to find" << MAPPINGS_FILE << "while recovering backup"; + qCCritical(backup_supervisor) << " Error:" << zip.getZipError(); + backup.corruptedBackup = true; + _allBackupsLoadedSuccessfully = false; + return; + } + + QuaZipFile zipFile { &zip }; + if (!zipFile.open(QFile::ReadOnly)) { + qCCritical(backup_supervisor) << "Could not open backup file:" << zip.getZipName(); + qCCritical(backup_supervisor) << " Error:" << zip.getZipError(); + backup.corruptedBackup = true; + _allBackupsLoadedSuccessfully = false; + return; + } + + QJsonParseError error; + auto document = QJsonDocument::fromJson(zipFile.readAll(), &error); + if (document.isNull() || !document.isObject()) { + qCCritical(backup_supervisor) << "Could not parse backup file to JSON object:" << zip.getZipName(); + qCCritical(backup_supervisor) << " Error:" << error.errorString(); + backup.corruptedBackup = true; + _allBackupsLoadedSuccessfully = false; + return; + } + + auto jsonObject = document.object(); + for (auto it = begin(jsonObject); it != end(jsonObject); ++it) { + const auto& assetPath = it.key(); + const auto& assetHash = it.value().toString(); + + if (!AssetUtils::isValidHash(assetHash)) { + qCCritical(backup_supervisor) << "Corrupted mapping in backup file" << zip.getZipName() << ":" << it.key(); + backup.corruptedBackup = true; + _allBackupsLoadedSuccessfully = false; + return; + } + + backup.mappings[assetPath] = assetHash; + _assetsInBackups.insert(assetHash); + } + + return; +} + +void BackupSupervisor::createBackup(QuaZip& zip) { + qDebug() << Q_FUNC_INFO; + if (operationInProgress()) { + qCWarning(backup_supervisor) << "There is already an operation in progress."; + return; + } + + if (_lastMappingsRefresh == 0) { + qCWarning(backup_supervisor) << "Current mappings not yet loaded."; + return; + } + + static constexpr quint64 MAX_REFRESH_TIME = 15 * 60 * 1000 * 1000; + if (usecTimestampNow() - _lastMappingsRefresh > MAX_REFRESH_TIME) { + qCWarning(backup_supervisor) << "Backing up asset mappings that appear old."; + } + + AssetServerBackup backup; + backup.filePath = zip.getZipName().toStdString(); + + QJsonObject jsonObject; + for (const auto& mapping : _currentMappings) { + backup.mappings[mapping.first] = mapping.second; + _assetsInBackups.insert(mapping.second); + jsonObject.insert(mapping.first, mapping.second); + } + QJsonDocument document(jsonObject); + + QuaZipFile zipFile { &zip }; + if (!zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(MAPPINGS_FILE))) { + qCDebug(backup_supervisor) << "testCreate(): outFile.open()"; + return; + } + zipFile.write(document.toJson()); + zipFile.close(); + if (zipFile.getZipError() != UNZ_OK) { + qCDebug(backup_supervisor) << "testCreate(): outFile.close(): " << zipFile.getZipError(); + return; + } + _backups.push_back(backup); +} + +void BackupSupervisor::recoverBackup(QuaZip& zip) { + if (operationInProgress()) { + qCWarning(backup_supervisor) << "There is already a backup/restore in progress."; + return; + } + + if (_lastMappingsRefresh == 0) { + qCWarning(backup_supervisor) << "Current mappings not yet loaded."; + return; + } + + static constexpr quint64 MAX_REFRESH_TIME = 15 * 60 * 1000 * 1000; + if (usecTimestampNow() - _lastMappingsRefresh > MAX_REFRESH_TIME) { + qCWarning(backup_supervisor) << "Backing up asset mappings that appear old."; + } + + startOperation(); + + auto it = find_if(begin(_backups), end(_backups), [&](const std::vector::value_type& value) { + return value.filePath == zip.getZipName().toStdString(); + }); + if (it == end(_backups)) { + qCDebug(backup_supervisor) << "Could not find backup"; + stopOperation(); + return; + } + + const auto& newMappings = it->mappings; + computeServerStateDifference(_currentMappings, newMappings); + + restoreAllAssets(); +} + +void BackupSupervisor::deleteBackup(QuaZip& zip) { + if (operationInProgress()) { + qCWarning(backup_supervisor) << "There is a backup/restore in progress."; + return; + } + + auto it = find_if(begin(_backups), end(_backups), [&](const std::vector::value_type& value) { + return value.filePath == zip.getZipName().toStdString(); + }); + if (it == end(_backups)) { + qCDebug(backup_supervisor) << "Could not find backup"; + return; + } + + refreshAssetsInBackups(); + checkForAssetsToDelete(); +} + +void BackupSupervisor::consolidateBackup(QuaZip& zip) { + } void BackupSupervisor::refreshMappings() { @@ -57,179 +258,69 @@ void BackupSupervisor::refreshMappings() { if (request->getError() == MappingRequest::NoError) { const auto& mappings = request->getMappings(); - qDebug() << "Refreshed" << mappings.size() << "asset mappings!"; + qCDebug(backup_supervisor) << "Refreshed" << mappings.size() << "asset mappings!"; _currentMappings.clear(); for (const auto& mapping : mappings) { _currentMappings.insert({ mapping.first, mapping.second.hash }); } _lastMappingsRefresh = usecTimestampNow(); + + downloadMissingFiles(_currentMappings); } else { - qCritical() << "Could not refresh asset server mappings."; - qCritical() << " Error:" << request->getErrorString(); + qCCritical(backup_supervisor) << "Could not refresh asset server mappings."; + qCCritical(backup_supervisor) << " Error:" << request->getErrorString(); } request->deleteLater(); + + // Launch next mappings request + static constexpr int MAPPINGS_REFRESH_INTERVAL = 30 * 1000; + _mappingsRefreshTimer.start(MAPPINGS_REFRESH_INTERVAL); }); request->start(); } -void BackupSupervisor::loadAllBackups() { - _backups.clear(); - _assetsInBackups.clear(); - _assetsOnDisk.clear(); - _allBackupsLoadedSuccessfully = true; +void BackupSupervisor::downloadMissingFiles(const AssetUtils::Mappings& mappings) { + auto wasEmpty = _assetsLeftToRequest.empty(); - QDir assetsDir { _assetsDirectory }; - auto assetNames = assetsDir.entryList(QDir::Files); - qDebug() << "Loading" << assetNames.size() << "assets."; - - // store all valid hashes - copy_if(begin(assetNames), end(assetNames), - inserter(_assetsOnDisk, begin(_assetsOnDisk)), AssetUtils::isValidHash); - - QDir backupsDir { _backupsDirectory }; - auto files = backupsDir.entryList({ MAPPINGS_PREFIX + "*.json" }, QDir::Files); - qDebug() << "Loading" << files.size() << "backups."; - - for (const auto& fileName : files) { - auto filePath = backupsDir.filePath(fileName); - auto success = loadBackup(filePath); - if (!success) { - qCritical() << "Failed to load backup file" << filePath; - _allBackupsLoadedSuccessfully = false; - } - } - - vector missingAssets; - set_difference(begin(_assetsInBackups), end(_assetsInBackups), - begin(_assetsOnDisk), end(_assetsOnDisk), - back_inserter(missingAssets)); - if (missingAssets.size() > 0) { - qWarning() << "Found" << missingAssets.size() << "assets missing."; - } - - vector deprecatedAssets; - set_difference(begin(_assetsOnDisk), end(_assetsOnDisk), - begin(_assetsInBackups), end(_assetsInBackups), - back_inserter(deprecatedAssets)); - - if (deprecatedAssets.size() > 0) { - qDebug() << "Found" << deprecatedAssets.size() << "assets to delete."; - if (_allBackupsLoadedSuccessfully) { - for (const auto& hash : deprecatedAssets) { - QFile::remove(_assetsDirectory + hash); - } - } else { - qWarning() << "Some backups did not load properly, aborting deleting for safety."; - } - } -} - -bool BackupSupervisor::loadBackup(const QString& backupFile) { - _backups.push_back({ backupFile.toStdString(), {}, false }); - auto& backup = _backups.back(); - - QFile file { backupFile }; - if (!file.open(QFile::ReadOnly)) { - qCritical() << "Could not open backup file:" << backupFile; - backup.corruptedBackup = true; - return false; - } - QJsonParseError error; - auto document = QJsonDocument::fromJson(file.readAll(), &error); - if (document.isNull() || !document.isObject()) { - qCritical() << "Could not parse backup file to JSON object:" << backupFile; - qCritical() << " Error:" << error.errorString(); - backup.corruptedBackup = true; - return false; - } - - auto jsonObject = document.object(); - for (auto it = begin(jsonObject); it != end(jsonObject); ++it) { - const auto& assetPath = it.key(); - const auto& assetHash = it.value().toString(); - - if (!AssetUtils::isValidHash(assetHash)) { - qCritical() << "Corrupted mapping in backup file" << backupFile << ":" << it.key(); - backup.corruptedBackup = true; - return false; - } - - backup.mappings[assetPath] = assetHash; - _assetsInBackups.insert(assetHash); - } - - _backups.push_back(backup); - return true; -} - -void BackupSupervisor::backupAssetServer() { - if (backupInProgress() || restoreInProgress()) { - qWarning() << "There is already a backup/restore in progress."; - return; - } - - if (_lastMappingsRefresh == 0) { - qWarning() << "Current mappings not yet loaded, "; - return; - } - - static constexpr quint64 MAX_REFRESH_TIME = 15 * 60 * 1000 * 1000; - if (usecTimestampNow() - _lastMappingsRefresh > MAX_REFRESH_TIME) { - qWarning() << "Backing up asset mappings that appear old."; - } - - startBackup(); - - if (!writeBackupFile(_currentMappings)) { - finishBackup(); - return; - } - - assert(!_backups.empty()); - const auto& mappings = _backups.back().mappings; - backupMissingFiles(mappings); -} - -void BackupSupervisor::backupMissingFiles(const AssetUtils::Mappings& mappings) { - _assetsLeftToRequest.reserve(mappings.size()); - for (auto& mapping : mappings) { + for (const auto& mapping : mappings) { const auto& hash = mapping.second; if (_assetsOnDisk.find(hash) == end(_assetsOnDisk)) { - _assetsLeftToRequest.push_back(hash); + _assetsLeftToRequest.insert(hash); } } - backupNextMissingFile(); + // If we were empty, that means no download chain was already going, start one. + if (wasEmpty) { + downloadNextMissingFile(); + } } -void BackupSupervisor::backupNextMissingFile() { +void BackupSupervisor::downloadNextMissingFile() { if (_assetsLeftToRequest.empty()) { - finishBackup(); return; } - - auto hash = _assetsLeftToRequest.back(); - _assetsLeftToRequest.pop_back(); + auto hash = *begin(_assetsLeftToRequest); auto assetClient = DependencyManager::get(); auto assetRequest = assetClient->createRequest(hash); QObject::connect(assetRequest, &AssetRequest::finished, this, [this](AssetRequest* request) { if (request->getError() == AssetRequest::NoError) { - qDebug() << "Got" << request->getHash(); + qCDebug(backup_supervisor) << "Backing up asset" << request->getHash(); bool success = writeAssetFile(request->getHash(), request->getData()); if (!success) { - qCritical() << "Failed to write asset file" << request->getHash(); + qCCritical(backup_supervisor) << "Failed to write asset file" << request->getHash(); } } else { - qCritical() << "Failed to backup asset" << request->getHash(); + qCCritical(backup_supervisor) << "Failed to backup asset" << request->getHash(); } - backupNextMissingFile(); + _assetsLeftToRequest.erase(request->getHash()); + downloadNextMissingFile(); request->deleteLater(); }); @@ -237,73 +328,27 @@ void BackupSupervisor::backupNextMissingFile() { assetRequest->start(); } -bool BackupSupervisor::writeBackupFile(const AssetUtils::Mappings& mappings) { - auto filename = MAPPINGS_PREFIX + QDateTime::currentDateTimeUtc().toString(Qt::ISODate) + ".json"; - QFile file { PathUtils::getAppDataPath() + BACKUPS_DIR + filename }; - if (!file.open(QFile::WriteOnly)) { - qCritical() << "Could not open backup file" << file.fileName(); - return false; - } - - AssetServerBackup backup; - QJsonObject jsonObject; - for (auto& mapping : mappings) { - backup.mappings[mapping.first] = mapping.second; - _assetsInBackups.insert(mapping.second); - jsonObject.insert(mapping.first, mapping.second); - } - - QJsonDocument document(jsonObject); - file.write(document.toJson()); - - backup.filePath = file.fileName().toStdString(); - _backups.push_back(backup); - - return true; -} - bool BackupSupervisor::writeAssetFile(const AssetUtils::AssetHash& hash, const QByteArray& data) { QDir assetsDir { _assetsDirectory }; QFile file { assetsDir.filePath(hash) }; if (!file.open(QFile::WriteOnly)) { - qCritical() << "Could not open backup file" << file.fileName(); + qCCritical(backup_supervisor) << "Could not open backup file" << file.fileName(); return false; } - file.write(data); + auto bytesWritten = file.write(data); + if (bytesWritten != data.size()) { + qCCritical(backup_supervisor) << "Could not write data to file" << file.fileName(); + file.remove(); + return false; + } _assetsOnDisk.insert(hash); return true; } -void BackupSupervisor::restoreAssetServer(int backupIndex) { - if (backupInProgress() || restoreInProgress()) { - qWarning() << "There is already a backup/restore in progress."; - return; - } - - auto assetClient = DependencyManager::get(); - auto request = assetClient->createGetAllMappingsRequest(); - - QObject::connect(request, &GetAllMappingsRequest::finished, this, [this, backupIndex](GetAllMappingsRequest* request) { - if (request->getError() == MappingRequest::NoError) { - const auto& newMappings = _backups.at(backupIndex).mappings; - computeServerStateDifference(request->getMappings(), newMappings); - - restoreAllAssets(); - } else { - finishRestore(); - } - - request->deleteLater(); - }); - - startRestore(); - request->start(); -} - -void BackupSupervisor::computeServerStateDifference(const AssetUtils::AssetMappings& currentMappings, +void BackupSupervisor::computeServerStateDifference(const AssetUtils::Mappings& currentMappings, const AssetUtils::Mappings& newMappings) { _mappingsLeftToSet.reserve((int)newMappings.size()); _assetsLeftToUpload.reserve((int)newMappings.size()); @@ -312,7 +357,7 @@ void BackupSupervisor::computeServerStateDifference(const AssetUtils::AssetMappi set currentAssets; for (const auto& currentMapping : currentMappings) { const auto& currentPath = currentMapping.first; - const auto& currentHash = currentMapping.second.hash; + const auto& currentHash = currentMapping.second; if (newMappings.find(currentPath) == end(newMappings)) { _mappingsLeftToDelete.push_back(currentPath); @@ -325,7 +370,7 @@ void BackupSupervisor::computeServerStateDifference(const AssetUtils::AssetMappi const auto& newHash = newMapping.second; auto it = currentMappings.find(newPath); - if (it == end(currentMappings) || it->second.hash != newHash) { + if (it == end(currentMappings) || it->second != newHash) { _mappingsLeftToSet.push_back({ newPath, newHash }); } if (currentAssets.find(newHash) == end(currentAssets)) { @@ -333,9 +378,9 @@ void BackupSupervisor::computeServerStateDifference(const AssetUtils::AssetMappi } } - qDebug() << "Mappings to set:" << _mappingsLeftToSet.size(); - qDebug() << "Mappings to del:" << _mappingsLeftToDelete.size(); - qDebug() << "Assets to upload:" << _assetsLeftToUpload.size(); + qCDebug(backup_supervisor) << "Mappings to set:" << _mappingsLeftToSet.size(); + qCDebug(backup_supervisor) << "Mappings to del:" << _mappingsLeftToDelete.size(); + qCDebug(backup_supervisor) << "Assets to upload:" << _assetsLeftToUpload.size(); } void BackupSupervisor::restoreAllAssets() { @@ -358,8 +403,8 @@ void BackupSupervisor::restoreNextAsset() { QObject::connect(request, &AssetUpload::finished, this, [this](AssetUpload* request) { if (request->getError() != AssetUpload::NoError) { - qCritical() << "Failed to restore asset:" << request->getFilename(); - qCritical() << " Error:" << request->getErrorString(); + qCCritical(backup_supervisor) << "Failed to restore asset:" << request->getFilename(); + qCCritical(backup_supervisor) << " Error:" << request->getErrorString(); } restoreNextAsset(); @@ -376,12 +421,12 @@ void BackupSupervisor::updateMappings() { auto request = assetClient->createSetMappingRequest(mapping.first, mapping.second); QObject::connect(request, &SetMappingRequest::finished, this, [this](SetMappingRequest* request) { if (request->getError() != MappingRequest::NoError) { - qCritical() << "Failed to set mapping:" << request->getPath(); - qCritical() << " Error:" << request->getErrorString(); + qCCritical(backup_supervisor) << "Failed to set mapping:" << request->getPath(); + qCCritical(backup_supervisor) << " Error:" << request->getErrorString(); } if (--_mappingRequestsInFlight == 0) { - finishRestore(); + stopOperation(); } request->deleteLater(); @@ -395,12 +440,12 @@ void BackupSupervisor::updateMappings() { auto request = assetClient->createDeleteMappingsRequest(_mappingsLeftToDelete); QObject::connect(request, &DeleteMappingsRequest::finished, this, [this](DeleteMappingsRequest* request) { if (request->getError() != MappingRequest::NoError) { - qCritical() << "Failed to delete mappings"; - qCritical() << " Error:" << request->getErrorString(); + qCCritical(backup_supervisor) << "Failed to delete mappings"; + qCCritical(backup_supervisor) << " Error:" << request->getErrorString(); } if (--_mappingRequestsInFlight == 0) { - finishRestore(); + stopOperation(); } request->deleteLater(); @@ -410,15 +455,3 @@ void BackupSupervisor::updateMappings() { request->start(); ++_mappingRequestsInFlight; } -bool BackupSupervisor::deleteBackup(int backupIndex) { - if (backupInProgress() || restoreInProgress()) { - qWarning() << "There is a backup/restore in progress."; - return false; - } - const auto& filePath = _backups.at(backupIndex).filePath; - auto success = QFile::remove(filePath.c_str()); - - loadAllBackups(); - - return success; -} diff --git a/domain-server/src/BackupSupervisor.h b/domain-server/src/BackupSupervisor.h index dd293c7fd5..a89be66742 100644 --- a/domain-server/src/BackupSupervisor.h +++ b/domain-server/src/BackupSupervisor.h @@ -37,48 +37,45 @@ class BackupSupervisor : public QObject { Q_OBJECT public: - BackupSupervisor(); + BackupSupervisor(const QString& backupDirectory); - void backupAssetServer(); - void restoreAssetServer(int backupIndex); - bool deleteBackup(int backupIndex); + void loadBackup(QuaZip& zip); + void createBackup(QuaZip& zip); + void recoverBackup(QuaZip& zip); + void deleteBackup(QuaZip& zip); + void consolidateBackup(QuaZip& zip); - const std::vector& getBackups() const { return _backups; }; - - bool backupInProgress() const { return _backupInProgress; } - bool restoreInProgress() const { return _restoreInProgress; } - - AssetUtils::Mappings getCurrentMappings() const { return _currentMappings; } - quint64 getLastRefreshTimestamp() const { return _lastMappingsRefresh; } + bool operationInProgress() const { return _operationInProgress; } private: void refreshMappings(); - void loadAllBackups(); - bool loadBackup(const QString& backupFile); + void refreshAssetsInBackups(); + void refreshAssetsOnDisk(); + void checkForMissingAssets(); + void checkForAssetsToDelete(); - void startBackup() { _backupInProgress = true; } - void finishBackup() { _backupInProgress = false; } - void backupMissingFiles(const AssetUtils::Mappings& mappings); - void backupNextMissingFile(); - bool writeBackupFile(const AssetUtils::Mappings& mappings); + void startOperation() { _operationInProgress = true; } + void stopOperation() { _operationInProgress = false; } + + void downloadMissingFiles(const AssetUtils::Mappings& mappings); + void downloadNextMissingFile(); bool writeAssetFile(const AssetUtils::AssetHash& hash, const QByteArray& data); - void startRestore() { _restoreInProgress = true; } - void finishRestore() { _restoreInProgress = false; } - void computeServerStateDifference(const AssetUtils::AssetMappings& currentMappings, + void computeServerStateDifference(const AssetUtils::Mappings& currentMappings, const AssetUtils::Mappings& newMappings); void restoreAllAssets(); void restoreNextAsset(); void updateMappings(); - QString _backupsDirectory; QString _assetsDirectory; - + QTimer _mappingsRefreshTimer; quint64 _lastMappingsRefresh { 0 }; AssetUtils::Mappings _currentMappings; + bool _operationInProgress { false }; + // Internal storage for backups on disk bool _allBackupsLoadedSuccessfully { false }; std::vector _backups; @@ -86,64 +83,13 @@ private: std::set _assetsOnDisk; // Internal storage for backup in progress - bool _backupInProgress { false }; - std::vector _assetsLeftToRequest; + std::set _assetsLeftToRequest; // Internal storage for restore in progress - bool _restoreInProgress { false }; std::vector _assetsLeftToUpload; std::vector> _mappingsLeftToSet; AssetUtils::AssetPathList _mappingsLeftToDelete; int _mappingRequestsInFlight { 0 }; - - QTimer _mappingsRefreshTimer; -}; - - -#include -class AssetsBackupHandler { -public: - AssetsBackupHandler(BackupSupervisor* backupSupervisor) : _backupSupervisor(backupSupervisor) {} - - void loadBackup(const QuaZip& zip) {} - - void createBackup(QuaZip& zip) const { - quint64 lastRefreshTimestamp = _backupSupervisor->getLastRefreshTimestamp(); - AssetUtils::Mappings mappings = _backupSupervisor->getCurrentMappings(); - - if (lastRefreshTimestamp == 0) { - qWarning() << "Current mappings not yet loaded, "; - return; - } - - static constexpr quint64 MAX_REFRESH_TIME = 15 * 60 * 1000 * 1000; - if (usecTimestampNow() - lastRefreshTimestamp > MAX_REFRESH_TIME) { - qWarning() << "Backing up asset mappings that appear old."; - } - - QJsonObject jsonObject; - for (const auto& mapping : mappings) { - jsonObject.insert(mapping.first, mapping.second); - } - QJsonDocument document(jsonObject); - - QuaZipFile zipFile { &zip }; - if (!zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo("mappings.json"))) { - qDebug() << "testCreate(): outFile.open()"; - } - zipFile.write(document.toJson()); - zipFile.close(); - if (zipFile.getZipError() != UNZ_OK) { - qDebug() << "testCreate(): outFile.close(): " << zipFile.getZipError(); - } - } - - void recoverBackup(const QuaZip& zip) const {} - void deleteBackup(const QuaZip& zip) {} - void consolidateBackup(QuaZip& zip) const {} - -private: - BackupSupervisor* _backupSupervisor; }; #endif /* hifi_BackupSupervisor_h */ diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 39ae63bc16..7c1c7f2cd7 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -47,10 +47,7 @@ DomainContentBackupManager::DomainContentBackupManager(const QString& backupDire bool debugTimestampNow) : _backupDirectory(backupDirectory), _persistInterval(persistInterval), - _initialLoadComplete(false), - _lastCheck(0), - _debugTimestampNow(debugTimestampNow), - _lastTimeDebug(0) { + _lastCheck(usecTimestampNow()) { parseSettings(settings); } @@ -101,7 +98,7 @@ void DomainContentBackupManager::parseSettings(const QJsonObject& settings) { qCDebug(domain_server) << " lastBackup: NEVER"; } - _backupRules << newRule; + _backupRules.push_back(newRule); } } else { qCDebug(domain_server) << "BACKUP RULES: NONE"; @@ -123,6 +120,10 @@ int64_t DomainContentBackupManager::getMostRecentBackupTimeInSecs(const QString& return mostRecentBackupInSecs; } +void DomainContentBackupManager::setup() { + load(); +} + bool DomainContentBackupManager::process() { if (isStillRunning()) { constexpr int64_t MSECS_TO_USECS = 1000; @@ -139,18 +140,6 @@ bool DomainContentBackupManager::process() { } } - // if we were asked to debugTimestampNow do that now... - if (_debugTimestampNow) { - - quint64 now = usecTimestampNow(); - quint64 sinceLastDebug = now - _lastTimeDebug; - quint64 DEBUG_TIMESTAMP_INTERVAL = 600000000; // every 10 minutes - - if (sinceLastDebug > DEBUG_TIMESTAMP_INTERVAL) { - _lastTimeDebug = usecTimestampNow(true); // ask for debug output - } - } - return isStillRunning(); } @@ -250,6 +239,36 @@ void DomainContentBackupManager::removeOldBackupVersions(const BackupRule& rule) } } +void DomainContentBackupManager::load() { + QDir backupDir { _backupDirectory }; + if (backupDir.exists()) { + + auto matchingFiles = backupDir.entryInfoList({ "backup-*.zip" }, QDir::Files | QDir::NoSymLinks, QDir::Name); + + for (const auto& file : matchingFiles) { + QFile backupFile { file.absoluteFilePath() }; + if (!backupFile.open(QIODevice::ReadOnly)) { + qCritical() << "Could not open file:" << file.absoluteFilePath(); + qCritical() << " ERROR:" << backupFile.errorString(); + continue; + } + + QuaZip zip { &backupFile }; + if (!zip.open(QuaZip::mdUnzip)) { + qCritical() << "Could not open backup archive:" << file.absoluteFilePath(); + qCritical() << " ERROR:" << zip.getZipError(); + continue; + } + + for (auto& handler : _backupHandlers) { + handler.loadBackup(zip); + } + + zip.close(); + } + } +} + void DomainContentBackupManager::backup() { auto nowDateTime = QDateTime::currentDateTime(); auto nowSeconds = nowDateTime.toSecsSinceEpoch(); @@ -268,9 +287,12 @@ void DomainContentBackupManager::backup() { auto timestamp = QDateTime::currentDateTime().toString(DATETIME_FORMAT); auto fileName = "backup-" + rule.extensionFormat + timestamp + ".zip"; QuaZip zip(_backupDirectory + "/" + fileName); - zip.open(QuaZip::mdAdd); + if (!zip.open(QuaZip::mdAdd)) { + qDebug() << "Could not open archive"; + } - for (const auto& handler : _backupHandlers) { + for (auto& handler : _backupHandlers) { + qDebug() << "Backup handler"; handler.createBackup(zip); } diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h index 67fc51f8f3..bb1d4f0116 100644 --- a/domain-server/src/DomainContentBackupManager.h +++ b/domain-server/src/DomainContentBackupManager.h @@ -41,20 +41,18 @@ public: bool debugTimestampNow = false); void addBackupHandler(BackupHandler handler); - bool isInitialLoadComplete() const { return _initialLoadComplete; } void aboutToFinish(); /// call this to inform the persist thread that the owner is about to finish to support final persist void replaceData(QByteArray data); -signals: - void loadCompleted(); - protected: /// Implements generic processing behavior for this thread. - bool process() override; + virtual void setup() override; + virtual bool process() override; void persist(); + void load(); void backup(); void removeOldBackupVersions(const BackupRule& rule); bool getMostRecentBackup(const QString& format, QString& mostRecentBackupFileName, QDateTime& mostRecentBackupTime); @@ -64,16 +62,10 @@ protected: private: QString _backupDirectory; std::vector _backupHandlers; - int _persistInterval; - bool _initialLoadComplete; + int _persistInterval { 0 }; - time_t _lastPersistTime; - int64_t _lastCheck; - bool _wantBackup{ true }; - QVector _backupRules; - - bool _debugTimestampNow; - int64_t _lastTimeDebug; + int64_t _lastCheck { 0 }; + std::vector _backupRules; }; #endif // hifi_DomainContentBackupManager_h diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index ed14bf3bdc..06d3549ff8 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -296,8 +296,8 @@ DomainServer::DomainServer(int argc, char* argv[]) : maybeHandleReplacementEntityFile(); _contentManager.reset(new DomainContentBackupManager(getContentBackupDir(), _settingsManager.responseObjectForType("6")["entity_server_settings"].toObject())); - _contentManager->addBackupHandler(EntitiesBackupHandler(getEntitiesFilePath())); - _contentManager->addBackupHandler(AssetsBackupHandler(&_backupSupervisor)); + _contentManager->addBackupHandler(new EntitiesBackupHandler(getEntitiesFilePath())); + _contentManager->addBackupHandler(new BackupSupervisor(getContentBackupDir())); _contentManager->initialize(true); } diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 645327225b..ee0350665e 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -275,8 +275,6 @@ private: QHash> _pendingOAuthConnections; QThread _assetClientThread; - - BackupSupervisor _backupSupervisor; }; From 272f95efa294f24ddf91cc1b36af5cf278557925 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 13 Feb 2018 18:13:07 -0800 Subject: [PATCH 264/569] Specify wich packet can ignore verification at DS level --- libraries/networking/src/LimitedNodeList.cpp | 6 ++++-- libraries/networking/src/udt/PacketHeaders.h | 17 +++++++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 3516fe948a..9dbbc570dd 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -315,8 +315,10 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe } if (sourceNode) { - if (!PacketTypeEnum::getNonVerifiedPackets().contains(headerType) && - !isDomainServer()) { + bool verifiedPacket = !PacketTypeEnum::getNonVerifiedPackets().contains(headerType); + bool ignoreVerification = isDomainServer() && PacketTypeEnum::getDomainIgnoredVerificationPackets().contains(headerType); + + if (verifiedPacket && !ignoreVerification) { QByteArray packetHeaderHash = NLPacket::verificationHashInHeader(packet); QByteArray expectedHash = NLPacket::hashForPacketAndSecret(packet, sourceNode->getConnectionSecret()); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 7cd02608a1..b263823fa4 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -187,14 +187,19 @@ public: const static QSet getDomainSourcedPackets() { const static QSet DOMAIN_SOURCED_PACKETS = QSet() - << PacketTypeEnum::Value::AssetMappingOperation - << PacketTypeEnum::Value::AssetMappingOperationReply - << PacketTypeEnum::Value::AssetGet - << PacketTypeEnum::Value::AssetGetReply - << PacketTypeEnum::Value::AssetUpload - << PacketTypeEnum::Value::AssetUploadReply; + << PacketTypeEnum::Value::AssetMappingOperation + << PacketTypeEnum::Value::AssetGet + << PacketTypeEnum::Value::AssetUpload; return DOMAIN_SOURCED_PACKETS; } + + const static QSet getDomainIgnoredVerificationPackets() { + const static QSet DOMAIN_IGNORED_VERIFICATION_PACKETS = QSet() + << PacketTypeEnum::Value::AssetMappingOperationReply + << PacketTypeEnum::Value::AssetGetReply + << PacketTypeEnum::Value::AssetUploadReply; + return DOMAIN_IGNORED_VERIFICATION_PACKETS; + } }; using PacketType = PacketTypeEnum::Value; From c41ad1a699c79f6e9a1ec2bd7b6dbe7a2a0ba33f Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 13 Feb 2018 15:59:51 -0800 Subject: [PATCH 265/569] Add consolidate --- domain-server/src/BackupSupervisor.cpp | 46 +++++++++++++++++-- domain-server/src/BackupSupervisor.h | 2 +- .../src/DomainContentBackupManager.cpp | 28 +++++++++++ .../src/DomainContentBackupManager.h | 1 + 4 files changed, 72 insertions(+), 5 deletions(-) diff --git a/domain-server/src/BackupSupervisor.cpp b/domain-server/src/BackupSupervisor.cpp index e0f0378fd4..4cb42787ba 100644 --- a/domain-server/src/BackupSupervisor.cpp +++ b/domain-server/src/BackupSupervisor.cpp @@ -104,7 +104,7 @@ void BackupSupervisor::checkForAssetsToDelete() { } void BackupSupervisor::loadBackup(QuaZip& zip) { - _backups.push_back({ zip.getZipName().toStdString(), {}, false }); + _backups.push_back({ zip.getZipName(), {}, false }); auto& backup = _backups.back(); if (!zip.setCurrentFile(MAPPINGS_FILE)) { @@ -171,7 +171,7 @@ void BackupSupervisor::createBackup(QuaZip& zip) { } AssetServerBackup backup; - backup.filePath = zip.getZipName().toStdString(); + backup.filePath = zip.getZipName(); QJsonObject jsonObject; for (const auto& mapping : _currentMappings) { @@ -214,7 +214,7 @@ void BackupSupervisor::recoverBackup(QuaZip& zip) { startOperation(); auto it = find_if(begin(_backups), end(_backups), [&](const std::vector::value_type& value) { - return value.filePath == zip.getZipName().toStdString(); + return value.filePath == zip.getZipName(); }); if (it == end(_backups)) { qCDebug(backup_supervisor) << "Could not find backup"; @@ -235,7 +235,7 @@ void BackupSupervisor::deleteBackup(QuaZip& zip) { } auto it = find_if(begin(_backups), end(_backups), [&](const std::vector::value_type& value) { - return value.filePath == zip.getZipName().toStdString(); + return value.filePath == zip.getZipName(); }); if (it == end(_backups)) { qCDebug(backup_supervisor) << "Could not find backup"; @@ -247,6 +247,44 @@ void BackupSupervisor::deleteBackup(QuaZip& zip) { } void BackupSupervisor::consolidateBackup(QuaZip& zip) { + if (operationInProgress()) { + qCWarning(backup_supervisor) << "There is a backup/restore in progress."; + return; + } + QFileInfo zipInfo(zip.getZipName()); + + auto it = find_if(begin(_backups), end(_backups), [&](const std::vector::value_type& value) { + QFileInfo info(value.filePath); + return info.fileName() == zipInfo.fileName(); + }); + if (it == end(_backups)) { + qCDebug(backup_supervisor) << "Could not find backup" << zip.getZipName(); + return; + } + + for (const auto& mapping : it->mappings) { + const auto& hash = mapping.second; + + QDir assetsDir { _assetsDirectory }; + QFile file { assetsDir.filePath(hash) }; + if (!file.open(QFile::ReadOnly)) { + qCCritical(backup_supervisor) << "Could not open asset file" << file.fileName(); + continue; + } + + QuaZipFile zipFile { &zip }; + static const QString ZIP_ASSETS_FOLDER = "files/"; + if (!zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(ZIP_ASSETS_FOLDER + hash))) { + qCDebug(backup_supervisor) << "testCreate(): outFile.open()"; + continue; + } + zipFile.write(file.readAll()); + zipFile.close(); + if (zipFile.getZipError() != UNZ_OK) { + qCDebug(backup_supervisor) << "testCreate(): outFile.close(): " << zipFile.getZipError(); + continue; + } + } } diff --git a/domain-server/src/BackupSupervisor.h b/domain-server/src/BackupSupervisor.h index a89be66742..d0f6e52ac6 100644 --- a/domain-server/src/BackupSupervisor.h +++ b/domain-server/src/BackupSupervisor.h @@ -28,7 +28,7 @@ class QuaZip; struct AssetServerBackup { - std::string filePath; + QString filePath; AssetUtils::Mappings mappings; bool corruptedBackup; }; diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 7c1c7f2cd7..3f9b3f20d8 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -308,3 +308,31 @@ void DomainContentBackupManager::backup() { } } } + +void DomainContentBackupManager::consolidate(QString fileName) { + QDir backupDir { _backupDirectory }; + if (backupDir.exists()) { + auto filePath = backupDir.absoluteFilePath(fileName); + + auto copyFilePath = QDir::tempPath() + "/" + fileName; + + auto copySuccess = QFile::copy(filePath, copyFilePath); + if (!copySuccess) { + qCritical() << "Failed to create full backup."; + return; + } + + QuaZip zip(copyFilePath); + if (!zip.open(QuaZip::mdAdd)) { + qCritical() << "Could not open backup archive:" << filePath; + qCritical() << " ERROR:" << zip.getZipError(); + return; + } + + for (auto& handler : _backupHandlers) { + handler.consolidateBackup(zip); + } + + zip.close(); + } +} diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h index bb1d4f0116..69163b4ead 100644 --- a/domain-server/src/DomainContentBackupManager.h +++ b/domain-server/src/DomainContentBackupManager.h @@ -54,6 +54,7 @@ protected: void persist(); void load(); void backup(); + void consolidate(QString fileName); void removeOldBackupVersions(const BackupRule& rule); bool getMostRecentBackup(const QString& format, QString& mostRecentBackupFileName, QDateTime& mostRecentBackupTime); int64_t getMostRecentBackupTimeInSecs(const QString& format); From d4b4c55673744e144dcb54938ff75cdb0e9e169f Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 13 Feb 2018 18:38:13 -0800 Subject: [PATCH 266/569] Remove unecessary debug --- domain-server/src/BackupHandler.h | 3 --- domain-server/src/BackupSupervisor.cpp | 1 - domain-server/src/DomainContentBackupManager.cpp | 1 - 3 files changed, 5 deletions(-) diff --git a/domain-server/src/BackupHandler.h b/domain-server/src/BackupHandler.h index 5c859165b7..332afe22f7 100644 --- a/domain-server/src/BackupHandler.h +++ b/domain-server/src/BackupHandler.h @@ -84,10 +84,7 @@ public: void loadBackup(QuaZip& zip) {} void createBackup(QuaZip& zip) const { - qDebug() << "Creating a backup from handler"; - QFile entitiesFile { _entitiesFilePath }; - qDebug() << entitiesFile.size(); if (entitiesFile.open(QIODevice::ReadOnly)) { QuaZipFile zipFile { &zip }; diff --git a/domain-server/src/BackupSupervisor.cpp b/domain-server/src/BackupSupervisor.cpp index 4cb42787ba..832fc8a3ff 100644 --- a/domain-server/src/BackupSupervisor.cpp +++ b/domain-server/src/BackupSupervisor.cpp @@ -154,7 +154,6 @@ void BackupSupervisor::loadBackup(QuaZip& zip) { } void BackupSupervisor::createBackup(QuaZip& zip) { - qDebug() << Q_FUNC_INFO; if (operationInProgress()) { qCWarning(backup_supervisor) << "There is already an operation in progress."; return; diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 3f9b3f20d8..56571f1b8c 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -292,7 +292,6 @@ void DomainContentBackupManager::backup() { } for (auto& handler : _backupHandlers) { - qDebug() << "Backup handler"; handler.createBackup(zip); } From 69298246c4726a80aef6eda423024cc0a4ec4d22 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 14 Feb 2018 15:44:51 -0800 Subject: [PATCH 267/569] CR --- domain-server/src/BackupHandler.h | 2 +- domain-server/src/BackupSupervisor.cpp | 32 ++++++++----------- domain-server/src/BackupSupervisor.h | 1 - .../src/DomainContentBackupManager.cpp | 9 ++++-- domain-server/src/DomainServer.cpp | 2 +- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/domain-server/src/BackupHandler.h b/domain-server/src/BackupHandler.h index 332afe22f7..4643d183b2 100644 --- a/domain-server/src/BackupHandler.h +++ b/domain-server/src/BackupHandler.h @@ -1,6 +1,6 @@ // // BackupHandler.h -// assignment-client +// domain-server/src // // Created by Clement Brisset on 2/5/18. // Copyright 2018 High Fidelity, Inc. diff --git a/domain-server/src/BackupSupervisor.cpp b/domain-server/src/BackupSupervisor.cpp index 832fc8a3ff..869f85c6cc 100644 --- a/domain-server/src/BackupSupervisor.cpp +++ b/domain-server/src/BackupSupervisor.cpp @@ -31,12 +31,11 @@ using namespace std; Q_DECLARE_LOGGING_CATEGORY(backup_supervisor) Q_LOGGING_CATEGORY(backup_supervisor, "hifi.backup-supervisor"); -BackupSupervisor::BackupSupervisor(const QString& backupDirectory) { - _assetsDirectory = backupDirectory + ASSETS_DIR; - QDir assetsDir { _assetsDirectory }; - if (!assetsDir.exists()) { - assetsDir.mkpath("."); - } +BackupSupervisor::BackupSupervisor(const QString& backupDirectory) : + _assetsDirectory(backupDirectory + ASSETS_DIR) +{ + // Make sure the asset directory exists. + QDir(_assetsDirectory).mkpath("."); refreshAssetsOnDisk(); @@ -166,7 +165,7 @@ void BackupSupervisor::createBackup(QuaZip& zip) { static constexpr quint64 MAX_REFRESH_TIME = 15 * 60 * 1000 * 1000; if (usecTimestampNow() - _lastMappingsRefresh > MAX_REFRESH_TIME) { - qCWarning(backup_supervisor) << "Backing up asset mappings that appear old."; + qCWarning(backup_supervisor) << "Backing up asset mappings that might be stale."; } AssetServerBackup backup; @@ -182,13 +181,13 @@ void BackupSupervisor::createBackup(QuaZip& zip) { QuaZipFile zipFile { &zip }; if (!zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(MAPPINGS_FILE))) { - qCDebug(backup_supervisor) << "testCreate(): outFile.open()"; + qCDebug(backup_supervisor) << "Could not open zip file:" << zipFile.getZipError(); return; } zipFile.write(document.toJson()); zipFile.close(); if (zipFile.getZipError() != UNZ_OK) { - qCDebug(backup_supervisor) << "testCreate(): outFile.close(): " << zipFile.getZipError(); + qCDebug(backup_supervisor) << "Could not close zip file: " << zipFile.getZipError(); return; } _backups.push_back(backup); @@ -207,7 +206,7 @@ void BackupSupervisor::recoverBackup(QuaZip& zip) { static constexpr quint64 MAX_REFRESH_TIME = 15 * 60 * 1000 * 1000; if (usecTimestampNow() - _lastMappingsRefresh > MAX_REFRESH_TIME) { - qCWarning(backup_supervisor) << "Backing up asset mappings that appear old."; + qCWarning(backup_supervisor) << "Current asset mappings that might be stale."; } startOperation(); @@ -216,7 +215,7 @@ void BackupSupervisor::recoverBackup(QuaZip& zip) { return value.filePath == zip.getZipName(); }); if (it == end(_backups)) { - qCDebug(backup_supervisor) << "Could not find backup"; + qCDebug(backup_supervisor) << "Could not find backup" << zip.getZipName() << "to restore."; stopOperation(); return; } @@ -237,7 +236,7 @@ void BackupSupervisor::deleteBackup(QuaZip& zip) { return value.filePath == zip.getZipName(); }); if (it == end(_backups)) { - qCDebug(backup_supervisor) << "Could not find backup"; + qCDebug(backup_supervisor) << "Could not find backup" << zip.getZipName() << "to delete."; return; } @@ -257,7 +256,7 @@ void BackupSupervisor::consolidateBackup(QuaZip& zip) { return info.fileName() == zipInfo.fileName(); }); if (it == end(_backups)) { - qCDebug(backup_supervisor) << "Could not find backup" << zip.getZipName(); + qCDebug(backup_supervisor) << "Could not find backup" << zip.getZipName() << "to consolidate."; return; } @@ -274,13 +273,13 @@ void BackupSupervisor::consolidateBackup(QuaZip& zip) { QuaZipFile zipFile { &zip }; static const QString ZIP_ASSETS_FOLDER = "files/"; if (!zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(ZIP_ASSETS_FOLDER + hash))) { - qCDebug(backup_supervisor) << "testCreate(): outFile.open()"; + qCDebug(backup_supervisor) << "Could not open zip file:" << zipFile.getZipError(); continue; } zipFile.write(file.readAll()); zipFile.close(); if (zipFile.getZipError() != UNZ_OK) { - qCDebug(backup_supervisor) << "testCreate(): outFile.close(): " << zipFile.getZipError(); + qCDebug(backup_supervisor) << "Could not close zip file: " << zipFile.getZipError(); continue; } } @@ -294,9 +293,6 @@ void BackupSupervisor::refreshMappings() { QObject::connect(request, &GetAllMappingsRequest::finished, this, [this](GetAllMappingsRequest* request) { if (request->getError() == MappingRequest::NoError) { const auto& mappings = request->getMappings(); - - qCDebug(backup_supervisor) << "Refreshed" << mappings.size() << "asset mappings!"; - _currentMappings.clear(); for (const auto& mapping : mappings) { _currentMappings.insert({ mapping.first, mapping.second.hash }); diff --git a/domain-server/src/BackupSupervisor.h b/domain-server/src/BackupSupervisor.h index d0f6e52ac6..9fedcca19b 100644 --- a/domain-server/src/BackupSupervisor.h +++ b/domain-server/src/BackupSupervisor.h @@ -21,7 +21,6 @@ #include #include -#include #include diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 56571f1b8c..ed5d99f927 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -47,7 +47,11 @@ DomainContentBackupManager::DomainContentBackupManager(const QString& backupDire bool debugTimestampNow) : _backupDirectory(backupDirectory), _persistInterval(persistInterval), - _lastCheck(usecTimestampNow()) { + _lastCheck(usecTimestampNow()) +{ + // Make sure the backup directory exists. + QDir(_backupDirectory).mkpath("."); + parseSettings(settings); } @@ -288,7 +292,8 @@ void DomainContentBackupManager::backup() { auto fileName = "backup-" + rule.extensionFormat + timestamp + ".zip"; QuaZip zip(_backupDirectory + "/" + fileName); if (!zip.open(QuaZip::mdAdd)) { - qDebug() << "Could not open archive"; + qDebug() << "Could not open backup archive:" << zip.getZipName(); + qDebug() << " ERROR:" << zip.getZipError(); } for (auto& handler : _backupHandlers) { diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 06d3549ff8..8ccba3d942 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -295,7 +295,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : } maybeHandleReplacementEntityFile(); - _contentManager.reset(new DomainContentBackupManager(getContentBackupDir(), _settingsManager.responseObjectForType("6")["entity_server_settings"].toObject())); + _contentManager.reset(new DomainContentBackupManager(getContentBackupDir(), _settingsManager.settingsResponseObjectForType("6")["entity_server_settings"].toObject())); _contentManager->addBackupHandler(new EntitiesBackupHandler(getEntitiesFilePath())); _contentManager->addBackupHandler(new BackupSupervisor(getContentBackupDir())); _contentManager->initialize(true); From e63b692d80f584eac5d70751b9e01d2a1a8b8cf9 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 7 Feb 2018 16:53:29 -0800 Subject: [PATCH 268/569] Add BackupHandler for entity file backups --- domain-server/src/BackupHandler.h | 34 ++++++++++++-- domain-server/src/BackupSupervisor.h | 47 +++++++++++++++++++ .../src/DomainContentBackupManager.cpp | 31 ++++++++++-- .../src/DomainContentBackupManager.h | 5 ++ domain-server/src/DomainServer.cpp | 2 + libraries/entities/src/EntityTree.cpp | 1 + 6 files changed, 113 insertions(+), 7 deletions(-) diff --git a/domain-server/src/BackupHandler.h b/domain-server/src/BackupHandler.h index 4643d183b2..b790591bea 100644 --- a/domain-server/src/BackupHandler.h +++ b/domain-server/src/BackupHandler.h @@ -83,7 +83,10 @@ public: void loadBackup(QuaZip& zip) {} - void createBackup(QuaZip& zip) const { + // Create a skeleton backup + void createBackup(QuaZip& zip) { + qDebug() << "Creating a backup from handler"; + QFile entitiesFile { _entitiesFilePath }; if (entitiesFile.open(QIODevice::ReadOnly)) { @@ -97,9 +100,32 @@ public: } } - void recoverBackup(QuaZip& zip) const {} - void deleteBackup(QuaZip& zip) {} - void consolidateBackup(QuaZip& zip) const {} + // Recover from a full backup + void recoverBackup(QuaZip& zip) { + if (!zip.setCurrentFile("models.json.gz")) { + qWarning() << "Failed to find models.json.gz while recovering backup"; + return; + } + QuaZipFile zipFile { &zip }; + zipFile.open(QIODevice::ReadOnly); + auto data = zipFile.readAll(); + + QFile entitiesFile { _entitiesFilePath }; + + if (entitiesFile.open(QIODevice::WriteOnly)) { + entitiesFile.write(data); + } + + zipFile.close(); + } + + // Delete a skeleton backup + void deleteBackup(QuaZip& zip) { + } + + // Create a full backup + void consolidateBackup(QuaZip& zip) { + } private: QString _entitiesFilePath; diff --git a/domain-server/src/BackupSupervisor.h b/domain-server/src/BackupSupervisor.h index 9fedcca19b..1023622971 100644 --- a/domain-server/src/BackupSupervisor.h +++ b/domain-server/src/BackupSupervisor.h @@ -91,4 +91,51 @@ private: int _mappingRequestsInFlight { 0 }; }; + +#include +class AssetsBackupHandler { +public: + AssetsBackupHandler(BackupSupervisor* backupSupervisor) : _backupSupervisor(backupSupervisor) {} + + void loadBackup(QuaZip& zip) {} + + void createBackup(QuaZip& zip) { + quint64 lastRefreshTimestamp = _backupSupervisor->getLastRefreshTimestamp(); + AssetUtils::Mappings mappings = _backupSupervisor->getCurrentMappings(); + + if (lastRefreshTimestamp == 0) { + qWarning() << "Current mappings not yet loaded, "; + return; + } + + static constexpr quint64 MAX_REFRESH_TIME = 15 * 60 * 1000 * 1000; + if (usecTimestampNow() - lastRefreshTimestamp > MAX_REFRESH_TIME) { + qWarning() << "Backing up asset mappings that appear old."; + } + + QJsonObject jsonObject; + for (const auto& mapping : mappings) { + jsonObject.insert(mapping.first, mapping.second); + } + QJsonDocument document(jsonObject); + + QuaZipFile zipFile { &zip }; + if (!zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo("mappings.json"))) { + qDebug() << "testCreate(): outFile.open()"; + } + zipFile.write(document.toJson()); + zipFile.close(); + if (zipFile.getZipError() != UNZ_OK) { + qDebug() << "testCreate(): outFile.close(): " << zipFile.getZipError(); + } + } + + void recoverBackup(QuaZip& zip) {} + void deleteBackup(QuaZip& zip) {} + void consolidateBackup(QuaZip& zip) {} + +private: + BackupSupervisor* _backupSupervisor; +}; + #endif /* hifi_BackupSupervisor_h */ diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index ed5d99f927..b0a80531f8 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -216,10 +216,35 @@ bool DomainContentBackupManager::getMostRecentBackup(const QString& format, return bestBackupFound; } +bool DomainContentBackupManager::recoverFromBackup(const QString& backupName) { + qDebug() << "Recoving from" << backupName; + + QDir backupDir { _backupDirectory }; + QFile backupFile { backupDir.filePath(backupName) }; + if (backupFile.open(QIODevice::ReadOnly)) { + QuaZip zip { &backupFile }; + if (!zip.open(QuaZip::Mode::mdUnzip)) { + qWarning() << "Failed to unzip file: " << backupName; + backupFile.close(); + return false; + } + + for (auto& handler : _backupHandlers) { + handler.recoverBackup(zip); + } + + backupFile.close(); + } + + qDebug() << "Successfully recovered from " << backupName; + + return true; +} + void DomainContentBackupManager::removeOldBackupVersions(const BackupRule& rule) { QDir backupDir { _backupDirectory }; if (backupDir.exists() && rule.maxBackupVersions > 0) { - qCDebug(domain_server) << "Rolling old backup versions for rule" << rule.name << "..."; + qCDebug(domain_server) << "Rolling old backup versions for rule" << rule.name; auto matchingFiles = backupDir.entryInfoList({ rule.extensionFormat + "*.zip" }, QDir::Files | QDir::NoSymLinks, QDir::Name); @@ -235,11 +260,11 @@ void DomainContentBackupManager::removeOldBackupVersions(const BackupRule& rule) } } - qCDebug(domain_server) << "Done rolling old backup versions..."; + qCDebug(domain_server) << "Done removing old backup versions"; } else { qCDebug(domain_server) << "Rolling backups for rule" << rule.name << "." << " Max Rolled Backup Versions less than 1 [" << rule.maxBackupVersions << "]." - << " No need to roll backups..."; + << " No need to roll backups"; } } diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h index 69163b4ead..d0dd9cf2c6 100644 --- a/domain-server/src/DomainContentBackupManager.h +++ b/domain-server/src/DomainContentBackupManager.h @@ -46,6 +46,11 @@ public: void replaceData(QByteArray data); + bool recoverFromBackup(const QString& backupName); + +signals: + void loadCompleted(); + protected: /// Implements generic processing behavior for this thread. virtual void setup() override; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 8ccba3d942..fe6a303e08 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -299,6 +299,8 @@ DomainServer::DomainServer(int argc, char* argv[]) : _contentManager->addBackupHandler(new EntitiesBackupHandler(getEntitiesFilePath())); _contentManager->addBackupHandler(new BackupSupervisor(getContentBackupDir())); _contentManager->initialize(true); + + _contentManager->recoverFromBackup("backup-daily_rolling-2018-02-06_15-13-50.zip"); } void DomainServer::parseCommandLine() { diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index f632bcf140..fc3e793b45 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2246,6 +2246,7 @@ bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer } entityDescription["DataVersion"] = _persistDataVersion; entityDescription["Id"] = _persistID; + qDebug() << "Writing to map: " << _persistDataVersion << _persistID; QScriptEngine scriptEngine; RecurseOctreeToMapOperator theOperator(entityDescription, element, &scriptEngine, skipDefaultValues, skipThoseWithBadParents, _myAvatar); From 8b07e7e28ff8eea62fb60bf81c73bdb05c10fc36 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 8 Feb 2018 22:13:52 -0800 Subject: [PATCH 269/569] Add backup DS APIs Add backup apis --- domain-server/src/BackupHandler.h | 11 +- domain-server/src/BackupSupervisor.h | 47 ------ .../src/DomainContentBackupManager.cpp | 144 ++++++++++++++---- .../src/DomainContentBackupManager.h | 18 ++- domain-server/src/DomainServer.cpp | 72 ++++++++- .../embedded-webserver/src/HTTPConnection.cpp | 53 ++++++- .../embedded-webserver/src/HTTPConnection.h | 6 + .../embedded-webserver/src/HTTPManager.cpp | 15 +- 8 files changed, 270 insertions(+), 96 deletions(-) diff --git a/domain-server/src/BackupHandler.h b/domain-server/src/BackupHandler.h index b790591bea..ad1fc6b793 100644 --- a/domain-server/src/BackupHandler.h +++ b/domain-server/src/BackupHandler.h @@ -95,7 +95,7 @@ public: zipFile.write(entitiesFile.readAll()); zipFile.close(); if (zipFile.getZipError() != UNZ_OK) { - qDebug() << "testCreate(): outFile.close(): " << zipFile.getZipError(); + qDebug() << "Failed to zip models.json.gz: " << zipFile.getZipError(); } } } @@ -107,7 +107,10 @@ public: return; } QuaZipFile zipFile { &zip }; - zipFile.open(QIODevice::ReadOnly); + if (!zipFile.open(QIODevice::ReadOnly)) { + qWarning() << "Failed to open models.json.gz in backup"; + return; + } auto data = zipFile.readAll(); QFile entitiesFile { _entitiesFilePath }; @@ -117,6 +120,10 @@ public: } zipFile.close(); + + if (zipFile.getZipError() != UNZ_OK) { + qDebug() << "Failed to zip models.json.gz: " << zipFile.getZipError(); + } } // Delete a skeleton backup diff --git a/domain-server/src/BackupSupervisor.h b/domain-server/src/BackupSupervisor.h index 1023622971..9fedcca19b 100644 --- a/domain-server/src/BackupSupervisor.h +++ b/domain-server/src/BackupSupervisor.h @@ -91,51 +91,4 @@ private: int _mappingRequestsInFlight { 0 }; }; - -#include -class AssetsBackupHandler { -public: - AssetsBackupHandler(BackupSupervisor* backupSupervisor) : _backupSupervisor(backupSupervisor) {} - - void loadBackup(QuaZip& zip) {} - - void createBackup(QuaZip& zip) { - quint64 lastRefreshTimestamp = _backupSupervisor->getLastRefreshTimestamp(); - AssetUtils::Mappings mappings = _backupSupervisor->getCurrentMappings(); - - if (lastRefreshTimestamp == 0) { - qWarning() << "Current mappings not yet loaded, "; - return; - } - - static constexpr quint64 MAX_REFRESH_TIME = 15 * 60 * 1000 * 1000; - if (usecTimestampNow() - lastRefreshTimestamp > MAX_REFRESH_TIME) { - qWarning() << "Backing up asset mappings that appear old."; - } - - QJsonObject jsonObject; - for (const auto& mapping : mappings) { - jsonObject.insert(mapping.first, mapping.second); - } - QJsonDocument document(jsonObject); - - QuaZipFile zipFile { &zip }; - if (!zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo("mappings.json"))) { - qDebug() << "testCreate(): outFile.open()"; - } - zipFile.write(document.toJson()); - zipFile.close(); - if (zipFile.getZipError() != UNZ_OK) { - qDebug() << "testCreate(): outFile.close(): " << zipFile.getZipError(); - } - } - - void recoverBackup(QuaZip& zip) {} - void deleteBackup(QuaZip& zip) {} - void consolidateBackup(QuaZip& zip) {} - -private: - BackupSupervisor* _backupSupervisor; -}; - #endif /* hifi_BackupSupervisor_h */ diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index b0a80531f8..29f6b7948f 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include "DomainServer.h" #include "DomainContentBackupManager.h" @@ -36,7 +37,8 @@ const int DomainContentBackupManager::DEFAULT_PERSIST_INTERVAL = 1000 * 30; // // Backup format looks like: daily_backup-TIMESTAMP.zip const static QString DATETIME_FORMAT { "yyyy-MM-dd_HH-mm-ss" }; const static QString DATETIME_FORMAT_RE("\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}"); - +static const QString AUTOMATIC_BACKUP_PREFIX{ "autobackup-" }; +static const QString MANUAL_BACKUP_PREFIX{ "backup-" }; void DomainContentBackupManager::addBackupHandler(BackupHandler handler) { _backupHandlers.push_back(std::move(handler)); } @@ -83,7 +85,7 @@ void DomainContentBackupManager::parseSettings(const QJsonObject& settings) { auto name = obj["Name"].toString(); auto format = obj["format"].toString(); - format = name.replace(" ", "_").toLower() + "-"; + format = name.replace(" ", "_").toLower(); qCDebug(domain_server) << " Name:" << name; qCDebug(domain_server) << " format:" << format; @@ -129,6 +131,14 @@ void DomainContentBackupManager::setup() { } bool DomainContentBackupManager::process() { + if (!_initialLoadComplete) { + QDir backupDir { _backupDirectory }; + if (!backupDir.exists()) { + backupDir.mkpath("."); + } + _initialLoadComplete = true; + } + if (isStillRunning()) { constexpr int64_t MSECS_TO_USECS = 1000; constexpr int64_t USECS_TO_SLEEP = 10 * MSECS_TO_USECS; // every 10ms @@ -140,7 +150,7 @@ bool DomainContentBackupManager::process() { if (sinceLastSave > intervalToCheck) { _lastCheck = now; - persist(); + backup(); } } @@ -149,32 +159,18 @@ bool DomainContentBackupManager::process() { void DomainContentBackupManager::aboutToFinish() { qCDebug(domain_server) << "Persist thread about to finish..."; - persist(); -} - -void DomainContentBackupManager::persist() { - QDir backupDir { _backupDirectory }; - backupDir.mkpath("."); - - // create our "lock" file to indicate we're saving. - QString lockFileName = _backupDirectory + "/running.lock"; - - std::ofstream lockFile(qPrintable(lockFileName), std::ios::out | std::ios::binary); - if (lockFile.is_open()) { - backup(); - - lockFile.close(); - remove(qPrintable(lockFileName)); - } + backup(); + qCDebug(domain_server) << "Persist thread done with about to finish..."; + _stopThread = true; } bool DomainContentBackupManager::getMostRecentBackup(const QString& format, QString& mostRecentBackupFileName, QDateTime& mostRecentBackupTime) { - QRegExp formatRE { QRegExp::escape(format) + "(" + DATETIME_FORMAT_RE + ")" + "\\.zip" }; + QRegExp formatRE { AUTOMATIC_BACKUP_PREFIX + QRegExp::escape(format) + "\\-(" + DATETIME_FORMAT_RE + ")" + "\\.zip" }; QStringList filters; - filters << format + "*.zip"; + filters << AUTOMATIC_BACKUP_PREFIX + format + "*.zip"; bool bestBackupFound = false; QString bestBackupFile; @@ -216,7 +212,32 @@ bool DomainContentBackupManager::getMostRecentBackup(const QString& format, return bestBackupFound; } +bool DomainContentBackupManager::deleteBackup(const QString& backupName) { + if (QThread::currentThread() != thread()) { + bool result{ false }; + BLOCKING_INVOKE_METHOD(this, "deleteBackup", + Q_RETURN_ARG(bool, result), + Q_ARG(const QString&, backupName)); + return result; + } + + QDir backupDir { _backupDirectory }; + QFile backupFile { backupDir.filePath(backupName) }; + if (backupFile.remove()) { + return true; + } + return false; +} + bool DomainContentBackupManager::recoverFromBackup(const QString& backupName) { + if (QThread::currentThread() != thread()) { + bool result{ false }; + BLOCKING_INVOKE_METHOD(this, "recoverFromBackup", + Q_RETURN_ARG(bool, result), + Q_ARG(const QString&, backupName)); + return result; + } + qDebug() << "Recoving from" << backupName; QDir backupDir { _backupDirectory }; @@ -226,7 +247,6 @@ bool DomainContentBackupManager::recoverFromBackup(const QString& backupName) { if (!zip.open(QuaZip::Mode::mdUnzip)) { qWarning() << "Failed to unzip file: " << backupName; backupFile.close(); - return false; } for (auto& handler : _backupHandlers) { @@ -234,11 +254,43 @@ bool DomainContentBackupManager::recoverFromBackup(const QString& backupName) { } backupFile.close(); + qDebug() << "Successfully recovered from " << backupName; + return true; + } else { + qWarning() << "Invalid id: " << backupName; + return false; + } +} + +std::vector DomainContentBackupManager::getAllBackups() { + std::vector backups; + + QDir backupDir { _backupDirectory }; + auto matchingFiles = + backupDir.entryInfoList({ AUTOMATIC_BACKUP_PREFIX + "*.zip", MANUAL_BACKUP_PREFIX + "*.zip" }, + QDir::Files | QDir::NoSymLinks, QDir::Name); + QString prefixFormat = "(" + QRegExp::escape(AUTOMATIC_BACKUP_PREFIX) + "|" + QRegExp::escape(MANUAL_BACKUP_PREFIX) + ")"; + QString nameFormat = "(.+)"; + QString dateTimeFormat = "(" + DATETIME_FORMAT_RE + ")"; + QRegExp backupNameFormat { prefixFormat + nameFormat + "-" + dateTimeFormat + "\\.zip" }; + + for (const auto& fileInfo : matchingFiles) { + auto fileName = fileInfo.fileName(); + if (backupNameFormat.exactMatch(fileName)) { + auto type = backupNameFormat.cap(1); + auto name = backupNameFormat.cap(2); + auto dateTime = backupNameFormat.cap(3); + auto createdAt = QDateTime::fromString(dateTime, DATETIME_FORMAT); + if (!createdAt.isValid()) { + continue; + } + + BackupItemInfo backup { fileInfo.fileName(), name, fileInfo.absoluteFilePath(), createdAt, type == MANUAL_BACKUP_PREFIX }; + backups.push_back(backup); + } } - qDebug() << "Successfully recovered from " << backupName; - - return true; + return backups; } void DomainContentBackupManager::removeOldBackupVersions(const BackupRule& rule) { @@ -247,9 +299,10 @@ void DomainContentBackupManager::removeOldBackupVersions(const BackupRule& rule) qCDebug(domain_server) << "Rolling old backup versions for rule" << rule.name; auto matchingFiles = - backupDir.entryInfoList({ rule.extensionFormat + "*.zip" }, QDir::Files | QDir::NoSymLinks, QDir::Name); + backupDir.entryInfoList({ AUTOMATIC_BACKUP_PREFIX + rule.extensionFormat + "*.zip" }, QDir::Files | QDir::NoSymLinks, QDir::Name); int backupsToDelete = matchingFiles.length() - rule.maxBackupVersions; + qCDebug(domain_server) << "Found" << matchingFiles.length() << "backups, deleting " << backupsToDelete << "backup(s)"; for (int i = 0; i < backupsToDelete; ++i) { auto fileInfo = matchingFiles[i].absoluteFilePath(); QFile backupFile(fileInfo); @@ -313,6 +366,7 @@ void DomainContentBackupManager::backup() { qCDebug(domain_server) << "Time since last backup [" << secondsSinceLastBackup << "] for rule [" << rule.name << "] exceeds backup interval [" << rule.intervalSeconds << "] doing backup now..."; +<<<<<<< HEAD auto timestamp = QDateTime::currentDateTime().toString(DATETIME_FORMAT); auto fileName = "backup-" + rule.extensionFormat + timestamp + ".zip"; QuaZip zip(_backupDirectory + "/" + fileName); @@ -323,11 +377,17 @@ void DomainContentBackupManager::backup() { for (auto& handler : _backupHandlers) { handler.createBackup(zip); +======= + bool success; + QString path; + std::tie(success, path) = createBackup(AUTOMATIC_BACKUP_PREFIX, rule.extensionFormat); + if (!success) { + qCWarning(domain_server) << "Failed to create backup for" << rule.name << "at" << path; + continue; +>>>>>>> dd86471a42... Add backup DS APIs } - zip.close(); - - qDebug() << "Created backup: " << fileName; + qDebug() << "Created backup: " << path; rule.lastBackupSeconds = nowSeconds; @@ -365,3 +425,27 @@ void DomainContentBackupManager::consolidate(QString fileName) { zip.close(); } } + +void DomainContentBackupManager::createManualBackup(const QString& name) { + createBackup(MANUAL_BACKUP_PREFIX, name); +} + +std::pair DomainContentBackupManager::createBackup(const QString& prefix, const QString& name) { + auto timestamp = QDateTime::currentDateTime().toString(DATETIME_FORMAT); + auto fileName = prefix + name + "-" + timestamp + ".zip"; + auto path = _backupDirectory + "/" + fileName; + QuaZip zip(path); + if (!zip.open(QuaZip::mdAdd)) { + qCWarning(domain_server) << "Failed to open zip file at " << path; + qCWarning(domain_server) << " ERROR:" << zip.getZipError(); + return { false, path }; + } + + for (auto& handler : _backupHandlers) { + handler.createBackup(zip); + } + + zip.close(); + + return { true, path }; +} diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h index d0dd9cf2c6..461d4dd794 100644 --- a/domain-server/src/DomainContentBackupManager.h +++ b/domain-server/src/DomainContentBackupManager.h @@ -21,6 +21,14 @@ #include "BackupHandler.h" +struct BackupItemInfo { + QString id; + QString name; + QString absolutePath; + QDateTime createdAt; + bool isManualBackup; +}; + class DomainContentBackupManager : public GenericThread { Q_OBJECT public: @@ -41,12 +49,18 @@ public: bool debugTimestampNow = false); void addBackupHandler(BackupHandler handler); + bool isInitialLoadComplete() const { return _initialLoadComplete; } + std::vector getAllBackups(); void aboutToFinish(); /// call this to inform the persist thread that the owner is about to finish to support final persist void replaceData(QByteArray data); + void createManualBackup(const QString& name); + +public slots: bool recoverFromBackup(const QString& backupName); + bool deleteBackup(const QString& backupName); signals: void loadCompleted(); @@ -56,7 +70,6 @@ protected: virtual void setup() override; virtual bool process() override; - void persist(); void load(); void backup(); void consolidate(QString fileName); @@ -65,10 +78,13 @@ protected: int64_t getMostRecentBackupTimeInSecs(const QString& format); void parseSettings(const QJsonObject& settings); + std::pair createBackup(const QString& prefix, const QString& name); + private: QString _backupDirectory; std::vector _backupHandlers; int _persistInterval { 0 }; + bool _initialLoadComplete { false }; int64_t _lastCheck { 0 }; std::vector _backupRules; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index fe6a303e08..1949c40566 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -300,7 +300,10 @@ DomainServer::DomainServer(int argc, char* argv[]) : _contentManager->addBackupHandler(new BackupSupervisor(getContentBackupDir())); _contentManager->initialize(true); - _contentManager->recoverFromBackup("backup-daily_rolling-2018-02-06_15-13-50.zip"); + qDebug() << "Existing backups:"; + for (auto& backup : _contentManager->getAllBackups()) { + qDebug() << " Backup: " << backup.name << backup.createdAt; + } } void DomainServer::parseCommandLine() { @@ -1736,6 +1739,12 @@ void DomainServer::processOctreeDataPersistMessage(QSharedPointerreadAll(); auto filePath = getEntitiesFilePath(); + QDir dir(getEntitiesDirPath()); + if (!dir.exists()) { + qCDebug(domain_server) << "Creating entities content directory:" << dir.absolutePath(); + dir.mkpath("."); + } + QFile f(filePath); if (f.open(QIODevice::WriteOnly)) { f.write(data); @@ -1746,12 +1755,12 @@ void DomainServer::processOctreeDataPersistMessage(QSharedPointerrespond(HTTPConnection::StatusCode200, nodesDocument.toJson(), qPrintable(JSON_MIME_TYPE)); + return true; + } else if (url.path().startsWith(URI_API_BACKUPS_RECOVER)) { + auto id = url.path().mid(QString(URI_API_BACKUPS_RECOVER).length()); + _contentManager->recoverFromBackup(id); + QJsonObject rootJSON; + rootJSON["success"] = true; + QJsonDocument docJSON(rootJSON); + connection->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); + return true; + } else if (url.path() == URI_API_BACKUPS) { + QJsonObject rootJSON; + QJsonArray backupsJSON; + + auto backups = _contentManager->getAllBackups(); + + for (const auto& backup : backups) { + QJsonObject obj; + obj["id"] = backup.id; + obj["name"] = backup.name; + obj["createdAtMillis"] = backup.createdAt.toMSecsSinceEpoch(); + obj["isManualBackup"] = backup.isManualBackup; + backupsJSON.push_back(obj); + } + + rootJSON["backups"] = backupsJSON; + QJsonDocument docJSON(rootJSON); + + connection->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); return true; } else if (url.path() == URI_RESTART) { connection->respond(HTTPConnection::StatusCode200); @@ -2213,6 +2254,20 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return true; + } else if (url.path() == URI_API_BACKUPS) { + qDebug() << "GOt request to create a backup:"; + auto params = connection->parseUrlEncodedForm(); + auto it = params.find("name"); + if (it == params.end()) { + connection->respond(HTTPConnection::StatusCode400, "Bad request, missing `name`"); + return true; + } + + _contentManager->createManualBackup(it.value()); + + connection->respond(HTTPConnection::StatusCode200); + return true; + } else if (url.path() == "/domain_settings") { auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH); if (!accessTokenVariant) { @@ -2311,7 +2366,16 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QRegExp allNodesDeleteRegex(ALL_NODE_DELETE_REGEX_STRING); QRegExp nodeDeleteRegex(NODE_DELETE_REGEX_STRING); - if (nodeDeleteRegex.indexIn(url.path()) != -1) { + if (url.path().startsWith(URI_API_BACKUPS_ID)) { + auto id = url.path().mid(QString(URI_API_BACKUPS_ID).length()); + auto success = _contentManager->deleteBackup(id); + QJsonObject rootJSON; + rootJSON["success"] = success; + QJsonDocument docJSON(rootJSON); + connection->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); + return true; + + } else if (nodeDeleteRegex.indexIn(url.path()) != -1) { // this is a request to DELETE one node by UUID // pull the captured string, if it exists diff --git a/libraries/embedded-webserver/src/HTTPConnection.cpp b/libraries/embedded-webserver/src/HTTPConnection.cpp index a61bc95f8b..6496cc3f68 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.cpp +++ b/libraries/embedded-webserver/src/HTTPConnection.cpp @@ -133,12 +133,33 @@ QList HTTPConnection::parseFormData() const { } void HTTPConnection::respond(const char* code, const QByteArray& content, const char* contentType, const Headers& headers) { + QByteArray data(content); + auto device { std::unique_ptr(new QBuffer()) }; + device->setBuffer(new QByteArray(content)); + if (device->open(QIODevice::ReadOnly)) { + respond(code, std::move(device), contentType, headers); + } else { + qCritical() << "Error opening QBuffer to respond to " << _requestUrl.path(); + } +} + +void HTTPConnection::respond(const char* code, std::unique_ptr device, const char* contentType, const Headers& headers) { + _responseDevice = std::move(device); + _socket->write("HTTP/1.1 "); + + if (_responseDevice->isSequential()) { + qWarning() << "Error responding to HTTPConnection: sequential IO devices not supported"; + _socket->write(StatusCode500); + _socket->write("\r\n"); + _socket->disconnect(SIGNAL(readyRead()), this); + _socket->disconnectFromHost(); + return; + } + _socket->write(code); _socket->write("\r\n"); - int csize = content.size(); - for (Headers::const_iterator it = headers.constBegin(), end = headers.constEnd(); it != end; it++) { _socket->write(it.key()); @@ -146,6 +167,8 @@ void HTTPConnection::respond(const char* code, const QByteArray& content, const _socket->write(it.value()); _socket->write("\r\n"); } + + int csize = _responseDevice->size(); if (csize > 0) { _socket->write("Content-Length: "); _socket->write(QByteArray::number(csize)); @@ -157,20 +180,35 @@ void HTTPConnection::respond(const char* code, const QByteArray& content, const } _socket->write("Connection: close\r\n\r\n"); - if (csize > 0) { - _socket->write(content); + if (_responseDevice->atEnd()) { + _socket->disconnectFromHost(); + } else { + constexpr size_t HTTP_RESPONSE_CHUNK_SIZE = 1024 * 10; + int totalToBeWritten = csize; + connect(_socket, &QTcpSocket::bytesWritten, this, [this, totalToBeWritten](size_t bytes) mutable { + if (!_responseDevice->atEnd()) { + totalToBeWritten -= _socket->write(_responseDevice->read(HTTP_RESPONSE_CHUNK_SIZE)); + if (_responseDevice->atEnd()) { + _socket->disconnectFromHost(); + disconnect(_socket, &QTcpSocket::bytesWritten, this, nullptr); + } + } + }); + } // make sure we receive no further read notifications - _socket->disconnect(SIGNAL(readyRead()), this); - - _socket->disconnectFromHost(); + disconnect(_socket, &QTcpSocket::readyRead, this, nullptr); } void HTTPConnection::readRequest() { if (!_socket->canReadLine()) { return; } + if (!_requestUrl.isEmpty()) { + qDebug() << "Request URL was already set"; + return; + } // parse out the method and resource QByteArray line = _socket->readLine().trimmed(); if (line.startsWith("HEAD")) { @@ -249,6 +287,7 @@ void HTTPConnection::readContent() { if (_socket->bytesAvailable() < size) { return; } + qDebug() << "Reading content"; _socket->read(_requestContent.data(), size); _socket->disconnect(this, SLOT(readContent())); diff --git a/libraries/embedded-webserver/src/HTTPConnection.h b/libraries/embedded-webserver/src/HTTPConnection.h index 966fc26949..9c435b14a0 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.h +++ b/libraries/embedded-webserver/src/HTTPConnection.h @@ -87,6 +87,9 @@ public: void respond (const char* code, const QByteArray& content = QByteArray(), const char* contentType = DefaultContentType, const Headers& headers = Headers()); + void respond (const char* code, std::unique_ptr device, + const char* contentType = DefaultContentType, + const Headers& headers = Headers()); protected slots: @@ -127,6 +130,9 @@ protected: /// The content of the request. QByteArray _requestContent; + + /// Response content + std::unique_ptr _responseDevice; }; #endif // hifi_HTTPConnection_h diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index fd127a2e92..bd1b545412 100644 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -98,13 +98,14 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, // file exists, serve it static QMimeDatabase mimeDatabase; - QFile localFile(filePath); - localFile.open(QIODevice::ReadOnly); - QByteArray localFileData = localFile.readAll(); + auto localFile = std::unique_ptr(new QFile(filePath)); + localFile->open(QIODevice::ReadOnly); + QByteArray localFileData; QFileInfo localFileInfo(filePath); if (localFileInfo.completeSuffix() == "shtml") { + localFileData = localFile->readAll(); // this is a file that may have some SSI statements // the only thing we support is the include directive, but check the contents for that @@ -153,8 +154,12 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, ? QString { "text/html" } : mimeDatabase.mimeTypeForFile(filePath).name(); - connection->respond(HTTPConnection::StatusCode200, localFileData, qPrintable(mimeType)); - + if (localFileData.isNull()) { + connection->respond(HTTPConnection::StatusCode200, std::move(localFile), qPrintable(mimeType)); + } else { + connection->respond(HTTPConnection::StatusCode200, localFileData, qPrintable(mimeType)); + } + return true; } } From dd398da2e0baa1306ce754edd73a70e49144be6e Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 14 Feb 2018 15:36:50 -0800 Subject: [PATCH 270/569] Update DS to use promises for backup APIs --- .../src/DomainContentBackupManager.cpp | 50 ++++++++++--------- .../src/DomainContentBackupManager.h | 6 ++- domain-server/src/DomainServer.cpp | 29 ++++++----- 3 files changed, 48 insertions(+), 37 deletions(-) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 29f6b7948f..5c4d70d7ad 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -212,54 +212,58 @@ bool DomainContentBackupManager::getMostRecentBackup(const QString& format, return bestBackupFound; } -bool DomainContentBackupManager::deleteBackup(const QString& backupName) { +void DomainContentBackupManager::deleteBackup(MiniPromise::Promise promise, const QString& backupName) { if (QThread::currentThread() != thread()) { - bool result{ false }; - BLOCKING_INVOKE_METHOD(this, "deleteBackup", - Q_RETURN_ARG(bool, result), - Q_ARG(const QString&, backupName)); - return result; + QMetaObject::invokeMethod(this, "deleteBackup", Q_ARG(MiniPromise::Promise, promise), + Q_ARG(const QString&, backupName)); + return; } + bool success { false }; QDir backupDir { _backupDirectory }; QFile backupFile { backupDir.filePath(backupName) }; if (backupFile.remove()) { - return true; + success = true; } - return false; + promise->resolve({ + { "success", success } + }); } -bool DomainContentBackupManager::recoverFromBackup(const QString& backupName) { +void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise, const QString& backupName) { if (QThread::currentThread() != thread()) { - bool result{ false }; - BLOCKING_INVOKE_METHOD(this, "recoverFromBackup", - Q_RETURN_ARG(bool, result), - Q_ARG(const QString&, backupName)); - return result; + QMetaObject::invokeMethod(this, "recoverFromBackup", Q_ARG(MiniPromise::Promise, promise), + Q_ARG(const QString&, backupName)); + return; } qDebug() << "Recoving from" << backupName; + bool success { false }; QDir backupDir { _backupDirectory }; QFile backupFile { backupDir.filePath(backupName) }; if (backupFile.open(QIODevice::ReadOnly)) { QuaZip zip { &backupFile }; if (!zip.open(QuaZip::Mode::mdUnzip)) { qWarning() << "Failed to unzip file: " << backupName; - backupFile.close(); + success = false; + } else { + for (auto& handler : _backupHandlers) { + handler.recoverBackup(zip); + } + + qDebug() << "Successfully recovered from " << backupName; + success = true; } - - for (auto& handler : _backupHandlers) { - handler.recoverBackup(zip); - } - backupFile.close(); - qDebug() << "Successfully recovered from " << backupName; - return true; } else { + success = false; qWarning() << "Invalid id: " << backupName; - return false; } + + promise->resolve({ + { "success", success } + }); } std::vector DomainContentBackupManager::getAllBackups() { diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h index 461d4dd794..6d6f07a19e 100644 --- a/domain-server/src/DomainContentBackupManager.h +++ b/domain-server/src/DomainContentBackupManager.h @@ -21,6 +21,8 @@ #include "BackupHandler.h" +#include + struct BackupItemInfo { QString id; QString name; @@ -59,8 +61,8 @@ public: void createManualBackup(const QString& name); public slots: - bool recoverFromBackup(const QString& backupName); - bool deleteBackup(const QString& backupName); + void recoverFromBackup(MiniPromise::Promise promise, const QString& backupName); + void deleteBackup(MiniPromise::Promise promise, const QString& backupName); signals: void loadCompleted(); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 1949c40566..0e057972ca 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2124,11 +2124,14 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return true; } else if (url.path().startsWith(URI_API_BACKUPS_RECOVER)) { auto id = url.path().mid(QString(URI_API_BACKUPS_RECOVER).length()); - _contentManager->recoverFromBackup(id); - QJsonObject rootJSON; - rootJSON["success"] = true; - QJsonDocument docJSON(rootJSON); - connection->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); + auto deferred = makePromise("recoverFromBackup"); + deferred->then([connection, JSON_MIME_TYPE](QString error, QVariantMap result) { + QJsonObject rootJSON; + rootJSON["success"] = result["success"].toBool(); + QJsonDocument docJSON(rootJSON); + connection->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); + }); + _contentManager->recoverFromBackup(deferred, id); return true; } else if (url.path() == URI_API_BACKUPS) { QJsonObject rootJSON; @@ -2368,11 +2371,15 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url if (url.path().startsWith(URI_API_BACKUPS_ID)) { auto id = url.path().mid(QString(URI_API_BACKUPS_ID).length()); - auto success = _contentManager->deleteBackup(id); - QJsonObject rootJSON; - rootJSON["success"] = success; - QJsonDocument docJSON(rootJSON); - connection->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); + auto deferred = makePromise("deleteBackup"); + deferred->then([connection, JSON_MIME_TYPE](QString error, QVariantMap result) { + QJsonObject rootJSON; + rootJSON["success"] = result["success"].toBool(); + QJsonDocument docJSON(rootJSON); + connection->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); + }); + _contentManager->deleteBackup(deferred, id); + return true; } else if (nodeDeleteRegex.indexIn(url.path()) != -1) { @@ -3310,8 +3317,6 @@ void DomainServer::maybeHandleReplacementEntityFile() { } void DomainServer::handleOctreeFileReplacement(QByteArray octreeFile) { - // enumerate the nodes and find any octree type servers with active sockets - //Assume we have compressed data auto compressedOctree = octreeFile; QByteArray jsonOctree; From 80b03b904610911577d58d21d4d59de0cc93b39a Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 14 Feb 2018 16:09:16 -0800 Subject: [PATCH 271/569] Make backup directory in content manager const --- domain-server/src/DomainContentBackupManager.cpp | 13 ------------- domain-server/src/DomainContentBackupManager.h | 2 +- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 5c4d70d7ad..159d6d6e95 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -370,25 +370,12 @@ void DomainContentBackupManager::backup() { qCDebug(domain_server) << "Time since last backup [" << secondsSinceLastBackup << "] for rule [" << rule.name << "] exceeds backup interval [" << rule.intervalSeconds << "] doing backup now..."; -<<<<<<< HEAD - auto timestamp = QDateTime::currentDateTime().toString(DATETIME_FORMAT); - auto fileName = "backup-" + rule.extensionFormat + timestamp + ".zip"; - QuaZip zip(_backupDirectory + "/" + fileName); - if (!zip.open(QuaZip::mdAdd)) { - qDebug() << "Could not open backup archive:" << zip.getZipName(); - qDebug() << " ERROR:" << zip.getZipError(); - } - - for (auto& handler : _backupHandlers) { - handler.createBackup(zip); -======= bool success; QString path; std::tie(success, path) = createBackup(AUTOMATIC_BACKUP_PREFIX, rule.extensionFormat); if (!success) { qCWarning(domain_server) << "Failed to create backup for" << rule.name << "at" << path; continue; ->>>>>>> dd86471a42... Add backup DS APIs } qDebug() << "Created backup: " << path; diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h index 6d6f07a19e..792695acce 100644 --- a/domain-server/src/DomainContentBackupManager.h +++ b/domain-server/src/DomainContentBackupManager.h @@ -83,7 +83,7 @@ protected: std::pair createBackup(const QString& prefix, const QString& name); private: - QString _backupDirectory; + const QString _backupDirectory; std::vector _backupHandlers; int _persistInterval { 0 }; bool _initialLoadComplete { false }; From 8a69c69bec02016ebdea92ea19e50d15cf9e1b74 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 14 Feb 2018 16:49:23 -0800 Subject: [PATCH 272/569] CR --- domain-server/src/BackupHandler.h | 15 ++++++------ .../src/DomainContentBackupManager.cpp | 23 +++++-------------- .../src/DomainContentBackupManager.h | 2 -- domain-server/src/DomainServer.cpp | 16 +++++++------ .../embedded-webserver/src/HTTPConnection.cpp | 1 - libraries/entities/src/EntityTree.cpp | 1 - 6 files changed, 23 insertions(+), 35 deletions(-) diff --git a/domain-server/src/BackupHandler.h b/domain-server/src/BackupHandler.h index ad1fc6b793..3e25af83e8 100644 --- a/domain-server/src/BackupHandler.h +++ b/domain-server/src/BackupHandler.h @@ -79,14 +79,14 @@ private: #include class EntitiesBackupHandler { public: - EntitiesBackupHandler(QString entitiesFilePath) : _entitiesFilePath(entitiesFilePath) {} + EntitiesBackupHandler(QString entitiesFilePath, QString entitiesReplacementFilePath) + : _entitiesFilePath(entitiesFilePath) + , _entitiesReplacementFilePath {} void loadBackup(QuaZip& zip) {} // Create a skeleton backup void createBackup(QuaZip& zip) { - qDebug() << "Creating a backup from handler"; - QFile entitiesFile { _entitiesFilePath }; if (entitiesFile.open(QIODevice::ReadOnly)) { @@ -95,7 +95,7 @@ public: zipFile.write(entitiesFile.readAll()); zipFile.close(); if (zipFile.getZipError() != UNZ_OK) { - qDebug() << "Failed to zip models.json.gz: " << zipFile.getZipError(); + qCritical() << "Failed to zip models.json.gz: " << zipFile.getZipError(); } } } @@ -108,12 +108,12 @@ public: } QuaZipFile zipFile { &zip }; if (!zipFile.open(QIODevice::ReadOnly)) { - qWarning() << "Failed to open models.json.gz in backup"; + qCritical() << "Failed to open models.json.gz in backup"; return; } auto data = zipFile.readAll(); - QFile entitiesFile { _entitiesFilePath }; + QFile entitiesFile { _entitiesReplacementFilePath }; if (entitiesFile.open(QIODevice::WriteOnly)) { entitiesFile.write(data); @@ -122,7 +122,7 @@ public: zipFile.close(); if (zipFile.getZipError() != UNZ_OK) { - qDebug() << "Failed to zip models.json.gz: " << zipFile.getZipError(); + qCritical() << "Failed to zip models.json.gz: " << zipFile.getZipError(); } } @@ -136,6 +136,7 @@ public: private: QString _entitiesFilePath; + QString _entitiesReplacementFilePath; }; #endif /* hifi_BackupHandler_h */ diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 159d6d6e95..6f311613d5 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -35,10 +35,10 @@ const int DomainContentBackupManager::DEFAULT_PERSIST_INTERVAL = 1000 * 30; // every 30 seconds // Backup format looks like: daily_backup-TIMESTAMP.zip -const static QString DATETIME_FORMAT { "yyyy-MM-dd_HH-mm-ss" }; -const static QString DATETIME_FORMAT_RE("\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}"); -static const QString AUTOMATIC_BACKUP_PREFIX{ "autobackup-" }; -static const QString MANUAL_BACKUP_PREFIX{ "backup-" }; +static const QString DATETIME_FORMAT { "yyyy-MM-dd_HH-mm-ss" }; +static const QString DATETIME_FORMAT_RE { "\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}" }; +static const QString AUTOMATIC_BACKUP_PREFIX { "autobackup-" }; +static const QString MANUAL_BACKUP_PREFIX { "backup-" }; void DomainContentBackupManager::addBackupHandler(BackupHandler handler) { _backupHandlers.push_back(std::move(handler)); } @@ -131,14 +131,6 @@ void DomainContentBackupManager::setup() { } bool DomainContentBackupManager::process() { - if (!_initialLoadComplete) { - QDir backupDir { _backupDirectory }; - if (!backupDir.exists()) { - backupDir.mkpath("."); - } - _initialLoadComplete = true; - } - if (isStillRunning()) { constexpr int64_t MSECS_TO_USECS = 1000; constexpr int64_t USECS_TO_SLEEP = 10 * MSECS_TO_USECS; // every 10ms @@ -219,12 +211,9 @@ void DomainContentBackupManager::deleteBackup(MiniPromise::Promise promise, cons return; } - bool success { false }; QDir backupDir { _backupDirectory }; QFile backupFile { backupDir.filePath(backupName) }; - if (backupFile.remove()) { - success = true; - } + auto success = backupFile.remove(); promise->resolve({ { "success", success } }); @@ -237,7 +226,7 @@ void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise, return; } - qDebug() << "Recoving from" << backupName; + qDebug() << "Recovering from" << backupName; bool success { false }; QDir backupDir { _backupDirectory }; diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h index 792695acce..cfeae9c8b9 100644 --- a/domain-server/src/DomainContentBackupManager.h +++ b/domain-server/src/DomainContentBackupManager.h @@ -51,7 +51,6 @@ public: bool debugTimestampNow = false); void addBackupHandler(BackupHandler handler); - bool isInitialLoadComplete() const { return _initialLoadComplete; } std::vector getAllBackups(); void aboutToFinish(); /// call this to inform the persist thread that the owner is about to finish to support final persist @@ -86,7 +85,6 @@ private: const QString _backupDirectory; std::vector _backupHandlers; int _persistInterval { 0 }; - bool _initialLoadComplete { false }; int64_t _lastCheck { 0 }; std::vector _backupRules; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 0e057972ca..718c5ff402 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -296,7 +296,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : maybeHandleReplacementEntityFile(); _contentManager.reset(new DomainContentBackupManager(getContentBackupDir(), _settingsManager.settingsResponseObjectForType("6")["entity_server_settings"].toObject())); - _contentManager->addBackupHandler(new EntitiesBackupHandler(getEntitiesFilePath())); + _contentManager->addBackupHandler(new EntitiesBackupHandler(getEntitiesFilePath(), getEntitiesReplacementFilePath())); _contentManager->addBackupHandler(new BackupSupervisor(getContentBackupDir())); _contentManager->initialize(true); @@ -1936,7 +1936,6 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url const QString URI_API_BACKUPS = "/api/backups"; const QString URI_API_BACKUPS_ID = "/api/backups/"; const QString URI_API_BACKUPS_RECOVER = "/api/backups/recover/"; - //const QString URI_API_BACKUPS_CREATE = "/api/backups"; const QString UUID_REGEX_STRING = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"; @@ -2127,9 +2126,11 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url auto deferred = makePromise("recoverFromBackup"); deferred->then([connection, JSON_MIME_TYPE](QString error, QVariantMap result) { QJsonObject rootJSON; - rootJSON["success"] = result["success"].toBool(); + auto success = result["success"].toBool(); + rootJSON["success"] = success; QJsonDocument docJSON(rootJSON); - connection->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); + connection->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(), + JSON_MIME_TYPE.toUtf8()); }); _contentManager->recoverFromBackup(deferred, id); return true; @@ -2258,7 +2259,6 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return true; } else if (url.path() == URI_API_BACKUPS) { - qDebug() << "GOt request to create a backup:"; auto params = connection->parseUrlEncodedForm(); auto it = params.find("name"); if (it == params.end()) { @@ -2374,9 +2374,11 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url auto deferred = makePromise("deleteBackup"); deferred->then([connection, JSON_MIME_TYPE](QString error, QVariantMap result) { QJsonObject rootJSON; - rootJSON["success"] = result["success"].toBool(); + auto success = result["success"].toBool(); + rootJSON["success"] = success; QJsonDocument docJSON(rootJSON); - connection->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); + connection->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(), + JSON_MIME_TYPE.toUtf8()); }); _contentManager->deleteBackup(deferred, id); diff --git a/libraries/embedded-webserver/src/HTTPConnection.cpp b/libraries/embedded-webserver/src/HTTPConnection.cpp index 6496cc3f68..1368a9f54c 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.cpp +++ b/libraries/embedded-webserver/src/HTTPConnection.cpp @@ -287,7 +287,6 @@ void HTTPConnection::readContent() { if (_socket->bytesAvailable() < size) { return; } - qDebug() << "Reading content"; _socket->read(_requestContent.data(), size); _socket->disconnect(this, SLOT(readContent())); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index fc3e793b45..f632bcf140 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2246,7 +2246,6 @@ bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer } entityDescription["DataVersion"] = _persistDataVersion; entityDescription["Id"] = _persistID; - qDebug() << "Writing to map: " << _persistDataVersion << _persistID; QScriptEngine scriptEngine; RecurseOctreeToMapOperator theOperator(entityDescription, element, &scriptEngine, skipDefaultValues, skipThoseWithBadParents, _myAvatar); From b6240e8622c8591ee57972abc5a2c719b608395b Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 14 Feb 2018 17:02:11 -0800 Subject: [PATCH 273/569] Move backup recover API to POST --- domain-server/src/DomainServer.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 718c5ff402..416c8e39b6 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2120,19 +2120,6 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url // send the response connection->respond(HTTPConnection::StatusCode200, nodesDocument.toJson(), qPrintable(JSON_MIME_TYPE)); - return true; - } else if (url.path().startsWith(URI_API_BACKUPS_RECOVER)) { - auto id = url.path().mid(QString(URI_API_BACKUPS_RECOVER).length()); - auto deferred = makePromise("recoverFromBackup"); - deferred->then([connection, JSON_MIME_TYPE](QString error, QVariantMap result) { - QJsonObject rootJSON; - auto success = result["success"].toBool(); - rootJSON["success"] = success; - QJsonDocument docJSON(rootJSON); - connection->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(), - JSON_MIME_TYPE.toUtf8()); - }); - _contentManager->recoverFromBackup(deferred, id); return true; } else if (url.path() == URI_API_BACKUPS) { QJsonObject rootJSON; @@ -2279,8 +2266,21 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url } } else if (url.path() == URI_API_DOMAINS) { - return forwardMetaverseAPIRequest(connection, "/api/v1/domains", "domain", { "label" }); + + } else if (url.path().startsWith(URI_API_BACKUPS_RECOVER)) { + auto id = url.path().mid(QString(URI_API_BACKUPS_RECOVER).length()); + auto deferred = makePromise("recoverFromBackup"); + deferred->then([connection, JSON_MIME_TYPE](QString error, QVariantMap result) { + QJsonObject rootJSON; + auto success = result["success"].toBool(); + rootJSON["success"] = success; + QJsonDocument docJSON(rootJSON); + connection->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(), + JSON_MIME_TYPE.toUtf8()); + }); + _contentManager->recoverFromBackup(deferred, id); + return true; } } else if (connection->requestOperation() == QNetworkAccessManager::PutOperation) { if (url.path() == URI_API_DOMAINS) { From f2b6823748cf0c257aa421a3cbf1718c65788245 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 15 Feb 2018 08:20:19 -0800 Subject: [PATCH 274/569] Fix initializer in EntitiesBackupHandler --- domain-server/src/BackupHandler.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/src/BackupHandler.h b/domain-server/src/BackupHandler.h index 3e25af83e8..7d1a0bdb24 100644 --- a/domain-server/src/BackupHandler.h +++ b/domain-server/src/BackupHandler.h @@ -81,7 +81,7 @@ class EntitiesBackupHandler { public: EntitiesBackupHandler(QString entitiesFilePath, QString entitiesReplacementFilePath) : _entitiesFilePath(entitiesFilePath) - , _entitiesReplacementFilePath {} + , _entitiesReplacementFilePath(entitiesReplacementFilePath) {} void loadBackup(QuaZip& zip) {} From 2cfa91be0688ead85dd702b6e43f1fd534727900 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 15 Feb 2018 08:48:43 -0800 Subject: [PATCH 275/569] Add memory include to HTTPConnection --- libraries/embedded-webserver/src/HTTPConnection.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/embedded-webserver/src/HTTPConnection.h b/libraries/embedded-webserver/src/HTTPConnection.h index 9c435b14a0..a020dfdca9 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.h +++ b/libraries/embedded-webserver/src/HTTPConnection.h @@ -26,6 +26,8 @@ #include #include +#include + class QTcpSocket; class HTTPManager; class MaskFilter; From b832e118cc0adec0392949fcfee1b880de76af45 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 15 Feb 2018 09:14:20 -0800 Subject: [PATCH 276/569] Add 'override' to BackupHandler methods --- domain-server/src/BackupHandler.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/domain-server/src/BackupHandler.h b/domain-server/src/BackupHandler.h index 7d1a0bdb24..045fcedc71 100644 --- a/domain-server/src/BackupHandler.h +++ b/domain-server/src/BackupHandler.h @@ -54,19 +54,19 @@ private: struct Model : Concept { Model(T* x) : data(x) {} - void loadBackup(QuaZip& zip) { + void loadBackup(QuaZip& zip) override { data->loadBackup(zip); } - void createBackup(QuaZip& zip) { + void createBackup(QuaZip& zip) override { data->createBackup(zip); } - void recoverBackup(QuaZip& zip) { + void recoverBackup(QuaZip& zip) override { data->recoverBackup(zip); } - void deleteBackup(QuaZip& zip) { + void deleteBackup(QuaZip& zip) override { data->deleteBackup(zip); } - void consolidateBackup(QuaZip& zip) { + void consolidateBackup(QuaZip& zip) override { data->consolidateBackup(zip); } From 145a8b385b7432dc906c9f1faefd9f12ebbd4435 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 15 Feb 2018 09:36:59 -0800 Subject: [PATCH 277/569] Move HTTP_RESPONSE_CHUNK_SIZE into lambda for http response --- libraries/embedded-webserver/src/HTTPConnection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/embedded-webserver/src/HTTPConnection.cpp b/libraries/embedded-webserver/src/HTTPConnection.cpp index 1368a9f54c..6d0126b3d1 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.cpp +++ b/libraries/embedded-webserver/src/HTTPConnection.cpp @@ -183,9 +183,9 @@ void HTTPConnection::respond(const char* code, std::unique_ptr device if (_responseDevice->atEnd()) { _socket->disconnectFromHost(); } else { - constexpr size_t HTTP_RESPONSE_CHUNK_SIZE = 1024 * 10; int totalToBeWritten = csize; connect(_socket, &QTcpSocket::bytesWritten, this, [this, totalToBeWritten](size_t bytes) mutable { + constexpr size_t HTTP_RESPONSE_CHUNK_SIZE = 1024 * 10; if (!_responseDevice->atEnd()) { totalToBeWritten -= _socket->write(_responseDevice->read(HTTP_RESPONSE_CHUNK_SIZE)); if (_responseDevice->atEnd()) { From 1aba89b908cc29565b600beff5d4539f27496d19 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 15 Feb 2018 09:38:47 -0800 Subject: [PATCH 278/569] Fix style of init list --- domain-server/src/BackupHandler.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/domain-server/src/BackupHandler.h b/domain-server/src/BackupHandler.h index 045fcedc71..5fea3be6af 100644 --- a/domain-server/src/BackupHandler.h +++ b/domain-server/src/BackupHandler.h @@ -79,9 +79,9 @@ private: #include class EntitiesBackupHandler { public: - EntitiesBackupHandler(QString entitiesFilePath, QString entitiesReplacementFilePath) - : _entitiesFilePath(entitiesFilePath) - , _entitiesReplacementFilePath(entitiesReplacementFilePath) {} + EntitiesBackupHandler(QString entitiesFilePath, QString entitiesReplacementFilePath) : + _entitiesFilePath(entitiesFilePath), + _entitiesReplacementFilePath(entitiesReplacementFilePath) {} void loadBackup(QuaZip& zip) {} From beb595266df7c23f50f6a1aa663dd353cb4468c1 Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Thu, 15 Feb 2018 21:45:35 +0300 Subject: [PATCH 279/569] fix 4x 'fromQml' slots execution note: per discussion with Austion & Seth, TabletRoot should be the only entity sending 'sendToScript' signals to C++ --- libraries/qml/src/qml/OffscreenSurface.cpp | 2 +- libraries/ui/src/ui/OffscreenQmlSurface.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index 87fc8a3025..a84f3feb4d 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -331,9 +331,9 @@ void OffscreenSurface::finishQmlLoad(QQmlComponent* qmlComponent, qmlComponent->deleteLater(); onItemCreated(qmlContext, newItem); - connect(newItem, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(QVariant))); if (!rootCreated) { + connect(newItem, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(QVariant))); onRootCreated(); emit rootItemCreated(newItem); // Call this callback after rootitem is set, otherwise VrMenu wont work diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index ea34f3de76..749a60a578 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -305,7 +305,6 @@ void OffscreenQmlSurface::onItemCreated(QQmlContext* qmlContext, QQuickItem* new qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(eventBridge, qmlContext)); } - connect(newItem, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(QVariant))); } void OffscreenQmlSurface::onRootCreated() { From 4b2e907ada0ce5b64606d9fbf2e4c42b793e4d0c Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 15 Feb 2018 11:02:14 -0800 Subject: [PATCH 280/569] Update entities recover backup to reset id and version --- domain-server/src/BackupHandler.h | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/domain-server/src/BackupHandler.h b/domain-server/src/BackupHandler.h index 5fea3be6af..f2735e5adf 100644 --- a/domain-server/src/BackupHandler.h +++ b/domain-server/src/BackupHandler.h @@ -77,6 +77,8 @@ private: }; #include +#include + class EntitiesBackupHandler { public: EntitiesBackupHandler(QString entitiesFilePath, QString entitiesReplacementFilePath) : @@ -111,18 +113,27 @@ public: qCritical() << "Failed to open models.json.gz in backup"; return; } - auto data = zipFile.readAll(); + auto rawData = zipFile.readAll(); + + zipFile.close(); + + OctreeUtils::RawOctreeData data; + if (!OctreeUtils::readOctreeDataInfoFromData(rawData, &data)) { + qCritical() << "Unable to parse octree data during backup recovery"; + return; + } + + data.resetIdAndVersion(); + + if (zipFile.getZipError() != UNZ_OK) { + qCritical() << "Failed to unzip models.json.gz: " << zipFile.getZipError(); + return; + } QFile entitiesFile { _entitiesReplacementFilePath }; if (entitiesFile.open(QIODevice::WriteOnly)) { - entitiesFile.write(data); - } - - zipFile.close(); - - if (zipFile.getZipError() != UNZ_OK) { - qCritical() << "Failed to zip models.json.gz: " << zipFile.getZipError(); + entitiesFile.write(data.toGzippedByteArray()); } } From df809f5a3eb2b80bc73f39789554bf60dde3332a Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 15 Feb 2018 11:02:29 -0800 Subject: [PATCH 281/569] Cleanup logging for backup cleanup --- .../src/DomainContentBackupManager.cpp | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 6f311613d5..347923a282 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -295,18 +295,21 @@ void DomainContentBackupManager::removeOldBackupVersions(const BackupRule& rule) backupDir.entryInfoList({ AUTOMATIC_BACKUP_PREFIX + rule.extensionFormat + "*.zip" }, QDir::Files | QDir::NoSymLinks, QDir::Name); int backupsToDelete = matchingFiles.length() - rule.maxBackupVersions; - qCDebug(domain_server) << "Found" << matchingFiles.length() << "backups, deleting " << backupsToDelete << "backup(s)"; - for (int i = 0; i < backupsToDelete; ++i) { - auto fileInfo = matchingFiles[i].absoluteFilePath(); - QFile backupFile(fileInfo); - if (backupFile.remove()) { - qCDebug(domain_server) << "Removed old backup: " << backupFile.fileName(); - } else { - qCDebug(domain_server) << "Failed to remove old backup: " << backupFile.fileName(); + if (backupsToDelete <= 0) { + qCDebug(domain_server) << "Found" << matchingFiles.length() << "backups, no backups need to be deleted"; + } else { + qCDebug(domain_server) << "Found" << matchingFiles.length() << "backups, deleting " << backupsToDelete << "backup(s)"; + for (int i = 0; i < backupsToDelete; ++i) { + auto fileInfo = matchingFiles[i].absoluteFilePath(); + QFile backupFile(fileInfo); + if (backupFile.remove()) { + qCDebug(domain_server) << "Removed old backup: " << backupFile.fileName(); + } else { + qCDebug(domain_server) << "Failed to remove old backup: " << backupFile.fileName(); + } } + qCDebug(domain_server) << "Done removing old backup versions"; } - - qCDebug(domain_server) << "Done removing old backup versions"; } else { qCDebug(domain_server) << "Rolling backups for rule" << rule.name << "." << " Max Rolled Backup Versions less than 1 [" << rule.maxBackupVersions << "]." From efb2473fcf19737f63032331a632f8cca9672337 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 15 Feb 2018 11:02:55 -0800 Subject: [PATCH 282/569] Updaet createManualBackup to defer response until creation is done --- domain-server/src/DomainContentBackupManager.cpp | 16 ++++++++++++++-- domain-server/src/DomainContentBackupManager.h | 3 +-- domain-server/src/DomainServer.cpp | 12 ++++++++++-- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 347923a282..66655ea966 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -409,8 +409,20 @@ void DomainContentBackupManager::consolidate(QString fileName) { } } -void DomainContentBackupManager::createManualBackup(const QString& name) { - createBackup(MANUAL_BACKUP_PREFIX, name); +void DomainContentBackupManager::createManualBackup(MiniPromise::Promise promise, const QString& name) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "createManualBackup", Q_ARG(MiniPromise::Promise, promise), + Q_ARG(const QString&, name)); + return; + } + + bool success; + QString path; + std::tie(success, path) = createBackup(MANUAL_BACKUP_PREFIX, name); + + promise->resolve({ + { "success", success } + }); } std::pair DomainContentBackupManager::createBackup(const QString& prefix, const QString& name) { diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h index cfeae9c8b9..5cf8d4698f 100644 --- a/domain-server/src/DomainContentBackupManager.h +++ b/domain-server/src/DomainContentBackupManager.h @@ -57,9 +57,8 @@ public: void replaceData(QByteArray data); - void createManualBackup(const QString& name); - public slots: + void createManualBackup(MiniPromise::Promise promise, const QString& name); void recoverFromBackup(MiniPromise::Promise promise, const QString& backupName); void deleteBackup(MiniPromise::Promise promise, const QString& backupName); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 416c8e39b6..da8527bf16 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2253,9 +2253,17 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return true; } - _contentManager->createManualBackup(it.value()); + auto deferred = makePromise("createManualBackup"); + deferred->then([connection, JSON_MIME_TYPE](QString error, QVariantMap result) { + QJsonObject rootJSON; + auto success = result["success"].toBool(); + rootJSON["success"] = success; + QJsonDocument docJSON(rootJSON); + connection->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(), + JSON_MIME_TYPE.toUtf8()); + }); + _contentManager->createManualBackup(deferred, it.value()); - connection->respond(HTTPConnection::StatusCode200); return true; } else if (url.path() == "/domain_settings") { From 145a0df082654b960a225c276ff1328ccac67a29 Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 15 Feb 2018 14:14:07 -0500 Subject: [PATCH 283/569] interim checkin --- interface/src/Application.cpp | 4 +- interface/src/ui/overlays/Base3DOverlay.h | 4 +- interface/src/ui/overlays/ModelOverlay.cpp | 2 +- interface/src/ui/overlays/ModelOverlay.h | 2 +- interface/src/ui/overlays/Shape3DOverlay.cpp | 6 +- interface/src/ui/overlays/Shape3DOverlay.h | 2 +- libraries/entities-renderer/CMakeLists.txt | 2 +- .../src/RenderableEntityItem.h | 4 +- .../src/RenderableModelEntityItem.cpp | 19 +- .../src/RenderableModelEntityItem.h | 3 +- .../graphics-scripting/BufferViewHelpers.cpp | 416 +++++++++- .../graphics-scripting/BufferViewHelpers.h | 35 +- .../BufferViewScripting.cpp | 4 +- .../src/graphics-scripting/Forward.h | 107 +++ .../GraphicsScriptingUtil.cpp | 3 + .../GraphicsScriptingUtil.h | 85 +++ .../ModelScriptingInterface.cpp | 152 ++-- .../ModelScriptingInterface.h | 10 +- .../src/graphics-scripting/ScriptableMesh.cpp | 714 +++++++++--------- .../src/graphics-scripting/ScriptableMesh.h | 198 +++-- .../src/graphics-scripting/ScriptableModel.h | 81 +- 21 files changed, 1219 insertions(+), 634 deletions(-) create mode 100644 libraries/graphics-scripting/src/graphics-scripting/Forward.h create mode 100644 libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp create mode 100644 libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f24969ce60..bdef2f456b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -600,7 +600,9 @@ public: QString error; scriptable::ModelProviderPointer provider; - if (auto entityInterface = getEntityModelProvider(static_cast(uuid))) { + if (uuid.isNull()) { + provider = nullptr; + } else if (auto entityInterface = getEntityModelProvider(static_cast(uuid))) { provider = entityInterface; } else if (auto overlayInterface = getOverlayModelProvider(static_cast(uuid))) { provider = overlayInterface; diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 0c8bc5aacb..6ccad338c9 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -13,7 +13,7 @@ #include #include -#include +#include #include "Overlay.h" namespace model { class Mesh; } @@ -37,7 +37,7 @@ public: virtual bool is3D() const override { return true; } virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override { subItems.push_back(getRenderItemID()); return (uint32_t) subItems.size(); } - virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); } + virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); } // TODO: consider implementing registration points in this class glm::vec3 getCenter() const { return getWorldPosition(); } diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 5a80ca1abf..e007591ce0 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -630,7 +630,7 @@ uint32_t ModelOverlay::fetchMetaSubItems(render::ItemIDs& subItems) const { return 0; } -scriptable::ScriptableModel ModelOverlay::getScriptableModel(bool* ok) { +scriptable::ScriptableModelBase ModelOverlay::getScriptableModel(bool* ok) { if (!_model || !_model->isLoaded()) { return Base3DOverlay::getScriptableModel(ok); } diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 32d9a08c70..8dc386c733 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -59,7 +59,7 @@ public: void setDrawInFront(bool drawInFront) override; void setDrawHUDLayer(bool drawHUDLayer) override; - virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; + virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override; protected: Transform evalRenderTransform() override; diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index 8bb3d16888..54423feef6 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -180,15 +180,15 @@ Transform Shape3DOverlay::evalRenderTransform() { return transform; } -scriptable::ScriptableModel Shape3DOverlay::getScriptableModel(bool* ok) { +scriptable::ScriptableModelBase Shape3DOverlay::getScriptableModel(bool* ok) { auto geometryCache = DependencyManager::get(); auto vertexColor = ColorUtils::toVec3(_color); - scriptable::ScriptableModel result; + scriptable::ScriptableModelBase result; result.metadata = { { "origin", "Shape3DOverlay::"+shapeStrings[_shape] }, { "overlayID", getID() }, }; - result.meshes << geometryCache->meshFromShape(_shape, vertexColor); + result.append(geometryCache->meshFromShape(_shape, vertexColor), {{ "shape", shapeStrings[_shape] }}); if (ok) { *ok = true; } diff --git a/interface/src/ui/overlays/Shape3DOverlay.h b/interface/src/ui/overlays/Shape3DOverlay.h index 34f82af278..f5246d95ac 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.h +++ b/interface/src/ui/overlays/Shape3DOverlay.h @@ -37,7 +37,7 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; - virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; + virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override; protected: Transform evalRenderTransform() override; diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index 27ea04f642..3aa561f927 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -13,7 +13,7 @@ include_hifi_library_headers(fbx) include_hifi_library_headers(entities) include_hifi_library_headers(avatars) include_hifi_library_headers(controllers) -include_hifi_library_headers(graphics-scripting) # for ScriptableModel.h +include_hifi_library_headers(graphics-scripting) # for Forward.h target_bullet() target_polyvox() diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index f07b67fbd0..74759f4fe4 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -17,7 +17,7 @@ #include #include "AbstractViewStateInterface.h" #include "EntitiesRendererLogging.h" -#include +#include class EntityTreeRenderer; @@ -55,7 +55,7 @@ public: const uint64_t& getUpdateTime() const { return _updateTime; } - virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); } + virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); } protected: virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); } virtual void onAddToScene(const EntityItemPointer& entity); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 7b022fefac..3d6714a400 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -950,12 +950,9 @@ QStringList RenderableModelEntityItem::getJointNames() const { return result; } - -scriptable::ScriptableModel render::entities::ModelEntityRenderer::getScriptableModel(bool* ok) { +scriptable::ScriptableModelBase render::entities::ModelEntityRenderer::getScriptableModel(bool* ok) { ModelPointer model; - withReadLock([&] { - model = _model; - }); + withReadLock([&] { model = _model; }); if (!model || !model->isLoaded()) { return scriptable::ModelProvider::modelUnavailableError(ok); @@ -964,6 +961,18 @@ scriptable::ScriptableModel render::entities::ModelEntityRenderer::getScriptable return _model->getScriptableModel(ok); } +bool render::entities::ModelEntityRenderer::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer newModel, int meshIndex, int partIndex) { + qCDebug(entitiesrenderer) << "REPLACING RenderableModelEntityItem" << newModel->objectName(); + ModelPointer model; + withReadLock([&] { model = _model; }); + + if (!model || !model->isLoaded()) { + return false; + } + + return _model->replaceScriptableModelMeshPart(newModel, meshIndex, partIndex); +} + void RenderableModelEntityItem::simulateRelayedJoints() { ModelPointer model = getModel(); if (model && model->isLoaded()) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 3e952cb9a7..ffb83d3609 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -140,7 +140,8 @@ class ModelEntityRenderer : public TypedEntityRenderer #include +#include + +#include +#include + +#include #include +#include namespace glm { using hvec2 = glm::tvec2; using hvec4 = glm::tvec4; } //#define DEBUG_BUFFERVIEW_SCRIPTING -#ifdef DEBUG_BUFFERVIEW_SCRIPTING +//#ifdef DEBUG_BUFFERVIEW_SCRIPTING #include "DebugNames.h" - QLoggingCategory bufferview_helpers{"hifi.bufferview"}; -#endif +//#endif namespace { + QLoggingCategory bufferhelper_logging{"hifi.bufferview"}; const std::array XYZW = {{ "x", "y", "z", "w" }}; const std::array ZERO123 = {{ "0", "1", "2", "3" }}; } +gpu::BufferView buffer_helpers::getBufferView(graphics::MeshPointer mesh, gpu::Stream::Slot slot) { + return slot == gpu::Stream::POSITION ? mesh->getVertexBuffer() : mesh->getAttributeBuffer(slot); +} +QMap buffer_helpers::ATTRIBUTES{ + {"position", gpu::Stream::POSITION }, + {"normal", gpu::Stream::NORMAL }, + {"color", gpu::Stream::COLOR }, + {"tangent", gpu::Stream::TEXCOORD0 }, + {"skin_cluster_index", gpu::Stream::SKIN_CLUSTER_INDEX }, + {"skin_cluster_weight", gpu::Stream::SKIN_CLUSTER_WEIGHT }, + {"texcoord0", gpu::Stream::TEXCOORD0 }, + {"texcoord1", gpu::Stream::TEXCOORD1 }, + {"texcoord2", gpu::Stream::TEXCOORD2 }, + {"texcoord3", gpu::Stream::TEXCOORD3 }, + {"texcoord4", gpu::Stream::TEXCOORD4 }, +}; + + template QVariant getBufferViewElement(const gpu::BufferView& view, quint32 index, bool asArray = false) { return glmVecToVariant(view.get(index), asArray); @@ -61,14 +86,14 @@ static void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint3 packedTangent = tangentStruct.pack; } -bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v) { +bool buffer_helpers::fromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v) { const auto& element = view._element; const auto vecN = element.getScalarCount(); const auto dataType = element.getType(); const auto byteLength = element.getSize(); const auto BYTES_PER_ELEMENT = byteLength / vecN; #ifdef DEBUG_BUFFERVIEW_SCRIPTING - qCDebug(bufferview_helpers) << "bufferViewElementFromVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN; + qCDebug(bufferhelper_logging) << "bufferViewElementFromVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN; #endif if (BYTES_PER_ELEMENT == 1) { switch(vecN) { @@ -122,16 +147,34 @@ bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, co return false; } -QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) { +bool boundsCheck(const gpu::BufferView& view, quint32 index) { + const auto byteLength = view._element.getSize(); + return ( + index < view.getNumElements() && + index * byteLength < (view._size - 1) * byteLength + ); +} + +QVariant buffer_helpers::toVariant(const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) { const auto& element = view._element; const auto vecN = element.getScalarCount(); const auto dataType = element.getType(); const auto byteLength = element.getSize(); const auto BYTES_PER_ELEMENT = byteLength / vecN; Q_ASSERT(index < view.getNumElements()); - Q_ASSERT(index * vecN * BYTES_PER_ELEMENT < (view._size - vecN * BYTES_PER_ELEMENT)); + if (!boundsCheck(view, index)) { + // sanity checks + auto byteOffset = index * vecN * BYTES_PER_ELEMENT; + auto maxByteOffset = (view._size - 1) * vecN * BYTES_PER_ELEMENT; + if (byteOffset > maxByteOffset) { + qDebug() << "bufferViewElementToVariant -- byteOffset out of range " << byteOffset << " < " << maxByteOffset << DebugNames::stringFrom(dataType); + qDebug() << "bufferViewElementToVariant -- index: " << index << "numElements" << view.getNumElements(); + qDebug() << "bufferViewElementToVariant -- vecN: " << vecN << "byteLength" << byteLength << "BYTES_PER_ELEMENT" << BYTES_PER_ELEMENT; + } + Q_ASSERT(byteOffset <= maxByteOffset); + } #ifdef DEBUG_BUFFERVIEW_SCRIPTING - qCDebug(bufferview_helpers) << "bufferViewElementToVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN; + qCDebug(bufferhelper_logging) << "bufferViewElementToVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN; #endif if (BYTES_PER_ELEMENT == 1) { switch(vecN) { @@ -221,22 +264,137 @@ const T glmVecFromVariant(const QVariant& v) { } template -gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType) { +gpu::BufferView buffer_helpers::fromVector(const QVector& elements, const gpu::Element& elementType) { auto vertexBuffer = std::make_shared(elements.size() * sizeof(T), (gpu::Byte*)elements.data()); return { vertexBuffer, 0, vertexBuffer->getSize(),sizeof(T), elementType }; } +template<> gpu::BufferView buffer_helpers::fromVector(const QVector& elements, const gpu::Element& elementType) { return fromVector(elements, elementType); } +template<> gpu::BufferView buffer_helpers::fromVector(const QVector& elements, const gpu::Element& elementType) { return fromVector(elements, elementType); } -template<> gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType) { return bufferViewFromVector(elements, elementType); } -template<> gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType) { return bufferViewFromVector(elements, elementType); } +template struct getVec4;// { static T get(const gpu::BufferView& view, quint32 index, const char *hint); }; +template struct getScalar;// { static T get(const gpu::BufferView& view, quint32 index, const char *hint); }; -gpu::BufferView cloneBufferView(const gpu::BufferView& input) { +struct gotter { + static float error(const QString& name, const gpu::BufferView& view, quint32 index, const char *hint) { + qDebug() << QString("gotter:: unhandled type=%1(element=%2(%3)) size=%4(per=%5) vec%6 hint=%7 #%8") + .arg(name) + .arg(DebugNames::stringFrom(view._element.getType())) + .arg(view._element.getType()) + .arg(view._element.getSize()) + .arg(view._element.getSize() / view._element.getScalarCount()) + .arg(view._element.getScalarCount()) + .arg(hint) + .arg(view.getNumElements()); + Q_ASSERT(false); + assert(false); + return NAN; + } +}; +template struct getScalar : gotter { + static T get(const gpu::BufferView& view, quint32 index, const char *hint) { switch(view._element.getType()) { + case gpu::UINT32: return view.get(index); + case gpu::UINT16: return view.get(index); + case gpu::UINT8: return view.get(index); + case gpu::INT32: return view.get(index); + case gpu::INT16: return view.get(index); + case gpu::INT8: return view.get(index); + case gpu::FLOAT: return view.get(index); + case gpu::HALF: return T(glm::unpackSnorm1x8(view.get(index))); + default: break; + } return T(error("getScalar", view, index, hint)); + } +}; + +template struct getVec2 : gotter { static T get(const gpu::BufferView& view, quint32 index, const char *hint) { switch(view._element.getType()) { + case gpu::UINT32: return view.get(index); + case gpu::UINT16: return view.get(index); + case gpu::UINT8: return view.get(index); + case gpu::INT32: return view.get(index); + case gpu::INT16: return view.get(index); + case gpu::INT8: return view.get(index); + case gpu::FLOAT: return view.get(index); + case gpu::HALF: return glm::unpackSnorm2x8(view.get(index)); + default: break; + } return T(error("getVec2", view, index, hint)); }}; + + +template struct getVec3 : gotter { static T get(const gpu::BufferView& view, quint32 index, const char *hint) { switch(view._element.getType()) { + case gpu::UINT32: return view.get(index); + case gpu::UINT16: return view.get(index); + case gpu::UINT8: return view.get(index); + case gpu::INT32: return view.get(index); + case gpu::INT16: return view.get(index); + case gpu::INT8: return view.get(index); + case gpu::FLOAT: return view.get(index); + case gpu::HALF: + case gpu::NUINT8: + case gpu::NINT2_10_10_10: + if (view._element.getSize() == sizeof(glm::int32)) { + return getVec4::get(view, index, hint); + } + default: break; + } return T(error("getVec3", view, index, hint)); }}; + +template struct getVec4 : gotter { static T get(const gpu::BufferView& view, quint32 index, const char *hint) { + assert(view._element.getSize() == sizeof(glm::int32)); + switch(view._element.getType()) { + case gpu::UINT32: return view.get(index); + case gpu::UINT16: return view.get(index); + case gpu::UINT8: return view.get(index); + case gpu::INT32: return view.get(index); + case gpu::INT16: return view.get(index); + case gpu::INT8: return view.get(index); + case gpu::NUINT32: break; + case gpu::NUINT16: break; + case gpu::NUINT8: return glm::unpackUnorm4x8(view.get(index)); + case gpu::NUINT2: break; + case gpu::NINT32: break; + case gpu::NINT16: break; + case gpu::NINT8: break; + case gpu::COMPRESSED: break; + case gpu::NUM_TYPES: break; + case gpu::FLOAT: return view.get(index); + case gpu::HALF: return glm::unpackSnorm4x8(view.get(index)); + case gpu::NINT2_10_10_10: return glm::unpackSnorm3x10_1x2(view.get(index)); + } return T(error("getVec4", view, index, hint)); }}; + + +template +struct getVec { + static QVector __to_vector__(const gpu::BufferView& view, const char *hint) { + QVector result; + const quint32 count = (quint32)view.getNumElements(); + result.resize(count); + for (quint32 i = 0; i < count; i++) { + result[i] = FUNC::get(view, i, hint); + } + return result; + } + static T __to_scalar__(const gpu::BufferView& view, quint32 index, const char *hint) { + assert(boundsCheck(view, index)); + return FUNC::get(view, index, hint); + } +}; + +template <> QVector buffer_helpers::toVector(const gpu::BufferView& view, const char *hint) { return getVec,int>::__to_vector__(view, hint); } +template <> QVector buffer_helpers::toVector(const gpu::BufferView& view, const char *hint) { return getVec,glm::vec2>::__to_vector__(view, hint); } +template <> QVector buffer_helpers::toVector(const gpu::BufferView& view, const char *hint) { return getVec,glm::vec3>::__to_vector__(view, hint); } +template <> QVector buffer_helpers::toVector(const gpu::BufferView& view, const char *hint) { return getVec,glm::vec4>::__to_vector__(view, hint); } + + +template <> int buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { return getVec,int>::__to_scalar__(view, index, hint); } +template <> glm::vec2 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { return getVec,glm::vec2>::__to_scalar__(view, index, hint); } +template <> glm::vec3 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { return getVec,glm::vec3>::__to_scalar__(view, index, hint); } +template <> glm::vec4 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { return getVec,glm::vec4>::__to_scalar__(view, index, hint); } + +gpu::BufferView buffer_helpers::clone(const gpu::BufferView& input) { return gpu::BufferView( std::make_shared(input._buffer->getSize(), input._buffer->getData()), input._offset, input._size, input._stride, input._element ); } -gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numElements) { +gpu::BufferView buffer_helpers::resize(const gpu::BufferView& input, quint32 numElements) { auto effectiveSize = input._buffer->getSize() / input.getNumElements(); qDebug() << "resize input" << input.getNumElements() << input._buffer->getSize() << "effectiveSize" << effectiveSize; auto vsize = input._element.getSize() * numElements; @@ -248,3 +406,235 @@ gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numEleme qDebug() << "resized output" << output.getNumElements() << output._buffer->getSize(); return output; } + +graphics::MeshPointer buffer_helpers::cloneMesh(graphics::MeshPointer mesh) { + auto clone = std::make_shared(); + //[](graphics::Mesh* blah) { + //qCDebug(bufferhelper_logging) << "--- DELETING MESH POINTER" << blah; + // delete blah; + //}); + clone->displayName = (QString::fromStdString(mesh->displayName) + "-clone").toStdString(); + //qCInfo(bufferhelper_logging) << "+++ ALLOCATED MESH POINTER ScriptableMesh::cloneMesh" << clone->displayName << clone.get() << !!mesh; + clone->setIndexBuffer(buffer_helpers::clone(mesh->getIndexBuffer())); + clone->setPartBuffer(buffer_helpers::clone(mesh->getPartBuffer())); + auto attributeViews = buffer_helpers::gatherBufferViews(mesh); + for (const auto& a : attributeViews) { + auto& view = a.second; + auto slot = buffer_helpers::ATTRIBUTES[a.first]; + auto points = buffer_helpers::clone(view); + if (slot == gpu::Stream::POSITION) { + clone->setVertexBuffer(points); + } else { + clone->addAttribute(slot, points); + } + } + return clone; +} + + +/// --- buffer view <-> variant helpers + +namespace { + // expand the corresponding attribute buffer (creating it if needed) so that it matches POSITIONS size and specified element type + gpu::BufferView _expandedAttributeBuffer(const graphics::MeshPointer mesh, gpu::Stream::Slot slot) { + gpu::BufferView bufferView = buffer_helpers::getBufferView(mesh, slot); + const auto& elementType = bufferView._element; + //auto vecN = element.getScalarCount(); + //auto type = element.getType(); + //gpu::Element elementType = getVecNElement(type, vecN); + + gpu::Size elementSize = elementType.getSize(); + auto nPositions = mesh->getNumVertices(); + auto vsize = nPositions * elementSize; + auto diffTypes = (elementType.getType() != bufferView._element.getType() || + elementType.getSize() > bufferView._element.getSize() || + elementType.getScalarCount() > bufferView._element.getScalarCount() || + vsize > bufferView._size + ); + QString hint = QString("%1").arg(slot); +#ifdef DEBUG_BUFFERVIEW_SCRIPTING + hint = DebugNames::stringFrom(slot); +#endif +#ifdef DEV_BUILD + auto beforeCount = bufferView.getNumElements(); + auto beforeTotal = bufferView._size; +#endif + if (bufferView.getNumElements() < nPositions || diffTypes) { + if (!bufferView._buffer || bufferView.getNumElements() == 0) { + qCInfo(bufferhelper_logging).nospace() << "ScriptableMesh -- adding missing mesh attribute '" << hint << "' for BufferView"; + gpu::Byte *data = new gpu::Byte[vsize]; + memset(data, 0, vsize); + auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data); + delete[] data; + bufferView = gpu::BufferView(buffer, elementType); + mesh->addAttribute(slot, bufferView); + } else { + qCInfo(bufferhelper_logging) << "ScriptableMesh -- resizing Buffer current:" << hint << bufferView._buffer->getSize() << "wanted:" << vsize; + bufferView._element = elementType; + bufferView._buffer->resize(vsize); + bufferView._size = bufferView._buffer->getSize(); + } + } +#ifdef DEV_BUILD + auto afterCount = bufferView.getNumElements(); + auto afterTotal = bufferView._size; + if (beforeTotal != afterTotal || beforeCount != afterCount) { + QString typeName = QString("%1").arg(bufferView._element.getType()); +#ifdef DEBUG_BUFFERVIEW_SCRIPTING + typeName = DebugNames::stringFrom(bufferView._element.getType()); +#endif + qCDebug(bufferhelper_logging, "NOTE:: _expandedAttributeBuffer.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)", + hint.toStdString().c_str(), bufferView._element.getScalarCount(), + typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); + } +#endif + return bufferView; + } + + gpu::BufferView expandAttributeToMatchPositions(graphics::MeshPointer mesh, gpu::Stream::Slot slot) { + if (slot == gpu::Stream::POSITION) { + return buffer_helpers::getBufferView(mesh, slot); + } + return _expandedAttributeBuffer(mesh, slot); + } +} + +std::map buffer_helpers::gatherBufferViews(graphics::MeshPointer mesh, const QStringList& expandToMatchPositions) { + std::map attributeViews; + if (!mesh) { + return attributeViews; + } + for (const auto& a : buffer_helpers::ATTRIBUTES.toStdMap()) { + auto name = a.first; + auto slot = a.second; + auto view = getBufferView(mesh, slot); + auto beforeCount = view.getNumElements(); + auto beforeTotal = view._size; + if (expandToMatchPositions.contains(name)) { + expandAttributeToMatchPositions(mesh, slot); + } + if (beforeCount > 0) { + auto element = view._element; + auto vecN = element.getScalarCount(); + //auto type = element.getType(); + QString typeName = QString("%1").arg(element.getType()); +#ifdef DEBUG_BUFFERVIEW_SCRIPTING + typeName = DebugNames::stringFrom(element.getType()); +#endif + + attributeViews[name] = getBufferView(mesh, slot); + +#if DEV_BUILD + auto afterTotal = attributeViews[name]._size; + auto afterCount = attributeViews[name].getNumElements(); + if (beforeTotal != afterTotal || beforeCount != afterCount) { + qCDebug(bufferhelper_logging, "NOTE:: gatherBufferViews.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)", + name.toStdString().c_str(), vecN, typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); + } +#endif + } + } + return attributeViews; +} + + +bool buffer_helpers::recalculateNormals(graphics::MeshPointer mesh) { + qCInfo(bufferhelper_logging) << "Recalculating normals" << !!mesh; + if (!mesh) { + return false; + } + buffer_helpers::gatherBufferViews(mesh, { "normal", "color" }); // ensures #normals >= #positions + auto normals = mesh->getAttributeBuffer(gpu::Stream::NORMAL); + auto verts = mesh->getVertexBuffer(); + auto indices = mesh->getIndexBuffer(); + auto esize = indices._element.getSize(); + auto numPoints = indices.getNumElements(); + const auto TRIANGLE = 3; + quint32 numFaces = (quint32)numPoints / TRIANGLE; + //QVector faces; + QVector faceNormals; + QMap> vertexToFaces; + //faces.resize(numFaces); + faceNormals.resize(numFaces); + auto numNormals = normals.getNumElements(); + qCInfo(bufferhelper_logging) << QString("numFaces: %1, numNormals: %2, numPoints: %3").arg(numFaces).arg(numNormals).arg(numPoints); + if (normals.getNumElements() != verts.getNumElements()) { + return false; + } + for (quint32 i = 0; i < numFaces; i++) { + quint32 I = TRIANGLE * i; + quint32 i0 = esize == 4 ? indices.get(I+0) : indices.get(I+0); + quint32 i1 = esize == 4 ? indices.get(I+1) : indices.get(I+1); + quint32 i2 = esize == 4 ? indices.get(I+2) : indices.get(I+2); + + Triangle face = { + verts.get(i1), + verts.get(i2), + verts.get(i0) + }; + faceNormals[i] = face.getNormal(); + if (glm::isnan(faceNormals[i].x)) { + qCInfo(bufferhelper_logging) << i << i0 << i1 << i2 << glmVecToVariant(face.v0) << glmVecToVariant(face.v1) << glmVecToVariant(face.v2); + break; + } + vertexToFaces[glm::to_string(face.v0).c_str()] << i; + vertexToFaces[glm::to_string(face.v1).c_str()] << i; + vertexToFaces[glm::to_string(face.v2).c_str()] << i; + } + for (quint32 j = 0; j < numNormals; j++) { + //auto v = verts.get(j); + glm::vec3 normal { 0.0f, 0.0f, 0.0f }; + QString key { glm::to_string(verts.get(j)).c_str() }; + const auto& faces = vertexToFaces.value(key); + if (faces.size()) { + for (const auto i : faces) { + normal += faceNormals[i]; + } + normal *= 1.0f / (float)faces.size(); + } else { + static int logged = 0; + if (logged++ < 10) { + qCInfo(bufferhelper_logging) << "no faces for key!?" << key; + } + normal = verts.get(j); + } + if (glm::isnan(normal.x)) { + static int logged = 0; + if (logged++ < 10) { + qCInfo(bufferhelper_logging) << "isnan(normal.x)" << j << glmVecToVariant(normal); + } + break; + } + normals.edit(j) = glm::normalize(normal); + } + return true; +} + +QVariant buffer_helpers::toVariant(const glm::mat4& mat4) { + QVector floats; + floats.resize(16); + memcpy(floats.data(), &mat4, sizeof(glm::mat4)); + QVariant v; + v.setValue>(floats); + return v; +}; + +QVariant buffer_helpers::toVariant(const Extents& box) { + return QVariantMap{ + { "center", glmVecToVariant(box.minimum + (box.size() / 2.0f)) }, + { "minimum", glmVecToVariant(box.minimum) }, + { "maximum", glmVecToVariant(box.maximum) }, + { "dimensions", glmVecToVariant(box.size()) }, + }; +} + +QVariant buffer_helpers::toVariant(const AABox& box) { + return QVariantMap{ + { "brn", glmVecToVariant(box.getCorner()) }, + { "tfl", glmVecToVariant(box.calcTopFarLeft()) }, + { "center", glmVecToVariant(box.calcCenter()) }, + { "minimum", glmVecToVariant(box.getMinimumPoint()) }, + { "maximum", glmVecToVariant(box.getMaximumPoint()) }, + { "dimensions", glmVecToVariant(box.getDimensions()) }, + }; +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h index d0d42ca419..d963fd4b22 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h @@ -7,6 +7,8 @@ #pragma once #include +#include +#include namespace gpu { class BufferView; @@ -15,11 +17,34 @@ namespace gpu { template QVariant glmVecToVariant(const T& v, bool asArray = false); template const T glmVecFromVariant(const QVariant& v); -QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, bool asArray = false, const char* hint = ""); -bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v); -template gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType); +namespace graphics { + class Mesh; + using MeshPointer = std::shared_ptr; +} -gpu::BufferView cloneBufferView(const gpu::BufferView& input); -gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numElements); +class Extents; +class AABox; +struct buffer_helpers { + static graphics::MeshPointer cloneMesh(graphics::MeshPointer mesh); + static QMap ATTRIBUTES; + static std::map gatherBufferViews(graphics::MeshPointer mesh, const QStringList& expandToMatchPositions = QStringList()); + static bool recalculateNormals(graphics::MeshPointer meshProxy); + static gpu::BufferView getBufferView(graphics::MeshPointer mesh, quint8 slot); + + static QVariant toVariant(const Extents& box); + static QVariant toVariant(const AABox& box); + static QVariant toVariant(const glm::mat4& mat4); + static QVariant toVariant(const gpu::BufferView& view, quint32 index, bool asArray = false, const char* hint = ""); + + static bool fromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v); + + template static gpu::BufferView fromVector(const QVector& elements, const gpu::Element& elementType); + + template static QVector toVector(const gpu::BufferView& view, const char *hint = ""); + template static T convert(const gpu::BufferView& view, quint32 index, const char* hint = ""); + + static gpu::BufferView clone(const gpu::BufferView& input); + static gpu::BufferView resize(const gpu::BufferView& input, quint32 numElements); +}; diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp index 367c0589e9..ab6f2c92be 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp @@ -26,7 +26,7 @@ QScriptValue getBufferViewElement(QScriptEngine* js, const gpu::BufferView& view } QScriptValue bufferViewElementToScriptValue(QScriptEngine* engine, const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) { - QVariant result = bufferViewElementToVariant(view, index, asArray, hint); + QVariant result = buffer_helpers::toVariant(view, index, asArray, hint); if (!result.isValid()) { return QScriptValue::NullValue; } @@ -39,7 +39,7 @@ void setBufferViewElement(const gpu::BufferView& view, quint32 index, const QScr } bool bufferViewElementFromScriptValue(const QScriptValue& v, const gpu::BufferView& view, quint32 index) { - return bufferViewElementFromVariant(view, index, v.toVariant()); + return buffer_helpers::fromVariant(view, index, v.toVariant()); } // diff --git a/libraries/graphics-scripting/src/graphics-scripting/Forward.h b/libraries/graphics-scripting/src/graphics-scripting/Forward.h new file mode 100644 index 0000000000..15973b5852 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/Forward.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +namespace graphics { + class Mesh; +} +namespace gpu { + class BufferView; +} +class QScriptEngine; + +namespace scriptable { + using Mesh = graphics::Mesh; + using MeshPointer = std::shared_ptr; + using WeakMeshPointer = std::weak_ptr; + + class ScriptableModelBase; + using ScriptableModelBasePointer = QPointer; + + class ModelProvider; + using ModelProviderPointer = std::shared_ptr; + using WeakModelProviderPointer = std::weak_ptr; + + class ScriptableMeshBase : public QObject { + Q_OBJECT + public: + WeakModelProviderPointer provider; + ScriptableModelBasePointer model; + WeakMeshPointer mesh; + MeshPointer ownedMesh; + QVariantMap metadata; + ScriptableMeshBase(WeakModelProviderPointer provider, ScriptableModelBasePointer model, WeakMeshPointer mesh, const QVariantMap& metadata); + ScriptableMeshBase(WeakMeshPointer mesh = WeakMeshPointer()); + ScriptableMeshBase(MeshPointer mesh, const QVariantMap& metadata); + ScriptableMeshBase(const ScriptableMeshBase& other) { *this = other; } + ScriptableMeshBase& operator=(const ScriptableMeshBase& view); + virtual ~ScriptableMeshBase(); + Q_INVOKABLE const scriptable::MeshPointer getMeshPointer() const { return mesh.lock(); } + Q_INVOKABLE const scriptable::ModelProviderPointer getModelProviderPointer() const { return provider.lock(); } + Q_INVOKABLE const scriptable::ScriptableModelBasePointer getModelBasePointer() const { return model; } + }; + + // abstract container for holding one or more references to mesh pointers + class ScriptableModelBase : public QObject { + Q_OBJECT + public: + WeakModelProviderPointer provider; + QUuid objectID; // spatially nestable ID + QVariantMap metadata; + QVector meshes; + + ScriptableModelBase(QObject* parent = nullptr) : QObject(parent) {} + ScriptableModelBase(const ScriptableModelBase& other) { *this = other; } + ScriptableModelBase& operator=(const ScriptableModelBase& other) { + provider = other.provider; + objectID = other.objectID; + metadata = other.metadata; + for (auto& mesh : other.meshes) { + append(mesh); + } + return *this; + } + virtual ~ScriptableModelBase(); + + void mixin(const QVariantMap& other); + void append(const ScriptableModelBase& other, const QVariantMap& modelMetadata = QVariantMap()); + void append(scriptable::WeakMeshPointer mesh, const QVariantMap& metadata = QVariantMap()); + void append(const ScriptableMeshBase& mesh, const QVariantMap& metadata = QVariantMap()); + // TODO: in future containers for these could go here + // QVariantMap shapes; + // QVariantMap materials; + // QVariantMap armature; + }; + + // mixin class for Avatar/Entity/Overlay Rendering that expose their in-memory graphics::Meshes + class ModelProvider { + public: + QVariantMap metadata{ { "providerType", "unknown" } }; + static scriptable::ScriptableModelBase modelUnavailableError(bool* ok) { if (ok) { *ok = false; } return {}; } + virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) = 0; + + virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) { return false; } + }; + + // mixin class for resolving UUIDs into a corresponding ModelProvider + class ModelProviderFactory : public Dependency { + public: + virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) = 0; + }; + + using uint32 = quint32; + class ScriptableModel; + using ScriptableModelPointer = QPointer; + class ScriptableMesh; + using ScriptableMeshPointer = QPointer; + class ScriptableMeshPart; + using ScriptableMeshPartPointer = QPointer; + bool registerMetaTypes(QScriptEngine* engine); +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp new file mode 100644 index 0000000000..aabf83ff66 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp @@ -0,0 +1,3 @@ +#include "GraphicsScriptingUtil.h" + +Q_LOGGING_CATEGORY(graphics_scripting, "hifi.scripting.graphics") diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h new file mode 100644 index 0000000000..a536fc413c --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +Q_DECLARE_LOGGING_CATEGORY(graphics_scripting) + +namespace scriptable { + // derive current context's C++ QObject (based on current JS "this" value) + template T this_qobject_cast(QScriptEngine* engine) { + auto context = engine ? engine->currentContext() : nullptr; + return qscriptvalue_cast(context ? context->thisObject() : QScriptValue::NullValue); + } + // JS => QPointer + template QPointer qpointer_qobject_cast(const QScriptValue& value) { + auto obj = value.toQObject(); + qCInfo(graphics_scripting) << "qpointer_qobject_cast" << obj << value.toString(); + if (auto tmp = qobject_cast(obj)) { + return QPointer(tmp); + } + if (auto tmp = static_cast(obj)) { + return QPointer(tmp); + } + return nullptr; + } + inline QString toDebugString(QObject* tmp) { + return QString("%0 (0x%1%2)") + .arg(tmp ? tmp->metaObject()->className() : "QObject") + .arg(qulonglong(tmp), 16, 16, QChar('0')) + .arg(tmp && tmp->objectName().size() ? " name=" + tmp->objectName() : ""); + } + template QString toDebugString(std::shared_ptr tmp) { + return toDebugString(qobject_cast(tmp.get())); + } + + // C++ > QtOwned instance + template std::shared_ptr make_qtowned(Rest... rest) { + T* tmp = new T(rest...); + qCInfo(graphics_scripting) << "scriptable::make_qtowned" << toDebugString(tmp); + QString debug = toDebugString(tmp); + if (tmp) { + tmp->metadata["__ownership__"] = QScriptEngine::QtOwnership; + QObject::connect(tmp, &QObject::destroyed, [=]() { qCInfo(graphics_scripting) << "-------- ~scriptable::make_qtowned" << debug; }); + auto ptr = std::shared_ptr(tmp, [debug](T* tmp) { + //qDebug() << "~std::shared_ptr" << debug; + delete tmp; + }); + return ptr; + } else { + return std::shared_ptr(tmp); + } + } + // C++ > ScriptOwned JS instance + template QPointer make_scriptowned(Rest... rest) { + T* tmp = new T(rest...); + qCInfo(graphics_scripting) << "scriptable::make_scriptowned" << toDebugString(tmp); + if (tmp) { + tmp->metadata["__ownership__"] = QScriptEngine::ScriptOwnership; + //auto blah = (DeleterFunction)[](void* delme) { }; + return add_scriptowned_destructor(tmp); + } else { + return QPointer(tmp); + } + } + // C++ > ScriptOwned JS instance + template QPointer add_scriptowned_destructor(T* tmp) { + QString debug = toDebugString(tmp); + if (tmp) { + QObject::connect(tmp, &QObject::destroyed, [=]() { + qCInfo(graphics_scripting) << "-------- ~scriptable::make_scriptowned" << debug;// << !!customDeleter; + //if (customDeleter) { + // customDeleter(tmp); + //} + }); + } else { + qCInfo(graphics_scripting) << "add_scriptowned_destructor -- not connecting to null value" << debug; + } + return QPointer(tmp); + } +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp index ab85fb8265..ab9403a8ed 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp @@ -26,12 +26,11 @@ #include "BufferViewScripting.h" #include "ScriptableMesh.h" +#include "GraphicsScriptingUtil.h" #include "ModelScriptingInterface.moc" -namespace { - QLoggingCategory model_scripting { "hifi.model.scripting" }; -} +#include "RegisteredMetaTypes.h" ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(parent) { if (auto scriptEngine = qobject_cast(parent)) { @@ -39,8 +38,51 @@ ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(pare } } -void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName) { - auto handler = makeScopedHandlerObject(scopeOrCallback, methodOrName); +bool ModelScriptingInterface::updateMeshes(QUuid uuid, const scriptable::ScriptableMeshPointer mesh, int meshIndex, int partIndex) { + auto model = scriptable::make_qtowned(); + if (mesh) { + model->append(*mesh); + } + return updateMeshes(uuid, model.get()); +} + +bool ModelScriptingInterface::updateMeshes(QUuid uuid, const scriptable::ScriptableModelPointer model) { + auto appProvider = DependencyManager::get(); + qCDebug(graphics_scripting) << "appProvider" << appProvider.data(); + scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr; + QString providerType = provider ? provider->metadata.value("providerType").toString() : QString(); + if (providerType.isEmpty()) { + providerType = "unknown"; + } + bool success = false; + if (provider) { + qCDebug(graphics_scripting) << "fetching meshes from " << providerType << "..."; + auto scriptableMeshes = provider->getScriptableModel(&success); + qCDebug(graphics_scripting) << "//fetched meshes from " << providerType << "success:" <operator scriptable::ScriptableModelBasePointer(); + qCDebug(graphics_scripting) << "as base" << base; + if (base) { + //auto meshes = model->getConstMeshes(); + success = provider->replaceScriptableModelMeshPart(base, -1, -1); + + // for (uint32_t m = 0; success && m < meshes.size(); m++) { + // const auto& mesh = meshes.at(m); + // for (int p = 0; success && p < mesh->getNumParts(); p++) { + // qCDebug(graphics_scripting) << "provider->replaceScriptableModelMeshPart" << "meshIndex" << m << "partIndex" << p; + // success = provider->replaceScriptableModelMeshPart(base, m, p); + // //if (!success) { + // qCDebug(graphics_scripting) << "//provider->replaceScriptableModelMeshPart" << "meshIndex" << m << "partIndex" << p << success; + // } + // } + } + } + } + return success; +} + +void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue callback) { + auto handler = scriptable::jsBindCallback(callback); Q_ASSERT(handler.engine() == this->engine()); QPointer engine = dynamic_cast(handler.engine()); @@ -49,18 +91,23 @@ void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback QString error; auto appProvider = DependencyManager::get(); - qDebug() << "appProvider" << appProvider.data(); + qCDebug(graphics_scripting) << "appProvider" << appProvider.data(); scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr; QString providerType = provider ? provider->metadata.value("providerType").toString() : QString(); if (providerType.isEmpty()) { providerType = "unknown"; } if (provider) { - qCDebug(model_scripting) << "fetching meshes from " << providerType << "..."; + qCDebug(graphics_scripting) << "fetching meshes from " << providerType << "..."; auto scriptableMeshes = provider->getScriptableModel(&success); - qCDebug(model_scripting) << "//fetched meshes from " << providerType << "success:" <(scriptableMeshes); + QString debugString = scriptable::toDebugString(meshes); + QObject::connect(meshes, &QObject::destroyed, this, [=]() { + qCDebug(graphics_scripting) << "///fetched meshes" << debugString; + }); + if (meshes->objectName().isEmpty()) { meshes->setObjectName(providerType+"::meshes"); } @@ -75,20 +122,20 @@ void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback } if (!error.isEmpty()) { - qCWarning(model_scripting) << "ModelScriptingInterface::getMeshes ERROR" << error; + qCWarning(graphics_scripting) << "ModelScriptingInterface::getMeshes ERROR" << error; callScopedHandlerObject(handler, engine->makeError(error), QScriptValue::NullValue); } else { - callScopedHandlerObject(handler, QScriptValue::NullValue, engine->newQObject(meshes, QScriptEngine::ScriptOwnership)); + callScopedHandlerObject(handler, QScriptValue::NullValue, engine->toScriptValue(meshes)); } } QString ModelScriptingInterface::meshToOBJ(const scriptable::ScriptableModel& _in) { const auto& in = _in.getConstMeshes(); - qCDebug(model_scripting) << "meshToOBJ" << in.size(); + qCDebug(graphics_scripting) << "meshToOBJ" << in.size(); if (in.size()) { QList meshes; foreach (auto meshProxy, in) { - qCDebug(model_scripting) << "meshToOBJ" << meshProxy; + qCDebug(graphics_scripting) << "meshToOBJ" << meshProxy; if (meshProxy) { meshes.append(getMeshPointer(meshProxy)); } @@ -207,7 +254,7 @@ QScriptValue ModelScriptingInterface::appendMeshes(scriptable::ScriptableModel _ (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); - return engine()->toScriptValue(scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, result))); + return engine()->toScriptValue(scriptable::make_scriptowned(result)); } QScriptValue ModelScriptingInterface::transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform) { @@ -220,8 +267,7 @@ QScriptValue ModelScriptingInterface::transformMesh(scriptable::ScriptableMeshPo [&](glm::vec3 color){ return color; }, [&](glm::vec3 normal){ return glm::vec3(transform * glm::vec4(normal, 0.0f)); }, [&](uint32_t index){ return index; }); - scriptable::ScriptableMeshPointer resultProxy = scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, result)); - return engine()->toScriptValue(resultProxy); + return engine()->toScriptValue(scriptable::make_scriptowned(result)); } QScriptValue ModelScriptingInterface::getVertexCount(scriptable::ScriptableMeshPointer meshProxy) { @@ -270,7 +316,7 @@ QScriptValue ModelScriptingInterface::newMesh(const QVector& vertices sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); mesh->addAttribute(gpu::Stream::NORMAL, normalBufferView); } else { - qCWarning(model_scripting, "ModelScriptingInterface::newMesh normals must be same length as vertices"); + qCWarning(graphics_scripting, "ModelScriptingInterface::newMesh normals must be same length as vertices"); } // indices (faces) @@ -300,54 +346,10 @@ QScriptValue ModelScriptingInterface::newMesh(const QVector& vertices - scriptable::ScriptableMeshPointer meshProxy = scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, mesh)); - return engine()->toScriptValue(meshProxy); + return engine()->toScriptValue(scriptable::make_scriptowned(mesh)); } namespace { - QScriptValue meshPointerToScriptValue(QScriptEngine* engine, scriptable::ScriptableMeshPointer const &in) { - if (!in) { - return QScriptValue::NullValue; - } - return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); - } - - void meshPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPointer &out) { - auto obj = value.toQObject(); - qDebug() << "meshPointerFromScriptValue" << obj; - if (auto tmp = qobject_cast(obj)) { - out = tmp; - } - // FIXME: Why does above cast not work on Win32!? - if (!out) { - if (auto smp = static_cast(obj)) { - qDebug() << "meshPointerFromScriptValue2" << smp; - out = smp; - } - } - } - - QScriptValue modelPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModelPointer &in) { - return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); - // QScriptValue result = engine->newArray(); - // int i = 0; - // foreach(auto& mesh, in->getMeshes()) { - // result.setProperty(i++, meshPointerToScriptValue(engine, mesh)); - // } - // return result; - } - - void modelPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableModelPointer &out) { - const auto length = value.property("length").toInt32(); - qCDebug(model_scripting) << "in modelPointerFromScriptValue, length =" << length; - for (int i = 0; i < length; i++) { - if (const auto meshProxy = qobject_cast(value.property(i).toQObject())) { - out->meshes.append(meshProxy->getMeshPointer()); - } else { - qCDebug(model_scripting) << "null meshProxy" << i; - } - } - } // FIXME: MESHFACES: // QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const mesh::MeshFace &meshFace) { @@ -365,29 +367,11 @@ namespace { // qScriptValueToSequence(array, result); // } - QScriptValue qVectorUInt32ToScriptValue(QScriptEngine* engine, const QVector& vector) { - return qScriptValueFromSequence(engine, vector); - } - - void qVectorUInt32FromScriptValue(const QScriptValue& array, QVector& result) { - qScriptValueToSequence(array, result); - } } -int meshUint32 = qRegisterMetaType(); -namespace mesh { - int meshUint32 = qRegisterMetaType(); -} -int qVectorMeshUint32 = qRegisterMetaType>(); void ModelScriptingInterface::registerMetaTypes(QScriptEngine* engine) { - qScriptRegisterSequenceMetaType>(engine); - qScriptRegisterSequenceMetaType>(engine); - - qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue); - qScriptRegisterMetaType(engine, meshPointerToScriptValue, meshPointerFromScriptValue); - qScriptRegisterMetaType(engine, modelPointerToScriptValue, modelPointerFromScriptValue); - + scriptable::registerMetaTypes(engine); // FIXME: MESHFACES: remove if MeshFace is not needed anywhere // qScriptRegisterSequenceMetaType(engine); // qScriptRegisterMetaType(engine, meshFaceToScriptValue, meshFaceFromScriptValue); @@ -395,7 +379,7 @@ void ModelScriptingInterface::registerMetaTypes(QScriptEngine* engine) { } MeshPointer ModelScriptingInterface::getMeshPointer(const scriptable::ScriptableMesh& meshProxy) { - return meshProxy._mesh;//getMeshPointer(&meshProxy); + return meshProxy.getMeshPointer(); } MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMesh& meshProxy) { return getMeshPointer(&meshProxy); @@ -406,7 +390,7 @@ MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPo if (context()){ context()->throwError("expected meshProxy as first parameter"); } else { - qDebug() << "expected meshProxy as first parameter"; + qCDebug(graphics_scripting) << "expected meshProxy as first parameter"; } return result; } @@ -415,7 +399,7 @@ MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPo if (context()) { context()->throwError("expected valid meshProxy as first parameter"); } else { - qDebug() << "expected valid meshProxy as first parameter"; + qCDebug(graphics_scripting) << "expected valid meshProxy as first parameter"; } return result; } diff --git a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h index eac4df3216..fa7b885014 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h @@ -15,19 +15,17 @@ #include #include -#include - #include #include #include "ScriptableMesh.h" #include + class ModelScriptingInterface : public QObject, public QScriptable, public Dependency { Q_OBJECT public: ModelScriptingInterface(QObject* parent = nullptr); - static void registerMetaTypes(QScriptEngine* engine); public slots: /**jsdoc @@ -36,7 +34,9 @@ public slots: * @function ModelScriptingInterface.getMeshes * @param {EntityID} entityID The ID of the entity whose meshes are to be retrieve */ - void getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue()); + void getMeshes(QUuid uuid, QScriptValue callback); + bool updateMeshes(QUuid uuid, const scriptable::ScriptableModelPointer model); + bool updateMeshes(QUuid uuid, const scriptable::ScriptableMeshPointer mesh, int meshIndex=0, int partIndex=0); QString meshToOBJ(const scriptable::ScriptableModel& in); @@ -48,6 +48,8 @@ public slots: QScriptValue getVertexCount(scriptable::ScriptableMeshPointer meshProxy); QScriptValue getVertex(scriptable::ScriptableMeshPointer meshProxy, quint32 vertexIndex); + static void registerMetaTypes(QScriptEngine* engine); + private: scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMeshPointer meshProxy); scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMesh& meshProxy); diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp index 1b16a6d263..b83b901acd 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -9,17 +9,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "GraphicsScriptingUtil.h" #include "ScriptableMesh.h" #include #include #include -#include #include #include #include #include -#include #include "ScriptableMesh.moc" @@ -29,49 +28,45 @@ #include "OBJWriter.h" -QLoggingCategory mesh_logging { "hifi.scripting.mesh" }; - -// FIXME: unroll/resolve before PR -using namespace scriptable; -QMap ScriptableMesh::ATTRIBUTES{ - {"position", gpu::Stream::POSITION }, - {"normal", gpu::Stream::NORMAL }, - {"color", gpu::Stream::COLOR }, - {"tangent", gpu::Stream::TEXCOORD0 }, - {"skin_cluster_index", gpu::Stream::SKIN_CLUSTER_INDEX }, - {"skin_cluster_weight", gpu::Stream::SKIN_CLUSTER_WEIGHT }, - {"texcoord0", gpu::Stream::TEXCOORD0 }, - {"texcoord1", gpu::Stream::TEXCOORD1 }, - {"texcoord2", gpu::Stream::TEXCOORD2 }, - {"texcoord3", gpu::Stream::TEXCOORD3 }, - {"texcoord4", gpu::Stream::TEXCOORD4 }, -}; - - -QString scriptable::ScriptableModel::toString() const { - return QString("[ScriptableModel%1%2]") - .arg(objectID.isNull() ? "" : " objectID="+objectID.toString()) - .arg(objectName().isEmpty() ? "" : " name=" +objectName()); +namespace scriptable { + // QScriptValue jsBindCallback(QScriptValue callback); + // template QPointer qpointer_qobject_cast(const QScriptValue& value); + // template T this_qobject_cast(QScriptEngine* engine); + // template QPointer make_scriptowned(Rest... rest); } -const QVector scriptable::ScriptableModel::getConstMeshes() const { - QVector out; - for(const auto& mesh : meshes) { - const scriptable::ScriptableMeshPointer m = scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(const_cast(this), mesh)); - out << m; +scriptable::ScriptableMeshPart::ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex) + : parentMesh(parentMesh), partIndex(partIndex) { + setObjectName(QString("%1.part[%2]").arg(parentMesh ? parentMesh->objectName() : "").arg(partIndex)); +} + +scriptable::ScriptableMesh::ScriptableMesh(const ScriptableMeshBase& other) + : ScriptableMeshBase(other) { + auto mesh = getMeshPointer(); + QString name = mesh ? QString::fromStdString(mesh->modelName) : ""; + if (name.isEmpty()) { + name = mesh ? QString::fromStdString(mesh->displayName) : ""; } - return out; + auto parentModel = getParentModel(); + setObjectName(QString("%1#%2").arg(parentModel ? parentModel->objectName() : "").arg(name)); } -QVector scriptable::ScriptableModel::getMeshes() { - QVector out; - for(auto& mesh : meshes) { - scriptable::ScriptableMeshPointer m{new scriptable::ScriptableMesh(this, mesh)}; - out << m; + +QVector scriptable::ScriptableMesh::getMeshParts() const { + QVector out; + for (quint32 i = 0; i < getNumParts(); i++) { + out << scriptable::make_scriptowned(getSelf(), i); } return out; } -quint32 ScriptableMesh::getNumVertices() const { +quint32 scriptable::ScriptableMesh::getNumIndices() const { + if (auto mesh = getMeshPointer()) { + return (quint32)mesh->getNumIndices(); + } + return 0; +} + +quint32 scriptable::ScriptableMesh::getNumVertices() const { if (auto mesh = getMeshPointer()) { return (quint32)mesh->getNumVertices(); } @@ -87,16 +82,10 @@ quint32 ScriptableMesh::getNumVertices() const { // return glm::vec3(NAN); // } -namespace { - gpu::BufferView getBufferView(scriptable::MeshPointer mesh, gpu::Stream::Slot slot) { - return slot == gpu::Stream::POSITION ? mesh->getVertexBuffer() : mesh->getAttributeBuffer(slot); - } -} - -QVector ScriptableMesh::findNearbyIndices(const glm::vec3& origin, float epsilon) const { +QVector scriptable::ScriptableMesh::findNearbyIndices(const glm::vec3& origin, float epsilon) const { QVector result; if (auto mesh = getMeshPointer()) { - const auto& pos = getBufferView(mesh, gpu::Stream::POSITION); + const auto& pos = buffer_helpers::getBufferView(mesh, gpu::Stream::POSITION); const uint32_t num = (uint32_t)pos.getNumElements(); for (uint32_t i = 0; i < num; i++) { const auto& position = pos.get(i); @@ -108,40 +97,45 @@ QVector ScriptableMesh::findNearbyIndices(const glm::vec3& origin, floa return result; } -QVector ScriptableMesh::getIndices() const { +QVector scriptable::ScriptableMesh::getIndices() const { QVector result; if (auto mesh = getMeshPointer()) { - qCDebug(mesh_logging, "getTriangleIndices mesh %p", mesh.get()); + qCDebug(graphics_scripting, "getTriangleIndices mesh %p", mesh.get()); gpu::BufferView indexBufferView = mesh->getIndexBuffer(); if (quint32 count = (quint32)indexBufferView.getNumElements()) { result.resize(count); - auto buffer = indexBufferView._buffer; - if (indexBufferView._element.getSize() == 4) { + switch(indexBufferView._element.getType()) { + case gpu::UINT32: // memcpy(result.data(), buffer->getData(), result.size()*sizeof(quint32)); for (quint32 i = 0; i < count; i++) { result[i] = indexBufferView.get(i); } - } else { + break; + case gpu::UINT16: for (quint32 i = 0; i < count; i++) { result[i] = indexBufferView.get(i); } + break; + default: + assert(false); + Q_ASSERT(false); } } } return result; } -quint32 ScriptableMesh::getNumAttributes() const { +quint32 scriptable::ScriptableMesh::getNumAttributes() const { if (auto mesh = getMeshPointer()) { return (quint32)mesh->getNumAttributes(); } return 0; } -QVector ScriptableMesh::getAttributeNames() const { +QVector scriptable::ScriptableMesh::getAttributeNames() const { QVector result; if (auto mesh = getMeshPointer()) { - for (const auto& a : ATTRIBUTES.toStdMap()) { - auto bufferView = getBufferView(mesh, a.second); + for (const auto& a : buffer_helpers::ATTRIBUTES.toStdMap()) { + auto bufferView = buffer_helpers::getBufferView(mesh, a.second); if (bufferView.getNumElements() > 0) { result << a.first; } @@ -151,55 +145,49 @@ QVector ScriptableMesh::getAttributeNames() const { } // override -QVariantMap ScriptableMesh::getVertexAttributes(quint32 vertexIndex) const { +QVariantMap scriptable::ScriptableMesh::getVertexAttributes(quint32 vertexIndex) const { return getVertexAttributes(vertexIndex, getAttributeNames()); } -bool ScriptableMesh::setVertexAttributes(quint32 vertexIndex, QVariantMap attributes) { - //qDebug() << "setVertexAttributes" << vertexIndex << attributes; - for (auto& a : gatherBufferViews(getMeshPointer())) { +bool scriptable::ScriptableMesh::setVertexAttributes(quint32 vertexIndex, QVariantMap attributes) { + //qCInfo(graphics_scripting) << "setVertexAttributes" << vertexIndex << attributes; + metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); + for (auto& a : buffer_helpers::gatherBufferViews(getMeshPointer())) { const auto& name = a.first; const auto& value = attributes.value(name); if (value.isValid()) { auto& view = a.second; - //qCDebug(mesh_logging) << "setVertexAttributes" << vertexIndex << name; - bufferViewElementFromVariant(view, vertexIndex, value); + //qCDebug(graphics_scripting) << "setVertexAttributes" << vertexIndex << name; + buffer_helpers::fromVariant(view, vertexIndex, value); } else { - //qCDebug(mesh_logging) << "(skipping) setVertexAttributes" << vertexIndex << name; + //qCDebug(graphics_scripting) << "(skipping) setVertexAttributes" << vertexIndex << name; } } return true; } -int ScriptableMesh::_getSlotNumber(const QString& attributeName) const { +int scriptable::ScriptableMesh::_getSlotNumber(const QString& attributeName) const { if (auto mesh = getMeshPointer()) { - return ATTRIBUTES.value(attributeName, -1); + return buffer_helpers::ATTRIBUTES.value(attributeName, -1); } return -1; } -QVariantMap ScriptableMesh::getMeshExtents() const { +QVariantMap scriptable::ScriptableMesh::getMeshExtents() const { auto mesh = getMeshPointer(); auto box = mesh ? mesh->evalPartsBound(0, (int)mesh->getNumParts()) : AABox(); - return { - { "brn", glmVecToVariant(box.getCorner()) }, - { "tfl", glmVecToVariant(box.calcTopFarLeft()) }, - { "center", glmVecToVariant(box.calcCenter()) }, - { "min", glmVecToVariant(box.getMinimumPoint()) }, - { "max", glmVecToVariant(box.getMaximumPoint()) }, - { "dimensions", glmVecToVariant(box.getDimensions()) }, - }; + return buffer_helpers::toVariant(box).toMap(); } -quint32 ScriptableMesh::getNumParts() const { +quint32 scriptable::ScriptableMesh::getNumParts() const { if (auto mesh = getMeshPointer()) { return (quint32)mesh->getNumParts(); } return 0; } -QVariantMap ScriptableMesh::scaleToFit(float unitScale) { +QVariantMap scriptable::ScriptableMeshPart::scaleToFit(float unitScale) { if (auto mesh = getMeshPointer()) { auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts()); auto center = box.calcCenter(); @@ -208,10 +196,10 @@ QVariantMap ScriptableMesh::scaleToFit(float unitScale) { } return {}; } -QVariantMap ScriptableMesh::translate(const glm::vec3& translation) { +QVariantMap scriptable::ScriptableMeshPart::translate(const glm::vec3& translation) { return transform(glm::translate(translation)); } -QVariantMap ScriptableMesh::scale(const glm::vec3& scale, const glm::vec3& origin) { +QVariantMap scriptable::ScriptableMeshPart::scale(const glm::vec3& scale, const glm::vec3& origin) { if (auto mesh = getMeshPointer()) { auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts()); glm::vec3 center = glm::isnan(origin.x) ? box.calcCenter() : origin; @@ -219,10 +207,10 @@ QVariantMap ScriptableMesh::scale(const glm::vec3& scale, const glm::vec3& origi } return {}; } -QVariantMap ScriptableMesh::rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin) { +QVariantMap scriptable::ScriptableMeshPart::rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin) { return rotate(glm::quat(glm::radians(eulerAngles)), origin); } -QVariantMap ScriptableMesh::rotate(const glm::quat& rotation, const glm::vec3& origin) { +QVariantMap scriptable::ScriptableMeshPart::rotate(const glm::quat& rotation, const glm::vec3& origin) { if (auto mesh = getMeshPointer()) { auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts()); glm::vec3 center = glm::isnan(origin.x) ? box.calcCenter() : origin; @@ -230,184 +218,61 @@ QVariantMap ScriptableMesh::rotate(const glm::quat& rotation, const glm::vec3& o } return {}; } -QVariantMap ScriptableMesh::transform(const glm::mat4& transform) { +QVariantMap scriptable::ScriptableMeshPart::transform(const glm::mat4& transform) { if (auto mesh = getMeshPointer()) { - const auto& pos = getBufferView(mesh, gpu::Stream::POSITION); + const auto& pos = buffer_helpers::getBufferView(mesh, gpu::Stream::POSITION); const uint32_t num = (uint32_t)pos.getNumElements(); for (uint32_t i = 0; i < num; i++) { auto& position = pos.edit(i); position = transform * glm::vec4(position, 1.0f); } + return parentMesh->getMeshExtents(); } - return getMeshExtents(); + return {}; } -QVariantList ScriptableMesh::getAttributeValues(const QString& attributeName) const { +QVariantList scriptable::ScriptableMesh::getAttributeValues(const QString& attributeName) const { QVariantList result; auto slotNum = _getSlotNumber(attributeName); if (slotNum >= 0) { auto slot = (gpu::Stream::Slot)slotNum; - const auto& bufferView = getBufferView(getMeshPointer(), slot); + const auto& bufferView = buffer_helpers::getBufferView(getMeshPointer(), slot); if (auto len = bufferView.getNumElements()) { bool asArray = bufferView._element.getType() != gpu::FLOAT; for (quint32 i = 0; i < len; i++) { - result << bufferViewElementToVariant(bufferView, i, asArray, attributeName.toStdString().c_str()); + result << buffer_helpers::toVariant(bufferView, i, asArray, attributeName.toStdString().c_str()); } } } return result; } -QVariantMap ScriptableMesh::getVertexAttributes(quint32 vertexIndex, QVector names) const { +QVariantMap scriptable::ScriptableMesh::getVertexAttributes(quint32 vertexIndex, QVector names) const { QVariantMap result; auto mesh = getMeshPointer(); if (!mesh || vertexIndex >= getNumVertices()) { return result; } - for (const auto& a : ATTRIBUTES.toStdMap()) { + for (const auto& a : buffer_helpers::ATTRIBUTES.toStdMap()) { auto name = a.first; if (!names.contains(name)) { continue; } auto slot = a.second; - const gpu::BufferView& bufferView = getBufferView(mesh, slot); + const gpu::BufferView& bufferView = buffer_helpers::getBufferView(mesh, slot); if (vertexIndex < bufferView.getNumElements()) { bool asArray = bufferView._element.getType() != gpu::FLOAT; - result[name] = bufferViewElementToVariant(bufferView, vertexIndex, asArray, name.toStdString().c_str()); + result[name] = buffer_helpers::toVariant(bufferView, vertexIndex, asArray, name.toStdString().c_str()); } } return result; } -/// --- buffer view <-> variant helpers - -namespace { - // expand the corresponding attribute buffer (creating it if needed) so that it matches POSITIONS size and specified element type - gpu::BufferView _expandedAttributeBuffer(const scriptable::MeshPointer mesh, gpu::Stream::Slot slot, const gpu::Element& elementType) { - gpu::Size elementSize = elementType.getSize(); - gpu::BufferView bufferView = getBufferView(mesh, slot); - auto nPositions = mesh->getNumVertices(); - auto vsize = nPositions * elementSize; - auto diffTypes = (elementType.getType() != bufferView._element.getType() || - elementType.getSize() > bufferView._element.getSize() || - elementType.getScalarCount() > bufferView._element.getScalarCount() || - vsize > bufferView._size - ); - auto hint = DebugNames::stringFrom(slot); - -#ifdef DEV_BUILD - auto beforeCount = bufferView.getNumElements(); - auto beforeTotal = bufferView._size; -#endif - if (bufferView.getNumElements() < nPositions || diffTypes) { - if (!bufferView._buffer || bufferView.getNumElements() == 0) { - qCInfo(mesh_logging).nospace() << "ScriptableMesh -- adding missing mesh attribute '" << hint << "' for BufferView"; - gpu::Byte *data = new gpu::Byte[vsize]; - memset(data, 0, vsize); - auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data); - delete[] data; - bufferView = gpu::BufferView(buffer, elementType); - mesh->addAttribute(slot, bufferView); - } else { - qCInfo(mesh_logging) << "ScriptableMesh -- resizing Buffer current:" << hint << bufferView._buffer->getSize() << "wanted:" << vsize; - bufferView._element = elementType; - bufferView._buffer->resize(vsize); - bufferView._size = bufferView._buffer->getSize(); - } - } -#ifdef DEV_BUILD - auto afterCount = bufferView.getNumElements(); - auto afterTotal = bufferView._size; - if (beforeTotal != afterTotal || beforeCount != afterCount) { - auto typeName = DebugNames::stringFrom(bufferView._element.getType()); - qCDebug(mesh_logging, "NOTE:: _expandedAttributeBuffer.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)", - hint.toStdString().c_str(), bufferView._element.getScalarCount(), - typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); - } -#endif - return bufferView; - } - const gpu::Element UNUSED{ gpu::SCALAR, gpu::UINT8, gpu::RAW }; - - gpu::Element getVecNElement(gpu::Type T, int N) { - switch(N) { - case 2: return { gpu::VEC2, T, gpu::XY }; - case 3: return { gpu::VEC3, T, gpu::XYZ }; - case 4: return { gpu::VEC4, T, gpu::XYZW }; - } - Q_ASSERT(false); - return UNUSED; - } - - gpu::BufferView expandAttributeToMatchPositions(scriptable::MeshPointer mesh, gpu::Stream::Slot slot) { - if (slot == gpu::Stream::POSITION) { - return getBufferView(mesh, slot); - } - return _expandedAttributeBuffer(mesh, slot, getVecNElement(gpu::FLOAT, 3)); - } -} - -std::map ScriptableMesh::gatherBufferViews(scriptable::MeshPointer mesh, const QStringList& expandToMatchPositions) { - std::map attributeViews; - if (!mesh) { - return attributeViews; - } - for (const auto& a : ScriptableMesh::ATTRIBUTES.toStdMap()) { - auto name = a.first; - auto slot = a.second; - if (expandToMatchPositions.contains(name)) { - expandAttributeToMatchPositions(mesh, slot); - } - auto view = getBufferView(mesh, slot); - auto beforeCount = view.getNumElements(); - if (beforeCount > 0) { - auto element = view._element; - auto vecN = element.getScalarCount(); - auto type = element.getType(); - QString typeName = DebugNames::stringFrom(element.getType()); - auto beforeTotal = view._size; - - attributeViews[name] = _expandedAttributeBuffer(mesh, slot, getVecNElement(type, vecN)); - -#if DEV_BUILD - auto afterTotal = attributeViews[name]._size; - auto afterCount = attributeViews[name].getNumElements(); - if (beforeTotal != afterTotal || beforeCount != afterCount) { - qCDebug(mesh_logging, "NOTE:: gatherBufferViews.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)", - name.toStdString().c_str(), vecN, typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); - } -#endif - } - } - return attributeViews; -} - -QScriptValue ScriptableModel::mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName) { - auto context = scopeOrCallback.engine()->currentContext(); - auto _in = context->thisObject(); - qCInfo(mesh_logging) << "mapAttributeValues" << _in.toVariant().typeName() << _in.toVariant().toString() << _in.toQObject(); - auto model = qscriptvalue_cast(_in); - QVector in = model.getMeshes(); - if (in.size()) { - foreach (scriptable::ScriptableMeshPointer meshProxy, in) { - meshProxy->mapAttributeValues(scopeOrCallback, methodOrName); - } - return _in; - } else if (auto meshProxy = qobject_cast(_in.toQObject())) { - return meshProxy->mapAttributeValues(scopeOrCallback, methodOrName); - } else { - context->throwError("invalid ModelProxy || MeshProxyPointer"); - } - return false; -} - - - -QScriptValue ScriptableMesh::mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName) { +quint32 scriptable::ScriptableMesh::mapAttributeValues(QScriptValue _callback) { auto mesh = getMeshPointer(); if (!mesh) { - return false; + return 0; } - auto scopedHandler = makeScopedHandlerObject(scopeOrCallback, methodOrName); + auto scopedHandler = jsBindCallback(_callback); // input buffers gpu::BufferView positions = mesh->getVertexBuffer(); @@ -417,20 +282,25 @@ QScriptValue ScriptableMesh::mapAttributeValues(QScriptValue scopeOrCallback, QS // destructure so we can still invoke callback scoped, but with a custom signature (obj, i, jsMesh) auto scope = scopedHandler.property("scope"); auto callback = scopedHandler.property("callback"); - auto js = engine(); // cache value to avoid resolving each iteration - auto meshPart = thisObject();//js->toScriptValue(meshProxy); - + auto js = engine() ? engine() : scopedHandler.engine(); // cache value to avoid resolving each iteration + if (!js) { + return 0; + } + auto meshPart = js ? js->toScriptValue(getSelf()) : QScriptValue::NullValue; + qCInfo(graphics_scripting) << "mapAttributeValues" << mesh.get() << js->currentContext()->thisObject().toQObject(); auto obj = js->newObject(); - auto attributeViews = ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); - for (uint32_t i=0; i < nPositions; i++) { + auto attributeViews = buffer_helpers::gatherBufferViews(mesh, { "normal", "color" }); + metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); + uint32_t i = 0; + for (; i < nPositions; i++) { for (const auto& a : attributeViews) { bool asArray = a.second._element.getType() != gpu::FLOAT; obj.setProperty(a.first, bufferViewElementToScriptValue(js, a.second, i, asArray, a.first.toStdString().c_str())); } auto result = callback.call(scope, { obj, i, meshPart }); if (js->hasUncaughtException()) { - context()->throwValue(js->uncaughtException()); - return false; + js->currentContext()->throwValue(js->uncaughtException()); + return i; } if (result.isBool() && !result.toBool()) { @@ -450,15 +320,19 @@ QScriptValue ScriptableMesh::mapAttributeValues(QScriptValue scopeOrCallback, QS } } } - return thisObject(); + return i; } -QScriptValue ScriptableMesh::unrollVertices(bool recalcNormals) { +quint32 scriptable::ScriptableMeshPart::mapAttributeValues(QScriptValue callback) { + return parentMesh ? parentMesh->mapAttributeValues(callback) : 0; +} + +bool scriptable::ScriptableMeshPart::unrollVertices(bool recalcNormals) { auto meshProxy = this; auto mesh = getMeshPointer(); - qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices" << !!mesh<< !!meshProxy; + qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices" << !!mesh<< !!meshProxy; if (!mesh) { - return QScriptValue(); + return false; } auto positions = mesh->getVertexBuffer(); @@ -467,8 +341,9 @@ QScriptValue ScriptableMesh::unrollVertices(bool recalcNormals) { auto buffer = new gpu::Buffer(); buffer->resize(numPoints * sizeof(uint32_t)); auto newindices = gpu::BufferView(buffer, { gpu::SCALAR, gpu::UINT32, gpu::INDEX }); - qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices numPoints" << numPoints; - auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); + metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); + qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices numPoints" << numPoints; + auto attributeViews = buffer_helpers::gatherBufferViews(mesh); for (const auto& a : attributeViews) { auto& view = a.second; auto sz = view._element.getSize(); @@ -477,19 +352,21 @@ QScriptValue ScriptableMesh::unrollVertices(bool recalcNormals) { auto points = gpu::BufferView(buffer, view._element); auto src = (uint8_t*)view._buffer->getData(); auto dest = (uint8_t*)points._buffer->getData(); - auto slot = ScriptableMesh::ATTRIBUTES[a.first]; - qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices buffer" << a.first; - qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices source" << view.getNumElements(); - qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices dest" << points.getNumElements(); - qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices sz" << sz << src << dest << slot; + auto slot = buffer_helpers::ATTRIBUTES[a.first]; + if (0) { + qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices buffer" << a.first; + qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices source" << view.getNumElements(); + qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices dest" << points.getNumElements(); + qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices sz" << sz << src << dest << slot; + } auto esize = indices._element.getSize(); const char* hint= a.first.toStdString().c_str(); for(quint32 i = 0; i < numPoints; i++) { quint32 index = esize == 4 ? indices.get(i) : indices.get(i); newindices.edit(i) = i; - bufferViewElementFromVariant( + buffer_helpers::fromVariant( points, i, - bufferViewElementToVariant(view, index, false, hint) + buffer_helpers::toVariant(view, index, false, hint) ); } if (slot == gpu::Stream::POSITION) { @@ -505,62 +382,70 @@ QScriptValue ScriptableMesh::unrollVertices(bool recalcNormals) { return true; } -bool ScriptableMesh::replaceMeshData(scriptable::ScriptableMeshPointer src, const QVector& attributeNames) { +bool scriptable::ScriptableMeshPart::replaceMeshData(scriptable::ScriptableMeshPartPointer src, const QVector& attributeNames) { auto target = getMeshPointer(); auto source = src ? src->getMeshPointer() : nullptr; if (!target || !source) { - context()->throwError("ScriptableMesh::replaceMeshData -- expected dest and src to be valid mesh proxy pointers"); + if (context()) { + context()->throwError("ScriptableMeshPart::replaceMeshData -- expected dest and src to be valid mesh proxy pointers"); + } else { + qCWarning(graphics_scripting) << "ScriptableMeshPart::replaceMeshData -- expected dest and src to be valid mesh proxy pointers"; + } return false; } - QVector attributes = attributeNames.isEmpty() ? src->getAttributeNames() : attributeNames; + QVector attributes = attributeNames.isEmpty() ? src->parentMesh->getAttributeNames() : attributeNames; - //qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData -- source:" << source->displayName << "target:" << target->displayName << "attributes:" << attributes; + qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData -- " << + "source:" << QString::fromStdString(source->displayName) << + "target:" << QString::fromStdString(target->displayName) << + "attributes:" << attributes; + + metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); // remove attributes only found on target mesh, unless user has explicitly specified the relevant attribute names if (attributeNames.isEmpty()) { - auto attributeViews = ScriptableMesh::gatherBufferViews(target); + auto attributeViews = buffer_helpers::gatherBufferViews(target); for (const auto& a : attributeViews) { - auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + auto slot = buffer_helpers::ATTRIBUTES[a.first]; if (!attributes.contains(a.first)) { - //qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData -- pruning target attribute" << a.first << slot; + qCInfo(graphics_scripting) << "ScriptableMesh::replaceMeshData -- pruning target attribute" << a.first << slot; target->removeAttribute(slot); } } } - target->setVertexBuffer(cloneBufferView(source->getVertexBuffer())); - target->setIndexBuffer(cloneBufferView(source->getIndexBuffer())); - target->setPartBuffer(cloneBufferView(source->getPartBuffer())); + target->setVertexBuffer(buffer_helpers::clone(source->getVertexBuffer())); + target->setIndexBuffer(buffer_helpers::clone(source->getIndexBuffer())); + target->setPartBuffer(buffer_helpers::clone(source->getPartBuffer())); for (const auto& a : attributes) { - auto slot = ScriptableMesh::ATTRIBUTES[a]; + auto slot = buffer_helpers::ATTRIBUTES[a]; if (slot == gpu::Stream::POSITION) { continue; } - // auto& before = target->getAttributeBuffer(slot); + auto& before = target->getAttributeBuffer(slot); auto& input = source->getAttributeBuffer(slot); if (input.getNumElements() == 0) { - //qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData buffer is empty -- pruning" << a << slot; + qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData buffer is empty -- pruning" << a << slot; target->removeAttribute(slot); } else { - // if (before.getNumElements() == 0) { - // qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData target buffer is empty -- adding" << a << slot; - // } else { - // qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData target buffer exists -- updating" << a << slot; - // } - target->addAttribute(slot, cloneBufferView(input)); + if (before.getNumElements() == 0) { + qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData target buffer is empty -- adding" << a << slot; + } else { + qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData target buffer exists -- updating" << a << slot; + } + target->addAttribute(slot, buffer_helpers::clone(input)); } - // auto& after = target->getAttributeBuffer(slot); - // qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements(); + auto& after = target->getAttributeBuffer(slot); + qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements(); } return true; } -bool ScriptableMesh::dedupeVertices(float epsilon) { - scriptable::ScriptableMeshPointer meshProxy = this; +bool scriptable::ScriptableMeshPart::dedupeVertices(float epsilon) { auto mesh = getMeshPointer(); if (!mesh) { return false; @@ -573,6 +458,7 @@ bool ScriptableMesh::dedupeVertices(float epsilon) { uniqueVerts.reserve((int)numPositions); QMap remapIndices; + metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); for (quint32 i = 0; i < numPositions; i++) { const quint32 numUnique = uniqueVerts.size(); const auto& position = positions.get(i); @@ -590,7 +476,7 @@ bool ScriptableMesh::dedupeVertices(float epsilon) { } } - qCInfo(mesh_logging) << "//VERTS before" << numPositions << "after" << uniqueVerts.size(); + qCInfo(graphics_scripting) << "//VERTS before" << numPositions << "after" << uniqueVerts.size(); auto indices = mesh->getIndexBuffer(); auto numIndices = indices.getNumElements(); @@ -600,34 +486,34 @@ bool ScriptableMesh::dedupeVertices(float epsilon) { for (quint32 i = 0; i < numIndices; i++) { quint32 index = esize == 4 ? indices.get(i) : indices.get(i); if (remapIndices.contains(index)) { - //qCInfo(mesh_logging) << i << index << "->" << remapIndices[index]; + //qCInfo(graphics_scripting) << i << index << "->" << remapIndices[index]; newIndices << remapIndices[index]; } else { - qCInfo(mesh_logging) << i << index << "!remapIndices[index]"; + qCInfo(graphics_scripting) << i << index << "!remapIndices[index]"; } } - mesh->setIndexBuffer(bufferViewFromVector(newIndices, { gpu::SCALAR, gpu::UINT32, gpu::INDEX })); - mesh->setVertexBuffer(bufferViewFromVector(uniqueVerts, { gpu::VEC3, gpu::FLOAT, gpu::XYZ })); + mesh->setIndexBuffer(buffer_helpers::fromVector(newIndices, { gpu::SCALAR, gpu::UINT32, gpu::INDEX })); + mesh->setVertexBuffer(buffer_helpers::fromVector(uniqueVerts, { gpu::VEC3, gpu::FLOAT, gpu::XYZ })); - auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); + auto attributeViews = buffer_helpers::gatherBufferViews(mesh); quint32 numUniqueVerts = uniqueVerts.size(); for (const auto& a : attributeViews) { auto& view = a.second; - auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + auto slot = buffer_helpers::ATTRIBUTES[a.first]; if (slot == gpu::Stream::POSITION) { continue; } - qCInfo(mesh_logging) << "ScriptableMesh::dedupeVertices" << a.first << slot << view.getNumElements(); - auto newView = resizedBufferView(view, numUniqueVerts); - qCInfo(mesh_logging) << a.first << "before: #" << view.getNumElements() << "after: #" << newView.getNumElements(); + qCInfo(graphics_scripting) << "ScriptableMeshPart::dedupeVertices" << a.first << slot << view.getNumElements(); + auto newView = buffer_helpers::resize(view, numUniqueVerts); + qCInfo(graphics_scripting) << a.first << "before: #" << view.getNumElements() << "after: #" << newView.getNumElements(); quint32 numElements = (quint32)view.getNumElements(); for (quint32 i = 0; i < numElements; i++) { quint32 fromVertexIndex = i; quint32 toVertexIndex = remapIndices.contains(fromVertexIndex) ? remapIndices[fromVertexIndex] : fromVertexIndex; - bufferViewElementFromVariant( + buffer_helpers::fromVariant( newView, toVertexIndex, - bufferViewElementToVariant(view, fromVertexIndex, false, "dedupe") + buffer_helpers::toVariant(view, fromVertexIndex, false, "dedupe") ); } mesh->addAttribute(slot, newView); @@ -635,120 +521,202 @@ bool ScriptableMesh::dedupeVertices(float epsilon) { return true; } -QScriptValue ScriptableMesh::cloneMesh(bool recalcNormals) { +scriptable::ScriptableMeshPointer scriptable::ScriptableMesh::cloneMesh(bool recalcNormals) { auto mesh = getMeshPointer(); if (!mesh) { - return QScriptValue::NullValue; + qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh -- !meshPointer"; + return nullptr; } - graphics::MeshPointer clone(new graphics::Mesh()); - clone->displayName = mesh->displayName + "-clone"; - qCInfo(mesh_logging) << "ScriptableMesh::cloneMesh" << !!mesh; - if (!mesh) { - return QScriptValue::NullValue; - } - - clone->setIndexBuffer(cloneBufferView(mesh->getIndexBuffer())); - clone->setPartBuffer(cloneBufferView(mesh->getPartBuffer())); - auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); - for (const auto& a : attributeViews) { - auto& view = a.second; - auto slot = ScriptableMesh::ATTRIBUTES[a.first]; - qCInfo(mesh_logging) << "ScriptableMesh::cloneVertices buffer" << a.first << slot; - auto points = cloneBufferView(view); - qCInfo(mesh_logging) << "ScriptableMesh::cloneVertices source" << view.getNumElements(); - qCInfo(mesh_logging) << "ScriptableMesh::cloneVertices dest" << points.getNumElements(); - if (slot == gpu::Stream::POSITION) { - clone->setVertexBuffer(points); - } else { - clone->addAttribute(slot, points); - } - } - - auto result = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, clone)); + qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh..."; + auto clone = buffer_helpers::cloneMesh(mesh); + + qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh..."; if (recalcNormals) { - result->recalculateNormals(); + buffer_helpers::recalculateNormals(clone); } - return engine()->toScriptValue(result); + qCDebug(graphics_scripting) << clone.get();// << metadata; + auto meshPointer = scriptable::make_scriptowned(provider, model, clone, metadata); + clone.reset(); // free local reference + qCInfo(graphics_scripting) << "========= ScriptableMesh::cloneMesh..." << meshPointer << meshPointer->ownedMesh.use_count(); + //scriptable::MeshPointer* ppMesh = new scriptable::MeshPointer(); + //*ppMesh = clone; + + if (meshPointer) { + scriptable::WeakMeshPointer delme = meshPointer->mesh; + QString debugString = scriptable::toDebugString(meshPointer); + QObject::connect(meshPointer, &QObject::destroyed, meshPointer, [=]() { + qCWarning(graphics_scripting) << "*************** cloneMesh/Destroy"; + qCWarning(graphics_scripting) << "*************** " << debugString << delme.lock().get(); + if (!delme.expired()) { + qCWarning(graphics_scripting) << "cloneMesh -- potential memory leak..." << debugString << delme.lock().get(); + } + }); + } + + meshPointer->metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); + return scriptable::ScriptableMeshPointer(meshPointer); } -bool ScriptableMesh::recalculateNormals() { - scriptable::ScriptableMeshPointer meshProxy = this; - qCInfo(mesh_logging) << "Recalculating normals" << !!meshProxy; - auto mesh = getMeshPointer(); - if (!mesh) { - return false; - } - ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); // ensures #normals >= #positions - auto normals = mesh->getAttributeBuffer(gpu::Stream::NORMAL); - auto verts = mesh->getVertexBuffer(); - auto indices = mesh->getIndexBuffer(); - auto esize = indices._element.getSize(); - auto numPoints = indices.getNumElements(); - const auto TRIANGLE = 3; - quint32 numFaces = (quint32)numPoints / TRIANGLE; - //QVector faces; - QVector faceNormals; - QMap> vertexToFaces; - //faces.resize(numFaces); - faceNormals.resize(numFaces); - auto numNormals = normals.getNumElements(); - qCInfo(mesh_logging) << QString("numFaces: %1, numNormals: %2, numPoints: %3").arg(numFaces).arg(numNormals).arg(numPoints); - if (normals.getNumElements() != verts.getNumElements()) { - return false; - } - for (quint32 i = 0; i < numFaces; i++) { - quint32 I = TRIANGLE * i; - quint32 i0 = esize == 4 ? indices.get(I+0) : indices.get(I+0); - quint32 i1 = esize == 4 ? indices.get(I+1) : indices.get(I+1); - quint32 i2 = esize == 4 ? indices.get(I+2) : indices.get(I+2); - - Triangle face = { - verts.get(i1), - verts.get(i2), - verts.get(i0) - }; - faceNormals[i] = face.getNormal(); - if (glm::isnan(faceNormals[i].x)) { - qCInfo(mesh_logging) << i << i0 << i1 << i2 << vec3toVariant(face.v0) << vec3toVariant(face.v1) << vec3toVariant(face.v2); - break; - } - vertexToFaces[glm::to_string(face.v0).c_str()] << i; - vertexToFaces[glm::to_string(face.v1).c_str()] << i; - vertexToFaces[glm::to_string(face.v2).c_str()] << i; - } - for (quint32 j = 0; j < numNormals; j++) { - //auto v = verts.get(j); - glm::vec3 normal { 0.0f, 0.0f, 0.0f }; - QString key { glm::to_string(verts.get(j)).c_str() }; - const auto& faces = vertexToFaces.value(key); - if (faces.size()) { - for (const auto i : faces) { - normal += faceNormals[i]; - } - normal *= 1.0f / (float)faces.size(); - } else { - static int logged = 0; - if (logged++ < 10) { - qCInfo(mesh_logging) << "no faces for key!?" << key; - } - normal = verts.get(j); - } - if (glm::isnan(normal.x)) { - static int logged = 0; - if (logged++ < 10) { - qCInfo(mesh_logging) << "isnan(normal.x)" << j << vec3toVariant(normal); - } - break; - } - normals.edit(j) = glm::normalize(normal); - } - return true; +scriptable::ScriptableMeshBase::ScriptableMeshBase(scriptable::WeakModelProviderPointer provider, scriptable::ScriptableModelBasePointer model, scriptable::WeakMeshPointer mesh, const QVariantMap& metadata) + : provider(provider), model(model), mesh(mesh), metadata(metadata) {} +scriptable::ScriptableMeshBase::ScriptableMeshBase(scriptable::WeakMeshPointer mesh) : scriptable::ScriptableMeshBase(scriptable::WeakModelProviderPointer(), nullptr, mesh, QVariantMap()) { } +scriptable::ScriptableMeshBase::ScriptableMeshBase(scriptable::MeshPointer mesh, const QVariantMap& metadata) + : ScriptableMeshBase(WeakModelProviderPointer(), nullptr, mesh, metadata) { + ownedMesh = mesh; +} +//scriptable::ScriptableMeshBase::ScriptableMeshBase(const scriptable::ScriptableMeshBase& other) { *this = other; } +scriptable::ScriptableMeshBase& scriptable::ScriptableMeshBase::operator=(const scriptable::ScriptableMeshBase& view) { + provider = view.provider; + model = view.model; + mesh = view.mesh; + ownedMesh = view.ownedMesh; + metadata = view.metadata; + return *this; +} + scriptable::ScriptableMeshBase::~ScriptableMeshBase() { + ownedMesh.reset(); + qCInfo(graphics_scripting) << "//~ScriptableMeshBase" << this << "ownedMesh:" << ownedMesh.use_count() << "mesh:" << mesh.use_count(); } -QString ScriptableMesh::toOBJ() { +scriptable::ScriptableMesh::~ScriptableMesh() { + ownedMesh.reset(); + qCInfo(graphics_scripting) << "//~ScriptableMesh" << this << "ownedMesh:" << ownedMesh.use_count() << "mesh:" << mesh.use_count(); +} + +QString scriptable::ScriptableMeshPart::toOBJ() { if (!getMeshPointer()) { - context()->throwError(QString("null mesh")); + if (context()) { + context()->throwError(QString("null mesh")); + } else { + qCWarning(graphics_scripting) << "null mesh"; + } + return QString(); } return writeOBJToString({ getMeshPointer() }); } +namespace { + template + QScriptValue qObjectToScriptValue(QScriptEngine* engine, const T& object) { + if (!object) { + return QScriptValue::NullValue; + } + auto ownership = object->metadata.value("__ownership__"); + return engine->newQObject( + object, + ownership.isValid() ? static_cast(ownership.toInt()) : QScriptEngine::QtOwnership + //, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects + ); + } + + QScriptValue meshPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableMeshPointer& in) { + return qObjectToScriptValue(engine, in); + } + QScriptValue meshPartPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableMeshPartPointer& in) { + return qObjectToScriptValue(engine, in); + } + QScriptValue modelPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModelPointer& in) { + return qObjectToScriptValue(engine, in); + } + + void meshPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPointer &out) { + out = scriptable::qpointer_qobject_cast(value); + } + void modelPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableModelPointer &out) { + out = scriptable::qpointer_qobject_cast(value); + } + void meshPartPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPartPointer &out) { + out = scriptable::qpointer_qobject_cast(value); + } + + // FIXME: MESHFACES: + // QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const mesh::MeshFace &meshFace) { + // QScriptValue obj = engine->newObject(); + // obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices)); + // return obj; + // } + // void meshFaceFromScriptValue(const QScriptValue &object, mesh::MeshFace& meshFaceResult) { + // qScriptValueToSequence(object.property("vertices"), meshFaceResult.vertexIndices); + // } + // QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector) { + // return qScriptValueFromSequence(engine, vector); + // } + // void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result) { + // qScriptValueToSequence(array, result); + // } + + QScriptValue qVectorUInt32ToScriptValue(QScriptEngine* engine, const QVector& vector) { + return qScriptValueFromSequence(engine, vector); + } + + void qVectorUInt32FromScriptValue(const QScriptValue& array, QVector& result) { + qScriptValueToSequence(array, result); + } + + QVector metaTypeIds{ + qRegisterMetaType("uint32"), + qRegisterMetaType("scriptable::uint32"), + qRegisterMetaType>(), + qRegisterMetaType>("QVector"), + qRegisterMetaType(), + qRegisterMetaType(), + qRegisterMetaType(), + }; +} + +namespace scriptable { + bool registerMetaTypes(QScriptEngine* engine) { + qScriptRegisterSequenceMetaType>(engine); + qScriptRegisterSequenceMetaType>(engine); + qScriptRegisterSequenceMetaType>(engine); + + qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue); + qScriptRegisterMetaType(engine, modelPointerToScriptValue, modelPointerFromScriptValue); + qScriptRegisterMetaType(engine, meshPointerToScriptValue, meshPointerFromScriptValue); + qScriptRegisterMetaType(engine, meshPartPointerToScriptValue, meshPartPointerFromScriptValue); + + return metaTypeIds.size(); + } + // callback helper that lets C++ method signatures remain simple (ie: taking a single callback argument) while + // still supporting extended Qt signal-like (scope, "methodName") and (scope, function(){}) "this" binding conventions + QScriptValue jsBindCallback(QScriptValue callback) { + if (callback.isObject() && callback.property("callback").isFunction()) { + return callback; + } + auto engine = callback.engine(); + auto context = engine ? engine->currentContext() : nullptr; + auto length = context ? context->argumentCount() : 0; + QScriptValue scope = context ? context->thisObject() : QScriptValue::NullValue; + QScriptValue method; + qCInfo(graphics_scripting) << "jsBindCallback" << engine << length << scope.toQObject() << method.toString(); + int i = 0; + for (; context && i < length; i++) { + if (context->argument(i).strictlyEquals(callback)) { + method = context->argument(i+1); + } + } + if (method.isFunction() || method.isString()) { + scope = callback; + } else { + method = callback; + } + qCInfo(graphics_scripting) << "scope:" << scope.toQObject() << "method:" << method.toString(); + return ::makeScopedHandlerObject(scope, method); + } +} + +bool scriptable::GraphicsScriptingInterface::updateMeshPart(ScriptableMeshPointer mesh, ScriptableMeshPartPointer part) { + Q_ASSERT(mesh); + Q_ASSERT(part); + Q_ASSERT(part->parentMesh); + auto tmp = exportMeshPart(mesh, part->partIndex); + if (part->parentMesh == mesh) { + qCInfo(graphics_scripting) << "updateMeshPart -- update via clone" << mesh << part; + tmp->replaceMeshData(part->cloneMeshPart()); + return false; + } else { + qCInfo(graphics_scripting) << "updateMeshPart -- update via inplace" << mesh << part; + tmp->replaceMeshData(part); + return true; + } +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h index 257285fa90..c655167c2b 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -11,142 +11,188 @@ #include +//#include #include +#include #include #include -namespace graphics { - class Mesh; -} -namespace gpu { - class BufferView; -} namespace scriptable { - class ScriptableMeshPart; - using ScriptableMeshPartPointer = QPointer; - class ScriptableMesh : public QObject, QScriptable { + + QScriptValue jsBindCallback(QScriptValue callback); + class ScriptableMesh : public ScriptableMeshBase, QScriptable { Q_OBJECT public: - Q_PROPERTY(quint32 numParts READ getNumParts) - Q_PROPERTY(quint32 numAttributes READ getNumAttributes) - Q_PROPERTY(quint32 numVertices READ getNumVertices) - Q_PROPERTY(quint32 numIndices READ getNumIndices) - Q_PROPERTY(QVariantMap metadata MEMBER _metadata) + Q_PROPERTY(uint32 numParts READ getNumParts) + Q_PROPERTY(uint32 numAttributes READ getNumAttributes) + Q_PROPERTY(uint32 numVertices READ getNumVertices) + Q_PROPERTY(uint32 numIndices READ getNumIndices) + Q_PROPERTY(QVariantMap metadata MEMBER metadata) Q_PROPERTY(QVector attributeNames READ getAttributeNames) + Q_PROPERTY(QVector parts READ getMeshParts) + Q_PROPERTY(bool valid READ hasValidMesh) + bool hasValidMesh() const { return (bool)getMeshPointer(); } + Q_PROPERTY(bool validOwned READ hasValidOwnedMesh) + bool hasValidOwnedMesh() const { return (bool)getOwnedMeshPointer(); } - static QMap ATTRIBUTES; - static std::map gatherBufferViews(MeshPointer mesh, const QStringList& expandToMatchPositions = QStringList()); + operator const ScriptableMeshBase*() const { return (qobject_cast(this)); } + ScriptableMesh(scriptable::MeshPointer mesh) : ScriptableMeshBase(mesh) { ownedMesh = mesh; } + ScriptableMesh(WeakModelProviderPointer provider, ScriptableModelBasePointer model, MeshPointer mesh, const QVariantMap& metadata) + : ScriptableMeshBase(provider, model, mesh, metadata) { ownedMesh = mesh; } + //ScriptableMesh& operator=(const ScriptableMesh& other) { model=other.model; mesh=other.mesh; metadata=other.metadata; return *this; }; + //ScriptableMesh() : QObject(), model(nullptr) {} + //ScriptableMesh(const ScriptableMesh& other) : QObject(), model(other.model), mesh(other.mesh), metadata(other.metadata) {} + ScriptableMesh(const ScriptableMeshBase& other); + ScriptableMesh(const ScriptableMesh& other) : ScriptableMeshBase(other) {}; + virtual ~ScriptableMesh(); - ScriptableMesh& operator=(const ScriptableMesh& other) { _model=other._model; _mesh=other._mesh; _metadata=other._metadata; return *this; }; - ScriptableMesh() : QObject(), _model(nullptr) {} - ScriptableMesh(ScriptableModelPointer parent, scriptable::MeshPointer mesh) : QObject(), _model(parent), _mesh(mesh) {} - ScriptableMesh(const ScriptableMesh& other) : QObject(), _model(other._model), _mesh(other._mesh), _metadata(other._metadata) {} - ~ScriptableMesh() { qDebug() << "~ScriptableMesh" << this; } - - scriptable::MeshPointer getMeshPointer() const { return _mesh; } + Q_INVOKABLE const scriptable::ScriptableModelPointer getParentModel() const { return qobject_cast(model); } + Q_INVOKABLE const scriptable::MeshPointer getOwnedMeshPointer() const { return ownedMesh; } + scriptable::ScriptableMeshPointer getSelf() const { return const_cast(this); } public slots: - quint32 getNumParts() const; - quint32 getNumVertices() const; - quint32 getNumAttributes() const; - quint32 getNumIndices() const { return 0; } + uint32 getNumParts() const; + uint32 getNumVertices() const; + uint32 getNumAttributes() const; + uint32 getNumIndices() const; QVector getAttributeNames() const; + QVector getMeshParts() const; - QVariantMap getVertexAttributes(quint32 vertexIndex) const; - QVariantMap getVertexAttributes(quint32 vertexIndex, QVector attributes) const; + QVariantMap getVertexAttributes(uint32 vertexIndex) const; + QVariantMap getVertexAttributes(uint32 vertexIndex, QVector attributes) const; - QVector getIndices() const; - QVector findNearbyIndices(const glm::vec3& origin, float epsilon = 1e-6) const; + QVector getIndices() const; + QVector findNearbyIndices(const glm::vec3& origin, float epsilon = 1e-6) const; QVariantMap getMeshExtents() const; - bool setVertexAttributes(quint32 vertexIndex, QVariantMap attributes); - QVariantMap scaleToFit(float unitScale); + bool setVertexAttributes(uint32 vertexIndex, QVariantMap attributes); QVariantList getAttributeValues(const QString& attributeName) const; int _getSlotNumber(const QString& attributeName) const; + scriptable::ScriptableMeshPointer cloneMesh(bool recalcNormals = false); + public: + operator bool() const { return !mesh.expired(); } + + public slots: + // QScriptEngine-specific wrappers + uint32 mapAttributeValues(QScriptValue callback); + }; + + // TODO: part-specific wrapper for working with raw geometries + class ScriptableMeshPart : public QObject, QScriptable { + Q_OBJECT + public: + Q_PROPERTY(uint32 partIndex MEMBER partIndex CONSTANT) + Q_PROPERTY(int numElementsPerFace MEMBER _elementsPerFace CONSTANT) + Q_PROPERTY(QString topology MEMBER _topology CONSTANT) + + Q_PROPERTY(uint32 numFaces READ getNumFaces) + Q_PROPERTY(uint32 numAttributes READ getNumAttributes) + Q_PROPERTY(uint32 numVertices READ getNumVertices) + Q_PROPERTY(uint32 numIndices READ getNumIndices) + Q_PROPERTY(QVector attributeNames READ getAttributeNames) + + Q_PROPERTY(QVariantMap metadata MEMBER metadata) + + //Q_PROPERTY(scriptable::ScriptableMeshPointer parentMesh MEMBER parentMesh CONSTANT HIDE) + + ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex); + ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { parentMesh=view.parentMesh; return *this; }; + ScriptableMeshPart(const ScriptableMeshPart& other) : parentMesh(other.parentMesh), partIndex(other.partIndex) {} + ~ScriptableMeshPart() { qDebug() << "~ScriptableMeshPart" << this; } + + public slots: + scriptable::ScriptableMeshPointer getParentMesh() const { return parentMesh; } + uint32 getNumAttributes() const { return parentMesh ? parentMesh->getNumAttributes() : 0; } + uint32 getNumVertices() const { return parentMesh ? parentMesh->getNumVertices() : 0; } + uint32 getNumIndices() const { return parentMesh ? parentMesh->getNumIndices() : 0; } + uint32 getNumFaces() const { return parentMesh ? parentMesh->getNumIndices() / _elementsPerFace : 0; } + QVector getAttributeNames() const { return parentMesh ? parentMesh->getAttributeNames() : QVector(); } + QVector getFace(uint32 faceIndex) const { + auto inds = parentMesh ? parentMesh->getIndices() : QVector(); + return faceIndex+2 < (uint32)inds.size() ? inds.mid(faceIndex*3, 3) : QVector(); + } + QVariantMap scaleToFit(float unitScale); QVariantMap translate(const glm::vec3& translation); QVariantMap scale(const glm::vec3& scale, const glm::vec3& origin = glm::vec3(NAN)); QVariantMap rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin = glm::vec3(NAN)); QVariantMap rotate(const glm::quat& rotation, const glm::vec3& origin = glm::vec3(NAN)); QVariantMap transform(const glm::mat4& transform); - public: - operator bool() const { return _mesh != nullptr; } - ScriptableModelPointer _model; - scriptable::MeshPointer _mesh; - QVariantMap _metadata; + bool unrollVertices(bool recalcNormals = false); + bool dedupeVertices(float epsilon = 1e-6); + bool recalculateNormals() { return buffer_helpers::recalculateNormals(getMeshPointer()); } + + bool replaceMeshData(scriptable::ScriptableMeshPartPointer source, const QVector& attributeNames = QVector()); + scriptable::ScriptableMeshPartPointer cloneMeshPart(bool recalcNormals = false) { + if (parentMesh) { + if (auto clone = parentMesh->cloneMesh(recalcNormals)) { + return clone->getMeshParts().value(partIndex); + } + } + return nullptr; + } + QString toOBJ(); public slots: // QScriptEngine-specific wrappers - QScriptValue mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue()); - bool dedupeVertices(float epsilon = 1e-6); - bool recalculateNormals(); - QScriptValue cloneMesh(bool recalcNormals = true); - QScriptValue unrollVertices(bool recalcNormals = true); - bool replaceMeshData(scriptable::ScriptableMeshPointer source, const QVector& attributeNames = QVector()); - QString toOBJ(); - }; - - // TODO: part-specific wrapper for working with raw geometries - class ScriptableMeshPart : public QObject { - Q_OBJECT - public: - Q_PROPERTY(QString topology READ getTopology) - Q_PROPERTY(quint32 numFaces READ getNumFaces) - - ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { parentMesh=view.parentMesh; return *this; }; - ScriptableMeshPart(const ScriptableMeshPart& other) : parentMesh(other.parentMesh) {} - ScriptableMeshPart() {} - ~ScriptableMeshPart() { qDebug() << "~ScriptableMeshPart" << this; } - - public slots: - QString getTopology() const { return "triangles"; } - quint32 getNumFaces() const { return parentMesh.getIndices().size() / 3; } - QVector getFace(quint32 faceIndex) const { - auto inds = parentMesh.getIndices(); - return faceIndex+2 < (quint32)inds.size() ? inds.mid(faceIndex*3, 3) : QVector(); - } + uint32 mapAttributeValues(QScriptValue callback); public: - scriptable::ScriptableMesh parentMesh; - int partIndex; + scriptable::ScriptableMeshPointer parentMesh; + uint32 partIndex; + QVariantMap metadata; + protected: + int _elementsPerFace{ 3 }; + QString _topology{ "triangles" }; + scriptable::MeshPointer getMeshPointer() const { return parentMesh ? parentMesh->getMeshPointer() : nullptr; } }; - class GraphicsScriptingInterface : public QObject { + class GraphicsScriptingInterface : public QObject, QScriptable { Q_OBJECT public: GraphicsScriptingInterface(QObject* parent = nullptr) : QObject(parent) {} GraphicsScriptingInterface(const GraphicsScriptingInterface& other) {} - public slots: - ScriptableMeshPart exportMeshPart(ScriptableMesh mesh, int part) { return {}; } + public slots: + ScriptableMeshPartPointer exportMeshPart(ScriptableMeshPointer mesh, int part=0) { + return ScriptableMeshPartPointer(new ScriptableMeshPart(mesh, part)); + } + bool updateMeshPart(ScriptableMeshPointer mesh, ScriptableMeshPartPointer part); }; + + // callback helper that lets C++ method signatures remain simple (ie: taking a single callback argument) while + // still supporting extended Qt signal-like (scope, "methodName") and (scope, function(){}) "this" binding conventions + QScriptValue jsBindCallback(QScriptValue callback); + + // derive a corresponding C++ class instance from the current script engine's thisObject + template T this_qobject_cast(QScriptEngine* engine); } Q_DECLARE_METATYPE(scriptable::ScriptableMeshPointer) Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(scriptable::ScriptableMeshPartPointer) +Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(scriptable::GraphicsScriptingInterface) // FIXME: MESHFACES: faces were supported in the original Model.* API -- are they still needed/used/useful for anything yet? #include namespace mesh { - using uint32 = quint32; class MeshFace; using MeshFaces = QVector; class MeshFace { public: MeshFace() {} - MeshFace(QVector vertexIndices) : vertexIndices(vertexIndices) {} + MeshFace(QVector vertexIndices) : vertexIndices(vertexIndices) {} ~MeshFace() {} - QVector vertexIndices; + QVector vertexIndices; // TODO -- material... }; }; Q_DECLARE_METATYPE(mesh::MeshFace) Q_DECLARE_METATYPE(QVector) -Q_DECLARE_METATYPE(mesh::uint32) -Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(scriptable::uint32) +Q_DECLARE_METATYPE(QVector) diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h index 4ba5a993b1..97a73ddd61 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h @@ -1,56 +1,24 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include +#include "Forward.h" -#include - -namespace graphics { - class Mesh; -} -namespace gpu { - class BufferView; -} class QScriptValue; - namespace scriptable { - using Mesh = graphics::Mesh; - using MeshPointer = std::shared_ptr; - - class ScriptableModel; - using ScriptableModelPointer = QPointer; - class ScriptableMesh; - using ScriptableMeshPointer = QPointer; - - // abstract container for holding one or more scriptable meshes - class ScriptableModel : public QObject { + class ScriptableModel : public ScriptableModelBase { Q_OBJECT public: - QUuid objectID; - QVariantMap metadata; - QVector meshes; - - Q_PROPERTY(QVector meshes READ getMeshes) Q_PROPERTY(QUuid objectID MEMBER objectID CONSTANT) Q_PROPERTY(QVariantMap metadata MEMBER metadata CONSTANT) - Q_INVOKABLE QString toString() const; + Q_PROPERTY(uint32 numMeshes READ getNumMeshes) + Q_PROPERTY(QVector meshes READ getMeshes) - ScriptableModel(QObject* parent = nullptr) : QObject(parent) {} - ScriptableModel(const ScriptableModel& other) : objectID(other.objectID), metadata(other.metadata), meshes(other.meshes) {} - ScriptableModel& operator=(const ScriptableModel& view) { objectID = view.objectID; metadata = view.metadata; meshes = view.meshes; return *this; } - ~ScriptableModel() { qDebug() << "~ScriptableModel" << this; } - - void mixin(const ScriptableModel& other) { - for (const auto& key : other.metadata.keys()) { metadata[key] = other.metadata[key]; } - for (const auto& mesh : other.meshes) { meshes << mesh; } - } + ScriptableModel(QObject* parent = nullptr) : ScriptableModelBase(parent) {} + ScriptableModel(const ScriptableModel& other) : ScriptableModelBase(other) {} + ScriptableModel(const ScriptableModelBase& other) : ScriptableModelBase(other) {} + ScriptableModel& operator=(const ScriptableModelBase& view) { ScriptableModelBase::operator=(view); return *this; } + virtual ~ScriptableModel() { qDebug() << "~ScriptableModel" << this; } + Q_INVOKABLE scriptable::ScriptableModelPointer cloneModel(const QVariantMap& options = QVariantMap()); // TODO: in future accessors for these could go here // QVariantMap shapes; // QVariantMap materials; @@ -58,28 +26,23 @@ namespace scriptable { QVector getMeshes(); const QVector getConstMeshes() const; + operator scriptable::ScriptableModelBasePointer() { + QPointer p; + p = qobject_cast(this); + return p; + } + // QScriptEngine-specific wrappers - Q_INVOKABLE QScriptValue mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName); + Q_INVOKABLE uint32 mapAttributeValues(QScriptValue callback); + Q_INVOKABLE QString toString() const; + Q_INVOKABLE uint32 getNumMeshes() { return meshes.size(); } }; - // mixin class for Avatar/Entity/Overlay Rendering that expose their in-memory graphics::Meshes - class ModelProvider { - public: - QVariantMap metadata; - static scriptable::ScriptableModel modelUnavailableError(bool* ok) { if (ok) { *ok = false; } return {}; } - virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) = 0; - }; - using ModelProviderPointer = std::shared_ptr; - - // mixin class for Application to resolve UUIDs into a corresponding ModelProvider - class ModelProviderFactory : public Dependency { - public: - virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) = 0; - }; } Q_DECLARE_METATYPE(scriptable::MeshPointer) -Q_DECLARE_METATYPE(scriptable::ScriptableModel) +Q_DECLARE_METATYPE(scriptable::WeakMeshPointer) Q_DECLARE_METATYPE(scriptable::ScriptableModelPointer) - +Q_DECLARE_METATYPE(scriptable::ScriptableModelBase) +Q_DECLARE_METATYPE(scriptable::ScriptableModelBasePointer) From b21b98c81066a85be556dec87a238650729dfe00 Mon Sep 17 00:00:00 2001 From: samcake Date: Thu, 15 Feb 2018 11:24:20 -0800 Subject: [PATCH 284/569] Add a way to early abort a task from a Job, apply that to the highlight effect to shave unecessary work --- .../render-utils/src/HighlightEffect.cpp | 2 +- libraries/task/src/task/Task.cpp | 6 ++-- libraries/task/src/task/Task.h | 28 +++++++++++++------ 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp index de13188733..0bf8e7fa71 100644 --- a/libraries/render-utils/src/HighlightEffect.cpp +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -455,7 +455,7 @@ void SelectionToHighlight::run(const render::RenderContextPointer& renderContext } if (numLayers == 0) { - renderContext->abortTask(); + renderContext->taskFlow.abortTask(); } } diff --git a/libraries/task/src/task/Task.cpp b/libraries/task/src/task/Task.cpp index bb65f15b7c..621d77d7bf 100644 --- a/libraries/task/src/task/Task.cpp +++ b/libraries/task/src/task/Task.cpp @@ -20,15 +20,15 @@ JobContext::JobContext(const QLoggingCategory& category) : JobContext::~JobContext() { } -void JobContext::resetTaskFlow() { +void TaskFlow::reset() { _doAbortTask = false; } -void JobContext::abortTask() { +void TaskFlow::abortTask() { _doAbortTask = true; } -bool JobContext::doAbortTask() const { +bool TaskFlow::doAbortTask() const { return _doAbortTask; } diff --git a/libraries/task/src/task/Task.h b/libraries/task/src/task/Task.h index 1f1fb79ee1..34cdbb5439 100644 --- a/libraries/task/src/task/Task.h +++ b/libraries/task/src/task/Task.h @@ -27,6 +27,21 @@ template class JobT; template class TaskT; class JobNoIO {}; +class TaskFlow { +public: + TaskFlow() = default; + ~TaskFlow() = default; + + // called after each job + void reset(); + + void abortTask(); + bool doAbortTask() const; + +protected: + bool _doAbortTask{ false }; +}; + class JobContext { public: JobContext(const QLoggingCategory& category); @@ -35,15 +50,10 @@ public: std::shared_ptr jobConfig { nullptr }; const QLoggingCategory& profileCategory; - // control flow commands - void resetTaskFlow(); - void abortTask(); - - // Check command flow - bool doAbortTask() const; + // Task flow control + TaskFlow taskFlow{}; protected: - bool _doAbortTask{ false }; }; using JobContextPointer = std::shared_ptr; @@ -316,8 +326,8 @@ public: if (config->alwaysEnabled || config->enabled) { for (auto job : TaskConcept::_jobs) { job.run(jobContext); - if (jobContext->doAbortTask()) { - jobContext->resetTaskFlow(); + if (jobContext->taskFlow.doAbortTask()) { + jobContext->taskFlow.reset(); return; } } From b8fb08dc74d80cd43469338640a579680637fe2e Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 15 Feb 2018 11:24:22 -0800 Subject: [PATCH 285/569] Avatar support --- .../qml/hifi/commerce/checkout/Checkout.qml | 15 ++++++++++----- .../qml/hifi/commerce/purchases/PurchasedItem.qml | 2 ++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 981d5c5b9a..916e67e37f 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -40,8 +40,9 @@ Rectangle { property bool isCertified; property string itemType; property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar"]; - property var buttonTextNormal: ["REZ IT", "WEAR IT", "REPLACE CONTENT SET", "INSTALL IT", "WEAR IT"]; - property var buttonTextClicked: ["REZZED", "WORN", "CONTENT SET REPLACED!", "INSTALLED", "WORN"] + property var itemTypesText: ["entity", "wearable", "content set", "app", "avatar"]; + property var buttonTextNormal: ["REZ", "WEAR", "REPLACE CONTENT SET", "INSTALL", "WEAR"]; + property var buttonTextClicked: ["REZZED!", "WORN!", "CONTENT SET REPLACED!", "INSTALLED!", "AVATAR CHANGED!"] property var buttonGlyph: [hifi.glyphs.wand, hifi.glyphs.hat, hifi.glyphs.globe, hifi.glyphs.install, hifi.glyphs.avatar]; property bool shouldBuyWithControlledFailure: false; property bool debugCheckoutSuccess: false; @@ -529,6 +530,7 @@ Rectangle { // "Buy" button HifiControlsUit.Button { id: buyButton; + visible: !(root.itemType === "avatar" && viewInMyPurchasesButton.visible) enabled: (root.balanceAfterPurchase >= 0 && ownershipStatusReceived && balanceReceived) || (!root.isCertified); color: viewInMyPurchasesButton.visible ? hifi.buttons.white : hifi.buttons.blue; colorScheme: hifi.colorSchemes.light; @@ -576,7 +578,7 @@ Rectangle { id: cancelPurchaseButton; color: hifi.buttons.noneBorderlessGray; colorScheme: hifi.colorSchemes.light; - anchors.top: buyButton.bottom; + anchors.top: buyButton.visible ? buyButton.bottom : viewInMyPurchasesButton.bottom; anchors.topMargin: 16; height: 40; anchors.left: parent.left; @@ -622,8 +624,9 @@ Rectangle { RalewaySemiBold { id: completeText2; - text: "The item " + '' + root.itemName + '' + - " has been added to your Purchases and a receipt will appear in your Wallet's transaction history."; + text: "The " + (root.itemTypesText)[itemTypesArray.indexOf(root.itemType)] + + '' + root.itemName + '' + + " has been added to your Purchases and a receipt will appear in your Wallet's transaction history."; // Text size size: 20; // Anchors @@ -701,6 +704,8 @@ Rectangle { "root.visible = false;rezzedNotifContainer.visible = true; rezzedNotifContainerTimer.start();" + "UserActivityLogger.commerceEntityRezzed('" + root.itemId + "', 'checkout', '" + root.itemType + "');"; lightboxPopup.visible = true; + } else if (root.itemType === "avatar") { + Avatar.skeletonModelURL = root.itemHref; } else { sendToScript({method: 'checkout_rezClicked', itemHref: root.itemHref, itemType: root.itemType}); rezzedNotifContainer.visible = true; diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index d19a16e74e..7b2cae5188 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -476,6 +476,8 @@ Item { onClicked: { if (root.itemType === "contentSet") { sendToPurchases({method: 'showReplaceContentLightbox', itemId: root.itemId, itemHref: root.itemHref}); + } else if (root.itemType === "avatar") { + Avatar.skeletonModelURL = root.itemHref; } else { sendToPurchases({method: 'purchases_rezClicked', itemHref: root.itemHref, itemType: root.itemType}); root.showConfirmation = true; From ce93b9a1f4ea001587a0d8ecc39d4473326e1cf8 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 14 Feb 2018 16:48:37 -0800 Subject: [PATCH 286/569] Simplify BackupHandler pattern --- domain-server/src/BackupHandler.h | 64 +++---------------- domain-server/src/BackupSupervisor.h | 4 +- .../src/DomainContentBackupManager.cpp | 11 ++-- .../src/DomainContentBackupManager.h | 4 +- domain-server/src/DomainServer.cpp | 4 +- 5 files changed, 22 insertions(+), 65 deletions(-) diff --git a/domain-server/src/BackupHandler.h b/domain-server/src/BackupHandler.h index f2735e5adf..eb9c35f236 100644 --- a/domain-server/src/BackupHandler.h +++ b/domain-server/src/BackupHandler.h @@ -18,68 +18,22 @@ #include -class BackupHandler { +class BackupHandlerInterface { public: - template - BackupHandler(T* x) : _self(new Model(x)) {} + virtual ~BackupHandlerInterface() = default; - void loadBackup(QuaZip& zip) { - _self->loadBackup(zip); - } - void createBackup(QuaZip& zip) { - _self->createBackup(zip); - } - void recoverBackup(QuaZip& zip) { - _self->recoverBackup(zip); - } - void deleteBackup(QuaZip& zip) { - _self->deleteBackup(zip); - } - void consolidateBackup(QuaZip& zip) { - _self->consolidateBackup(zip); - } - -private: - struct Concept { - virtual ~Concept() = default; - - virtual void loadBackup(QuaZip& zip) = 0; - virtual void createBackup(QuaZip& zip) = 0; - virtual void recoverBackup(QuaZip& zip) = 0; - virtual void deleteBackup(QuaZip& zip) = 0; - virtual void consolidateBackup(QuaZip& zip) = 0; - }; - - template - struct Model : Concept { - Model(T* x) : data(x) {} - - void loadBackup(QuaZip& zip) override { - data->loadBackup(zip); - } - void createBackup(QuaZip& zip) override { - data->createBackup(zip); - } - void recoverBackup(QuaZip& zip) override { - data->recoverBackup(zip); - } - void deleteBackup(QuaZip& zip) override { - data->deleteBackup(zip); - } - void consolidateBackup(QuaZip& zip) override { - data->consolidateBackup(zip); - } - - std::unique_ptr data; - }; - - std::unique_ptr _self; + virtual void loadBackup(QuaZip& zip) = 0; + virtual void createBackup(QuaZip& zip) = 0; + virtual void recoverBackup(QuaZip& zip) = 0; + virtual void deleteBackup(QuaZip& zip) = 0; + virtual void consolidateBackup(QuaZip& zip) = 0; }; +using BackupHandlerPointer = std::unique_ptr; #include #include -class EntitiesBackupHandler { +class EntitiesBackupHandler : public BackupHandlerInterface { public: EntitiesBackupHandler(QString entitiesFilePath, QString entitiesReplacementFilePath) : _entitiesFilePath(entitiesFilePath), diff --git a/domain-server/src/BackupSupervisor.h b/domain-server/src/BackupSupervisor.h index 9fedcca19b..0d0d21a174 100644 --- a/domain-server/src/BackupSupervisor.h +++ b/domain-server/src/BackupSupervisor.h @@ -24,6 +24,8 @@ #include +#include "BackupHandler.h" + class QuaZip; struct AssetServerBackup { @@ -32,7 +34,7 @@ struct AssetServerBackup { bool corruptedBackup; }; -class BackupSupervisor : public QObject { +class BackupSupervisor : public QObject, public BackupHandlerInterface { Q_OBJECT public: diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 66655ea966..2b990b170e 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -39,7 +39,8 @@ static const QString DATETIME_FORMAT { "yyyy-MM-dd_HH-mm-ss" }; static const QString DATETIME_FORMAT_RE { "\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}" }; static const QString AUTOMATIC_BACKUP_PREFIX { "autobackup-" }; static const QString MANUAL_BACKUP_PREFIX { "backup-" }; -void DomainContentBackupManager::addBackupHandler(BackupHandler handler) { + +void DomainContentBackupManager::addBackupHandler(BackupHandlerPointer handler) { _backupHandlers.push_back(std::move(handler)); } @@ -238,7 +239,7 @@ void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise, success = false; } else { for (auto& handler : _backupHandlers) { - handler.recoverBackup(zip); + handler->recoverBackup(zip); } qDebug() << "Successfully recovered from " << backupName; @@ -339,7 +340,7 @@ void DomainContentBackupManager::load() { } for (auto& handler : _backupHandlers) { - handler.loadBackup(zip); + handler->loadBackup(zip); } zip.close(); @@ -402,7 +403,7 @@ void DomainContentBackupManager::consolidate(QString fileName) { } for (auto& handler : _backupHandlers) { - handler.consolidateBackup(zip); + handler->consolidateBackup(zip); } zip.close(); @@ -437,7 +438,7 @@ std::pair DomainContentBackupManager::createBackup(const QString& } for (auto& handler : _backupHandlers) { - handler.createBackup(zip); + handler->createBackup(zip); } zip.close(); diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h index 5cf8d4698f..a3606929d5 100644 --- a/domain-server/src/DomainContentBackupManager.h +++ b/domain-server/src/DomainContentBackupManager.h @@ -50,7 +50,7 @@ public: int persistInterval = DEFAULT_PERSIST_INTERVAL, bool debugTimestampNow = false); - void addBackupHandler(BackupHandler handler); + void addBackupHandler(BackupHandlerPointer handler); std::vector getAllBackups(); void aboutToFinish(); /// call this to inform the persist thread that the owner is about to finish to support final persist @@ -82,7 +82,7 @@ protected: private: const QString _backupDirectory; - std::vector _backupHandlers; + std::vector _backupHandlers; int _persistInterval { 0 }; int64_t _lastCheck { 0 }; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index da8527bf16..a8ceebd6e7 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -296,8 +296,8 @@ DomainServer::DomainServer(int argc, char* argv[]) : maybeHandleReplacementEntityFile(); _contentManager.reset(new DomainContentBackupManager(getContentBackupDir(), _settingsManager.settingsResponseObjectForType("6")["entity_server_settings"].toObject())); - _contentManager->addBackupHandler(new EntitiesBackupHandler(getEntitiesFilePath(), getEntitiesReplacementFilePath())); - _contentManager->addBackupHandler(new BackupSupervisor(getContentBackupDir())); + _contentManager->addBackupHandler(BackupHandlerPointer(new EntitiesBackupHandler(getEntitiesFilePath(), getEntitiesReplacementFilePath()))); + _contentManager->addBackupHandler(BackupHandlerPointer(new BackupSupervisor(getContentBackupDir()))); _contentManager->initialize(true); qDebug() << "Existing backups:"; From 4482f9c83c67def4fef5444a52f6d85e531ce53f Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 14 Feb 2018 16:49:12 -0800 Subject: [PATCH 287/569] Queue all requests until the AS is fully setup --- assignment-client/src/assets/AssetServer.cpp | 45 +++++++++++++++++--- assignment-client/src/assets/AssetServer.h | 5 +++ domain-server/src/BackupSupervisor.cpp | 5 +-- 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index 1ae65290ff..0be557bccd 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -257,12 +257,10 @@ AssetServer::AssetServer(ReceivedMessage& message) : _transferTaskPool.setMaxThreadCount(TASK_POOL_THREAD_COUNT); _bakingTaskPool.setMaxThreadCount(1); + // Queue all requests until the Asset Server is fully setup auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerListener(PacketType::AssetGet, this, "handleAssetGet"); - packetReceiver.registerListener(PacketType::AssetGetInfo, this, "handleAssetGetInfo"); - packetReceiver.registerListener(PacketType::AssetUpload, this, "handleAssetUpload"); - packetReceiver.registerListener(PacketType::AssetMappingOperation, this, "handleAssetMappingOperation"); - + packetReceiver.registerListenerForTypes({ PacketType::AssetGet, PacketType::AssetGetInfo, PacketType::AssetUpload, PacketType::AssetMappingOperation }, this, "queueRequests"); + #ifdef Q_OS_WIN updateConsumedCores(); QTimer* timer = new QTimer(this); @@ -417,6 +415,43 @@ void AssetServer::completeSetup() { PathUtils::removeTemporaryApplicationDirs(); PathUtils::removeTemporaryApplicationDirs("Oven"); + + // We're fully setup, remove the request queueing and replay all requests + auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); + packetReceiver.unregisterListener(this); + packetReceiver.registerListener(PacketType::AssetGet, this, "handleAssetGet"); + packetReceiver.registerListener(PacketType::AssetGetInfo, this, "handleAssetGetInfo"); + packetReceiver.registerListener(PacketType::AssetUpload, this, "handleAssetUpload"); + packetReceiver.registerListener(PacketType::AssetMappingOperation, this, "handleAssetMappingOperation"); + + replayRequests(); +} + +void AssetServer::queueRequests(QSharedPointer packet, SharedNodePointer senderNode) { + _queuedRequests.push_back({ packet, senderNode }); +} + +void AssetServer::replayRequests() { + for (const auto& request : _queuedRequests) { + switch (request.first->getType()) { + case PacketType::AssetGet: + handleAssetGet(request.first, request.second); + break; + case PacketType::AssetGetInfo: + handleAssetGetInfo(request.first, request.second); + break; + case PacketType::AssetUpload: + handleAssetUpload(request.first, request.second); + break; + case PacketType::AssetMappingOperation: + handleAssetMappingOperation(request.first, request.second); + break; + default: + qWarning() << "Unknown queued request type:" << request.first->getType(); + break; + } + } + _queuedRequests.clear(); } void AssetServer::cleanupUnmappedFiles() { diff --git a/assignment-client/src/assets/AssetServer.h b/assignment-client/src/assets/AssetServer.h index c6336a3a4d..b8aac800ed 100644 --- a/assignment-client/src/assets/AssetServer.h +++ b/assignment-client/src/assets/AssetServer.h @@ -49,6 +49,7 @@ public slots: private slots: void completeSetup(); + void queueRequests(QSharedPointer packet, SharedNodePointer senderNode); void handleAssetGetInfo(QSharedPointer packet, SharedNodePointer senderNode); void handleAssetGet(QSharedPointer packet, SharedNodePointer senderNode); void handleAssetUpload(QSharedPointer packetList, SharedNodePointer senderNode); @@ -57,6 +58,8 @@ private slots: void sendStatsPacket() override; private: + void replayRequests(); + void handleGetMappingOperation(ReceivedMessage& message, NLPacketList& replyPacket); void handleGetAllMappingOperation(NLPacketList& replyPacket); void handleSetMappingOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket); @@ -120,6 +123,8 @@ private: QHash> _pendingBakes; QThreadPool _bakingTaskPool; + QVector, SharedNodePointer>> _queuedRequests; + bool _wasColorTextureCompressionEnabled { false }; bool _wasGrayscaleTextureCompressionEnabled { false }; bool _wasNormalTextureCompressionEnabled { false }; diff --git a/domain-server/src/BackupSupervisor.cpp b/domain-server/src/BackupSupervisor.cpp index 869f85c6cc..0cbded4e43 100644 --- a/domain-server/src/BackupSupervisor.cpp +++ b/domain-server/src/BackupSupervisor.cpp @@ -46,9 +46,8 @@ BackupSupervisor::BackupSupervisor(const QString& backupDirectory) : auto nodeList = DependencyManager::get(); QObject::connect(nodeList.data(), &LimitedNodeList::nodeAdded, this, [this](SharedNodePointer node) { if (node->getType() == NodeType::AssetServer) { - // Give the Asset Server some time to bootup. - static constexpr int ASSET_SERVER_BOOTUP_MARGIN = 1 * 1000; - _mappingsRefreshTimer.start(ASSET_SERVER_BOOTUP_MARGIN); + // run immediately for the first time. + _mappingsRefreshTimer.start(0); } }); } From d8d05fe0456831cd9dadde546c3635f09eafa46c Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 14 Feb 2018 17:24:00 -0800 Subject: [PATCH 288/569] Rename backup supervisor --- ...Supervisor.cpp => AssetsBackupHandler.cpp} | 42 +++++++++---------- ...ckupSupervisor.h => AssetsBackupHandler.h} | 24 +++++------ domain-server/src/DomainServer.cpp | 4 +- domain-server/src/DomainServer.h | 2 +- 4 files changed, 36 insertions(+), 36 deletions(-) rename domain-server/src/{BackupSupervisor.cpp => AssetsBackupHandler.cpp} (93%) rename domain-server/src/{BackupSupervisor.h => AssetsBackupHandler.h} (85%) diff --git a/domain-server/src/BackupSupervisor.cpp b/domain-server/src/AssetsBackupHandler.cpp similarity index 93% rename from domain-server/src/BackupSupervisor.cpp rename to domain-server/src/AssetsBackupHandler.cpp index 0cbded4e43..3dc4851762 100644 --- a/domain-server/src/BackupSupervisor.cpp +++ b/domain-server/src/AssetsBackupHandler.cpp @@ -1,5 +1,5 @@ // -// BackupSupervisor.cpp +// AssetsBackupHandler.cpp // domain-server/src // // Created by Clement Brisset on 1/12/18. @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "BackupSupervisor.h" +#include "AssetsBackupHandler.h" #include #include @@ -31,7 +31,7 @@ using namespace std; Q_DECLARE_LOGGING_CATEGORY(backup_supervisor) Q_LOGGING_CATEGORY(backup_supervisor, "hifi.backup-supervisor"); -BackupSupervisor::BackupSupervisor(const QString& backupDirectory) : +AssetsBackupHandler::AssetsBackupHandler(const QString& backupDirectory) : _assetsDirectory(backupDirectory + ASSETS_DIR) { // Make sure the asset directory exists. @@ -41,7 +41,7 @@ BackupSupervisor::BackupSupervisor(const QString& backupDirectory) : _mappingsRefreshTimer.setTimerType(Qt::CoarseTimer); _mappingsRefreshTimer.setSingleShot(true); - QObject::connect(&_mappingsRefreshTimer, &QTimer::timeout, this, &BackupSupervisor::refreshMappings); + QObject::connect(&_mappingsRefreshTimer, &QTimer::timeout, this, &AssetsBackupHandler::refreshMappings); auto nodeList = DependencyManager::get(); QObject::connect(nodeList.data(), &LimitedNodeList::nodeAdded, this, [this](SharedNodePointer node) { @@ -53,7 +53,7 @@ BackupSupervisor::BackupSupervisor(const QString& backupDirectory) : } -void BackupSupervisor::refreshAssetsOnDisk() { +void AssetsBackupHandler::refreshAssetsOnDisk() { QDir assetsDir { _assetsDirectory }; auto assetNames = assetsDir.entryList(QDir::Files); @@ -64,7 +64,7 @@ void BackupSupervisor::refreshAssetsOnDisk() { } -void BackupSupervisor::refreshAssetsInBackups() { +void AssetsBackupHandler::refreshAssetsInBackups() { _assetsInBackups.clear(); for (const auto& backup : _backups) { for (const auto& mapping : backup.mappings) { @@ -73,7 +73,7 @@ void BackupSupervisor::refreshAssetsInBackups() { } } -void BackupSupervisor::checkForMissingAssets() { +void AssetsBackupHandler::checkForMissingAssets() { vector missingAssets; set_difference(begin(_assetsInBackups), end(_assetsInBackups), begin(_assetsOnDisk), end(_assetsOnDisk), @@ -83,7 +83,7 @@ void BackupSupervisor::checkForMissingAssets() { } } -void BackupSupervisor::checkForAssetsToDelete() { +void AssetsBackupHandler::checkForAssetsToDelete() { vector deprecatedAssets; set_difference(begin(_assetsOnDisk), end(_assetsOnDisk), begin(_assetsInBackups), end(_assetsInBackups), @@ -101,7 +101,7 @@ void BackupSupervisor::checkForAssetsToDelete() { } } -void BackupSupervisor::loadBackup(QuaZip& zip) { +void AssetsBackupHandler::loadBackup(QuaZip& zip) { _backups.push_back({ zip.getZipName(), {}, false }); auto& backup = _backups.back(); @@ -151,7 +151,7 @@ void BackupSupervisor::loadBackup(QuaZip& zip) { return; } -void BackupSupervisor::createBackup(QuaZip& zip) { +void AssetsBackupHandler::createBackup(QuaZip& zip) { if (operationInProgress()) { qCWarning(backup_supervisor) << "There is already an operation in progress."; return; @@ -192,7 +192,7 @@ void BackupSupervisor::createBackup(QuaZip& zip) { _backups.push_back(backup); } -void BackupSupervisor::recoverBackup(QuaZip& zip) { +void AssetsBackupHandler::recoverBackup(QuaZip& zip) { if (operationInProgress()) { qCWarning(backup_supervisor) << "There is already a backup/restore in progress."; return; @@ -225,7 +225,7 @@ void BackupSupervisor::recoverBackup(QuaZip& zip) { restoreAllAssets(); } -void BackupSupervisor::deleteBackup(QuaZip& zip) { +void AssetsBackupHandler::deleteBackup(QuaZip& zip) { if (operationInProgress()) { qCWarning(backup_supervisor) << "There is a backup/restore in progress."; return; @@ -243,7 +243,7 @@ void BackupSupervisor::deleteBackup(QuaZip& zip) { checkForAssetsToDelete(); } -void BackupSupervisor::consolidateBackup(QuaZip& zip) { +void AssetsBackupHandler::consolidateBackup(QuaZip& zip) { if (operationInProgress()) { qCWarning(backup_supervisor) << "There is a backup/restore in progress."; return; @@ -285,7 +285,7 @@ void BackupSupervisor::consolidateBackup(QuaZip& zip) { } -void BackupSupervisor::refreshMappings() { +void AssetsBackupHandler::refreshMappings() { auto assetClient = DependencyManager::get(); auto request = assetClient->createGetAllMappingsRequest(); @@ -314,7 +314,7 @@ void BackupSupervisor::refreshMappings() { request->start(); } -void BackupSupervisor::downloadMissingFiles(const AssetUtils::Mappings& mappings) { +void AssetsBackupHandler::downloadMissingFiles(const AssetUtils::Mappings& mappings) { auto wasEmpty = _assetsLeftToRequest.empty(); for (const auto& mapping : mappings) { @@ -330,7 +330,7 @@ void BackupSupervisor::downloadMissingFiles(const AssetUtils::Mappings& mappings } } -void BackupSupervisor::downloadNextMissingFile() { +void AssetsBackupHandler::downloadNextMissingFile() { if (_assetsLeftToRequest.empty()) { return; } @@ -360,7 +360,7 @@ void BackupSupervisor::downloadNextMissingFile() { assetRequest->start(); } -bool BackupSupervisor::writeAssetFile(const AssetUtils::AssetHash& hash, const QByteArray& data) { +bool AssetsBackupHandler::writeAssetFile(const AssetUtils::AssetHash& hash, const QByteArray& data) { QDir assetsDir { _assetsDirectory }; QFile file { assetsDir.filePath(hash) }; if (!file.open(QFile::WriteOnly)) { @@ -380,7 +380,7 @@ bool BackupSupervisor::writeAssetFile(const AssetUtils::AssetHash& hash, const Q return true; } -void BackupSupervisor::computeServerStateDifference(const AssetUtils::Mappings& currentMappings, +void AssetsBackupHandler::computeServerStateDifference(const AssetUtils::Mappings& currentMappings, const AssetUtils::Mappings& newMappings) { _mappingsLeftToSet.reserve((int)newMappings.size()); _assetsLeftToUpload.reserve((int)newMappings.size()); @@ -415,11 +415,11 @@ void BackupSupervisor::computeServerStateDifference(const AssetUtils::Mappings& qCDebug(backup_supervisor) << "Assets to upload:" << _assetsLeftToUpload.size(); } -void BackupSupervisor::restoreAllAssets() { +void AssetsBackupHandler::restoreAllAssets() { restoreNextAsset(); } -void BackupSupervisor::restoreNextAsset() { +void AssetsBackupHandler::restoreNextAsset() { if (_assetsLeftToUpload.empty()) { updateMappings(); return; @@ -447,7 +447,7 @@ void BackupSupervisor::restoreNextAsset() { request->start(); } -void BackupSupervisor::updateMappings() { +void AssetsBackupHandler::updateMappings() { auto assetClient = DependencyManager::get(); for (const auto& mapping : _mappingsLeftToSet) { auto request = assetClient->createSetMappingRequest(mapping.first, mapping.second); diff --git a/domain-server/src/BackupSupervisor.h b/domain-server/src/AssetsBackupHandler.h similarity index 85% rename from domain-server/src/BackupSupervisor.h rename to domain-server/src/AssetsBackupHandler.h index 0d0d21a174..184f30ab9b 100644 --- a/domain-server/src/BackupSupervisor.h +++ b/domain-server/src/AssetsBackupHandler.h @@ -1,5 +1,5 @@ // -// BackupSupervisor.h +// AssetsBackupHandler.h // domain-server/src // // Created by Clement Brisset on 1/12/18. @@ -9,8 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef hifi_BackupSupervisor_h -#define hifi_BackupSupervisor_h +#ifndef hifi_AssetsBackupHandler_h +#define hifi_AssetsBackupHandler_h #include #include @@ -28,17 +28,11 @@ class QuaZip; -struct AssetServerBackup { - QString filePath; - AssetUtils::Mappings mappings; - bool corruptedBackup; -}; - -class BackupSupervisor : public QObject, public BackupHandlerInterface { +class AssetsBackupHandler : public QObject, public BackupHandlerInterface { Q_OBJECT public: - BackupSupervisor(const QString& backupDirectory); + AssetsBackupHandler(const QString& backupDirectory); void loadBackup(QuaZip& zip); void createBackup(QuaZip& zip); @@ -75,6 +69,12 @@ private: quint64 _lastMappingsRefresh { 0 }; AssetUtils::Mappings _currentMappings; + struct AssetServerBackup { + QString filePath; + AssetUtils::Mappings mappings; + bool corruptedBackup; + }; + bool _operationInProgress { false }; // Internal storage for backups on disk @@ -93,4 +93,4 @@ private: int _mappingRequestsInFlight { 0 }; }; -#endif /* hifi_BackupSupervisor_h */ +#endif /* hifi_AssetsBackupHandler_h */ diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index a8ceebd6e7..11b6a2d441 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -45,7 +45,7 @@ #include #include -#include "BackupSupervisor.h" +#include "AssetsBackupHandler.h" #include "DomainServerNodeData.h" #include "NodeConnectionData.h" @@ -297,7 +297,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : _contentManager.reset(new DomainContentBackupManager(getContentBackupDir(), _settingsManager.settingsResponseObjectForType("6")["entity_server_settings"].toObject())); _contentManager->addBackupHandler(BackupHandlerPointer(new EntitiesBackupHandler(getEntitiesFilePath(), getEntitiesReplacementFilePath()))); - _contentManager->addBackupHandler(BackupHandlerPointer(new BackupSupervisor(getContentBackupDir()))); + _contentManager->addBackupHandler(BackupHandlerPointer(new AssetsBackupHandler(getContentBackupDir()))); _contentManager->initialize(true); qDebug() << "Existing backups:"; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index ee0350665e..afe2a1cc7c 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -26,7 +26,7 @@ #include #include -#include "BackupSupervisor.h" +#include "AssetsBackupHandler.h" #include "DomainGatekeeper.h" #include "DomainMetadata.h" #include "DomainServerSettingsManager.h" From 9fca92facd27f12a0daff1455b0dc560fda4db46 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 14 Feb 2018 18:11:55 -0800 Subject: [PATCH 289/569] Move EntitiesBackupHandler to its own file --- domain-server/src/AssetsBackupHandler.h | 3 - domain-server/src/BackupHandler.h | 79 +------------------ .../src/DomainContentBackupManager.cpp | 6 +- .../src/DomainContentBackupManager.h | 1 + domain-server/src/DomainServer.cpp | 1 + domain-server/src/EntitiesBackupHandler.cpp | 73 +++++++++++++++++ domain-server/src/EntitiesBackupHandler.h | 42 ++++++++++ 7 files changed, 124 insertions(+), 81 deletions(-) create mode 100644 domain-server/src/EntitiesBackupHandler.cpp create mode 100644 domain-server/src/EntitiesBackupHandler.h diff --git a/domain-server/src/AssetsBackupHandler.h b/domain-server/src/AssetsBackupHandler.h index 184f30ab9b..b78206b7b1 100644 --- a/domain-server/src/AssetsBackupHandler.h +++ b/domain-server/src/AssetsBackupHandler.h @@ -21,13 +21,10 @@ #include #include - #include #include "BackupHandler.h" -class QuaZip; - class AssetsBackupHandler : public QObject, public BackupHandlerInterface { Q_OBJECT diff --git a/domain-server/src/BackupHandler.h b/domain-server/src/BackupHandler.h index eb9c35f236..8599dafb29 100644 --- a/domain-server/src/BackupHandler.h +++ b/domain-server/src/BackupHandler.h @@ -14,9 +14,7 @@ #include -#include - -#include +class QuaZip; class BackupHandlerInterface { public: @@ -28,80 +26,7 @@ public: virtual void deleteBackup(QuaZip& zip) = 0; virtual void consolidateBackup(QuaZip& zip) = 0; }; + using BackupHandlerPointer = std::unique_ptr; -#include -#include - -class EntitiesBackupHandler : public BackupHandlerInterface { -public: - EntitiesBackupHandler(QString entitiesFilePath, QString entitiesReplacementFilePath) : - _entitiesFilePath(entitiesFilePath), - _entitiesReplacementFilePath(entitiesReplacementFilePath) {} - - void loadBackup(QuaZip& zip) {} - - // Create a skeleton backup - void createBackup(QuaZip& zip) { - QFile entitiesFile { _entitiesFilePath }; - - if (entitiesFile.open(QIODevice::ReadOnly)) { - QuaZipFile zipFile { &zip }; - zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo("models.json.gz", _entitiesFilePath)); - zipFile.write(entitiesFile.readAll()); - zipFile.close(); - if (zipFile.getZipError() != UNZ_OK) { - qCritical() << "Failed to zip models.json.gz: " << zipFile.getZipError(); - } - } - } - - // Recover from a full backup - void recoverBackup(QuaZip& zip) { - if (!zip.setCurrentFile("models.json.gz")) { - qWarning() << "Failed to find models.json.gz while recovering backup"; - return; - } - QuaZipFile zipFile { &zip }; - if (!zipFile.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open models.json.gz in backup"; - return; - } - auto rawData = zipFile.readAll(); - - zipFile.close(); - - OctreeUtils::RawOctreeData data; - if (!OctreeUtils::readOctreeDataInfoFromData(rawData, &data)) { - qCritical() << "Unable to parse octree data during backup recovery"; - return; - } - - data.resetIdAndVersion(); - - if (zipFile.getZipError() != UNZ_OK) { - qCritical() << "Failed to unzip models.json.gz: " << zipFile.getZipError(); - return; - } - - QFile entitiesFile { _entitiesReplacementFilePath }; - - if (entitiesFile.open(QIODevice::WriteOnly)) { - entitiesFile.write(data.toGzippedByteArray()); - } - } - - // Delete a skeleton backup - void deleteBackup(QuaZip& zip) { - } - - // Create a full backup - void consolidateBackup(QuaZip& zip) { - } - -private: - QString _entitiesFilePath; - QString _entitiesReplacementFilePath; -}; - #endif /* hifi_BackupHandler_h */ diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 2b990b170e..f39737c92e 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "DomainContentBackupManager.h" + #include #include @@ -25,13 +27,15 @@ #include #include +#include + #include #include #include #include #include "DomainServer.h" -#include "DomainContentBackupManager.h" + const int DomainContentBackupManager::DEFAULT_PERSIST_INTERVAL = 1000 * 30; // every 30 seconds // Backup format looks like: daily_backup-TIMESTAMP.zip diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h index a3606929d5..1e1b2360a8 100644 --- a/domain-server/src/DomainContentBackupManager.h +++ b/domain-server/src/DomainContentBackupManager.h @@ -16,6 +16,7 @@ #include #include +#include #include diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 11b6a2d441..4c72423f74 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -47,6 +47,7 @@ #include "AssetsBackupHandler.h" #include "DomainServerNodeData.h" +#include "EntitiesBackupHandler.h" #include "NodeConnectionData.h" #include diff --git a/domain-server/src/EntitiesBackupHandler.cpp b/domain-server/src/EntitiesBackupHandler.cpp new file mode 100644 index 0000000000..a95d68b007 --- /dev/null +++ b/domain-server/src/EntitiesBackupHandler.cpp @@ -0,0 +1,73 @@ +// +// EntitiesBackupHandler.cpp +// domain-server/src +// +// Created by Clement Brisset on 2/14/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "EntitiesBackupHandler.h" + +#include + +#include +#include + +#include + +EntitiesBackupHandler::EntitiesBackupHandler(QString entitiesFilePath, QString entitiesReplacementFilePath) : + _entitiesFilePath(entitiesFilePath), + _entitiesReplacementFilePath(entitiesReplacementFilePath) +{ +} + +void EntitiesBackupHandler::createBackup(QuaZip& zip) { + QFile entitiesFile { _entitiesFilePath }; + + if (entitiesFile.open(QIODevice::ReadOnly)) { + QuaZipFile zipFile { &zip }; + zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo("models.json.gz", _entitiesFilePath)); + zipFile.write(entitiesFile.readAll()); + zipFile.close(); + if (zipFile.getZipError() != UNZ_OK) { + qCritical() << "Failed to zip models.json.gz: " << zipFile.getZipError(); + } + } +} + +void EntitiesBackupHandler::recoverBackup(QuaZip& zip) { + if (!zip.setCurrentFile("models.json.gz")) { + qWarning() << "Failed to find models.json.gz while recovering backup"; + return; + } + QuaZipFile zipFile { &zip }; + if (!zipFile.open(QIODevice::ReadOnly)) { + qCritical() << "Failed to open models.json.gz in backup"; + return; + } + auto rawData = zipFile.readAll(); + + zipFile.close(); + + OctreeUtils::RawOctreeData data; + if (!OctreeUtils::readOctreeDataInfoFromData(rawData, &data)) { + qCritical() << "Unable to parse octree data during backup recovery"; + return; + } + + data.resetIdAndVersion(); + + if (zipFile.getZipError() != UNZ_OK) { + qCritical() << "Failed to unzip models.json.gz: " << zipFile.getZipError(); + return; + } + + QFile entitiesFile { _entitiesReplacementFilePath }; + + if (entitiesFile.open(QIODevice::WriteOnly)) { + entitiesFile.write(data.toGzippedByteArray()); + } +} diff --git a/domain-server/src/EntitiesBackupHandler.h b/domain-server/src/EntitiesBackupHandler.h new file mode 100644 index 0000000000..6f66483a87 --- /dev/null +++ b/domain-server/src/EntitiesBackupHandler.h @@ -0,0 +1,42 @@ +// +// EntitiesBackupHandler.h +// domain-server/src +// +// Created by Clement Brisset on 2/14/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_EntitiesBackupHandler_h +#define hifi_EntitiesBackupHandler_h + +#include + +#include "BackupHandler.h" + +class EntitiesBackupHandler : public BackupHandlerInterface { +public: + EntitiesBackupHandler(QString entitiesFilePath, QString entitiesReplacementFilePath); + + void loadBackup(QuaZip& zip) {} + + // Create a skeleton backup + void createBackup(QuaZip& zip); + + // Recover from a full backup + void recoverBackup(QuaZip& zip); + + // Delete a skeleton backup + void deleteBackup(QuaZip& zip) {} + + // Create a full backup + void consolidateBackup(QuaZip& zip) {} + +private: + QString _entitiesFilePath; + QString _entitiesReplacementFilePath; +}; + +#endif /* hifi_EntitiesBackupHandler_h */ From d6e281408144f0db5bb102ca4a0b99dd460cd63f Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 15 Feb 2018 11:25:07 -0800 Subject: [PATCH 290/569] Write assets to disk when recovering full backup --- domain-server/src/AssetsBackupHandler.cpp | 152 ++++++++++++++-------- domain-server/src/AssetsBackupHandler.h | 1 + 2 files changed, 99 insertions(+), 54 deletions(-) diff --git a/domain-server/src/AssetsBackupHandler.cpp b/domain-server/src/AssetsBackupHandler.cpp index 3dc4851762..a9f56a0c5b 100644 --- a/domain-server/src/AssetsBackupHandler.cpp +++ b/domain-server/src/AssetsBackupHandler.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -23,13 +24,14 @@ #include #include -const QString ASSETS_DIR = "/assets/"; -const QString MAPPINGS_FILE = "mappings.json"; +static const QString ASSETS_DIR = "/assets/"; +static const QString MAPPINGS_FILE = "mappings.json"; +static const QString ZIP_ASSETS_FOLDER = "files"; using namespace std; -Q_DECLARE_LOGGING_CATEGORY(backup_supervisor) -Q_LOGGING_CATEGORY(backup_supervisor, "hifi.backup-supervisor"); +Q_DECLARE_LOGGING_CATEGORY(asset_backup) +Q_LOGGING_CATEGORY(asset_backup, "hifi.asset-backup"); AssetsBackupHandler::AssetsBackupHandler(const QString& backupDirectory) : _assetsDirectory(backupDirectory + ASSETS_DIR) @@ -39,6 +41,10 @@ AssetsBackupHandler::AssetsBackupHandler(const QString& backupDirectory) : refreshAssetsOnDisk(); + setupRefreshTimer(); +} + +void AssetsBackupHandler::setupRefreshTimer() { _mappingsRefreshTimer.setTimerType(Qt::CoarseTimer); _mappingsRefreshTimer.setSingleShot(true); QObject::connect(&_mappingsRefreshTimer, &QTimer::timeout, this, &AssetsBackupHandler::refreshMappings); @@ -50,9 +56,14 @@ AssetsBackupHandler::AssetsBackupHandler(const QString& backupDirectory) : _mappingsRefreshTimer.start(0); } }); + QObject::connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, [this](SharedNodePointer node) { + if (node->getType() == NodeType::AssetServer) { + // run immediately for the first time. + _mappingsRefreshTimer.stop(); + } + }); } - void AssetsBackupHandler::refreshAssetsOnDisk() { QDir assetsDir { _assetsDirectory }; auto assetNames = assetsDir.entryList(QDir::Files); @@ -79,7 +90,7 @@ void AssetsBackupHandler::checkForMissingAssets() { begin(_assetsOnDisk), end(_assetsOnDisk), back_inserter(missingAssets)); if (missingAssets.size() > 0) { - qCWarning(backup_supervisor) << "Found" << missingAssets.size() << "assets missing."; + qCWarning(asset_backup) << "Found" << missingAssets.size() << "backup assets missing from disk."; } } @@ -90,24 +101,26 @@ void AssetsBackupHandler::checkForAssetsToDelete() { back_inserter(deprecatedAssets)); if (deprecatedAssets.size() > 0) { - qCDebug(backup_supervisor) << "Found" << deprecatedAssets.size() << "assets to delete."; + qCDebug(asset_backup) << "Found" << deprecatedAssets.size() << "backup assets to delete from disk."; if (_allBackupsLoadedSuccessfully) { for (const auto& hash : deprecatedAssets) { QFile::remove(_assetsDirectory + hash); } } else { - qCWarning(backup_supervisor) << "Some backups did not load properly, aborting deleting for safety."; + qCWarning(asset_backup) << "Some backups did not load properly, aborting delete operation for safety."; } } } void AssetsBackupHandler::loadBackup(QuaZip& zip) { + Q_ASSERT(QThread::currentThread() == thread()); + _backups.push_back({ zip.getZipName(), {}, false }); auto& backup = _backups.back(); if (!zip.setCurrentFile(MAPPINGS_FILE)) { - qCCritical(backup_supervisor) << "Failed to find" << MAPPINGS_FILE << "while recovering backup"; - qCCritical(backup_supervisor) << " Error:" << zip.getZipError(); + qCCritical(asset_backup) << "Failed to find" << MAPPINGS_FILE << "while loading backup"; + qCCritical(asset_backup) << " Error:" << zip.getZipError(); backup.corruptedBackup = true; _allBackupsLoadedSuccessfully = false; return; @@ -115,8 +128,8 @@ void AssetsBackupHandler::loadBackup(QuaZip& zip) { QuaZipFile zipFile { &zip }; if (!zipFile.open(QFile::ReadOnly)) { - qCCritical(backup_supervisor) << "Could not open backup file:" << zip.getZipName(); - qCCritical(backup_supervisor) << " Error:" << zip.getZipError(); + qCCritical(asset_backup) << "Could not unzip backup file for load:" << zip.getZipName(); + qCCritical(asset_backup) << " Error:" << zip.getZipError(); backup.corruptedBackup = true; _allBackupsLoadedSuccessfully = false; return; @@ -125,8 +138,8 @@ void AssetsBackupHandler::loadBackup(QuaZip& zip) { QJsonParseError error; auto document = QJsonDocument::fromJson(zipFile.readAll(), &error); if (document.isNull() || !document.isObject()) { - qCCritical(backup_supervisor) << "Could not parse backup file to JSON object:" << zip.getZipName(); - qCCritical(backup_supervisor) << " Error:" << error.errorString(); + qCCritical(asset_backup) << "Could not parse backup file to JSON object for load:" << zip.getZipName(); + qCCritical(asset_backup) << " Error:" << error.errorString(); backup.corruptedBackup = true; _allBackupsLoadedSuccessfully = false; return; @@ -138,33 +151,37 @@ void AssetsBackupHandler::loadBackup(QuaZip& zip) { const auto& assetHash = it.value().toString(); if (!AssetUtils::isValidHash(assetHash)) { - qCCritical(backup_supervisor) << "Corrupted mapping in backup file" << zip.getZipName() << ":" << it.key(); + qCCritical(asset_backup) << "Corrupted mapping in loading backup file" << zip.getZipName() << ":" << it.key(); backup.corruptedBackup = true; _allBackupsLoadedSuccessfully = false; - return; + continue; } backup.mappings[assetPath] = assetHash; _assetsInBackups.insert(assetHash); } + checkForMissingAssets(); + checkForAssetsToDelete(); return; } void AssetsBackupHandler::createBackup(QuaZip& zip) { + Q_ASSERT(QThread::currentThread() == thread()); + if (operationInProgress()) { - qCWarning(backup_supervisor) << "There is already an operation in progress."; + qCWarning(asset_backup) << "There is already an operation in progress."; return; } if (_lastMappingsRefresh == 0) { - qCWarning(backup_supervisor) << "Current mappings not yet loaded."; + qCWarning(asset_backup) << "Current mappings not yet loaded."; return; } static constexpr quint64 MAX_REFRESH_TIME = 15 * 60 * 1000 * 1000; if (usecTimestampNow() - _lastMappingsRefresh > MAX_REFRESH_TIME) { - qCWarning(backup_supervisor) << "Backing up asset mappings that might be stale."; + qCWarning(asset_backup) << "Backing up asset mappings that might be stale."; } AssetServerBackup backup; @@ -180,43 +197,65 @@ void AssetsBackupHandler::createBackup(QuaZip& zip) { QuaZipFile zipFile { &zip }; if (!zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(MAPPINGS_FILE))) { - qCDebug(backup_supervisor) << "Could not open zip file:" << zipFile.getZipError(); + qCDebug(asset_backup) << "Could not open zip file:" << zipFile.getZipError(); return; } zipFile.write(document.toJson()); zipFile.close(); if (zipFile.getZipError() != UNZ_OK) { - qCDebug(backup_supervisor) << "Could not close zip file: " << zipFile.getZipError(); + qCDebug(asset_backup) << "Could not close zip file: " << zipFile.getZipError(); return; } _backups.push_back(backup); } void AssetsBackupHandler::recoverBackup(QuaZip& zip) { + Q_ASSERT(QThread::currentThread() == thread()); + if (operationInProgress()) { - qCWarning(backup_supervisor) << "There is already a backup/restore in progress."; + qCWarning(asset_backup) << "There is already a backup/restore in progress."; return; } if (_lastMappingsRefresh == 0) { - qCWarning(backup_supervisor) << "Current mappings not yet loaded."; + qCWarning(asset_backup) << "Current mappings not yet loaded."; return; } static constexpr quint64 MAX_REFRESH_TIME = 15 * 60 * 1000 * 1000; if (usecTimestampNow() - _lastMappingsRefresh > MAX_REFRESH_TIME) { - qCWarning(backup_supervisor) << "Current asset mappings that might be stale."; + qCWarning(asset_backup) << "Current asset mappings that might be stale."; } - startOperation(); - auto it = find_if(begin(_backups), end(_backups), [&](const std::vector::value_type& value) { return value.filePath == zip.getZipName(); }); if (it == end(_backups)) { - qCDebug(backup_supervisor) << "Could not find backup" << zip.getZipName() << "to restore."; - stopOperation(); - return; + qCDebug(asset_backup) << "Could not find backup" << zip.getZipName() << "to restore."; + + loadBackup(zip); + + QuaZipDir zipDir { &zip, ZIP_ASSETS_FOLDER }; + + auto assetNames = zipDir.entryList(QDir::Files); + for (const auto& asset : assetNames) { + if (AssetUtils::isValidHash(asset)) { + if (!zip.setCurrentFile(MAPPINGS_FILE)) { + qCCritical(asset_backup) << "Failed to find" << asset << "while recovering backup"; + qCCritical(asset_backup) << " Error:" << zip.getZipError(); + continue; + } + + QuaZipFile zipFile { &zip }; + if (!zipFile.open(QFile::ReadOnly)) { + qCCritical(asset_backup) << "Could not unzip asset file:" << asset; + qCCritical(asset_backup) << " Error:" << zip.getZipError(); + continue; + } + + writeAssetFile(asset, zipFile.readAll()); + } + } } const auto& newMappings = it->mappings; @@ -226,8 +265,10 @@ void AssetsBackupHandler::recoverBackup(QuaZip& zip) { } void AssetsBackupHandler::deleteBackup(QuaZip& zip) { + Q_ASSERT(QThread::currentThread() == thread()); + if (operationInProgress()) { - qCWarning(backup_supervisor) << "There is a backup/restore in progress."; + qCWarning(asset_backup) << "There is a backup/restore in progress."; return; } @@ -235,7 +276,7 @@ void AssetsBackupHandler::deleteBackup(QuaZip& zip) { return value.filePath == zip.getZipName(); }); if (it == end(_backups)) { - qCDebug(backup_supervisor) << "Could not find backup" << zip.getZipName() << "to delete."; + qCDebug(asset_backup) << "Could not find backup" << zip.getZipName() << "to delete."; return; } @@ -244,8 +285,10 @@ void AssetsBackupHandler::deleteBackup(QuaZip& zip) { } void AssetsBackupHandler::consolidateBackup(QuaZip& zip) { + Q_ASSERT(QThread::currentThread() == thread()); + if (operationInProgress()) { - qCWarning(backup_supervisor) << "There is a backup/restore in progress."; + qCWarning(asset_backup) << "There is a backup/restore in progress."; return; } QFileInfo zipInfo(zip.getZipName()); @@ -255,7 +298,7 @@ void AssetsBackupHandler::consolidateBackup(QuaZip& zip) { return info.fileName() == zipInfo.fileName(); }); if (it == end(_backups)) { - qCDebug(backup_supervisor) << "Could not find backup" << zip.getZipName() << "to consolidate."; + qCDebug(asset_backup) << "Could not find backup" << zip.getZipName() << "to consolidate."; return; } @@ -265,20 +308,19 @@ void AssetsBackupHandler::consolidateBackup(QuaZip& zip) { QDir assetsDir { _assetsDirectory }; QFile file { assetsDir.filePath(hash) }; if (!file.open(QFile::ReadOnly)) { - qCCritical(backup_supervisor) << "Could not open asset file" << file.fileName(); + qCCritical(asset_backup) << "Could not open asset file" << file.fileName(); continue; } QuaZipFile zipFile { &zip }; - static const QString ZIP_ASSETS_FOLDER = "files/"; - if (!zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(ZIP_ASSETS_FOLDER + hash))) { - qCDebug(backup_supervisor) << "Could not open zip file:" << zipFile.getZipError(); + if (!zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(ZIP_ASSETS_FOLDER + "/" + hash))) { + qCDebug(asset_backup) << "Could not open zip file:" << zipFile.getZipError(); continue; } zipFile.write(file.readAll()); zipFile.close(); if (zipFile.getZipError() != UNZ_OK) { - qCDebug(backup_supervisor) << "Could not close zip file: " << zipFile.getZipError(); + qCDebug(asset_backup) << "Could not close zip file: " << zipFile.getZipError(); continue; } } @@ -300,8 +342,8 @@ void AssetsBackupHandler::refreshMappings() { downloadMissingFiles(_currentMappings); } else { - qCCritical(backup_supervisor) << "Could not refresh asset server mappings."; - qCCritical(backup_supervisor) << " Error:" << request->getErrorString(); + qCCritical(asset_backup) << "Could not refresh asset server mappings."; + qCCritical(asset_backup) << " Error:" << request->getErrorString(); } request->deleteLater(); @@ -341,14 +383,14 @@ void AssetsBackupHandler::downloadNextMissingFile() { QObject::connect(assetRequest, &AssetRequest::finished, this, [this](AssetRequest* request) { if (request->getError() == AssetRequest::NoError) { - qCDebug(backup_supervisor) << "Backing up asset" << request->getHash(); + qCDebug(asset_backup) << "Backing up asset" << request->getHash(); bool success = writeAssetFile(request->getHash(), request->getData()); if (!success) { - qCCritical(backup_supervisor) << "Failed to write asset file" << request->getHash(); + qCCritical(asset_backup) << "Failed to write asset file" << request->getHash(); } } else { - qCCritical(backup_supervisor) << "Failed to backup asset" << request->getHash(); + qCCritical(asset_backup) << "Failed to backup asset" << request->getHash(); } _assetsLeftToRequest.erase(request->getHash()); @@ -364,13 +406,13 @@ bool AssetsBackupHandler::writeAssetFile(const AssetUtils::AssetHash& hash, cons QDir assetsDir { _assetsDirectory }; QFile file { assetsDir.filePath(hash) }; if (!file.open(QFile::WriteOnly)) { - qCCritical(backup_supervisor) << "Could not open backup file" << file.fileName(); + qCCritical(asset_backup) << "Could not open asset file for write:" << file.fileName(); return false; } auto bytesWritten = file.write(data); if (bytesWritten != data.size()) { - qCCritical(backup_supervisor) << "Could not write data to file" << file.fileName(); + qCCritical(asset_backup) << "Could not write data to file" << file.fileName(); file.remove(); return false; } @@ -410,9 +452,9 @@ void AssetsBackupHandler::computeServerStateDifference(const AssetUtils::Mapping } } - qCDebug(backup_supervisor) << "Mappings to set:" << _mappingsLeftToSet.size(); - qCDebug(backup_supervisor) << "Mappings to del:" << _mappingsLeftToDelete.size(); - qCDebug(backup_supervisor) << "Assets to upload:" << _assetsLeftToUpload.size(); + qCDebug(asset_backup) << "Mappings to set:" << _mappingsLeftToSet.size(); + qCDebug(asset_backup) << "Mappings to del:" << _mappingsLeftToDelete.size(); + qCDebug(asset_backup) << "Assets to upload:" << _assetsLeftToUpload.size(); } void AssetsBackupHandler::restoreAllAssets() { @@ -420,6 +462,8 @@ void AssetsBackupHandler::restoreAllAssets() { } void AssetsBackupHandler::restoreNextAsset() { + startOperation(); + if (_assetsLeftToUpload.empty()) { updateMappings(); return; @@ -435,8 +479,8 @@ void AssetsBackupHandler::restoreNextAsset() { QObject::connect(request, &AssetUpload::finished, this, [this](AssetUpload* request) { if (request->getError() != AssetUpload::NoError) { - qCCritical(backup_supervisor) << "Failed to restore asset:" << request->getFilename(); - qCCritical(backup_supervisor) << " Error:" << request->getErrorString(); + qCCritical(asset_backup) << "Failed to restore asset:" << request->getFilename(); + qCCritical(asset_backup) << " Error:" << request->getErrorString(); } restoreNextAsset(); @@ -453,8 +497,8 @@ void AssetsBackupHandler::updateMappings() { auto request = assetClient->createSetMappingRequest(mapping.first, mapping.second); QObject::connect(request, &SetMappingRequest::finished, this, [this](SetMappingRequest* request) { if (request->getError() != MappingRequest::NoError) { - qCCritical(backup_supervisor) << "Failed to set mapping:" << request->getPath(); - qCCritical(backup_supervisor) << " Error:" << request->getErrorString(); + qCCritical(asset_backup) << "Failed to set mapping:" << request->getPath(); + qCCritical(asset_backup) << " Error:" << request->getErrorString(); } if (--_mappingRequestsInFlight == 0) { @@ -472,8 +516,8 @@ void AssetsBackupHandler::updateMappings() { auto request = assetClient->createDeleteMappingsRequest(_mappingsLeftToDelete); QObject::connect(request, &DeleteMappingsRequest::finished, this, [this](DeleteMappingsRequest* request) { if (request->getError() != MappingRequest::NoError) { - qCCritical(backup_supervisor) << "Failed to delete mappings"; - qCCritical(backup_supervisor) << " Error:" << request->getErrorString(); + qCCritical(asset_backup) << "Failed to delete mappings"; + qCCritical(asset_backup) << " Error:" << request->getErrorString(); } if (--_mappingRequestsInFlight == 0) { diff --git a/domain-server/src/AssetsBackupHandler.h b/domain-server/src/AssetsBackupHandler.h index b78206b7b1..2ef454998e 100644 --- a/domain-server/src/AssetsBackupHandler.h +++ b/domain-server/src/AssetsBackupHandler.h @@ -40,6 +40,7 @@ public: bool operationInProgress() const { return _operationInProgress; } private: + void setupRefreshTimer(); void refreshMappings(); void refreshAssetsInBackups(); From f07b1fa4c52af97f9adab2ba6e9678a75fe6aa2b Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 15 Feb 2018 11:32:29 -0800 Subject: [PATCH 291/569] Revert "FBX node IDs aren't alphanumerically ordered per logical structure" This reverts commit a7ec4501e65396d4c7bf2316dd73188cea7a3c7d. Because remainingModels is a QSet, the order is not guaranteed. Therefore the same code iterating over the same items will sometimes have a different ordering. See docs for QSet, http://doc.qt.io/qt-5/qset.html This was bug was causing scrambled avatars, because both the transmitter and receiver of the AvatarData packets make the strong assumption that the joint orders are same. When they are not the avatar's appear scrambled. --- libraries/fbx/src/FBXReader.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 4ed1ca38dc..14f12b5d1b 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1481,6 +1481,11 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } while (!remainingModels.isEmpty()) { QString first = *remainingModels.constBegin(); + foreach (const QString& id, remainingModels) { + if (id < first) { + first = id; + } + } QString topID = getTopModelID(_connectionParentMap, models, first, url); appendModelIDs(_connectionParentMap.value(topID), _connectionChildMap, models, remainingModels, modelIDs, true); } From 777862f253c32afcfa1bd0988a986da0d5b7c7da Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Thu, 15 Feb 2018 11:50:38 -0800 Subject: [PATCH 292/569] can cast shadow flag now works correctly (UI/JS aspects). --- libraries/entities/src/EntityItemProperties.cpp | 9 +++++++++ libraries/entities/src/EntityItemProperties.h | 2 +- scripts/system/html/js/entityProperties.js | 8 ++++---- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 3c3c0742da..86404c6504 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1506,6 +1506,15 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape()); } + // Only models and shapes (including cubes and spheres) can cast shadows + if (properties.getType() == EntityTypes::Model || + properties.getType() == EntityTypes::Shape || + properties.getType() == EntityTypes::Box || + properties.getType() == EntityTypes::Sphere) { + + APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, properties.getCanCastShadow()); + } + APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL()); APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, properties.getActionData()); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index dcec1a1f81..349d32f806 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -113,6 +113,7 @@ public: // bool _fooChanged { false }; DEFINE_PROPERTY(PROP_VISIBLE, Visible, visible, bool, ENTITY_ITEM_DEFAULT_VISIBLE); + DEFINE_PROPERTY(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool, ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW); DEFINE_PROPERTY_REF_WITH_SETTER(PROP_POSITION, Position, position, glm::vec3, ENTITY_ITEM_ZERO_VEC3); DEFINE_PROPERTY_REF(PROP_DIMENSIONS, Dimensions, dimensions, glm::vec3, ENTITY_ITEM_DEFAULT_DIMENSIONS); DEFINE_PROPERTY_REF(PROP_ROTATION, Rotation, rotation, glm::quat, ENTITY_ITEM_DEFAULT_ROTATION); @@ -128,7 +129,6 @@ public: DEFINE_PROPERTY_REF(PROP_SCRIPT, Script, script, QString, ENTITY_ITEM_DEFAULT_SCRIPT); DEFINE_PROPERTY(PROP_SCRIPT_TIMESTAMP, ScriptTimestamp, scriptTimestamp, quint64, ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP); DEFINE_PROPERTY_REF(PROP_COLLISION_SOUND_URL, CollisionSoundURL, collisionSoundURL, QString, ENTITY_ITEM_DEFAULT_COLLISION_SOUND_URL); - DEFINE_PROPERTY(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool, ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW); DEFINE_PROPERTY_REF(PROP_COLOR, Color, color, xColor, particle::DEFAULT_COLOR); DEFINE_PROPERTY_REF(PROP_COLOR_SPREAD, ColorSpread, colorSpread, xColor, particle::DEFAULT_COLOR_SPREAD); DEFINE_PROPERTY_REF(PROP_COLOR_START, ColorStart, colorStart, xColor, particle::DEFAULT_COLOR); diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index fca43c4665..1b4df84f40 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -983,11 +983,11 @@ function loaded() { properties.color.green + "," + properties.color.blue + ")"; } - //if (properties.type === "Model" || - // properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { + if (properties.type === "Model" || + properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { - elCanCastShadow = properties.canCastShadow; - //} + elCanCastShadow.checked = properties.canCastShadow; + } if (properties.type === "Model") { elModelURL.value = properties.modelURL; From df7a8389b3102c9c4dc4b0bb74168f97a16343ab Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Thu, 15 Feb 2018 11:51:06 -0800 Subject: [PATCH 293/569] Fixed possible crash. --- libraries/render-utils/src/RenderShadowTask.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 24a14a697c..7806c95330 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -121,8 +121,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con assert(lightStage); // Exit if current keylight does not cast shadows - bool castShadows = lightStage->getCurrentKeyLight()->getCastShadows(); - if (!castShadows) { + if (!lightStage->getCurrentKeyLight() || !lightStage->getCurrentKeyLight()->getCastShadows()) { return; } From c70b655fbc5a170e180e9d99f800fb39d73c6af2 Mon Sep 17 00:00:00 2001 From: samcake Date: Thu, 15 Feb 2018 11:55:16 -0800 Subject: [PATCH 294/569] A bit more explanations to the Task Flow class --- libraries/task/src/task/Task.h | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/libraries/task/src/task/Task.h b/libraries/task/src/task/Task.h index 34cdbb5439..08df55dddd 100644 --- a/libraries/task/src/task/Task.h +++ b/libraries/task/src/task/Task.h @@ -27,21 +27,32 @@ template class JobT; template class TaskT; class JobNoIO {}; +// Task Flow control class is a simple per value object used to communicate flow control commands trhough the graph of tasks. +// From within the Job::Run function, you can access it from the JobCOntext and issue commands which will be picked up by the Task calling for the Job run. +// This is first introduced to provide a way to abort all the work from within a task job. see the "abortTask" call class TaskFlow { public: + // A job that wants to abort the rest of the other jobs execution in a task would issue that call "abortTask" and let the task early exit for this run. + // All the varyings produced by the aborted branch of jobs are left unmodified. + void abortTask(); + + // called by the task::run to perform flow control + // This should be considered private but still need to be accessible from the Task class TaskFlow() = default; ~TaskFlow() = default; - - // called after each job void reset(); - - void abortTask(); bool doAbortTask() const; protected: bool _doAbortTask{ false }; }; +// JobContext class is the base calss for the context object which is passed through all the Job::run calls thoughout the graph of jobs +// It is used to communicate to the job::run its context and various state information the job relies on. +// It specifically provide access to: +// - The taskFlow object allowing for messaging control flow commands from within a Job::run +// - The current Config object attached to the Job::run currently called. +// The JobContext can be derived to add more global state to it that Jobs can access class JobContext { public: JobContext(const QLoggingCategory& category); From 807237282263e941235ab48b525a4079c712259d Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 15 Feb 2018 13:14:56 -0800 Subject: [PATCH 295/569] Lots of changes --- .../resources/qml/controls-uit/Separator.qml | 4 +- .../qml/hifi/commerce/checkout/Checkout.qml | 69 ++++++++++--------- .../hifi/commerce/purchases/PurchasedItem.qml | 62 +++++++++++++---- .../qml/hifi/commerce/purchases/Purchases.qml | 16 +++-- interface/src/commerce/QmlCommerce.cpp | 8 +-- interface/src/commerce/QmlCommerce.h | 4 +- libraries/avatars/src/AvatarData.h | 3 +- libraries/avatars/src/ScriptAvatarData.cpp | 1 + libraries/avatars/src/ScriptAvatarData.h | 3 +- 9 files changed, 109 insertions(+), 61 deletions(-) diff --git a/interface/resources/qml/controls-uit/Separator.qml b/interface/resources/qml/controls-uit/Separator.qml index 5e2d278454..3350764ae9 100644 --- a/interface/resources/qml/controls-uit/Separator.qml +++ b/interface/resources/qml/controls-uit/Separator.qml @@ -14,8 +14,8 @@ import "../styles-uit" Item { property int colorScheme: 0; - readonly property var topColor: [ hifi.colors.baseGrayShadow, hifi.colors.faintGray ]; - readonly property var bottomColor: [ hifi.colors.baseGrayHighlight, hifi.colors.faintGray ]; + readonly property var topColor: [ hifi.colors.baseGrayShadow, hifi.colors.faintGray, "#89858C" ]; + readonly property var bottomColor: [ hifi.colors.baseGrayHighlight, hifi.colors.faintGray, "#89858C" ]; // Size height: colorScheme === 0 ? 2 : 1; diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 916e67e37f..08b100a64c 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -339,8 +339,8 @@ Rectangle { anchors.leftMargin: 16; width: paintedWidth; height: paintedHeight; - text: "Confirm Purchase:"; - color: hifi.colors.baseGray; + text: "Review Purchase:"; + color: hifi.colors.black; size: 28; } @@ -453,7 +453,7 @@ Rectangle { width: root.width; // Anchors anchors.top: separator2.bottom; - anchors.topMargin: 16; + anchors.topMargin: 0; anchors.left: parent.left; anchors.leftMargin: 16; anchors.right: parent.right; @@ -465,7 +465,7 @@ Rectangle { id: buyTextContainer; visible: buyText.text !== ""; anchors.top: parent.top; - anchors.topMargin: 16; + anchors.topMargin: 10; anchors.left: parent.left; anchors.right: parent.right; height: buyText.height + 30; @@ -517,8 +517,8 @@ Rectangle { color: hifi.buttons.blue; colorScheme: hifi.colorSchemes.light; anchors.top: buyTextContainer.visible ? buyTextContainer.bottom : checkoutActionButtonsContainer.top; - anchors.topMargin: 16; - height: 40; + anchors.topMargin: 10; + height: 50; anchors.left: parent.left; anchors.right: parent.right; text: "VIEW THIS ITEM IN MY PURCHASES"; @@ -536,8 +536,8 @@ Rectangle { colorScheme: hifi.colorSchemes.light; anchors.top: viewInMyPurchasesButton.visible ? viewInMyPurchasesButton.bottom : (buyTextContainer.visible ? buyTextContainer.bottom : checkoutActionButtonsContainer.top); - anchors.topMargin: 16; - height: 40; + anchors.topMargin: 10; + height: 50; anchors.left: parent.left; anchors.right: parent.right; text: ((root.isCertified) ? ((ownershipStatusReceived && balanceReceived) ? @@ -579,8 +579,8 @@ Rectangle { color: hifi.buttons.noneBorderlessGray; colorScheme: hifi.colorSchemes.light; anchors.top: buyButton.visible ? buyButton.bottom : viewInMyPurchasesButton.bottom; - anchors.topMargin: 16; - height: 40; + anchors.topMargin: 10; + height: 50; anchors.left: parent.left; anchors.right: parent.right; text: "Cancel" @@ -606,32 +606,32 @@ Rectangle { anchors.top: titleBarContainer.bottom; anchors.bottom: root.bottom; anchors.left: parent.left; - anchors.leftMargin: 16; + anchors.leftMargin: 20; anchors.right: parent.right; - anchors.rightMargin: 16; + anchors.rightMargin: 20; RalewayRegular { id: completeText; anchors.top: parent.top; - anchors.topMargin: 30; + anchors.topMargin: 18; anchors.left: parent.left; width: paintedWidth; height: paintedHeight; text: "Thank you for your order!"; color: hifi.colors.baseGray; - size: 28; + size: 36; } RalewaySemiBold { id: completeText2; text: "The " + (root.itemTypesText)[itemTypesArray.indexOf(root.itemType)] + - '' + root.itemName + '' + + ' ' + root.itemName + '' + " has been added to your Purchases and a receipt will appear in your Wallet's transaction history."; // Text size - size: 20; + size: 18; // Anchors anchors.top: completeText.bottom; - anchors.topMargin: 10; + anchors.topMargin: 15; height: paintedHeight; anchors.left: parent.left; anchors.right: parent.right; @@ -684,7 +684,7 @@ Rectangle { color: hifi.buttons.red; colorScheme: hifi.colorSchemes.light; anchors.top: completeText2.bottom; - anchors.topMargin: 30; + anchors.topMargin: 27; height: 50; anchors.left: parent.left; anchors.right: parent.right; @@ -700,12 +700,18 @@ Rectangle { lightboxPopup.button1text = "CANCEL"; lightboxPopup.button1method = "root.visible = false;" lightboxPopup.button2text = "CONFIRM"; - lightboxPopup.button2method = "Commerce.replaceContentSet('" + root.itemId + "', '" + root.itemHref + "');" + + lightboxPopup.button2method = "Commerce.replaceContentSet('" + root.itemHref + "');" + "root.visible = false;rezzedNotifContainer.visible = true; rezzedNotifContainerTimer.start();" + "UserActivityLogger.commerceEntityRezzed('" + root.itemId + "', 'checkout', '" + root.itemType + "');"; lightboxPopup.visible = true; } else if (root.itemType === "avatar") { - Avatar.skeletonModelURL = root.itemHref; + lightboxPopup.titleText = "Change Avatar"; + lightboxPopup.bodyText = "This will change your current avatar to " + root.itemName + " while retaining your wearables."; + lightboxPopup.button1text = "CANCEL"; + lightboxPopup.button1method = "root.visible = false;" + lightboxPopup.button2text = "CONFIRM"; + lightboxPopup.button2method = "Avatar.skeletonModelURL('" + root.itemHref + "'); root.visible = false;"; + lightboxPopup.visible = true; } else { sendToScript({method: 'checkout_rezClicked', itemHref: root.itemHref, itemType: root.itemType}); rezzedNotifContainer.visible = true; @@ -768,9 +774,9 @@ Rectangle { RalewaySemiBold { id: myPurchasesLink; - text: 'View this item in My Purchases'; + text: 'View this item in My Purchases'; // Text size - size: 20; + size: 18; // Anchors anchors.top: explainRezText.visible ? explainRezText.bottom : (noPermissionText.visible ? noPermissionText.bottom : rezNowButton.bottom); anchors.topMargin: 40; @@ -790,12 +796,12 @@ Rectangle { RalewaySemiBold { id: walletLink; - text: 'View receipt in Wallet'; + text: 'View receipt in Wallet'; // Text size - size: 20; + size: 18; // Anchors anchors.top: myPurchasesLink.bottom; - anchors.topMargin: 20; + anchors.topMargin: 16; height: paintedHeight; anchors.left: parent.left; anchors.right: parent.right; @@ -813,12 +819,12 @@ Rectangle { RalewayRegular { id: pendingText; text: 'Your item is marked "pending" while your purchase is being confirmed. ' + - 'Learn More'; + 'Learn More'; // Text size - size: 20; + size: 18; // Anchors anchors.top: walletLink.bottom; - anchors.topMargin: 60; + anchors.topMargin: 32; height: paintedHeight; anchors.left: parent.left; anchors.right: parent.right; @@ -844,11 +850,10 @@ Rectangle { color: hifi.buttons.noneBorderlessGray; colorScheme: hifi.colorSchemes.light; anchors.bottom: parent.bottom; - anchors.bottomMargin: 20; + anchors.bottomMargin: 54; anchors.right: parent.right; - anchors.rightMargin: 14; - width: parent.width/2 - anchors.rightMargin; - height: 60; + width: 193; + height: 44; text: "Continue Shopping"; onClicked: { sendToScript({method: 'checkout_continueShopping', itemId: itemId}); diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 7b2cae5188..be460ea4ef 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -20,6 +20,7 @@ import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls import "../wallet" as HifiWallet +import TabletScriptingInterface 1.0 // references XXX from root context @@ -56,7 +57,17 @@ Item { target: Commerce; onContentSetChanged: { - if (contentSetMarketplaceID === root.itemId) { + if (contentSetHref === root.itemHref) { + showConfirmation = true; + } + } + } + + Connections { + target: MyAvatar; + + onSkeletonModelURLChanged: { + if (skeletonModelURL === root.itemHref) { showConfirmation = true; } } @@ -275,19 +286,19 @@ Item { anchors.bottom: parent.bottom; width: 32; // Style - color: hifi.colors.lightGray; + color: hifi.colors.black; } RalewayRegular { id: viewCertificateText; text: "VIEW CERTIFICATE"; - size: 14; + size: 13; anchors.left: certificateIcon.right; anchors.leftMargin: 4; anchors.top: parent.top; anchors.bottom: parent.bottom; anchors.right: parent.right; - color: hifi.colors.lightGray; + color: hifi.colors.black; } MouseArea { @@ -297,13 +308,13 @@ Item { sendToPurchases({method: 'purchases_itemCertificateClicked', itemCertificateId: root.certificateId}); } onEntered: { - certificateIcon.color = hifi.colors.black; - viewCertificateText.color = hifi.colors.black; - } - onExited: { certificateIcon.color = hifi.colors.lightGray; viewCertificateText.color = hifi.colors.lightGray; } + onExited: { + certificateIcon.color = hifi.colors.black; + viewCertificateText.color = hifi.colors.black; + } } } @@ -317,14 +328,14 @@ Item { anchors.right: buttonContainer.left; anchors.rightMargin: 2; - FiraSansRegular { + RalewayRegular { anchors.left: parent.left; anchors.top: parent.top; anchors.bottom: parent.bottom; width: paintedWidth; text: "#" + root.itemEdition; - size: 15; - color: "#cc6a6a6a"; + size: 13; + color: hifi.colors.black; verticalAlignment: Text.AlignTop; } } @@ -471,13 +482,28 @@ Item { anchors.right: parent.right; anchors.rightMargin: 4; width: height; - enabled: root.hasPermissionToRezThis && root.purchaseStatus !== "invalidated"; + enabled: root.hasPermissionToRezThis && + root.purchaseStatus !== "invalidated" && + MyAvatar.skeletonModelURL !== root.itemHref; + + onHoveredChanged: { + if (hovered) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } + + onFocusChanged: { + if (focus) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } onClicked: { + Tablet.playSound(TabletEnums.ButtonClick); if (root.itemType === "contentSet") { - sendToPurchases({method: 'showReplaceContentLightbox', itemId: root.itemId, itemHref: root.itemHref}); + sendToPurchases({method: 'showReplaceContentLightbox', itemHref: root.itemHref}); } else if (root.itemType === "avatar") { - Avatar.skeletonModelURL = root.itemHref; + sendToPurchases({method: 'showChangeAvatarLightbox', itemName: root.itemName, itemHref: root.itemHref}); } else { sendToPurchases({method: 'purchases_rezClicked', itemHref: root.itemHref, itemType: root.itemType}); root.showConfirmation = true; @@ -547,7 +573,13 @@ Item { size: 15; verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter - text: (root.buttonTextNormal)[itemTypesArray.indexOf(root.itemType)] + text: { + if (MyAvatar.skeletonModelURL === root.itemHref) { + "CURRENT"; + } else { + (root.buttonTextNormal)[itemTypesArray.indexOf(root.itemType)]; + } + } } } } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 91fed207bc..df65a0a1ac 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -300,7 +300,7 @@ Rectangle { anchors.left: parent.left; anchors.leftMargin: 8; anchors.right: parent.right; - anchors.rightMargin: 12; + anchors.rightMargin: 16; anchors.top: parent.top; anchors.topMargin: 4; @@ -314,7 +314,7 @@ Rectangle { anchors.leftMargin: 16; width: paintedWidth; text: isShowingMyItems ? "My Items" : "My Purchases"; - color: hifi.colors.baseGray; + color: hifi.colors.black; size: 22; } @@ -348,7 +348,7 @@ Rectangle { HifiControlsUit.Separator { id: separator; - colorScheme: 1; + colorScheme: 2; anchors.left: parent.left; anchors.right: parent.right; anchors.top: filterBarContainer.bottom; @@ -445,7 +445,15 @@ Rectangle { lightboxPopup.button1text = "CANCEL"; lightboxPopup.button1method = "root.visible = false;" lightboxPopup.button2text = "CONFIRM"; - lightboxPopup.button2method = "Commerce.replaceContentSet('" + msg.itemId + "', '" + msg.itemHref + "'); root.visible = false;"; + lightboxPopup.button2method = "Commerce.replaceContentSet('" + msg.itemHref + "'); root.visible = false;"; + lightboxPopup.visible = true; + } else if (msg.method === "showChangeAvatarLightbox") { + lightboxPopup.titleText = "Change Avatar"; + lightboxPopup.bodyText = "This will change your current avatar to " + msg.itemName + " while retaining your wearables."; + lightboxPopup.button1text = "CANCEL"; + lightboxPopup.button1method = "root.visible = false;" + lightboxPopup.button2text = "CONFIRM"; + lightboxPopup.button2method = "MyAvatar.skeletonModelURL('" + msg.itemHref + "'); root.visible = false;"; lightboxPopup.visible = true; } else if (msg.method === "showPermissionsExplanation") { if (msg.itemType === "entity") { diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 5458925d55..36c1e422c5 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -168,15 +168,15 @@ void QmlCommerce::transferHfcToUsername(const QString& username, const int& amou ledger->transferHfcToUsername(key, username, amount, optionalMessage); } -void QmlCommerce::replaceContentSet(const QString& id, const QString& url) { - qApp->replaceDomainContent(url); +void QmlCommerce::replaceContentSet(const QString& itemHref) { + qApp->replaceDomainContent(itemHref); QJsonObject messageProperties = { { "status", "SuccessfulRequestToReplaceContent" }, - { "content_set_url", url } + { "content_set_url", itemHref } }; UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties); - emit contentSetChanged(id); + emit contentSetChanged(itemHref); } void QmlCommerce::alreadyOwned(const QString& marketplaceId) { diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index dc38dcad19..b621608190 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -49,7 +49,7 @@ signals: void transferHfcToNodeResult(QJsonObject result); void transferHfcToUsernameResult(QJsonObject result); - void contentSetChanged(const QString& contentSetMarketplaceID); + void contentSetChanged(const QString& contentSetHref); protected: Q_INVOKABLE void getWalletStatus(); @@ -77,7 +77,7 @@ protected: Q_INVOKABLE void transferHfcToUsername(const QString& username, const int& amount, const QString& optionalMessage); - Q_INVOKABLE void replaceContentSet(const QString& id, const QString& url); + Q_INVOKABLE void replaceContentSet(const QString& itemHref); }; #endif // hifi_QmlCommerce_h diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index f24bd51bde..6d9a13decf 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -371,7 +371,7 @@ class AvatarData : public QObject, public SpatiallyNestable { // The result is unique among all avatars present at the time. Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName WRITE setSessionDisplayName NOTIFY sessionDisplayNameChanged) Q_PROPERTY(bool lookAtSnappingEnabled MEMBER _lookAtSnappingEnabled NOTIFY lookAtSnappingChanged) - Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript) + Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript NOTIFY skeletonModelURLChanged) Q_PROPERTY(QVector attachmentData READ getAttachmentData WRITE setAttachmentData) Q_PROPERTY(QStringList jointNames READ getJointNames) @@ -697,6 +697,7 @@ public: signals: void displayNameChanged(); void sessionDisplayNameChanged(); + void skeletonModelURLChanged(); void lookAtSnappingChanged(bool enabled); void sessionUUIDChanged(); diff --git a/libraries/avatars/src/ScriptAvatarData.cpp b/libraries/avatars/src/ScriptAvatarData.cpp index 1fd001e536..8491e5368b 100644 --- a/libraries/avatars/src/ScriptAvatarData.cpp +++ b/libraries/avatars/src/ScriptAvatarData.cpp @@ -16,6 +16,7 @@ ScriptAvatarData::ScriptAvatarData(AvatarSharedPointer avatarData) : { QObject::connect(avatarData.get(), &AvatarData::displayNameChanged, this, &ScriptAvatarData::displayNameChanged); QObject::connect(avatarData.get(), &AvatarData::sessionDisplayNameChanged, this, &ScriptAvatarData::sessionDisplayNameChanged); + QObject::connect(avatarData.get(), &AvatarData::skeletonModelURLChanged, this, &ScriptAvatarData::skeletonModelURLChanged); QObject::connect(avatarData.get(), &AvatarData::lookAtSnappingChanged, this, &ScriptAvatarData::lookAtSnappingChanged); } diff --git a/libraries/avatars/src/ScriptAvatarData.h b/libraries/avatars/src/ScriptAvatarData.h index 68074b838d..13713ff15f 100644 --- a/libraries/avatars/src/ScriptAvatarData.h +++ b/libraries/avatars/src/ScriptAvatarData.h @@ -51,7 +51,7 @@ class ScriptAvatarData : public QObject { // // ATTACHMENT AND JOINT PROPERTIES // - Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript) + Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript NOTIFY skeletonModelURLChanged) Q_PROPERTY(QVector attachmentData READ getAttachmentData) Q_PROPERTY(QStringList jointNames READ getJointNames) @@ -132,6 +132,7 @@ public: signals: void displayNameChanged(); void sessionDisplayNameChanged(); + void skeletonModelURLChanged(); void lookAtSnappingChanged(bool enabled); public slots: From 3297e39c144581788015b5ea4cd5bc52505fda0a Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 15 Feb 2018 13:39:18 -0800 Subject: [PATCH 296/569] CR --- domain-server/src/AssetsBackupHandler.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/domain-server/src/AssetsBackupHandler.cpp b/domain-server/src/AssetsBackupHandler.cpp index a9f56a0c5b..ae9cb58343 100644 --- a/domain-server/src/AssetsBackupHandler.cpp +++ b/domain-server/src/AssetsBackupHandler.cpp @@ -58,7 +58,6 @@ void AssetsBackupHandler::setupRefreshTimer() { }); QObject::connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, [this](SharedNodePointer node) { if (node->getType() == NodeType::AssetServer) { - // run immediately for the first time. _mappingsRefreshTimer.stop(); } }); @@ -240,7 +239,7 @@ void AssetsBackupHandler::recoverBackup(QuaZip& zip) { auto assetNames = zipDir.entryList(QDir::Files); for (const auto& asset : assetNames) { if (AssetUtils::isValidHash(asset)) { - if (!zip.setCurrentFile(MAPPINGS_FILE)) { + if (!zip.setCurrentFile(zipDir.filePath(asset))) { qCCritical(asset_backup) << "Failed to find" << asset << "while recovering backup"; qCCritical(asset_backup) << " Error:" << zip.getZipError(); continue; From 72e89851f3c2936bbf3da1f065fb3bc4fb4b919e Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Thu, 15 Feb 2018 14:16:56 -0800 Subject: [PATCH 297/569] Added READ_ENTITY_PROPERTY_TO_PROPERTIES --- libraries/entities/src/EntityItemProperties.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 86404c6504..78c6c6a6b3 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1869,6 +1869,14 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int properties.getType() == EntityTypes::Box || properties.getType() == EntityTypes::Sphere) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE, QString, setShape); + } + + // Can cast shadow flag + if (properties.getType() == EntityTypes::Model || + properties.getType() == EntityTypes::Shape || + properties.getType() == EntityTypes::Box || + properties.getType() == EntityTypes::Sphere) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow); } From 811ecf9db8341428ecbf5bba66c7e7a87aeb61ee Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 15 Feb 2018 14:11:01 -0800 Subject: [PATCH 298/569] Fix --- .../resources/qml/hifi/commerce/checkout/Checkout.qml | 6 +++--- .../qml/hifi/commerce/purchases/PurchasedItem.qml | 5 +++-- .../resources/qml/hifi/commerce/purchases/Purchases.qml | 2 +- scripts/system/marketplaces/marketplaces.js | 8 ++++---- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 08b100a64c..891c8142bf 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -146,7 +146,7 @@ Rectangle { } onItemTypeChanged: { - if (root.itemType === "entity" || root.itemType === "wearable" || root.itemType === "contentSet") { + if (root.itemType === "entity" || root.itemType === "wearable" || root.itemType === "contentSet" || root.itemType === "avatar") { root.isCertified = true; } else { root.isCertified = false; @@ -679,7 +679,7 @@ Rectangle { id: rezNowButton; enabled: (root.itemType === "entity" && root.canRezCertifiedItems) || (root.itemType === "contentSet" && Entities.canReplaceContent()) || - root.itemType === "wearable"; + root.itemType === "wearable" || root.itemType === "avatar"; buttonGlyph: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)]; color: hifi.buttons.red; colorScheme: hifi.colorSchemes.light; @@ -710,7 +710,7 @@ Rectangle { lightboxPopup.button1text = "CANCEL"; lightboxPopup.button1method = "root.visible = false;" lightboxPopup.button2text = "CONFIRM"; - lightboxPopup.button2method = "Avatar.skeletonModelURL('" + root.itemHref + "'); root.visible = false;"; + lightboxPopup.button2method = "MyAvatar.useFullAvatarURL('" + root.itemHref + "'); root.visible = false;"; lightboxPopup.visible = true; } else { sendToScript({method: 'checkout_rezClicked', itemHref: root.itemHref, itemType: root.itemType}); diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index be460ea4ef..50ac34cd51 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -151,8 +151,9 @@ Item { anchors.topMargin: 4; anchors.left: itemPreviewImage.right; anchors.leftMargin: 8; - width: root.hasPermissionToRezThis ? (buttonContainer.x - itemPreviewImage.x - itemPreviewImage.width) : - Math.min(itemNameTextMetrics.tightBoundingRect.width + 2, buttonContainer.x - itemPreviewImage.x - itemPreviewImage.width - noPermissionGlyph.width + 2); + width: root.hasPermissionToRezThis ? (buttonContainer.x - itemPreviewImage.x - itemPreviewImage.width - anchors.leftMargin) : + Math.min(itemNameTextMetrics.tightBoundingRect.width + 2, + buttonContainer.x - itemPreviewImage.x - itemPreviewImage.width - anchors.leftMargin - noPermissionGlyph.width + 2); height: paintedHeight; // Text size size: 24; diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index df65a0a1ac..5593883e00 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -453,7 +453,7 @@ Rectangle { lightboxPopup.button1text = "CANCEL"; lightboxPopup.button1method = "root.visible = false;" lightboxPopup.button2text = "CONFIRM"; - lightboxPopup.button2method = "MyAvatar.skeletonModelURL('" + msg.itemHref + "'); root.visible = false;"; + lightboxPopup.button2method = "MyAvatar.useFullAvatarURL('" + msg.itemHref + "'); root.visible = false;"; lightboxPopup.visible = true; } else if (msg.method === "showPermissionsExplanation") { if (msg.itemType === "entity") { diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 95d9294063..c61272119d 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -75,10 +75,10 @@ var selectionDisplay = null; // for gridTool.js to ignore tablet.pushOntoStack(MARKETPLACE_CHECKOUT_QML_PATH); tablet.sendToQml({ method: 'updateCheckoutQML', params: { - itemId: 'e197e3d7-eafc-4aa5-9341-acee57174fe9', - itemName: 'Oasis', - itemPrice: (debugError ? 10 : 11), - itemHref: 'http://mpassets-staging.highfidelity.com/e197e3d7-eafc-4aa5-9341-acee57174fe9-v1/oasis_Aug15.json.gz', + itemId: '424611a2-73d0-4c03-9087-26a6a279257b', + itemName: '2018-02-15 Finnegon', + itemPrice: (debugError ? 10 : 3), + itemHref: 'http://devmpassets.highfidelity.com/424611a2-73d0-4c03-9087-26a6a279257b-v1/finnigon.fst', categories: ["Miscellaneous"] } }); From e4db09fef8845c10b8e9872d00c5d8e56e67e75e Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 15 Feb 2018 15:02:45 -0800 Subject: [PATCH 299/569] don't update avatar entities if the avatars ID is AVATAR_SELF_ID --- libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 86635cd3bf..1b1511e2c6 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -219,7 +219,7 @@ void Avatar::updateAvatarEntities() { return; } - if (getID() == QUuid()) { + if (getID() == QUuid() || getID() == AVATAR_SELF_ID) { return; // wait until MyAvatar gets an ID before doing this. } From f624e1b464bd17755058f516e4ae114cc759041f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 15 Feb 2018 15:10:51 -0800 Subject: [PATCH 300/569] add a content settings backup handler --- .../src/ContentSettingsBackupHandler.cpp | 66 +++++++++++++++++++ .../src/ContentSettingsBackupHandler.h | 35 ++++++++++ domain-server/src/DomainServer.cpp | 2 + .../src/DomainServerSettingsManager.h | 10 +-- domain-server/src/EntitiesBackupHandler.cpp | 14 ++-- 5 files changed, 116 insertions(+), 11 deletions(-) create mode 100644 domain-server/src/ContentSettingsBackupHandler.cpp create mode 100644 domain-server/src/ContentSettingsBackupHandler.h diff --git a/domain-server/src/ContentSettingsBackupHandler.cpp b/domain-server/src/ContentSettingsBackupHandler.cpp new file mode 100644 index 0000000000..be470bdd9f --- /dev/null +++ b/domain-server/src/ContentSettingsBackupHandler.cpp @@ -0,0 +1,66 @@ +// +// ContentSettingsBackupHandler.cpp +// domain-server/src +// +// Created by Stephen Birarda on 2/15/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ContentSettingsBackupHandler.h" + +#include +#include + +ContentSettingsBackupHandler::ContentSettingsBackupHandler(DomainServerSettingsManager& domainServerSettingsManager) : + _settingsManager(domainServerSettingsManager) +{ + +} + +static const QString CONTENT_SETTINGS_BACKUP_FILENAME = "content-settings.json"; + +void ContentSettingsBackupHandler::createBackup(QuaZip& zip) { + + // grab the content settings as JSON,excluding default values and values hidden from backup + QJsonObject contentSettingsJSON = _settingsManager.settingsResponseObjectForType("", true, false, true, false, true); + + // make a QJSonDocument using the object + QJsonDocument contentSettingsDocument { contentSettingsJSON }; + + QuaZipFile zipFile { &zip }; + + zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(CONTENT_SETTINGS_BACKUP_FILENAME)); + zipFile.write(contentSettingsDocument.toJson()); + zipFile.close(); + + if (zipFile.getZipError() != UNZ_OK) { + qCritical().nospace() << "Failed to zip " << CONTENT_SETTINGS_BACKUP_FILENAME << ": " << zipFile.getZipError(); + } +} + +void ContentSettingsBackupHandler::recoverBackup(QuaZip& zip) { + if (!zip.setCurrentFile(CONTENT_SETTINGS_BACKUP_FILENAME)) { + qWarning() << "Failed to find" << CONTENT_SETTINGS_BACKUP_FILENAME << "while recovering backup"; + return; + } + + QuaZipFile zipFile { &zip }; + if (!zipFile.open(QIODevice::ReadOnly)) { + qCritical() << "Failed to open" << CONTENT_SETTINGS_BACKUP_FILENAME << "in backup"; + return; + } + + auto rawData = zipFile.readAll(); + zipFile.close(); + + QJsonDocument jsonDocument = QJsonDocument::fromJson(rawData); + + if (!_settingsManager.restoreSettingsFromObject(jsonDocument.object(), ContentSettings)) { + qCritical() << "Failed to restore settings from" << CONTENT_SETTINGS_BACKUP_FILENAME << "in content archive"; + return; + } + +} diff --git a/domain-server/src/ContentSettingsBackupHandler.h b/domain-server/src/ContentSettingsBackupHandler.h new file mode 100644 index 0000000000..932b7c0c3f --- /dev/null +++ b/domain-server/src/ContentSettingsBackupHandler.h @@ -0,0 +1,35 @@ +// +// ContentSettingsBackupHandler.h +// domain-server/src +// +// Created by Stephen Birarda on 2/15/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ContentSettingsBackupHandler_h +#define hifi_ContentSettingsBackupHandler_h + +#include "BackupHandler.h" +#include "DomainServerSettingsManager.h" + +class ContentSettingsBackupHandler : public BackupHandlerInterface { +public: + ContentSettingsBackupHandler(DomainServerSettingsManager& domainServerSettingsManager); + + void loadBackup(QuaZip& zip) {}; + + void createBackup(QuaZip& zip); + + void recoverBackup(QuaZip& zip); + + void deleteBackup(QuaZip& zip) {}; + + void consolidateBackup(QuaZip& zip) {}; +private: + DomainServerSettingsManager& _settingsManager; +}; + +#endif // hifi_ContentSettingsBackupHandler_h diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 4c72423f74..d3bc5fdff1 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -46,6 +46,7 @@ #include #include "AssetsBackupHandler.h" +#include "ContentSettingsBackupHandler.h" #include "DomainServerNodeData.h" #include "EntitiesBackupHandler.h" #include "NodeConnectionData.h" @@ -299,6 +300,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : _contentManager.reset(new DomainContentBackupManager(getContentBackupDir(), _settingsManager.settingsResponseObjectForType("6")["entity_server_settings"].toObject())); _contentManager->addBackupHandler(BackupHandlerPointer(new EntitiesBackupHandler(getEntitiesFilePath(), getEntitiesReplacementFilePath()))); _contentManager->addBackupHandler(BackupHandlerPointer(new AssetsBackupHandler(getContentBackupDir()))); + _contentManager->addBackupHandler(BackupHandlerPointer(new ContentSettingsBackupHandler(_settingsManager))); _contentManager->initialize(true); qDebug() << "Existing backups:"; diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 9b2427b344..4316534685 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -111,6 +111,11 @@ public: void debugDumpGroupsState(); + QJsonObject settingsResponseObjectForType(const QString& typeValue, bool isAuthenticated = false, + bool includeDomainSettings = true, bool includeContentSettings = true, + bool includeDefaults = true, bool isForBackup = false); + bool restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType); + signals: void updateNodePermissions(); void settingsUpdated(); @@ -130,9 +135,6 @@ private: QStringList _argumentList; QJsonArray filteredDescriptionArray(bool isContentSettings); - QJsonObject settingsResponseObjectForType(const QString& typeValue, bool isAuthenticated = false, - bool includeDomainSettings = true, bool includeContentSettings = true, - bool includeDefaults = true, bool isForBackup = false); bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, SettingsType settingsType); void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap, @@ -143,8 +145,6 @@ private: void splitSettingsDescription(); - bool restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType); - double _descriptionVersion; QJsonArray _descriptionArray; diff --git a/domain-server/src/EntitiesBackupHandler.cpp b/domain-server/src/EntitiesBackupHandler.cpp index a95d68b007..6ad00d01c8 100644 --- a/domain-server/src/EntitiesBackupHandler.cpp +++ b/domain-server/src/EntitiesBackupHandler.cpp @@ -24,28 +24,30 @@ EntitiesBackupHandler::EntitiesBackupHandler(QString entitiesFilePath, QString e { } +static const QString ENTITIES_BACKUP_FILENAME = "models.json.gz"; + void EntitiesBackupHandler::createBackup(QuaZip& zip) { QFile entitiesFile { _entitiesFilePath }; if (entitiesFile.open(QIODevice::ReadOnly)) { QuaZipFile zipFile { &zip }; - zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo("models.json.gz", _entitiesFilePath)); + zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(ENTITIES_BACKUP_FILENAME, _entitiesFilePath)); zipFile.write(entitiesFile.readAll()); zipFile.close(); if (zipFile.getZipError() != UNZ_OK) { - qCritical() << "Failed to zip models.json.gz: " << zipFile.getZipError(); + qCritical().nospace() << "Failed to zip " << ENTITIES_BACKUP_FILENAME << ": " << zipFile.getZipError(); } } } void EntitiesBackupHandler::recoverBackup(QuaZip& zip) { - if (!zip.setCurrentFile("models.json.gz")) { - qWarning() << "Failed to find models.json.gz while recovering backup"; + if (!zip.setCurrentFile(ENTITIES_BACKUP_FILENAME)) { + qWarning() << "Failed to find" << ENTITIES_BACKUP_FILENAME << "while recovering backup"; return; } QuaZipFile zipFile { &zip }; if (!zipFile.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open models.json.gz in backup"; + qCritical() << "Failed to open" << ENTITIES_BACKUP_FILENAME << "in backup"; return; } auto rawData = zipFile.readAll(); @@ -61,7 +63,7 @@ void EntitiesBackupHandler::recoverBackup(QuaZip& zip) { data.resetIdAndVersion(); if (zipFile.getZipError() != UNZ_OK) { - qCritical() << "Failed to unzip models.json.gz: " << zipFile.getZipError(); + qCritical().nospace() << "Failed to unzip " << ENTITIES_BACKUP_FILENAME << ": " << zipFile.getZipError(); return; } From f5cad5683dbb4028c76beafbdafddb249655d0b7 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 15 Feb 2018 15:39:25 -0800 Subject: [PATCH 301/569] make sure backup handlers end up on the correct thread --- domain-server/src/DomainContentBackupManager.cpp | 3 --- domain-server/src/DomainServer.cpp | 12 ++++++++---- libraries/shared/src/GenericThread.cpp | 2 ++ libraries/shared/src/GenericThread.h | 1 + 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index f39737c92e..c68ff0c6ea 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -155,9 +155,6 @@ bool DomainContentBackupManager::process() { } void DomainContentBackupManager::aboutToFinish() { - qCDebug(domain_server) << "Persist thread about to finish..."; - backup(); - qCDebug(domain_server) << "Persist thread done with about to finish..."; _stopThread = true; } diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index d3bc5fdff1..599f09ae94 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -298,9 +298,13 @@ DomainServer::DomainServer(int argc, char* argv[]) : maybeHandleReplacementEntityFile(); _contentManager.reset(new DomainContentBackupManager(getContentBackupDir(), _settingsManager.settingsResponseObjectForType("6")["entity_server_settings"].toObject())); - _contentManager->addBackupHandler(BackupHandlerPointer(new EntitiesBackupHandler(getEntitiesFilePath(), getEntitiesReplacementFilePath()))); - _contentManager->addBackupHandler(BackupHandlerPointer(new AssetsBackupHandler(getContentBackupDir()))); - _contentManager->addBackupHandler(BackupHandlerPointer(new ContentSettingsBackupHandler(_settingsManager))); + + connect(_contentManager.get(), &DomainContentBackupManager::started, _contentManager.get(), [this](){ + _contentManager->addBackupHandler(BackupHandlerPointer(new EntitiesBackupHandler(getEntitiesFilePath(), getEntitiesReplacementFilePath()))); + _contentManager->addBackupHandler(BackupHandlerPointer(new AssetsBackupHandler(getContentBackupDir()))); + _contentManager->addBackupHandler(BackupHandlerPointer(new ContentSettingsBackupHandler(_settingsManager))); + }); + _contentManager->initialize(true); qDebug() << "Existing backups:"; @@ -382,7 +386,7 @@ DomainServer::~DomainServer() { if (_contentManager) { _contentManager->aboutToFinish(); - _contentManager->terminating(); + _contentManager->terminate(); } } diff --git a/libraries/shared/src/GenericThread.cpp b/libraries/shared/src/GenericThread.cpp index 2e126f12c9..50655820af 100644 --- a/libraries/shared/src/GenericThread.cpp +++ b/libraries/shared/src/GenericThread.cpp @@ -38,6 +38,8 @@ void GenericThread::initialize(bool isThreaded, QThread::Priority priority) { _thread->setObjectName(objectName()); // when the worker thread is started, call our engine's run.. + + connect(_thread, &QThread::started, this, &GenericThread::started); connect(_thread, &QThread::started, this, &GenericThread::threadRoutine); connect(_thread, &QThread::finished, this, &GenericThread::finished); diff --git a/libraries/shared/src/GenericThread.h b/libraries/shared/src/GenericThread.h index 09872b32cd..c1f946d6aa 100644 --- a/libraries/shared/src/GenericThread.h +++ b/libraries/shared/src/GenericThread.h @@ -47,6 +47,7 @@ public slots: void threadRoutine(); signals: + void started(); void finished(); protected: From 2d754edf741a6dfd3957f92535e694272776f5f5 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 15 Feb 2018 15:48:41 -0800 Subject: [PATCH 302/569] CR --- interface/src/Application.cpp | 39 +++++- interface/src/ui/overlays/ModelOverlay.cpp | 4 +- interface/src/ui/overlays/ModelOverlay.h | 4 +- interface/src/ui/overlays/Overlay.cpp | 4 +- interface/src/ui/overlays/Overlay.h | 6 +- .../src/avatars-renderer/Avatar.cpp | 4 +- .../src/avatars-renderer/Avatar.h | 6 +- libraries/avatars/src/AvatarData.h | 4 +- .../src/RenderableEntityItem.cpp | 6 +- .../src/RenderableEntityItem.h | 9 +- .../src/RenderableMaterialEntityItem.cpp | 4 +- .../src/RenderableModelEntityItem.cpp | 4 +- .../src/RenderableModelEntityItem.h | 5 +- .../src/RenderableShapeEntityItem.cpp | 4 +- libraries/entities/src/EntityItem.cpp | 10 +- libraries/entities/src/EntityItem.h | 11 +- libraries/entities/src/EntityTree.cpp | 32 +++-- libraries/entities/src/EntityTree.h | 31 +++-- libraries/entities/src/EntityTreeElement.cpp | 1 - libraries/entities/src/MaterialEntityItem.cpp | 66 ++++------ libraries/entities/src/MaterialEntityItem.h | 9 +- libraries/graphics/src/graphics/Material.cpp | 2 - libraries/graphics/src/graphics/Material.h | 39 +++--- .../src/model-networking/MaterialCache.cpp | 12 +- .../src/model-networking/MaterialCache.h | 6 +- .../src/model-networking/ModelCache.cpp | 2 +- .../render-utils/src/MeshPartPayload.cpp | 28 ++-- libraries/render-utils/src/MeshPartPayload.h | 8 +- libraries/render-utils/src/Model.cpp | 10 +- libraries/render-utils/src/Model.h | 6 +- .../render-utils/src/RenderPipelines.cpp | 122 +++++++++++++++++ libraries/render-utils/src/RenderPipelines.h | 22 ++++ libraries/render/CMakeLists.txt | 6 +- libraries/render/src/render/ShapePipeline.cpp | 123 +----------------- libraries/render/src/render/ShapePipeline.h | 4 - 35 files changed, 350 insertions(+), 303 deletions(-) create mode 100644 libraries/render-utils/src/RenderPipelines.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 70b94443ea..9fb2bdb684 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1593,7 +1593,38 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo } }); - EntityTree::setAddMaterialToAvatarOperator([](const QUuid& avatarID, graphics::MaterialPointer material, const QString& parentMaterialName) { + EntityTree::setAddMaterialToEntityOperator([&](const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) { + // try to find the renderable + auto renderable = getEntities()->renderableForEntityId(entityID); + if (renderable) { + renderable->addMaterial(material, parentMaterialName); + } + + // even if we don't find it, try to find the entity + auto entity = getEntities()->getEntity(entityID); + if (entity) { + entity->addMaterial(material, parentMaterialName); + return true; + } + return false; + }); + EntityTree::setRemoveMaterialFromEntityOperator([&](const QUuid& entityID, graphics::MaterialPointer material, const std::string& parentMaterialName) { + // try to find the renderable + auto renderable = getEntities()->renderableForEntityId(entityID); + if (renderable) { + renderable->removeMaterial(material, parentMaterialName); + } + + // even if we don't find it, try to find the entity + auto entity = getEntities()->getEntity(entityID); + if (entity) { + entity->removeMaterial(material, parentMaterialName); + return true; + } + return false; + }); + + EntityTree::setAddMaterialToAvatarOperator([](const QUuid& avatarID, graphics::MaterialLayer material, const std::string& parentMaterialName) { auto avatarManager = DependencyManager::get(); auto avatar = avatarManager->getAvatarBySessionID(avatarID); if (avatar) { @@ -1602,7 +1633,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo } return false; }); - EntityTree::setRemoveMaterialFromAvatarOperator([](const QUuid& avatarID, graphics::MaterialPointer material, const QString& parentMaterialName) { + EntityTree::setRemoveMaterialFromAvatarOperator([](const QUuid& avatarID, graphics::MaterialPointer material, const std::string& parentMaterialName) { auto avatarManager = DependencyManager::get(); auto avatar = avatarManager->getAvatarBySessionID(avatarID); if (avatar) { @@ -1612,7 +1643,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo return false; }); - EntityTree::setAddMaterialToOverlayOperator([&](const QUuid& overlayID, graphics::MaterialPointer material, const QString& parentMaterialName) { + EntityTree::setAddMaterialToOverlayOperator([&](const QUuid& overlayID, graphics::MaterialLayer material, const std::string& parentMaterialName) { auto overlay = _overlays.getOverlay(overlayID); if (overlay) { overlay->addMaterial(material, parentMaterialName); @@ -1620,7 +1651,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo } return false; }); - EntityTree::setRemoveMaterialFromOverlayOperator([&](const QUuid& overlayID, graphics::MaterialPointer material, const QString& parentMaterialName) { + EntityTree::setRemoveMaterialFromOverlayOperator([&](const QUuid& overlayID, graphics::MaterialPointer material, const std::string& parentMaterialName) { auto overlay = _overlays.getOverlay(overlayID); if (overlay) { overlay->removeMaterial(material, parentMaterialName); diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index e4ee889307..d23a0eb7ef 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -635,14 +635,14 @@ uint32_t ModelOverlay::fetchMetaSubItems(render::ItemIDs& subItems) const { return 0; } -void ModelOverlay::addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { +void ModelOverlay::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { Overlay::addMaterial(material, parentMaterialName); if (_model && _model->fetchRenderItemIDs().size() > 0) { _model->addMaterial(material, parentMaterialName); } } -void ModelOverlay::removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { +void ModelOverlay::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { Overlay::removeMaterial(material, parentMaterialName); if (_model && _model->fetchRenderItemIDs().size() > 0) { _model->removeMaterial(material, parentMaterialName); diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 5735854b3b..b38d5cd6d9 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -59,8 +59,8 @@ public: void setDrawInFront(bool drawInFront) override; void setDrawHUDLayer(bool drawHUDLayer) override; - void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) override; - void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) override; + void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) override; + void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) override; protected: Transform evalRenderTransform() override; diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index 766177e3a6..2c0c7c71b6 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -236,12 +236,12 @@ QVector qVectorOverlayIDFromScriptValue(const QScriptValue& array) { return newVector; } -void Overlay::addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { +void Overlay::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { std::lock_guard lock(_materialsLock); _materials[parentMaterialName].push(material); } -void Overlay::removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { +void Overlay::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { std::lock_guard lock(_materialsLock); _materials[parentMaterialName].remove(material); } \ No newline at end of file diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index ff45acddcf..f1be23ed39 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -91,8 +91,8 @@ public: unsigned int getStackOrder() const { return _stackOrder; } void setStackOrder(unsigned int stackOrder) { _stackOrder = stackOrder; } - virtual void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName); - virtual void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName); + virtual void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName); + virtual void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName); protected: float updatePulse(); @@ -120,7 +120,7 @@ protected: static const xColor DEFAULT_OVERLAY_COLOR; static const float DEFAULT_ALPHA; - std::unordered_map _materials; + std::unordered_map _materials; std::mutex _materialsLock; private: diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 796e73fcbc..9888150558 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1763,7 +1763,7 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const { } } -void Avatar::addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { +void Avatar::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { std::lock_guard lock(_materialsLock); _materials[parentMaterialName].push(material); if (_skeletonModel && _skeletonModel->fetchRenderItemIDs().size() > 0) { @@ -1771,7 +1771,7 @@ void Avatar::addMaterial(graphics::MaterialPointer material, const QString& pare } } -void Avatar::removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { +void Avatar::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { std::lock_guard lock(_materialsLock); _materials[parentMaterialName].remove(material); if (_skeletonModel && _skeletonModel->fetchRenderItemIDs().size() > 0) { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 14283a9188..b24fbeaef2 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -272,8 +272,8 @@ public: virtual void setAvatarEntityDataChanged(bool value) override; - void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) override; - void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) override; + void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) override; + void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) override; public slots: @@ -401,7 +401,7 @@ protected: ThreadSafeValueCache _unscaledEyeHeightCache { DEFAULT_AVATAR_EYE_HEIGHT }; - std::unordered_map _materials; + std::unordered_map _materials; std::mutex _materialsLock; void processMaterials(); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 48f7e19146..033756bfd2 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -696,8 +696,8 @@ public: bool getIsReplicated() const { return _isReplicated; } - virtual void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) {} - virtual void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) {} + virtual void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {} + virtual void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {} signals: void displayNameChanged(); diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index 9ccf58c04c..d3c9f3d4bd 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -147,8 +147,6 @@ EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _entity(entity emit requestRenderUpdate(); }); _materials = entity->getMaterials(); - connect(entity.get(), &EntityItem::addMaterialToRenderItem, this, &EntityRenderer::addMaterial); - connect(entity.get(), &EntityItem::removeMaterialFromRenderItem, this, &EntityRenderer::removeMaterial); } EntityRenderer::~EntityRenderer() { } @@ -404,12 +402,12 @@ void EntityRenderer::onRemoveFromScene(const EntityItemPointer& entity) { QObject::disconnect(this, &EntityRenderer::requestRenderUpdate, this, nullptr); } -void EntityRenderer::addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { +void EntityRenderer::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { std::lock_guard lock(_materialsLock); _materials[parentMaterialName].push(material); } -void EntityRenderer::removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { +void EntityRenderer::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { std::lock_guard lock(_materialsLock); _materials[parentMaterialName].remove(material); } \ No newline at end of file diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 4fc50ccc9a..ce652a758c 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -54,6 +54,9 @@ public: const uint64_t& getUpdateTime() const { return _updateTime; } + virtual void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName); + virtual void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName); + protected: virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); } virtual void onAddToScene(const EntityItemPointer& entity); @@ -101,10 +104,6 @@ protected: return result; } -public slots: - virtual void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName); - virtual void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName); - signals: void requestRenderUpdate(); @@ -133,7 +132,7 @@ protected: // Only touched on the rendering thread bool _renderUpdateQueued{ false }; - std::unordered_map _materials; + std::unordered_map _materials; std::mutex _materialsLock; private: diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp index 70d0234732..090891fe62 100644 --- a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp @@ -8,6 +8,8 @@ #include "RenderableMaterialEntityItem.h" +#include "RenderPipelines.h" + using namespace render; using namespace render::entities; @@ -250,7 +252,7 @@ void MaterialEntityRenderer::doRender(RenderArgs* args) { drawMaterial->setTextureTransforms(textureTransform); // bind the material - args->_shapePipeline->bindMaterial(drawMaterial, batch, args->_enableTexturing); + RenderPipelines::bindMaterial(drawMaterial, batch, args->_enableTexturing); args->_details._materialSwitches++; // Draw! diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 3205d68513..e395787462 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1467,14 +1467,14 @@ void ModelEntityRenderer::mapJoints(const TypedEntityPointer& entity, const QStr } } -void ModelEntityRenderer::addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { +void ModelEntityRenderer::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { Parent::addMaterial(material, parentMaterialName); if (_model && _model->fetchRenderItemIDs().size() > 0) { _model->addMaterial(material, parentMaterialName); } } -void ModelEntityRenderer::removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { +void ModelEntityRenderer::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { Parent::removeMaterial(material, parentMaterialName); if (_model && _model->fetchRenderItemIDs().size() > 0) { _model->removeMaterial(material, parentMaterialName); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 9d9b98ba98..25ae668a8e 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -143,9 +143,8 @@ class ModelEntityRenderer : public TypedEntityRenderergetMaterial(); - addMaterial(_material, "0"); + addMaterial(graphics::MaterialLayer(_material, 0), "0"); _shape = entity->getShape(); _position = entity->getWorldPosition(); @@ -127,7 +127,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { withReadLock([&] { geometryShape = geometryCache->getShapeForEntityShape(_shape); batch.setModelTransform(_renderTransform); // use a transform with scale, rotation, registration point and translation - mat = _materials["0"].top(); + mat = _materials["0"].top().material; if (mat) { outColor = glm::vec4(mat->getAlbedo(), mat->getOpacity()); if (_procedural.isReady()) { diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 3bb1406252..ec0bdbd2ae 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -2940,20 +2940,18 @@ void EntityItem::preDelete() { } } -void EntityItem::addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { +void EntityItem::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { std::lock_guard lock(_materialsLock); _materials[parentMaterialName].push(material); - emit addMaterialToRenderItem(material, parentMaterialName); } -void EntityItem::removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { +void EntityItem::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { std::lock_guard lock(_materialsLock); _materials[parentMaterialName].remove(material); - emit removeMaterialFromRenderItem(material, parentMaterialName); } -std::unordered_map EntityItem::getMaterials() { - std::unordered_map toReturn; +std::unordered_map EntityItem::getMaterials() { + std::unordered_map toReturn; { std::lock_guard lock(_materialsLock); toReturn = _materials; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 203b513a76..b12417c496 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -478,18 +478,15 @@ public: void setCauterized(bool value) { _cauterized = value; } bool getCauterized() const { return _cauterized; } - virtual void postAdd() {} virtual void preDelete(); virtual void postParentFixup() {} - void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName); - void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName); - std::unordered_map getMaterials(); + void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName); + void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName); + std::unordered_map getMaterials(); signals: void requestRenderUpdate(); - void addMaterialToRenderItem(graphics::MaterialPointer material, const QString& parentMaterialName); - void removeMaterialFromRenderItem(graphics::MaterialPointer material, const QString& parentMaterialName); protected: QHash _changeHandlers; @@ -644,7 +641,7 @@ protected: bool _cauterized { false }; // if true, don't draw because it would obscure 1st-person camera private: - std::unordered_map _materials; + std::unordered_map _materials; std::mutex _materialsLock; }; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 8abd5efc13..ad0066af4a 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2386,33 +2386,49 @@ QStringList EntityTree::getJointNames(const QUuid& entityID) const { return entity->getJointNames(); } -std::function EntityTree::_addMaterialToAvatarOperator = nullptr; -std::function EntityTree::_removeMaterialFromAvatarOperator = nullptr; -std::function EntityTree::_addMaterialToOverlayOperator = nullptr; -std::function EntityTree::_removeMaterialFromOverlayOperator = nullptr; +std::function EntityTree::_addMaterialToEntityOperator = nullptr; +std::function EntityTree::_removeMaterialFromEntityOperator = nullptr; +std::function EntityTree::_addMaterialToAvatarOperator = nullptr; +std::function EntityTree::_removeMaterialFromAvatarOperator = nullptr; +std::function EntityTree::_addMaterialToOverlayOperator = nullptr; +std::function EntityTree::_removeMaterialFromOverlayOperator = nullptr; -bool EntityTree::addMaterialToAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const QString& parentMaterialName) { +bool EntityTree::addMaterialToEntity(const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) { + if (_addMaterialToEntityOperator) { + return _addMaterialToEntityOperator(entityID, material, parentMaterialName); + } + return false; +} + +bool EntityTree::removeMaterialFromEntity(const QUuid& entityID, graphics::MaterialPointer material, const std::string& parentMaterialName) { + if (_removeMaterialFromEntityOperator) { + return _removeMaterialFromEntityOperator(entityID, material, parentMaterialName); + } + return false; +} + +bool EntityTree::addMaterialToAvatar(const QUuid& avatarID, graphics::MaterialLayer material, const std::string& parentMaterialName) { if (_addMaterialToAvatarOperator) { return _addMaterialToAvatarOperator(avatarID, material, parentMaterialName); } return false; } -bool EntityTree::removeMaterialFromAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const QString& parentMaterialName) { +bool EntityTree::removeMaterialFromAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const std::string& parentMaterialName) { if (_removeMaterialFromAvatarOperator) { return _removeMaterialFromAvatarOperator(avatarID, material, parentMaterialName); } return false; } -bool EntityTree::addMaterialToOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const QString& parentMaterialName) { +bool EntityTree::addMaterialToOverlay(const QUuid& overlayID, graphics::MaterialLayer material, const std::string& parentMaterialName) { if (_addMaterialToOverlayOperator) { return _addMaterialToOverlayOperator(overlayID, material, parentMaterialName); } return false; } -bool EntityTree::removeMaterialFromOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const QString& parentMaterialName) { +bool EntityTree::removeMaterialFromOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const std::string& parentMaterialName) { if (_removeMaterialFromOverlayOperator) { return _removeMaterialFromOverlayOperator(overlayID, material, parentMaterialName); } diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 568d552d8d..1dea98ded6 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -280,15 +280,20 @@ public: void setMyAvatar(std::shared_ptr myAvatar) { _myAvatar = myAvatar; } - static void setAddMaterialToAvatarOperator(std::function addMaterialToAvatarOperator) { _addMaterialToAvatarOperator = addMaterialToAvatarOperator; } - static void setRemoveMaterialFromAvatarOperator(std::function removeMaterialFromAvatarOperator) { _removeMaterialFromAvatarOperator = removeMaterialFromAvatarOperator; } - static bool addMaterialToAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const QString& parentMaterialName); - static bool removeMaterialFromAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const QString& parentMaterialName); + static void setAddMaterialToEntityOperator(std::function addMaterialToEntityOperator) { _addMaterialToEntityOperator = addMaterialToEntityOperator; } + static void setRemoveMaterialFromEntityOperator(std::function removeMaterialFromEntityOperator) { _removeMaterialFromEntityOperator = removeMaterialFromEntityOperator; } + static bool addMaterialToEntity(const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName); + static bool removeMaterialFromEntity(const QUuid& entityID, graphics::MaterialPointer material, const std::string& parentMaterialName); - static void setAddMaterialToOverlayOperator(std::function addMaterialToOverlayOperator) { _addMaterialToOverlayOperator = addMaterialToOverlayOperator; } - static void setRemoveMaterialFromOverlayOperator(std::function removeMaterialFromOverlayOperator) { _removeMaterialFromOverlayOperator = removeMaterialFromOverlayOperator; } - static bool addMaterialToOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const QString& parentMaterialName); - static bool removeMaterialFromOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const QString& parentMaterialName); + static void setAddMaterialToAvatarOperator(std::function addMaterialToAvatarOperator) { _addMaterialToAvatarOperator = addMaterialToAvatarOperator; } + static void setRemoveMaterialFromAvatarOperator(std::function removeMaterialFromAvatarOperator) { _removeMaterialFromAvatarOperator = removeMaterialFromAvatarOperator; } + static bool addMaterialToAvatar(const QUuid& avatarID, graphics::MaterialLayer material, const std::string& parentMaterialName); + static bool removeMaterialFromAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const std::string& parentMaterialName); + + static void setAddMaterialToOverlayOperator(std::function addMaterialToOverlayOperator) { _addMaterialToOverlayOperator = addMaterialToOverlayOperator; } + static void setRemoveMaterialFromOverlayOperator(std::function removeMaterialFromOverlayOperator) { _removeMaterialFromOverlayOperator = removeMaterialFromOverlayOperator; } + static bool addMaterialToOverlay(const QUuid& overlayID, graphics::MaterialLayer material, const std::string& parentMaterialName); + static bool removeMaterialFromOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const std::string& parentMaterialName); signals: void deletingEntity(const EntityItemID& entityID); @@ -398,10 +403,12 @@ private: std::shared_ptr _myAvatar{ nullptr }; - static std::function _addMaterialToAvatarOperator; - static std::function _removeMaterialFromAvatarOperator; - static std::function _addMaterialToOverlayOperator; - static std::function _removeMaterialFromOverlayOperator; + static std::function _addMaterialToEntityOperator; + static std::function _removeMaterialFromEntityOperator; + static std::function _addMaterialToAvatarOperator; + static std::function _removeMaterialFromAvatarOperator; + static std::function _addMaterialToOverlayOperator; + static std::function _removeMaterialFromOverlayOperator; }; #endif // hifi_EntityTree_h diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 980ed1c104..9e32bc3346 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -940,7 +940,6 @@ void EntityTreeElement::addEntityItem(EntityItemPointer entity) { }); bumpChangedContent(); entity->_element = getThisPointer(); - entity->postAdd(); } // will average a "common reduced LOD view" from the the child elements... diff --git a/libraries/entities/src/MaterialEntityItem.cpp b/libraries/entities/src/MaterialEntityItem.cpp index 91333e6e80..e3e3390449 100644 --- a/libraries/entities/src/MaterialEntityItem.cpp +++ b/libraries/entities/src/MaterialEntityItem.cpp @@ -27,6 +27,10 @@ MaterialEntityItem::MaterialEntityItem(const EntityItemID& entityItemID) : Entit _type = EntityTypes::Material; } +MaterialEntityItem::~MaterialEntityItem() { + removeMaterial(); +} + EntityItemProperties MaterialEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialURL, getMaterialURL); @@ -119,7 +123,7 @@ void MaterialEntityItem::debugDump() const { qCDebug(entities) << " MATERIAL EntityItem id:" << getEntityItemID() << "---------------------------------------------"; qCDebug(entities) << " name:" << _name; qCDebug(entities) << " material url:" << _materialURL; - qCDebug(entities) << " current material name:" << _currentMaterialName; + qCDebug(entities) << " current material name:" << _currentMaterialName.c_str(); qCDebug(entities) << " material mapping mode:" << _materialMappingMode; qCDebug(entities) << " priority:" << _priority; qCDebug(entities) << " parent material name:" << _parentMaterialName; @@ -139,7 +143,7 @@ void MaterialEntityItem::setUnscaledDimensions(const glm::vec3& value) { std::shared_ptr MaterialEntityItem::getMaterial() const { auto material = _parsedMaterials.networkMaterials.find(_currentMaterialName); if (material != _parsedMaterials.networkMaterials.end()) { - return material.value(); + return material->second; } else { return nullptr; } @@ -153,7 +157,7 @@ void MaterialEntityItem::setMaterialURL(const QString& materialURLString, bool u if (materialURLString.contains("?")) { auto split = materialURLString.split("?"); - _currentMaterialName = split.last(); + _currentMaterialName = split.last().toStdString(); } if (usingUserData) { @@ -183,9 +187,8 @@ void MaterialEntityItem::setMaterialURL(const QString& materialURLString, bool u } } -void MaterialEntityItem::setCurrentMaterialName(const QString& currentMaterialName) { - auto material = _parsedMaterials.networkMaterials.find(currentMaterialName); - if (material != _parsedMaterials.networkMaterials.end()) { +void MaterialEntityItem::setCurrentMaterialName(const std::string& currentMaterialName) { + if (_parsedMaterials.networkMaterials.find(currentMaterialName) != _parsedMaterials.networkMaterials.end()) { _currentMaterialName = currentMaterialName; } else if (_parsedMaterials.names.size() > 0) { _currentMaterialName = _parsedMaterials.names[0]; @@ -274,20 +277,15 @@ void MaterialEntityItem::removeMaterial() { } // Our parent could be an entity, an avatar, or an overlay - EntityTreePointer tree = getTree(); - if (tree) { - EntityItemPointer entity = tree->findEntityByEntityItemID(parentID); - if (entity) { - entity->removeMaterial(material, getParentMaterialName()); - return; - } - } - - if (EntityTree::removeMaterialFromAvatar(parentID, material, getParentMaterialName())) { + if (EntityTree::removeMaterialFromEntity(parentID, material, getParentMaterialName().toStdString())) { return; } - if (EntityTree::removeMaterialFromOverlay(parentID, material, getParentMaterialName())) { + if (EntityTree::removeMaterialFromAvatar(parentID, material, getParentMaterialName().toStdString())) { + return; + } + + if (EntityTree::removeMaterialFromOverlay(parentID, material, getParentMaterialName().toStdString())) { return; } @@ -306,23 +304,19 @@ void MaterialEntityItem::applyMaterial() { textureTransform.setRotation(glm::vec3(0, 0, glm::radians(_materialMappingRot))); textureTransform.setScale(glm::vec3(_materialMappingScale, 1)); material->setTextureTransforms(textureTransform); - material->setPriority(getPriority()); + + graphics::MaterialLayer materialLayer = graphics::MaterialLayer(material, getPriority()); // Our parent could be an entity, an avatar, or an overlay - EntityTreePointer tree = getTree(); - if (tree) { - EntityItemPointer entity = tree->findEntityByEntityItemID(parentID); - if (entity) { - entity->addMaterial(material, getParentMaterialName()); - return; - } - } - - if (EntityTree::addMaterialToAvatar(parentID, material, getParentMaterialName())) { + if (EntityTree::addMaterialToEntity(parentID, materialLayer, getParentMaterialName().toStdString())) { return; } - if (EntityTree::addMaterialToOverlay(parentID, material, getParentMaterialName())) { + if (EntityTree::addMaterialToAvatar(parentID, materialLayer, getParentMaterialName().toStdString())) { + return; + } + + if (EntityTree::addMaterialToOverlay(parentID, materialLayer, getParentMaterialName().toStdString())) { return; } @@ -330,20 +324,6 @@ void MaterialEntityItem::applyMaterial() { _retryApply = true; } -void MaterialEntityItem::postAdd() { - // postAdd is called every time we are added to a new octree cell, but we only need to update the material the first time - if (!_hasBeenAddedToOctree) { - removeMaterial(); - applyMaterial(); - _hasBeenAddedToOctree = true; - } -} - -void MaterialEntityItem::preDelete() { - EntityItem::preDelete(); - removeMaterial(); -} - void MaterialEntityItem::postParentFixup() { removeMaterial(); applyMaterial(); diff --git a/libraries/entities/src/MaterialEntityItem.h b/libraries/entities/src/MaterialEntityItem.h index 3c8df190bf..e203b707cc 100644 --- a/libraries/entities/src/MaterialEntityItem.h +++ b/libraries/entities/src/MaterialEntityItem.h @@ -21,6 +21,7 @@ public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); MaterialEntityItem(const EntityItemID& entityItemID); + ~MaterialEntityItem(); ALLOW_INSTANTIATION // This class can be instantiated @@ -55,8 +56,7 @@ public: QString getMaterialURL() const { return _materialURL; } void setMaterialURL(const QString& materialURLString, bool userDataChanged = false); - QString getCurrentMaterialName() const { return _currentMaterialName; } - void setCurrentMaterialName(const QString& currentMaterialName); + void setCurrentMaterialName(const std::string& currentMaterialName); MaterialMappingMode getMaterialMappingMode() const { return _materialMappingMode; } void setMaterialMappingMode(MaterialMappingMode mode) { _materialMappingMode = mode; } @@ -84,8 +84,6 @@ public: void applyMaterial(); void removeMaterial(); - void postAdd() override; - void preDelete() override; void postParentFixup() override; private: @@ -122,10 +120,9 @@ private: NetworkMaterialResourcePointer _networkMaterial; NetworkMaterialResource::ParsedMaterials _parsedMaterials; - QString _currentMaterialName; + std::string _currentMaterialName; bool _retryApply { false }; - bool _hasBeenAddedToOctree { false }; }; diff --git a/libraries/graphics/src/graphics/Material.cpp b/libraries/graphics/src/graphics/Material.cpp index 2bf58bc6fb..2300bc5098 100755 --- a/libraries/graphics/src/graphics/Material.cpp +++ b/libraries/graphics/src/graphics/Material.cpp @@ -17,8 +17,6 @@ using namespace graphics; using namespace gpu; -int materialPointerMetaID = qRegisterMetaType("graphics::MaterialPointer"); - Material::Material() : _key(0), _schemaBuffer(), diff --git a/libraries/graphics/src/graphics/Material.h b/libraries/graphics/src/graphics/Material.h index 4b1e7c82c1..632cf99391 100755 --- a/libraries/graphics/src/graphics/Material.h +++ b/libraries/graphics/src/graphics/Material.h @@ -354,17 +354,14 @@ public: size_t getTextureSize() const { calculateMaterialInfo(); return _textureSize; } bool hasTextureInfo() const { return _hasCalculatedTextureInfo; } - void setPriority(quint16 priority) { _priority = priority; } - quint16 getPriority() { return _priority; } - void setTextureTransforms(const Transform& transform); - const QString& getName() { return _name; } + const std::string& getName() { return _name; } - void setModel(const QString& model) { _model = model; } + void setModel(const std::string& model) { _model = model; } protected: - QString _name { "" }; + std::string _name { "" }; private: mutable MaterialKey _key; @@ -379,24 +376,36 @@ private: mutable bool _hasCalculatedTextureInfo { false }; bool calculateMaterialInfo() const; - quint16 _priority { 0 }; - - QString _model { "hifi_pbr" }; + std::string _model { "hifi_pbr" }; }; typedef std::shared_ptr< Material > MaterialPointer; -class MaterialCompare { +class MaterialLayer { public: - bool operator() (MaterialPointer left, MaterialPointer right) { - return left->getPriority() < right->getPriority(); + MaterialLayer(MaterialPointer material, quint16 priority) : material(material), priority(priority) {} + + MaterialPointer material { nullptr }; + quint16 priority { 0 }; +}; + +class MaterialLayerCompare { +public: + bool operator() (MaterialLayer left, MaterialLayer right) { + return left.priority < right.priority; } }; -class MultiMaterial : public std::priority_queue, MaterialCompare> { +class MultiMaterial : public std::priority_queue, MaterialLayerCompare> { public: bool remove(const MaterialPointer& value) { - auto it = std::find(c.begin(), c.end(), value); + auto it = c.begin(); + while (it != c.end()) { + if (it->material == value) { + break; + } + it++; + } if (it != c.end()) { c.erase(it); std::make_heap(c.begin(), c.end(), comp); @@ -409,6 +418,4 @@ public: }; -Q_DECLARE_METATYPE(graphics::MaterialPointer) - #endif diff --git a/libraries/model-networking/src/model-networking/MaterialCache.cpp b/libraries/model-networking/src/model-networking/MaterialCache.cpp index ef1d071d86..8d9d6571f8 100644 --- a/libraries/model-networking/src/model-networking/MaterialCache.cpp +++ b/libraries/model-networking/src/model-networking/MaterialCache.cpp @@ -73,22 +73,22 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater } } - return toReturn;; + return toReturn; } -std::pair> NetworkMaterialResource::parseJSONMaterial(const QJsonObject& materialJSON) { - QString name = ""; +std::pair> NetworkMaterialResource::parseJSONMaterial(const QJsonObject& materialJSON) { + std::string name = ""; std::shared_ptr material = std::make_shared(); for (auto& key : materialJSON.keys()) { if (key == "name") { auto nameJSON = materialJSON.value(key); if (nameJSON.isString()) { - name = nameJSON.toString(); + name = nameJSON.toString().toStdString(); } } else if (key == "model") { auto modelJSON = materialJSON.value(key); if (modelJSON.isString()) { - material->setModel(modelJSON.toString()); + material->setModel(modelJSON.toString().toStdString()); } } else if (key == "emissive") { glm::vec3 color; @@ -191,7 +191,7 @@ std::pair> NetworkMaterialResource::pa } } } - return std::pair>(name, material); + return std::pair>(name, material); } MaterialCache& MaterialCache::instance() { diff --git a/libraries/model-networking/src/model-networking/MaterialCache.h b/libraries/model-networking/src/model-networking/MaterialCache.h index 3188fd0094..468a12c677 100644 --- a/libraries/model-networking/src/model-networking/MaterialCache.h +++ b/libraries/model-networking/src/model-networking/MaterialCache.h @@ -24,8 +24,8 @@ public: typedef struct ParsedMaterials { uint version { 1 }; - std::vector names; - QHash> networkMaterials; + std::vector names; + std::unordered_map> networkMaterials; void reset() { version = 1; @@ -38,7 +38,7 @@ public: ParsedMaterials parsedMaterials; static ParsedMaterials parseJSONMaterials(const QJsonDocument& materialJSON); - static std::pair> parseJSONMaterial(const QJsonObject& materialJSON); + static std::pair> parseJSONMaterial(const QJsonObject& materialJSON); private: static bool parseJSONColor(const QJsonValue& array, glm::vec3& color, bool& isSRGB); diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 19ee05d1e2..d21e942581 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -619,7 +619,7 @@ NetworkMaterial::NetworkMaterial(const FBXMaterial& material, const QUrl& textur graphics::Material(*material._material), _textures(MapChannel::NUM_MAP_CHANNELS) { - _name = material.name; + _name = material.name.toStdString(); if (!material.albedoTexture.filename.isEmpty()) { auto map = fetchTextureMap(textureBaseUrl, material.albedoTexture, image::TextureUsage::ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP); _albedoTransform = material.albedoTexture.transform; diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index d24843c87d..c31cfed6f4 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -16,6 +16,8 @@ #include "DeferredLightingEffect.h" +#include "RenderPipelines.h" + using namespace render; namespace render { @@ -47,7 +49,7 @@ template <> void payloadRender(const MeshPartPayload::Pointer& payload, RenderAr MeshPartPayload::MeshPartPayload(const std::shared_ptr& mesh, int partIndex, graphics::MaterialPointer material) { updateMeshPart(mesh, partIndex); - addMaterial(material); + addMaterial(graphics::MaterialLayer(material, 0)); } void MeshPartPayload::updateMeshPart(const std::shared_ptr& drawMesh, int partIndex) { @@ -67,7 +69,7 @@ void MeshPartPayload::updateTransform(const Transform& transform, const Transfor _worldBound.transform(_drawTransform); } -void MeshPartPayload::addMaterial(graphics::MaterialPointer material) { +void MeshPartPayload::addMaterial(graphics::MaterialLayer material) { _drawMaterials.push(material); } @@ -89,8 +91,8 @@ void MeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits) builder.withLayered(); } - if (_drawMaterials.top()) { - auto matKey = _drawMaterials.top()->getKey(); + if (_drawMaterials.top().material) { + auto matKey = _drawMaterials.top().material->getKey(); if (matKey.isTranslucent()) { builder.withTransparent(); } @@ -109,8 +111,8 @@ Item::Bound MeshPartPayload::getBound() const { ShapeKey MeshPartPayload::getShapeKey() const { graphics::MaterialKey drawMaterialKey; - if (_drawMaterials.top()) { - drawMaterialKey = _drawMaterials.top()->getKey(); + if (_drawMaterials.top().material) { + drawMaterialKey = _drawMaterials.top().material->getKey(); } ShapeKey::Builder builder; @@ -164,7 +166,7 @@ void MeshPartPayload::render(RenderArgs* args) { bindMesh(batch); // apply material properties - args->_shapePipeline->bindMaterial(_drawMaterials.top(), batch, args->_enableTexturing); + RenderPipelines::bindMaterial(_drawMaterials.top().material, batch, args->_enableTexturing); args->_details._materialSwitches++; // Draw! @@ -256,7 +258,7 @@ void ModelMeshPartPayload::initCache(const ModelPointer& model) { auto networkMaterial = model->getGeometry()->getShapeMaterial(_shapeID); if (networkMaterial) { - addMaterial(networkMaterial); + addMaterial(graphics::MaterialLayer(networkMaterial, 0)); } } @@ -302,8 +304,8 @@ void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tag builder.withDeformed(); } - if (_drawMaterials.top()) { - auto matKey = _drawMaterials.top()->getKey(); + if (_drawMaterials.top().material) { + auto matKey = _drawMaterials.top().material->getKey(); if (matKey.isTranslucent()) { builder.withTransparent(); } @@ -333,8 +335,8 @@ void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, bool isWireframe } graphics::MaterialKey drawMaterialKey; - if (_drawMaterials.top()) { - drawMaterialKey = _drawMaterials.top()->getKey(); + if (_drawMaterials.top().material) { + drawMaterialKey = _drawMaterials.top().material->getKey(); } bool isTranslucent = drawMaterialKey.isTranslucent(); @@ -415,7 +417,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) { bindMesh(batch); // apply material properties - args->_shapePipeline->bindMaterial(_drawMaterials.top(), batch, args->_enableTexturing); + RenderPipelines::bindMaterial(_drawMaterials.top().material, batch, args->_enableTexturing); args->_details._materialSwitches++; // Draw! diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index e6893f1ea7..78ce12d695 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -65,11 +65,11 @@ public: graphics::Mesh::Part _drawPart; size_t getVerticesCount() const { return _drawMesh ? _drawMesh->getNumVertices() : 0; } - size_t getMaterialTextureSize() { return _drawMaterials.top() ? _drawMaterials.top()->getTextureSize() : 0; } - int getMaterialTextureCount() { return _drawMaterials.top() ? _drawMaterials.top()->getTextureCount() : 0; } - bool hasTextureInfo() const { return _drawMaterials.top() ? _drawMaterials.top()->hasTextureInfo() : false; } + size_t getMaterialTextureSize() { return _drawMaterials.top().material ? _drawMaterials.top().material->getTextureSize() : 0; } + int getMaterialTextureCount() { return _drawMaterials.top().material ? _drawMaterials.top().material->getTextureCount() : 0; } + bool hasTextureInfo() const { return _drawMaterials.top().material ? _drawMaterials.top().material->hasTextureInfo() : false; } - void addMaterial(graphics::MaterialPointer material); + void addMaterial(graphics::MaterialLayer material); void removeMaterial(graphics::MaterialPointer material); protected: diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 15cab065f3..3cc99c869f 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1535,7 +1535,7 @@ std::vector Model::getMeshIDsFromMaterialID(QString parentMaterial if (parentMaterialName.startsWith(MATERIAL_NAME_PREFIX)) { parentMaterialName.replace(0, MATERIAL_NAME_PREFIX.size(), QString("")); for (unsigned int i = 0; i < (unsigned int)_modelMeshMaterialNames.size(); i++) { - if (_modelMeshMaterialNames[i] == parentMaterialName) { + if (_modelMeshMaterialNames[i] == parentMaterialName.toStdString()) { toReturn.push_back(i); } } @@ -1548,8 +1548,8 @@ std::vector Model::getMeshIDsFromMaterialID(QString parentMaterial return toReturn; } -void Model::addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { - std::vector shapeIDs = getMeshIDsFromMaterialID(parentMaterialName); +void Model::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { + std::vector shapeIDs = getMeshIDsFromMaterialID(QString(parentMaterialName.c_str())); render::Transaction transaction; for (auto shapeID : shapeIDs) { if (shapeID < _modelMeshRenderItemIDs.size()) { @@ -1573,8 +1573,8 @@ void Model::addMaterial(graphics::MaterialPointer material, const QString& paren AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction); } -void Model::removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName) { - std::vector shapeIDs = getMeshIDsFromMaterialID(parentMaterialName); +void Model::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { + std::vector shapeIDs = getMeshIDsFromMaterialID(QString(parentMaterialName.c_str())); render::Transaction transaction; for (auto shapeID : shapeIDs) { if (shapeID < _modelMeshRenderItemIDs.size()) { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 6b59b62046..63e9aacb3d 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -318,8 +318,8 @@ public: void scaleToFit(); - void addMaterial(graphics::MaterialPointer material, const QString& parentMaterialName); - void removeMaterial(graphics::MaterialPointer material, const QString& parentMaterialName); + void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName); + void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName); public slots: void loadURLFinished(bool success); @@ -438,7 +438,7 @@ protected: render::ItemIDs _modelMeshRenderItemIDs; using ShapeInfo = struct { int meshIndex; }; std::vector _modelMeshRenderItemShapes; - std::vector _modelMeshMaterialNames; + std::vector _modelMeshMaterialNames; bool _addedToScene { false }; // has been added to scene bool _needsFixupInScene { true }; // needs to be removed/re-added to scene diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index ad7409b731..3c80a2d14c 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -618,3 +618,125 @@ void initZPassPipelines(ShapePlumber& shapePlumber, gpu::StatePointer state) { ShapeKey::Filter::Builder().withSkinned().withFade(), skinFadeProgram, state); } + +#include "RenderPipelines.h" +#include + +void RenderPipelines::bindMaterial(graphics::MaterialPointer material, gpu::Batch& batch, bool enableTextures) { + if (!material) { + return; + } + + auto textureCache = DependencyManager::get(); + + batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::MATERIAL, material->getSchemaBuffer()); + batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::TEXMAPARRAY, material->getTexMapArrayBuffer()); + + const auto& materialKey = material->getKey(); + const auto& textureMaps = material->getTextureMaps(); + + int numUnlit = 0; + if (materialKey.isUnlit()) { + numUnlit++; + } + + if (!enableTextures) { + batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getWhiteTexture()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, textureCache->getBlueTexture()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, textureCache->getBlackTexture()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, textureCache->getWhiteTexture()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, textureCache->getWhiteTexture()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture()); + return; + } + + // Albedo + if (materialKey.isAlbedoMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::ALBEDO_MAP); + if (itr != textureMaps.end() && itr->second->isDefined()) { + batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, itr->second->getTextureView()); + } else { + batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getGrayTexture()); + } + } + + // Roughness map + if (materialKey.isRoughnessMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::ROUGHNESS_MAP); + if (itr != textureMaps.end() && itr->second->isDefined()) { + batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, itr->second->getTextureView()); + + // texcoord are assumed to be the same has albedo + } else { + batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture()); + } + } + + // Normal map + if (materialKey.isNormalMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::NORMAL_MAP); + if (itr != textureMaps.end() && itr->second->isDefined()) { + batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, itr->second->getTextureView()); + + // texcoord are assumed to be the same has albedo + } else { + batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, textureCache->getBlueTexture()); + } + } + + // Metallic map + if (materialKey.isMetallicMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::METALLIC_MAP); + if (itr != textureMaps.end() && itr->second->isDefined()) { + batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, itr->second->getTextureView()); + + // texcoord are assumed to be the same has albedo + } else { + batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, textureCache->getBlackTexture()); + } + } + + // Occlusion map + if (materialKey.isOcclusionMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::OCCLUSION_MAP); + if (itr != textureMaps.end() && itr->second->isDefined()) { + batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, itr->second->getTextureView()); + + // texcoord are assumed to be the same has albedo + } else { + batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, textureCache->getWhiteTexture()); + } + } + + // Scattering map + if (materialKey.isScatteringMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::SCATTERING_MAP); + if (itr != textureMaps.end() && itr->second->isDefined()) { + batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, itr->second->getTextureView()); + + // texcoord are assumed to be the same has albedo + } else { + batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, textureCache->getWhiteTexture()); + } + } + + // Emissive / Lightmap + if (materialKey.isLightmapMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::LIGHTMAP_MAP); + + if (itr != textureMaps.end() && itr->second->isDefined()) { + batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, itr->second->getTextureView()); + } else { + batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getGrayTexture()); + } + } else if (materialKey.isEmissiveMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::EMISSIVE_MAP); + + if (itr != textureMaps.end() && itr->second->isDefined()) { + batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, itr->second->getTextureView()); + } else { + batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture()); + } + } +} diff --git a/libraries/render-utils/src/RenderPipelines.h b/libraries/render-utils/src/RenderPipelines.h new file mode 100644 index 0000000000..9b9ad2c001 --- /dev/null +++ b/libraries/render-utils/src/RenderPipelines.h @@ -0,0 +1,22 @@ +// +// RenderPipelines.h +// render-utils/src/ +// +// Created by Sam Gondelman on 2/15/18 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_RenderPipelines_h +#define hifi_RenderPipelines_h + +#include + +class RenderPipelines { +public: + static void bindMaterial(graphics::MaterialPointer material, gpu::Batch& batch, bool enableTextures); +}; + + +#endif // hifi_RenderPipelines_h diff --git a/libraries/render/CMakeLists.txt b/libraries/render/CMakeLists.txt index b0136028f6..2a888a5b18 100644 --- a/libraries/render/CMakeLists.txt +++ b/libraries/render/CMakeLists.txt @@ -2,9 +2,7 @@ set(TARGET_NAME render) AUTOSCRIBE_SHADER_LIB(gpu graphics) setup_hifi_library() -# render needs octree only for getAccuracyAngle(float, int), and model-networking for TextureCache -link_hifi_libraries(shared task ktx gpu graphics octree model-networking) -include_hifi_library_headers(networking) -include_hifi_library_headers(image) +# render needs octree only for getAccuracyAngle(float, int) +link_hifi_libraries(shared task ktx gpu graphics octree) target_nsight() diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp index 1e8c26c7f6..92e22d86f6 100644 --- a/libraries/render/src/render/ShapePipeline.cpp +++ b/libraries/render/src/render/ShapePipeline.cpp @@ -15,8 +15,6 @@ #include -#include - using namespace render; ShapePipeline::CustomFactoryMap ShapePipeline::_globalCustomFactoryMap; @@ -181,123 +179,4 @@ const ShapePipelinePointer ShapePlumber::pickPipeline(RenderArgs* args, const Ke } return shapePipeline; -} - -void ShapePipeline::bindMaterial(graphics::MaterialPointer material, gpu::Batch& batch, bool enableTextures) const { - if (!material) { - return; - } - - auto textureCache = DependencyManager::get(); - - batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::MATERIAL, material->getSchemaBuffer()); - batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::TEXMAPARRAY, material->getTexMapArrayBuffer()); - - const auto& materialKey = material->getKey(); - const auto& textureMaps = material->getTextureMaps(); - - int numUnlit = 0; - if (materialKey.isUnlit()) { - numUnlit++; - } - - if (!enableTextures) { - batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getWhiteTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, textureCache->getBlueTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, textureCache->getBlackTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, textureCache->getWhiteTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, textureCache->getWhiteTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture()); - return; - } - - // Albedo - if (materialKey.isAlbedoMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::ALBEDO_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, itr->second->getTextureView()); - } else { - batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getGrayTexture()); - } - } - - // Roughness map - if (materialKey.isRoughnessMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::ROUGHNESS_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, itr->second->getTextureView()); - - // texcoord are assumed to be the same has albedo - } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture()); - } - } - - // Normal map - if (materialKey.isNormalMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::NORMAL_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, itr->second->getTextureView()); - - // texcoord are assumed to be the same has albedo - } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, textureCache->getBlueTexture()); - } - } - - // Metallic map - if (materialKey.isMetallicMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::METALLIC_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, itr->second->getTextureView()); - - // texcoord are assumed to be the same has albedo - } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, textureCache->getBlackTexture()); - } - } - - // Occlusion map - if (materialKey.isOcclusionMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::OCCLUSION_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, itr->second->getTextureView()); - - // texcoord are assumed to be the same has albedo - } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, textureCache->getWhiteTexture()); - } - } - - // Scattering map - if (materialKey.isScatteringMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::SCATTERING_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, itr->second->getTextureView()); - - // texcoord are assumed to be the same has albedo - } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, textureCache->getWhiteTexture()); - } - } - - // Emissive / Lightmap - if (materialKey.isLightmapMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::LIGHTMAP_MAP); - - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, itr->second->getTextureView()); - } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getGrayTexture()); - } - } else if (materialKey.isEmissiveMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::EMISSIVE_MAP); - - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, itr->second->getTextureView()); - } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture()); - } - } -} +} \ No newline at end of file diff --git a/libraries/render/src/render/ShapePipeline.h b/libraries/render/src/render/ShapePipeline.h index af485cc298..1dd9f5da49 100644 --- a/libraries/render/src/render/ShapePipeline.h +++ b/libraries/render/src/render/ShapePipeline.h @@ -18,8 +18,6 @@ #include "Args.h" -#include - namespace render { class Item; class ShapePlumber; @@ -305,8 +303,6 @@ public: void prepareShapeItem(Args* args, const ShapeKey& key, const Item& shape); - void bindMaterial(graphics::MaterialPointer material, gpu::Batch& batch, bool enableTextures) const; - protected: friend class ShapePlumber; From 175532c444ea0dfd8b807c9e1ce91b72c85529cc Mon Sep 17 00:00:00 2001 From: Liv Erickson Date: Thu, 15 Feb 2018 15:51:54 -0800 Subject: [PATCH 303/569] show outline for zones when rotating --- scripts/system/libraries/entitySelectionTool.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 24ec12a2c8..44325d4a12 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1018,6 +1018,7 @@ SelectionDisplay = (function() { return; } + if (SelectionManager.hasSelection()) { var position = SelectionManager.worldPosition; var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation; @@ -1317,8 +1318,12 @@ SelectionDisplay = (function() { isActiveTool(handleScaleRTNCube) || isActiveTool(handleScaleLTFCube) || isActiveTool(handleScaleRTFCube) || isActiveTool(handleStretchXSphere) || isActiveTool(handleStretchYSphere) || isActiveTool(handleStretchZSphere)); - that.setHandleScaleEdgeVisible(!isActiveTool(handleRotatePitchRing) && !isActiveTool(handleRotateYawRing) && - !isActiveTool(handleRotateRollRing)); + + var showOutlineForZone = (SelectionManager.selections.length === 1 && + SelectionManager.savedProperties[SelectionManager.selections[0]].type === "Zone"); + that.setHandleScaleEdgeVisible(showOutlineForZone || (!isActiveTool(handleRotatePitchRing) && + !isActiveTool(handleRotateYawRing) && + !isActiveTool(handleRotateRollRing))); //keep cloner always hidden for now since you can hold Alt to clone while //translating an entity - we may bring cloner back for HMD only later From e06c95f5863d3aeb67f1c18d624acc25743e605f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 15 Feb 2018 15:51:49 -0800 Subject: [PATCH 304/569] make settings manager methods used for backup/restore thread safe --- .../src/DomainServerSettingsManager.cpp | 24 +++++++++++++++++++ .../src/DomainServerSettingsManager.h | 6 +++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 8febbd5769..5e3c8e9cee 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -1196,6 +1197,16 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection } bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType) { + + if (thread() != QThread::currentThread()) { + bool success; + QMetaObject::invokeMethod(this, "restoreSettingsFromObject", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, success), + Q_ARG(QJsonObject, settingsToRestore), + Q_ARG(SettingsType, settingsType)); + return success; + } + QJsonArray& filteredDescriptionArray = settingsType == DomainSettings ? _domainSettingsDescription : _contentSettingsDescription; @@ -1321,6 +1332,19 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt bool includeDefaults, bool isForBackup) { QJsonObject responseObject; + if (thread() != QThread::currentThread()) { + QMetaObject::invokeMethod(this, "settingsResponseObjectForType", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QJsonObject, responseObject), + Q_ARG(const QString&, typeValue), + Q_ARG(bool, isAuthenticated), + Q_ARG(bool, includeDomainSettings), + Q_ARG(bool, includeContentSettings), + Q_ARG(bool, includeDefaults), + Q_ARG(bool, isForBackup)); + + return responseObject; + } + if (!typeValue.isEmpty() || isAuthenticated) { // convert the string type value to a QJsonValue QJsonValue queryType = typeValue.isEmpty() ? QJsonValue() : QJsonValue(typeValue.toInt()); diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 4316534685..abc70751a8 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -111,10 +111,12 @@ public: void debugDumpGroupsState(); - QJsonObject settingsResponseObjectForType(const QString& typeValue, bool isAuthenticated = false, + /// thread safe method to retrieve a JSON representation of settings + Q_INVOKABLE QJsonObject settingsResponseObjectForType(const QString& typeValue, bool isAuthenticated = false, bool includeDomainSettings = true, bool includeContentSettings = true, bool includeDefaults = true, bool isForBackup = false); - bool restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType); + /// thread safe method to restore settings from a JSON object + Q_INVOKABLE bool restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType); signals: void updateNodePermissions(); From e71f2fa38788c8f74f5ede92749eab7f296a5743 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 15 Feb 2018 16:03:03 -0800 Subject: [PATCH 305/569] use QtHelpers macro for blocking invoke --- .../src/DomainServerSettingsManager.cpp | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 5e3c8e9cee..5f71890898 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -33,6 +33,8 @@ #include #include //for KillAvatarReason #include +#include + #include "DomainServerNodeData.h" const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json"; @@ -1200,10 +1202,11 @@ bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settings if (thread() != QThread::currentThread()) { bool success; - QMetaObject::invokeMethod(this, "restoreSettingsFromObject", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(bool, success), - Q_ARG(QJsonObject, settingsToRestore), - Q_ARG(SettingsType, settingsType)); + + BLOCKING_INVOKE_METHOD(this, "restoreSettingsFromObject", + Q_RETURN_ARG(bool, success), + Q_ARG(QJsonObject, settingsToRestore), + Q_ARG(SettingsType, settingsType)); return success; } @@ -1333,14 +1336,15 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt QJsonObject responseObject; if (thread() != QThread::currentThread()) { - QMetaObject::invokeMethod(this, "settingsResponseObjectForType", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QJsonObject, responseObject), - Q_ARG(const QString&, typeValue), - Q_ARG(bool, isAuthenticated), - Q_ARG(bool, includeDomainSettings), - Q_ARG(bool, includeContentSettings), - Q_ARG(bool, includeDefaults), - Q_ARG(bool, isForBackup)); + + BLOCKING_INVOKE_METHOD(this, "settingsResponseObjectForType", + Q_RETURN_ARG(QJsonObject, responseObject), + Q_ARG(const QString&, typeValue), + Q_ARG(bool, isAuthenticated), + Q_ARG(bool, includeDomainSettings), + Q_ARG(bool, includeContentSettings), + Q_ARG(bool, includeDefaults), + Q_ARG(bool, isForBackup)); return responseObject; } From c4e9c762582b027553d4113e72dcabafb9cb69f5 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 15 Feb 2018 15:14:54 -0800 Subject: [PATCH 306/569] Small UIT tweaks --- interface/resources/qml/controls-uit/TextField.qml | 5 ++--- .../qml/hifi/commerce/purchases/PurchasedItem.qml | 11 ++++++----- .../qml/hifi/commerce/purchases/Purchases.qml | 10 ++++++++++ interface/resources/qml/styles-uit/HifiConstants.qml | 2 +- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/interface/resources/qml/controls-uit/TextField.qml b/interface/resources/qml/controls-uit/TextField.qml index a21d1f8efd..3fc5d83129 100644 --- a/interface/resources/qml/controls-uit/TextField.qml +++ b/interface/resources/qml/controls-uit/TextField.qml @@ -34,11 +34,10 @@ TextField { placeholderText: textField.placeholderText - FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; } + FontLoader { id: firaSansRegular; source: "../../fonts/FiraSans-Regular.ttf"; } FontLoader { id: hifiGlyphs; source: "../../fonts/hifi-glyphs.ttf"; } - font.family: firaSansSemiBold.name + font.family: firaSansRegular.name font.pixelSize: hifi.fontSizes.textFieldInput - font.italic: textField.text == "" height: implicitHeight + 3 // Make surrounding box higher so that highlight is vertically centered. property alias textFieldLabel: textFieldLabel diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 50ac34cd51..272c542fde 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -46,6 +46,7 @@ Item { property var buttonGlyph: [hifi.glyphs.wand, hifi.glyphs.hat, hifi.glyphs.globe, hifi.glyphs.install, hifi.glyphs.avatar]; property bool showConfirmation: false; property bool hasPermissionToRezThis; + property bool permissionExplanationCardVisible; property string originalStatusText; property string originalStatusColor; @@ -151,7 +152,7 @@ Item { anchors.topMargin: 4; anchors.left: itemPreviewImage.right; anchors.leftMargin: 8; - width: root.hasPermissionToRezThis ? (buttonContainer.x - itemPreviewImage.x - itemPreviewImage.width - anchors.leftMargin) : + width: !noPermissionGlyph.visible ? (buttonContainer.x - itemPreviewImage.x - itemPreviewImage.width - anchors.leftMargin) : Math.min(itemNameTextMetrics.tightBoundingRect.width + 2, buttonContainer.x - itemPreviewImage.x - itemPreviewImage.width - anchors.leftMargin - noPermissionGlyph.width + 2); height: paintedHeight; @@ -181,7 +182,7 @@ Item { } HiFiGlyphs { id: noPermissionGlyph; - visible: !root.hasPermissionToRezThis; + visible: true// !root.hasPermissionToRezThis; anchors.verticalCenter: itemName.verticalCenter; anchors.left: itemName.right; anchors.leftMargin: itemName.truncated ? -14 : -2; @@ -203,14 +204,14 @@ Item { noPermissionGlyph.color = hifi.colors.redAccent; } onClicked: { - permissionExplanationCard.visible = true; + root.sendToPurchases({ method: 'openPermissionExplanationCard' }); } } } Rectangle { id: permissionExplanationCard; z: 995; - visible: false; + visible: root.permissionExplanationCardVisible; anchors.fill: parent; color: hifi.colors.white; @@ -261,7 +262,7 @@ Item { parent.text = hifi.glyphs.close; } onClicked: { - permissionExplanationCard.visible = false; + root.sendToPurchases({ method: 'openPermissionExplanationCard', closeAll: true }); } } } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 5593883e00..b01dfcfce3 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -393,6 +393,7 @@ Rectangle { numberSold: model.number_sold; limitedRun: model.limited_run; displayedItemCount: model.displayedItemCount; + permissionExplanationCardVisible: model.permissionExplanationCardVisible; itemType: { if (model.root_file_url.indexOf(".fst") > -1) { "avatar"; @@ -472,6 +473,14 @@ Rectangle { lightboxPopup.visible = true; } else if (msg.method === "setFilterText") { filterBar.text = msg.filterText; + } else if (msg.method === "openPermissionExplanationCard") { + for (var i = 0; i < filteredPurchasesModel.count; i++) { + if (i !== index || msg.closeAll) { + filteredPurchasesModel.setProperty(i, "permissionExplanationCardVisible", false); + } else { + filteredPurchasesModel.setProperty(i, "permissionExplanationCardVisible", true); + } + } } } } @@ -673,6 +682,7 @@ Rectangle { filteredPurchasesModel.clear(); for (var i = 0; i < tempPurchasesModel.count; i++) { filteredPurchasesModel.append(tempPurchasesModel.get(i)); + filteredPurchasesModel.setProperty(i, 'permissionExplanationCardVisible', false); } populateDisplayedItemCounts(); diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index 16b74f6b54..43de8333af 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -218,7 +218,7 @@ Item { readonly property var colorStart: [ colors.white, colors.primaryHighlight, "#d42043", "#343434", Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0) ] readonly property var colorFinish: [ colors.lightGrayText, colors.blueAccent, "#94132e", colors.black, Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0) ] readonly property var hoveredColor: [ colorStart[white], colorStart[blue], colorStart[red], colorFinish[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ] - readonly property var pressedColor: [ colorFinish[white], colorFinish[blue], colorFinish[red], colorStart[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ] + readonly property var pressedColor: [ colorFinish[white], colorFinish[blue], colorFinish[red], colorStart[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colors.lightGrayText ] readonly property var focusedColor: [ colors.lightGray50, colors.blueAccent, colors.redAccent, colors.darkGray, colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ] readonly property var disabledColorStart: [ colorStart[white], colors.baseGrayHighlight] readonly property var disabledColorFinish: [ colorFinish[white], colors.baseGrayShadow] From 9e99c5c744ea7621b37e4c0b7e36d84906ea82f4 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 15 Feb 2018 11:49:05 -0800 Subject: [PATCH 307/569] Add restart of ES during backup recovery --- domain-server/src/DomainServer.cpp | 2 ++ domain-server/src/EntitiesBackupHandler.cpp | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 599f09ae94..2f8d8f6d03 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1785,6 +1785,8 @@ QString DomainServer::getEntitiesReplacementFilePath() { void DomainServer::processOctreeDataRequestMessage(QSharedPointer message) { qDebug() << "Got request for octree data from " << message->getSenderSockAddr(); + maybeHandleReplacementEntityFile(); + bool remoteHasExistingData { false }; QUuid id; int version; diff --git a/domain-server/src/EntitiesBackupHandler.cpp b/domain-server/src/EntitiesBackupHandler.cpp index 6ad00d01c8..deb92ee0f6 100644 --- a/domain-server/src/EntitiesBackupHandler.cpp +++ b/domain-server/src/EntitiesBackupHandler.cpp @@ -16,6 +16,7 @@ #include #include +#include #include EntitiesBackupHandler::EntitiesBackupHandler(QString entitiesFilePath, QString entitiesReplacementFilePath) : @@ -71,5 +72,13 @@ void EntitiesBackupHandler::recoverBackup(QuaZip& zip) { if (entitiesFile.open(QIODevice::WriteOnly)) { entitiesFile.write(data.toGzippedByteArray()); + entitiesFile.close(); + + auto nodeList = DependencyManager::get(); + nodeList->eachMatchingNode([](const SharedNodePointer& otherNode) -> bool { + return otherNode->getType() == NodeType::EntityServer; + }, [nodeList](const SharedNodePointer& otherNode) { + QMetaObject::invokeMethod(nodeList.data(), "killNodeWithUUID", Q_ARG(const QUuid&, otherNode->getUUID())); + }); } } From dd0b8a0c2fd3a22a82857c06e6027c56735222be Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 15 Feb 2018 14:17:32 -0800 Subject: [PATCH 308/569] Add backup download API to DS --- .../src/DomainContentBackupManager.cpp | 78 +++++++++++++------ .../src/DomainContentBackupManager.h | 2 +- domain-server/src/DomainServer.cpp | 22 ++++++ 3 files changed, 76 insertions(+), 26 deletions(-) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index c68ff0c6ea..f56c41dacd 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -383,32 +383,60 @@ void DomainContentBackupManager::backup() { } } -void DomainContentBackupManager::consolidate(QString fileName) { - QDir backupDir { _backupDirectory }; - if (backupDir.exists()) { - auto filePath = backupDir.absoluteFilePath(fileName); - - auto copyFilePath = QDir::tempPath() + "/" + fileName; - - auto copySuccess = QFile::copy(filePath, copyFilePath); - if (!copySuccess) { - qCritical() << "Failed to create full backup."; - return; - } - - QuaZip zip(copyFilePath); - if (!zip.open(QuaZip::mdAdd)) { - qCritical() << "Could not open backup archive:" << filePath; - qCritical() << " ERROR:" << zip.getZipError(); - return; - } - - for (auto& handler : _backupHandlers) { - handler->consolidateBackup(zip); - } - - zip.close(); +void DomainContentBackupManager::consolidateBackup(MiniPromise::Promise promise, QString fileName) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "consolidateBackup", Q_ARG(MiniPromise::Promise, promise), + Q_ARG(const QString&, fileName)); + return; } + + QDir backupDir { _backupDirectory }; + if (!backupDir.exists()) { + qCritical() << "Backup directory does not exist, bailing consolidation of backup"; + promise->resolve({ { "success", false } }); + return; + } + + auto filePath = backupDir.absoluteFilePath(fileName); + + auto copyFilePath = QDir::tempPath() + "/" + fileName; + + { + QFile copyFile(copyFilePath); + copyFile.remove(); + copyFile.close(); + } + auto copySuccess = QFile::copy(filePath, copyFilePath); + if (!copySuccess) { + qCritical() << "Failed to create copy of backup."; + promise->resolve({ { "success", false } }); + return; + } + + QuaZip zip(copyFilePath); + if (!zip.open(QuaZip::mdAdd)) { + qCritical() << "Could not open backup archive:" << filePath; + qCritical() << " ERROR:" << zip.getZipError(); + promise->resolve({ { "success", false } }); + return; + } + + for (auto& handler : _backupHandlers) { + handler->consolidateBackup(zip); + } + + zip.close(); + + if (zip.getZipError() != UNZ_OK) { + qCritical() << "Failed to consolidate backup: " << zip.getZipError(); + promise->resolve({ { "success", false } }); + return; + } + + promise->resolve({ + { "success", true }, + { "backupFilePath", copyFilePath } + }); } void DomainContentBackupManager::createManualBackup(MiniPromise::Promise promise, const QString& name) { diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h index 1e1b2360a8..9ec7eb9950 100644 --- a/domain-server/src/DomainContentBackupManager.h +++ b/domain-server/src/DomainContentBackupManager.h @@ -62,6 +62,7 @@ public slots: void createManualBackup(MiniPromise::Promise promise, const QString& name); void recoverFromBackup(MiniPromise::Promise promise, const QString& backupName); void deleteBackup(MiniPromise::Promise promise, const QString& backupName); + void consolidateBackup(MiniPromise::Promise promise, QString fileName); signals: void loadCompleted(); @@ -73,7 +74,6 @@ protected: void load(); void backup(); - void consolidate(QString fileName); void removeOldBackupVersions(const BackupRule& rule); bool getMostRecentBackup(const QString& format, QString& mostRecentBackupFileName, QDateTime& mostRecentBackupTime); int64_t getMostRecentBackupTimeInSecs(const QString& format); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 2f8d8f6d03..b91e12a9cf 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2149,6 +2149,28 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QJsonDocument docJSON(rootJSON); connection->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); + return true; + } else if (url.path().startsWith(URI_API_BACKUPS_ID)) { + auto id = url.path().mid(QString(URI_API_BACKUPS_ID).length()); + auto deferred = makePromise("consolidateBackup"); + deferred->then([connection, JSON_MIME_TYPE](QString error, QVariantMap result) { + QJsonObject rootJSON; + auto success = result["success"].toBool(); + if (success) { + auto path = result["backupFilePath"].toString(); + auto file { std::unique_ptr(new QFile(path)) }; + if (file->open(QIODevice::ReadOnly)) { + connection->respond(HTTPConnection::StatusCode200, std::move(file)); + } else { + qCritical(domain_server) << "Unable to load consolidated backup at:" << path << result; + connection->respond(HTTPConnection::StatusCode500, "Error opening backup"); + } + } else { + connection->respond(HTTPConnection::StatusCode400); + } + }); + _contentManager->consolidateBackup(deferred, id); + return true; } else if (url.path() == URI_RESTART) { connection->respond(HTTPConnection::StatusCode200); From 2942a53a1d51b488d7e8818e180c25abb335f0a2 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 15 Feb 2018 15:55:14 -0800 Subject: [PATCH 309/569] Add recovery mode and full backup information to DS --- .../src/DomainContentBackupManager.cpp | 91 +++++++++++++++++-- .../src/DomainContentBackupManager.h | 14 +-- domain-server/src/DomainServer.cpp | 28 ++---- 3 files changed, 96 insertions(+), 37 deletions(-) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index f56c41dacd..54ea7e23d5 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -147,7 +147,24 @@ bool DomainContentBackupManager::process() { if (sinceLastSave > intervalToCheck) { _lastCheck = now; - backup(); + if (_isRecovering) { + bool anyHandlerIsRecovering { false }; + for (auto& handler : _backupHandlers) { + bool handlerIsRecovering { false }; + float progress { 0.0f }; + //std::tie = handler->getRecoveryStatus(); + if (handlerIsRecovering) { + anyHandlerIsRecovering = true; + emit recoveryCompleted(); + break; + } + } + _isRecovering = anyHandlerIsRecovering; + } + + if (!_isRecovering) { + backup(); + } } } @@ -213,6 +230,13 @@ void DomainContentBackupManager::deleteBackup(MiniPromise::Promise promise, cons return; } + if (_isRecovering && backupName == _recoveryFilename) { + promise->resolve({ + { "success", false } + }); + return; + } + QDir backupDir { _backupDirectory }; QFile backupFile { backupDir.filePath(backupName) }; auto success = backupFile.remove(); @@ -222,6 +246,13 @@ void DomainContentBackupManager::deleteBackup(MiniPromise::Promise promise, cons } void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise, const QString& backupName) { + if (_isRecovering) { + promise->resolve({ + { "success", false } + }); + return; + }; + if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "recoverFromBackup", Q_ARG(MiniPromise::Promise, promise), Q_ARG(const QString&, backupName)); @@ -239,11 +270,12 @@ void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise, qWarning() << "Failed to unzip file: " << backupName; success = false; } else { + _isRecovering = true; for (auto& handler : _backupHandlers) { handler->recoverBackup(zip); } - qDebug() << "Successfully recovered from " << backupName; + qDebug() << "Successfully started recovering from " << backupName; success = true; } backupFile.close(); @@ -257,8 +289,11 @@ void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise, }); } -std::vector DomainContentBackupManager::getAllBackups() { - std::vector backups; +void DomainContentBackupManager::getAllBackupInformation(MiniPromise::Promise promise) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "getAllBackupInformation", Q_ARG(MiniPromise::Promise, promise)); + return; + } QDir backupDir { _backupDirectory }; auto matchingFiles = @@ -269,6 +304,8 @@ std::vector DomainContentBackupManager::getAllBackups() { QString dateTimeFormat = "(" + DATETIME_FORMAT_RE + ")"; QRegExp backupNameFormat { prefixFormat + nameFormat + "-" + dateTimeFormat + "\\.zip" }; + QVariantList backups; + for (const auto& fileInfo : matchingFiles) { auto fileName = fileInfo.fileName(); if (backupNameFormat.exactMatch(fileName)) { @@ -280,12 +317,50 @@ std::vector DomainContentBackupManager::getAllBackups() { continue; } - BackupItemInfo backup { fileInfo.fileName(), name, fileInfo.absoluteFilePath(), createdAt, type == MANUAL_BACKUP_PREFIX }; - backups.push_back(backup); + bool isAvailable { true }; + float availabilityProgress { 0.0f }; + for (auto& handler : _backupHandlers) { + bool handlerIsAvailable { false }; + float progress { 0.0f }; + //std::tie = handler->isAvailable(); + //isAvailable = isAvailable && !handlerIsAvailable); + //availabilityProgress += progress / _backupHandlers.size(); + } + + backups.push_back(QVariantMap({ + { "id", fileInfo.fileName() }, + { "name", name }, + { "createdAtMillis", createdAt.toMSecsSinceEpoch() }, + { "isAvailable", isAvailable }, + { "availabilityProgress", availabilityProgress }, + { "isManualBackup", type == MANUAL_BACKUP_PREFIX } + })); } } - return backups; + float recoveryProgress = 0.0f; + bool isRecovering = _isRecovering.load(); + if (_isRecovering) { + for (auto& handler : _backupHandlers) { + bool handlerIsRecovering { false }; + float progress { 0.0f }; + //std::tie = handler->getRecoveryStatus(); + recoveryProgress += progress / _backupHandlers.size(); + } + } + + QVariantMap status { + { "isRecovering", isRecovering }, + { "recoveringBackupId", _recoveryFilename }, + { "recoveryProgress", recoveryProgress } + }; + + QVariantMap info { + { "backups", backups }, + { "status", status } + }; + + promise->resolve(info); } void DomainContentBackupManager::removeOldBackupVersions(const BackupRule& rule) { @@ -433,6 +508,8 @@ void DomainContentBackupManager::consolidateBackup(MiniPromise::Promise promise, return; } + qDebug() << "copyFilePath" << copyFilePath; + promise->resolve({ { "success", true }, { "backupFilePath", copyFilePath } diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h index 9ec7eb9950..790dff0fb4 100644 --- a/domain-server/src/DomainContentBackupManager.h +++ b/domain-server/src/DomainContentBackupManager.h @@ -24,14 +24,6 @@ #include -struct BackupItemInfo { - QString id; - QString name; - QString absolutePath; - QDateTime createdAt; - bool isManualBackup; -}; - class DomainContentBackupManager : public GenericThread { Q_OBJECT public: @@ -52,13 +44,13 @@ public: bool debugTimestampNow = false); void addBackupHandler(BackupHandlerPointer handler); - std::vector getAllBackups(); void aboutToFinish(); /// call this to inform the persist thread that the owner is about to finish to support final persist void replaceData(QByteArray data); public slots: + void getAllBackupInformation(MiniPromise::Promise promise); void createManualBackup(MiniPromise::Promise promise, const QString& name); void recoverFromBackup(MiniPromise::Promise promise, const QString& backupName); void deleteBackup(MiniPromise::Promise promise, const QString& backupName); @@ -66,6 +58,7 @@ public slots: signals: void loadCompleted(); + void recoveryCompleted(); protected: /// Implements generic processing behavior for this thread. @@ -86,6 +79,9 @@ private: std::vector _backupHandlers; int _persistInterval { 0 }; + std::atomic _isRecovering { false }; + QString _recoveryFilename { }; + int64_t _lastCheck { 0 }; std::vector _backupRules; }; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index b91e12a9cf..fe145b341b 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -307,10 +307,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : _contentManager->initialize(true); - qDebug() << "Existing backups:"; - for (auto& backup : _contentManager->getAllBackups()) { - qDebug() << " Backup: " << backup.name << backup.createdAt; - } + connect(_contentManager.get(), &DomainContentBackupManager::recoveryCompleted, this, &DomainServer::restart); } void DomainServer::parseCommandLine() { @@ -2131,24 +2128,13 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return true; } else if (url.path() == URI_API_BACKUPS) { - QJsonObject rootJSON; - QJsonArray backupsJSON; + auto deferred = makePromise("getAllBackupInformation"); + deferred->then([connection, JSON_MIME_TYPE](QString error, QVariantMap result) { + QJsonDocument docJSON(QJsonObject::fromVariantMap(result)); - auto backups = _contentManager->getAllBackups(); - - for (const auto& backup : backups) { - QJsonObject obj; - obj["id"] = backup.id; - obj["name"] = backup.name; - obj["createdAtMillis"] = backup.createdAt.toMSecsSinceEpoch(); - obj["isManualBackup"] = backup.isManualBackup; - backupsJSON.push_back(obj); - } - - rootJSON["backups"] = backupsJSON; - QJsonDocument docJSON(rootJSON); - - connection->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); + connection->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); + }); + _contentManager->getAllBackupInformation(deferred); return true; } else if (url.path().startsWith(URI_API_BACKUPS_ID)) { auto id = url.path().mid(QString(URI_API_BACKUPS_ID).length()); From 1120b12b8cb758578e16912961744a81a12b1880 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 15 Feb 2018 15:58:39 -0800 Subject: [PATCH 310/569] Fix argument to isAvailable --- domain-server/src/DomainContentBackupManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 54ea7e23d5..eccc3e904d 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -322,7 +322,7 @@ void DomainContentBackupManager::getAllBackupInformation(MiniPromise::Promise pr for (auto& handler : _backupHandlers) { bool handlerIsAvailable { false }; float progress { 0.0f }; - //std::tie = handler->isAvailable(); + //std::tie = handler->isAvailable(fileInfo.absoluteFilePath()); //isAvailable = isAvailable && !handlerIsAvailable); //availabilityProgress += progress / _backupHandlers.size(); } From a7ca5398990a9328856f1a1aca75e4397a78db3a Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 14 Feb 2018 16:48:37 -0800 Subject: [PATCH 311/569] Simplify BackupHandler pattern --- domain-server/src/BackupHandler.h | 1 - 1 file changed, 1 deletion(-) diff --git a/domain-server/src/BackupHandler.h b/domain-server/src/BackupHandler.h index 8599dafb29..960dde9b45 100644 --- a/domain-server/src/BackupHandler.h +++ b/domain-server/src/BackupHandler.h @@ -26,7 +26,6 @@ public: virtual void deleteBackup(QuaZip& zip) = 0; virtual void consolidateBackup(QuaZip& zip) = 0; }; - using BackupHandlerPointer = std::unique_ptr; #endif /* hifi_BackupHandler_h */ From b76e1b9750973416aa2a316e601e2c36057b829e Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 15 Feb 2018 15:46:35 -0800 Subject: [PATCH 312/569] Add backup status getters --- domain-server/src/AssetsBackupHandler.cpp | 51 +++++++++++++++++++---- domain-server/src/AssetsBackupHandler.h | 21 +++++----- domain-server/src/BackupHandler.h | 5 +++ domain-server/src/EntitiesBackupHandler.h | 13 +++--- 4 files changed, 66 insertions(+), 24 deletions(-) diff --git a/domain-server/src/AssetsBackupHandler.cpp b/domain-server/src/AssetsBackupHandler.cpp index ae9cb58343..e683c626ea 100644 --- a/domain-server/src/AssetsBackupHandler.cpp +++ b/domain-server/src/AssetsBackupHandler.cpp @@ -111,6 +111,42 @@ void AssetsBackupHandler::checkForAssetsToDelete() { } } + +std::pair AssetsBackupHandler::isAvailable(QString filePath) { + auto it = find_if(begin(_backups), end(_backups), [&](const std::vector::value_type& value) { + return value.filePath == filePath; + }); + if (it == end(_backups)) { + return { true, 1.0f }; + } + + float progress = (float)it->mappings.size(); + for (const auto& mapping : it->mappings) { + if (_assetsLeftToRequest.find(mapping.second) != end(_assetsLeftToRequest)) { + progress -= 1.0f; + } + } + progress /= (float)it->mappings.size(); + + return { false, progress }; +} + +std::pair AssetsBackupHandler::getRecoveryStatus() { + if (_assetsLeftToUpload.empty() && + _mappingsLeftToSet.empty() && + _mappingsLeftToDelete.empty() && + _mappingRequestsInFlight == 0) { + return { false, 1.0f }; + } + + float progress = (float)_numRestoreOperations; + progress -= (float)_assetsLeftToUpload.size(); + progress -= (float)_mappingRequestsInFlight; + progress /= (float)_numRestoreOperations; + + return { true, progress }; +} + void AssetsBackupHandler::loadBackup(QuaZip& zip) { Q_ASSERT(QThread::currentThread() == thread()); @@ -451,6 +487,11 @@ void AssetsBackupHandler::computeServerStateDifference(const AssetUtils::Mapping } } + _numRestoreOperations = _assetsLeftToUpload.size() + _mappingsLeftToSet.size(); + if (!_mappingsLeftToDelete.empty()) { + ++_numRestoreOperations; + } + qCDebug(asset_backup) << "Mappings to set:" << _mappingsLeftToSet.size(); qCDebug(asset_backup) << "Mappings to del:" << _mappingsLeftToDelete.size(); qCDebug(asset_backup) << "Assets to upload:" << _assetsLeftToUpload.size(); @@ -461,8 +502,6 @@ void AssetsBackupHandler::restoreAllAssets() { } void AssetsBackupHandler::restoreNextAsset() { - startOperation(); - if (_assetsLeftToUpload.empty()) { updateMappings(); return; @@ -500,9 +539,7 @@ void AssetsBackupHandler::updateMappings() { qCCritical(asset_backup) << " Error:" << request->getErrorString(); } - if (--_mappingRequestsInFlight == 0) { - stopOperation(); - } + --_mappingRequestsInFlight; request->deleteLater(); }); @@ -519,9 +556,7 @@ void AssetsBackupHandler::updateMappings() { qCCritical(asset_backup) << " Error:" << request->getErrorString(); } - if (--_mappingRequestsInFlight == 0) { - stopOperation(); - } + --_mappingRequestsInFlight; request->deleteLater(); }); diff --git a/domain-server/src/AssetsBackupHandler.h b/domain-server/src/AssetsBackupHandler.h index 2ef454998e..1421ddd400 100644 --- a/domain-server/src/AssetsBackupHandler.h +++ b/domain-server/src/AssetsBackupHandler.h @@ -31,13 +31,16 @@ class AssetsBackupHandler : public QObject, public BackupHandlerInterface { public: AssetsBackupHandler(const QString& backupDirectory); - void loadBackup(QuaZip& zip); - void createBackup(QuaZip& zip); - void recoverBackup(QuaZip& zip); - void deleteBackup(QuaZip& zip); - void consolidateBackup(QuaZip& zip); + std::pair isAvailable(QString filePath) override; + std::pair getRecoveryStatus() override; - bool operationInProgress() const { return _operationInProgress; } + void loadBackup(QuaZip& zip) override; + void createBackup(QuaZip& zip) override; + void recoverBackup(QuaZip& zip) override; + void deleteBackup(QuaZip& zip) override; + void consolidateBackup(QuaZip& zip) override; + + bool operationInProgress() { return getRecoveryStatus().first; } private: void setupRefreshTimer(); @@ -48,9 +51,6 @@ private: void checkForMissingAssets(); void checkForAssetsToDelete(); - void startOperation() { _operationInProgress = true; } - void stopOperation() { _operationInProgress = false; } - void downloadMissingFiles(const AssetUtils::Mappings& mappings); void downloadNextMissingFile(); bool writeAssetFile(const AssetUtils::AssetHash& hash, const QByteArray& data); @@ -73,8 +73,6 @@ private: bool corruptedBackup; }; - bool _operationInProgress { false }; - // Internal storage for backups on disk bool _allBackupsLoadedSuccessfully { false }; std::vector _backups; @@ -89,6 +87,7 @@ private: std::vector> _mappingsLeftToSet; AssetUtils::AssetPathList _mappingsLeftToDelete; int _mappingRequestsInFlight { 0 }; + int _numRestoreOperations { 0 }; // Used to compute a restore progress. }; #endif /* hifi_AssetsBackupHandler_h */ diff --git a/domain-server/src/BackupHandler.h b/domain-server/src/BackupHandler.h index 960dde9b45..d513820000 100644 --- a/domain-server/src/BackupHandler.h +++ b/domain-server/src/BackupHandler.h @@ -20,6 +20,11 @@ class BackupHandlerInterface { public: virtual ~BackupHandlerInterface() = default; + virtual std::pair isAvailable(QString filePath) = 0; + + // Returns whether a recovery is ongoing and a progress between 0 and 1 if one is. + virtual std::pair getRecoveryStatus() = 0; + virtual void loadBackup(QuaZip& zip) = 0; virtual void createBackup(QuaZip& zip) = 0; virtual void recoverBackup(QuaZip& zip) = 0; diff --git a/domain-server/src/EntitiesBackupHandler.h b/domain-server/src/EntitiesBackupHandler.h index 6f66483a87..4cff7b6a33 100644 --- a/domain-server/src/EntitiesBackupHandler.h +++ b/domain-server/src/EntitiesBackupHandler.h @@ -20,19 +20,22 @@ class EntitiesBackupHandler : public BackupHandlerInterface { public: EntitiesBackupHandler(QString entitiesFilePath, QString entitiesReplacementFilePath); - void loadBackup(QuaZip& zip) {} + std::pair isAvailable(QString filePath) override { return { true, 1.0f }; } + std::pair getRecoveryStatus() override { return { false, 1.0f }; } + + void loadBackup(QuaZip& zip) override {} // Create a skeleton backup - void createBackup(QuaZip& zip); + void createBackup(QuaZip& zip) override; // Recover from a full backup - void recoverBackup(QuaZip& zip); + void recoverBackup(QuaZip& zip) override; // Delete a skeleton backup - void deleteBackup(QuaZip& zip) {} + void deleteBackup(QuaZip& zip) override {} // Create a full backup - void consolidateBackup(QuaZip& zip) {} + void consolidateBackup(QuaZip& zip) override {} private: QString _entitiesFilePath; From 57410e4f1cc017c5aa23220fa96f6a445899d62a Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 15 Feb 2018 16:22:06 -0800 Subject: [PATCH 313/569] Remove ES restart after restore --- domain-server/src/EntitiesBackupHandler.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/domain-server/src/EntitiesBackupHandler.cpp b/domain-server/src/EntitiesBackupHandler.cpp index deb92ee0f6..6ad00d01c8 100644 --- a/domain-server/src/EntitiesBackupHandler.cpp +++ b/domain-server/src/EntitiesBackupHandler.cpp @@ -16,7 +16,6 @@ #include #include -#include #include EntitiesBackupHandler::EntitiesBackupHandler(QString entitiesFilePath, QString entitiesReplacementFilePath) : @@ -72,13 +71,5 @@ void EntitiesBackupHandler::recoverBackup(QuaZip& zip) { if (entitiesFile.open(QIODevice::WriteOnly)) { entitiesFile.write(data.toGzippedByteArray()); - entitiesFile.close(); - - auto nodeList = DependencyManager::get(); - nodeList->eachMatchingNode([](const SharedNodePointer& otherNode) -> bool { - return otherNode->getType() == NodeType::EntityServer; - }, [nodeList](const SharedNodePointer& otherNode) { - QMetaObject::invokeMethod(nodeList.data(), "killNodeWithUUID", Q_ARG(const QUuid&, otherNode->getUUID())); - }); } } From 771e4cd9f4d5e8324813f41011717ea423327525 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 15 Feb 2018 16:45:29 -0800 Subject: [PATCH 314/569] Hook up status and progress --- .../src/DomainContentBackupManager.cpp | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index eccc3e904d..37a8ecfce2 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -148,18 +148,15 @@ bool DomainContentBackupManager::process() { if (sinceLastSave > intervalToCheck) { _lastCheck = now; if (_isRecovering) { - bool anyHandlerIsRecovering { false }; - for (auto& handler : _backupHandlers) { - bool handlerIsRecovering { false }; - float progress { 0.0f }; - //std::tie = handler->getRecoveryStatus(); - if (handlerIsRecovering) { - anyHandlerIsRecovering = true; - emit recoveryCompleted(); - break; - } + using Value = std::vector::value_type; + bool isStillRecovering = std::any_of(begin(_backupHandlers), end(_backupHandlers), [](const Value& handler) { + return handler->getRecoveryStatus().first; + }); + + if (!isStillRecovering) { + _isRecovering = false; + emit recoveryCompleted(); } - _isRecovering = anyHandlerIsRecovering; } if (!_isRecovering) { @@ -320,11 +317,11 @@ void DomainContentBackupManager::getAllBackupInformation(MiniPromise::Promise pr bool isAvailable { true }; float availabilityProgress { 0.0f }; for (auto& handler : _backupHandlers) { - bool handlerIsAvailable { false }; + bool handlerIsAvailable { true }; float progress { 0.0f }; - //std::tie = handler->isAvailable(fileInfo.absoluteFilePath()); - //isAvailable = isAvailable && !handlerIsAvailable); - //availabilityProgress += progress / _backupHandlers.size(); + std::tie(handlerIsAvailable, progress) = handler->isAvailable(fileInfo.absoluteFilePath()); + isAvailable &= handlerIsAvailable; + availabilityProgress += progress / _backupHandlers.size(); } backups.push_back(QVariantMap({ @@ -342,9 +339,7 @@ void DomainContentBackupManager::getAllBackupInformation(MiniPromise::Promise pr bool isRecovering = _isRecovering.load(); if (_isRecovering) { for (auto& handler : _backupHandlers) { - bool handlerIsRecovering { false }; - float progress { 0.0f }; - //std::tie = handler->getRecoveryStatus(); + float progress = handler->getRecoveryStatus().second; recoveryProgress += progress / _backupHandlers.size(); } } From cae3e0a9dcceff2e182a012a6381743ca85905ba Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 15 Feb 2018 16:59:17 -0800 Subject: [PATCH 315/569] Add status func to ContentSettingsBackupHandler --- domain-server/src/ContentSettingsBackupHandler.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/domain-server/src/ContentSettingsBackupHandler.h b/domain-server/src/ContentSettingsBackupHandler.h index 932b7c0c3f..8a81392513 100644 --- a/domain-server/src/ContentSettingsBackupHandler.h +++ b/domain-server/src/ContentSettingsBackupHandler.h @@ -19,15 +19,18 @@ class ContentSettingsBackupHandler : public BackupHandlerInterface { public: ContentSettingsBackupHandler(DomainServerSettingsManager& domainServerSettingsManager); - void loadBackup(QuaZip& zip) {}; + std::pair isAvailable(QString filePath) override { return { true, 1.0f }; } + std::pair getRecoveryStatus() override { return { false, 1.0f }; } - void createBackup(QuaZip& zip); + void loadBackup(QuaZip& zip) override {} - void recoverBackup(QuaZip& zip); + void createBackup(QuaZip& zip) override; - void deleteBackup(QuaZip& zip) {}; + void recoverBackup(QuaZip& zip) override; - void consolidateBackup(QuaZip& zip) {}; + void deleteBackup(QuaZip& zip) override {} + + void consolidateBackup(QuaZip& zip) override {} private: DomainServerSettingsManager& _settingsManager; }; From 2b85634a21abec80f8f689013c2696afb19e2dc9 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 15 Feb 2018 17:03:55 -0800 Subject: [PATCH 316/569] Fix build error --- domain-server/src/BackupHandler.h | 2 ++ domain-server/src/EntitiesBackupHandler.h | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/domain-server/src/BackupHandler.h b/domain-server/src/BackupHandler.h index d513820000..1bd40cd9e4 100644 --- a/domain-server/src/BackupHandler.h +++ b/domain-server/src/BackupHandler.h @@ -14,6 +14,8 @@ #include +#include + class QuaZip; class BackupHandlerInterface { diff --git a/domain-server/src/EntitiesBackupHandler.h b/domain-server/src/EntitiesBackupHandler.h index 4cff7b6a33..1a6110f1cd 100644 --- a/domain-server/src/EntitiesBackupHandler.h +++ b/domain-server/src/EntitiesBackupHandler.h @@ -12,8 +12,6 @@ #ifndef hifi_EntitiesBackupHandler_h #define hifi_EntitiesBackupHandler_h -#include - #include "BackupHandler.h" class EntitiesBackupHandler : public BackupHandlerInterface { From 2d0b85af724ba640b9caf6ebca260df5c910e120 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 15 Feb 2018 17:28:59 -0800 Subject: [PATCH 317/569] workaround bug --- libraries/entities/src/MaterialEntityItem.cpp | 9 +++++---- libraries/entities/src/MaterialEntityItem.h | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/libraries/entities/src/MaterialEntityItem.cpp b/libraries/entities/src/MaterialEntityItem.cpp index e3e3390449..44ef34a3a4 100644 --- a/libraries/entities/src/MaterialEntityItem.cpp +++ b/libraries/entities/src/MaterialEntityItem.cpp @@ -27,10 +27,6 @@ MaterialEntityItem::MaterialEntityItem(const EntityItemID& entityItemID) : Entit _type = EntityTypes::Material; } -MaterialEntityItem::~MaterialEntityItem() { - removeMaterial(); -} - EntityItemProperties MaterialEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialURL, getMaterialURL); @@ -329,6 +325,11 @@ void MaterialEntityItem::postParentFixup() { applyMaterial(); } +void MaterialEntityItem::preDelete() { + EntityItem::preDelete(); + removeMaterial(); +} + void MaterialEntityItem::update(const quint64& now) { if (_retryApply) { applyMaterial(); diff --git a/libraries/entities/src/MaterialEntityItem.h b/libraries/entities/src/MaterialEntityItem.h index e203b707cc..f77077a782 100644 --- a/libraries/entities/src/MaterialEntityItem.h +++ b/libraries/entities/src/MaterialEntityItem.h @@ -21,7 +21,6 @@ public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); MaterialEntityItem(const EntityItemID& entityItemID); - ~MaterialEntityItem(); ALLOW_INSTANTIATION // This class can be instantiated @@ -85,6 +84,7 @@ public: void removeMaterial(); void postParentFixup() override; + void preDelete() override; private: // URL for this material. Currently, only JSON format is supported. Set to "userData" to use the user data to live edit a material. From 697f0c443cb0f3606ca2facc53dc35bd5ca98ae0 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 15 Feb 2018 17:43:02 -0800 Subject: [PATCH 318/569] Fix warning --- domain-server/src/AssetsBackupHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/src/AssetsBackupHandler.cpp b/domain-server/src/AssetsBackupHandler.cpp index e683c626ea..db39f2a731 100644 --- a/domain-server/src/AssetsBackupHandler.cpp +++ b/domain-server/src/AssetsBackupHandler.cpp @@ -487,7 +487,7 @@ void AssetsBackupHandler::computeServerStateDifference(const AssetUtils::Mapping } } - _numRestoreOperations = _assetsLeftToUpload.size() + _mappingsLeftToSet.size(); + _numRestoreOperations = (int)_assetsLeftToUpload.size() + (int)_mappingsLeftToSet.size(); if (!_mappingsLeftToDelete.empty()) { ++_numRestoreOperations; } From f6e9d2c6dd05358d3053795f81705c43bbde02b3 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 15 Feb 2018 18:16:30 -0800 Subject: [PATCH 319/569] Fix race condition in Asset Server --- assignment-client/src/assets/AssetServer.cpp | 28 ++++++++++++++++---- assignment-client/src/assets/AssetServer.h | 5 +++- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index 0be557bccd..4c6cba2e74 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -416,9 +416,9 @@ void AssetServer::completeSetup() { PathUtils::removeTemporaryApplicationDirs(); PathUtils::removeTemporaryApplicationDirs("Oven"); - // We're fully setup, remove the request queueing and replay all requests + qCDebug(asset_server) << "Overriding temporary queuing packet handler."; + // We're fully setup, override the request queueing handler and replay all requests auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.unregisterListener(this); packetReceiver.registerListener(PacketType::AssetGet, this, "handleAssetGet"); packetReceiver.registerListener(PacketType::AssetGetInfo, this, "handleAssetGetInfo"); packetReceiver.registerListener(PacketType::AssetUpload, this, "handleAssetUpload"); @@ -428,11 +428,30 @@ void AssetServer::completeSetup() { } void AssetServer::queueRequests(QSharedPointer packet, SharedNodePointer senderNode) { + qCDebug(asset_server) << "Queuing requests until fully setup"; + + QMutexLocker lock { &_queuedRequestsMutex }; _queuedRequests.push_back({ packet, senderNode }); + + // If we've stopped queueing but the callback was already in flight, + // then replay it immediately. + if (!_isQueueingRequests) { + lock.unlock(); + replayRequests(); + } } void AssetServer::replayRequests() { - for (const auto& request : _queuedRequests) { + RequestQueue queue; + { + QMutexLocker lock { &_queuedRequestsMutex }; + qSwap(queue, _queuedRequests); + _isQueueingRequests = false; + } + + qCDebug(asset_server) << "Replaying" << queue.size() << "requests."; + + for (const auto& request : queue) { switch (request.first->getType()) { case PacketType::AssetGet: handleAssetGet(request.first, request.second); @@ -447,11 +466,10 @@ void AssetServer::replayRequests() { handleAssetMappingOperation(request.first, request.second); break; default: - qWarning() << "Unknown queued request type:" << request.first->getType(); + qCWarning(asset_server) << "Unknown queued request type:" << request.first->getType(); break; } } - _queuedRequests.clear(); } void AssetServer::cleanupUnmappedFiles() { diff --git a/assignment-client/src/assets/AssetServer.h b/assignment-client/src/assets/AssetServer.h index b8aac800ed..c85fb89175 100644 --- a/assignment-client/src/assets/AssetServer.h +++ b/assignment-client/src/assets/AssetServer.h @@ -123,7 +123,10 @@ private: QHash> _pendingBakes; QThreadPool _bakingTaskPool; - QVector, SharedNodePointer>> _queuedRequests; + QMutex _queuedRequestsMutex; + bool _isQueueingRequests { true }; + using RequestQueue = QVector, SharedNodePointer>>; + RequestQueue _queuedRequests; bool _wasColorTextureCompressionEnabled { false }; bool _wasGrayscaleTextureCompressionEnabled { false }; From b30f98d5414759a1eae88505812cb481bb813844 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 15 Feb 2018 18:20:14 -0800 Subject: [PATCH 320/569] CR --- domain-server/src/DomainContentBackupManager.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 37a8ecfce2..5eb4e7627f 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -503,8 +503,6 @@ void DomainContentBackupManager::consolidateBackup(MiniPromise::Promise promise, return; } - qDebug() << "copyFilePath" << copyFilePath; - promise->resolve({ { "success", true }, { "backupFilePath", copyFilePath } From 81662b5edc3180e1ae2221c27374bd16437aaae1 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 15 Feb 2018 19:14:14 -0800 Subject: [PATCH 321/569] don't overwrite general description object with filtered one --- domain-server/src/DomainServerSettingsManager.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 8febbd5769..3fa36ceb90 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -1196,8 +1196,8 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection } bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType) { - QJsonArray& filteredDescriptionArray = settingsType == DomainSettings - ? _domainSettingsDescription : _contentSettingsDescription; + QJsonArray* filteredDescriptionArray = settingsType == DomainSettings + ? &_domainSettingsDescription : &_contentSettingsDescription; // grab a copy of the current config before restore, so that we can back out if something bad happens during QVariantMap preRestoreConfig = _configMap.getConfig(); @@ -1206,7 +1206,7 @@ bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settings // enumerate through the settings in the description // if we have one in the restore then use it, otherwise clear it from current settings - foreach(const QJsonValue& descriptionGroupValue, filteredDescriptionArray) { + foreach(const QJsonValue& descriptionGroupValue, *filteredDescriptionArray) { QJsonObject descriptionGroupObject = descriptionGroupValue.toObject(); QString groupKey = descriptionGroupObject[DESCRIPTION_NAME_KEY].toString(); QJsonArray descriptionGroupSettings = descriptionGroupObject[DESCRIPTION_SETTINGS_KEY].toArray(); @@ -1328,15 +1328,15 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt const QString AFFECTED_TYPES_JSON_KEY = "assignment-types"; // only enumerate the requested settings type (domain setting or content setting) - QJsonArray& filteredDescriptionArray = _descriptionArray; + QJsonArray* filteredDescriptionArray = &_descriptionArray; if (includeDomainSettings && !includeContentSettings) { - filteredDescriptionArray = _domainSettingsDescription; + filteredDescriptionArray = &_domainSettingsDescription; } else if (includeContentSettings && !includeDomainSettings) { - filteredDescriptionArray = _contentSettingsDescription; + filteredDescriptionArray = &_contentSettingsDescription; } // enumerate the groups in the potentially filtered object to find which settings to pass - foreach(const QJsonValue& groupValue, filteredDescriptionArray) { + foreach(const QJsonValue& groupValue, *filteredDescriptionArray) { QJsonObject groupObject = groupValue.toObject(); QString groupKey = groupObject[DESCRIPTION_NAME_KEY].toString(); QJsonArray groupSettingsArray = groupObject[DESCRIPTION_SETTINGS_KEY].toArray(); From 4bb8435ef884e760ca7374d25574a115ce1fa488 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 15 Feb 2018 19:14:14 -0800 Subject: [PATCH 322/569] don't overwrite general description object with filtered one --- .../src/DomainServerSettingsManager.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 5f71890898..85d6a046b5 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -1199,7 +1199,7 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection } bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType) { - + if (thread() != QThread::currentThread()) { bool success; @@ -1210,8 +1210,8 @@ bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settings return success; } - QJsonArray& filteredDescriptionArray = settingsType == DomainSettings - ? _domainSettingsDescription : _contentSettingsDescription; + QJsonArray* filteredDescriptionArray = settingsType == DomainSettings + ? &_domainSettingsDescription : &_contentSettingsDescription; // grab a copy of the current config before restore, so that we can back out if something bad happens during QVariantMap preRestoreConfig = _configMap.getConfig(); @@ -1220,7 +1220,7 @@ bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settings // enumerate through the settings in the description // if we have one in the restore then use it, otherwise clear it from current settings - foreach(const QJsonValue& descriptionGroupValue, filteredDescriptionArray) { + foreach(const QJsonValue& descriptionGroupValue, *filteredDescriptionArray) { QJsonObject descriptionGroupObject = descriptionGroupValue.toObject(); QString groupKey = descriptionGroupObject[DESCRIPTION_NAME_KEY].toString(); QJsonArray descriptionGroupSettings = descriptionGroupObject[DESCRIPTION_SETTINGS_KEY].toArray(); @@ -1356,15 +1356,15 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt const QString AFFECTED_TYPES_JSON_KEY = "assignment-types"; // only enumerate the requested settings type (domain setting or content setting) - QJsonArray& filteredDescriptionArray = _descriptionArray; + QJsonArray* filteredDescriptionArray = &_descriptionArray; if (includeDomainSettings && !includeContentSettings) { - filteredDescriptionArray = _domainSettingsDescription; + filteredDescriptionArray = &_domainSettingsDescription; } else if (includeContentSettings && !includeDomainSettings) { - filteredDescriptionArray = _contentSettingsDescription; + filteredDescriptionArray = &_contentSettingsDescription; } // enumerate the groups in the potentially filtered object to find which settings to pass - foreach(const QJsonValue& groupValue, filteredDescriptionArray) { + foreach(const QJsonValue& groupValue, *filteredDescriptionArray) { QJsonObject groupObject = groupValue.toObject(); QString groupKey = groupObject[DESCRIPTION_NAME_KEY].toString(); QJsonArray groupSettingsArray = groupObject[DESCRIPTION_SETTINGS_KEY].toArray(); From 29349d7bb2c198e859038c542af794161e5cba16 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 15 Feb 2018 19:18:51 -0800 Subject: [PATCH 323/569] fix for isAvailable boolean in AssetsBackupHandler --- domain-server/src/AssetsBackupHandler.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/domain-server/src/AssetsBackupHandler.cpp b/domain-server/src/AssetsBackupHandler.cpp index db39f2a731..c365b942af 100644 --- a/domain-server/src/AssetsBackupHandler.cpp +++ b/domain-server/src/AssetsBackupHandler.cpp @@ -120,13 +120,20 @@ std::pair AssetsBackupHandler::isAvailable(QString filePath) { return { true, 1.0f }; } - float progress = (float)it->mappings.size(); + int mappingsMissing = 0; for (const auto& mapping : it->mappings) { if (_assetsLeftToRequest.find(mapping.second) != end(_assetsLeftToRequest)) { - progress -= 1.0f; + ++mappingsMissing; } } - progress /= (float)it->mappings.size(); + + if (mappingsMissing == 0) { + return { true, 1.0f }; + } + + float progress = (float)it->mappings.size(); + progress -= (float)mappingsMissing; + progress /= it->mappings.size(); return { false, progress }; } From 2eb8ebcdbcf0a50fed1acda630398bad547d5e27 Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Fri, 16 Feb 2018 19:59:42 +0300 Subject: [PATCH 324/569] FB11508 - toolbar slowly sides downward --- interface/resources/qml/windows/Window.qml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml index a0ef73290a..4ed765db4b 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -89,6 +89,15 @@ Fadable { window.shown = value; } + // used to snap window content to pixel by translating content by negative fractional part of the x/y + // thus if x was 5.41, snapper's x will be -0.41 + // avoiding fractional window position is to avoid artifacts in text rendering when the fractional positions are present. + transform: Translate { + id: snapper + x: 0; + y: 0; + } + property var rectifier: Timer { property bool executing: false; interval: 100 @@ -97,8 +106,8 @@ Fadable { onTriggered: { executing = true; - x = Math.floor(x); - y = Math.floor(y); + snapper.x = Math.floor(x) - x; + snapper.y = Math.floor(y) - y; executing = false; } From 194c7f41015575a91b4aa64f1775cc533689b5ec Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Fri, 16 Feb 2018 09:28:15 -0800 Subject: [PATCH 325/569] WIP - for review. --- interface/src/avatar/MyAvatar.cpp | 8 +-- interface/src/avatar/MyAvatar.h | 2 - .../src/RenderableEntityItem.h | 1 + .../src/RenderableModelEntityItem.cpp | 4 ++ .../render-utils/src/CauterizedModel.cpp | 5 +- .../render-utils/src/MeshPartPayload.cpp | 10 +++- libraries/render-utils/src/MeshPartPayload.h | 4 +- libraries/render-utils/src/Model.cpp | 55 ++++++++++++++----- libraries/render-utils/src/Model.h | 6 ++ 9 files changed, 67 insertions(+), 28 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c25aaeeecd..97b8c71a32 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1112,6 +1112,7 @@ void MyAvatar::setEnableDebugDrawIKChains(bool isEnabled) { void MyAvatar::setEnableMeshVisible(bool isEnabled) { _skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); + _skeletonModel->setCanCastShadow(true, qApp->getMain3DScene()); } void MyAvatar::setEnableInverseKinematics(bool isEnabled) { @@ -1464,6 +1465,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { int skeletonModelChangeCount = _skeletonModelChangeCount; Avatar::setSkeletonModelURL(skeletonModelURL); _skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); + _skeletonModel->setCanCastShadow(true, qApp->getMain3DScene()); _headBoneSet.clear(); _cauterizationNeedsUpdate = true; @@ -1819,12 +1821,6 @@ void MyAvatar::attach(const QString& modelURL, const QString& jointName, Avatar::attach(modelURL, jointName, translation, rotation, scale, isSoft, allowDuplicates, useSaved); } -void MyAvatar::setVisibleInSceneIfReady(Model* model, const render::ScenePointer& scene, bool visible) { - if (model->isActive() && model->isRenderable()) { - model->setVisibleInScene(visible, scene, render::ItemKey::TAG_BITS_NONE, true); - } -} - void MyAvatar::initHeadBones() { int neckJointIndex = -1; if (_skeletonModel->isLoaded()) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 28af8b62fd..a62bc1a109 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -677,8 +677,6 @@ private: // These are made private for MyAvatar so that you will use the "use" methods instead virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; - void setVisibleInSceneIfReady(Model* model, const render::ScenePointer& scene, bool visiblity); - virtual void updatePalms() override {} void lateUpdatePalms(); diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index f8685df5da..a9bb12d087 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -124,6 +124,7 @@ protected: bool _isFading{ _entitiesShouldFadeFunction() }; bool _prevIsTransparent { false }; bool _visible { false }; + bool _canCastShadow { false }; bool _cauterized { false }; bool _moving { false }; bool _needsRenderUpdate { false }; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 56e3f96014..d3e1b62f5a 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1356,6 +1356,10 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } // TODO? early exit here when not visible? + if (model->canCastShadow() != _canCastShadow) { + model->setCanCastShadow(_canCastShadow, scene); + } + if (_needsCollisionGeometryUpdate) { setCollisionMeshKey(entity->getCollisionMeshKey()); _needsCollisionGeometryUpdate = false; diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 54dfd96a00..74b278c2c1 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -204,6 +204,7 @@ void CauterizedModel::updateRenderItems() { bool isWireframe = self->isWireframe(); bool isVisible = self->isVisible(); + bool canCastShadow = self->canCastShadow(); bool isLayeredInFront = self->isLayeredInFront(); bool isLayeredInHUD = self->isLayeredInHUD(); bool enableCauterization = self->getEnableCauterization(); @@ -219,7 +220,7 @@ void CauterizedModel::updateRenderItems() { bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex); transaction.updateItem(itemID, [modelTransform, clusterTransforms, clusterTransformsCauterized, invalidatePayloadShapeKey, - isWireframe, isVisible, isLayeredInFront, isLayeredInHUD, enableCauterization](CauterizedMeshPartPayload& data) { + isWireframe, isVisible, isLayeredInFront, isLayeredInHUD, canCastShadow, enableCauterization](CauterizedMeshPartPayload& data) { data.updateClusterBuffer(clusterTransforms, clusterTransformsCauterized); Transform renderTransform = modelTransform; @@ -249,7 +250,7 @@ void CauterizedModel::updateRenderItems() { data.updateTransformForCauterizedMesh(renderTransform); data.setEnableCauterization(enableCauterization); - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, render::ItemKey::TAG_BITS_ALL); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, render::ItemKey::TAG_BITS_ALL); data.setLayer(isLayeredInFront, isLayeredInHUD); data.setShapeKey(invalidatePayloadShapeKey, isWireframe); }); diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index da11535396..84f6c6aca3 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -71,7 +71,7 @@ void MeshPartPayload::updateMaterial(graphics::MaterialPointer drawMaterial) { _drawMaterial = drawMaterial; } -void MeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled) { +void MeshPartPayload::updateKey(bool isVisible, bool isLayered, bool canCastShadow, uint8_t tagBits, bool isGroupCulled) { ItemKey::Builder builder; builder.withTypeShape(); @@ -85,6 +85,10 @@ void MeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits, builder.withLayered(); } + if (canCastShadow) { + builder.withShadowCaster(); + } + if (isGroupCulled) { builder.withSubMetaCulled(); } @@ -421,6 +425,10 @@ void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tag builder.withLayered(); } + if (canCastShadow) { + builder.withShadowCaster(); + } + if (isGroupCulled) { builder.withSubMetaCulled(); } diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 40efc67572..e7996e94dc 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -33,7 +33,7 @@ public: typedef render::Payload Payload; typedef Payload::DataPointer Pointer; - virtual void updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled = false); + virtual void updateKey(bool isVisible, bool isLayered, bool canCastShadow, uint8_t tagBits, bool isGroupCulled = false); virtual void updateMeshPart(const std::shared_ptr& drawMesh, int partIndex); @@ -99,7 +99,7 @@ public: using TransformType = glm::mat4; #endif - void updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled = false) override; + void updateKey(bool isVisible, bool isLayered, bool canCastShadow, uint8_t tagBits, bool isGroupCulled = false) override; void updateClusterBuffer(const std::vector& clusterTransforms); void updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index bb8353c746..197008fc94 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -102,6 +102,7 @@ Model::Model(QObject* parent, SpatiallyNestable* spatiallyNestableOverride) : _snappedToRegistrationPoint(false), _url(HTTP_INVALID_COM), _isVisible(true), + _canCastShadow(false), _blendNumber(0), _appliedBlendNumber(0), _isWireframe(false) @@ -268,6 +269,7 @@ void Model::updateRenderItems() { bool isWireframe = self->isWireframe(); bool isVisible = self->isVisible(); + bool canCastShadow = self->canCastShadow(); uint8_t viewTagBits = self->getViewTagBits(); bool isLayeredInFront = self->isLayeredInFront(); bool isLayeredInHUD = self->isLayeredInHUD(); @@ -284,7 +286,7 @@ void Model::updateRenderItems() { transaction.updateItem(itemID, [modelTransform, clusterTransforms, invalidatePayloadShapeKey, isWireframe, isVisible, - viewTagBits, isLayeredInFront, + canCastShadow, viewTagBits, isLayeredInFront, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { data.updateClusterBuffer(clusterTransforms); @@ -301,7 +303,7 @@ void Model::updateRenderItems() { } data.updateTransformForSkinnedMesh(renderTransform, modelTransform); - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); data.setLayer(isLayeredInFront, isLayeredInHUD); data.setShapeKey(invalidatePayloadShapeKey, isWireframe); }); @@ -693,46 +695,68 @@ void Model::setVisibleInScene(bool isVisible, const render::ScenePointer& scene, bool isLayeredInFront = _isLayeredInFront; bool isLayeredInHUD = _isLayeredInHUD; - + bool canCastShadow = _canCastShadow; render::Transaction transaction; foreach (auto item, _modelMeshRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, + transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); }); } foreach(auto item, _collisionRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, + transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); }); } scene->enqueueTransaction(transaction); } } +void Model::setCanCastShadow(bool canCastShadow, const render::ScenePointer& scene) { + if (_canCastShadow != canCastShadow) { + _canCastShadow = canCastShadow; + + bool isVisible = _isVisible; + bool viewTagBits = _viewTagBits; + bool isLayeredInFront = _isLayeredInFront; + bool isLayeredInHUD = _isLayeredInHUD; + bool isGroupCulled = _isGroupCulled; + + render::Transaction transaction; + foreach (auto item, _modelMeshRenderItemsMap.keys()) { + transaction.updateItem(item, + [isVisible, viewTagBits, canCastShadow, isLayeredInFront, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { + data.updateKey(isVisible, viewTagBits, canCastShadow, isLayeredInFront || isLayeredInHUD, isGroupCulled); + }); + } + + scene->enqueueTransaction(transaction); + } +} void Model::setLayeredInFront(bool isLayeredInFront, const render::ScenePointer& scene) { if (_isLayeredInFront != isLayeredInFront) { _isLayeredInFront = isLayeredInFront; bool isVisible = _isVisible; + bool canCastShadow = _canCastShadow; uint8_t viewTagBits = _viewTagBits; bool isLayeredInHUD = _isLayeredInHUD; bool isGroupCulled = _isGroupCulled; render::Transaction transaction; foreach(auto item, _modelMeshRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, + transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); data.setLayer(isLayeredInFront, isLayeredInHUD); }); } foreach(auto item, _collisionRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, + transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); data.setLayer(isLayeredInFront, isLayeredInHUD); }); } @@ -745,22 +769,23 @@ void Model::setLayeredInHUD(bool isLayeredInHUD, const render::ScenePointer& sce _isLayeredInHUD = isLayeredInHUD; bool isVisible = _isVisible; + bool canCastShadow = _canCastShadow; uint8_t viewTagBits = _viewTagBits; bool isLayeredInFront = _isLayeredInFront; bool isGroupCulled = _isGroupCulled; render::Transaction transaction; foreach(auto item, _modelMeshRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, + transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); data.setLayer(isLayeredInFront, isLayeredInHUD); }); } foreach(auto item, _collisionRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, + transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); data.setLayer(isLayeredInFront, isLayeredInHUD); }); } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 57d2798a66..c0d2c32f8f 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -87,6 +87,10 @@ public: // new Scene/Engine rendering support void setVisibleInScene(bool isVisible, const render::ScenePointer& scene, uint8_t viewTagBits, bool isGroupCulled); + + bool canCastShadow() const { return _canCastShadow; } + void setCanCastShadow(bool canCastShadow, const render::ScenePointer& scene); + void setLayeredInFront(bool isLayeredInFront, const render::ScenePointer& scene); void setLayeredInHUD(bool isLayeredInHUD, const render::ScenePointer& scene); bool needsFixupInScene() const; @@ -401,6 +405,8 @@ protected: bool _isVisible; uint8_t _viewTagBits{ render::ItemKey::TAG_BITS_ALL }; + bool _canCastShadow; + gpu::Buffers _blendedVertexBuffers; QVector > > _dilatedTextures; From 47924a58f9732e62ef79efa065bd944781193ebd Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Fri, 16 Feb 2018 10:14:46 -0800 Subject: [PATCH 326/569] WIP - for review. --- libraries/render-utils/src/MeshPartPayload.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 84f6c6aca3..178f5782dd 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -411,7 +411,7 @@ void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& render _worldBound.transform(boundTransform); } -void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled) { +void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, bool canCastShadow, uint8_t tagBits, bool isGroupCulled) { ItemKey::Builder builder; builder.withTypeShape(); From efa55c0a63d09b4f484c9248c59854924ba56b4e Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 16 Feb 2018 13:07:17 -0800 Subject: [PATCH 327/569] Update backup delete to not break rolling backups and remove unused asset files --- domain-server/src/AssetsBackupHandler.cpp | 6 +++--- domain-server/src/AssetsBackupHandler.h | 2 +- domain-server/src/BackupHandler.h | 2 +- .../src/ContentSettingsBackupHandler.h | 2 +- .../src/DomainContentBackupManager.cpp | 19 ++++++++++++++++--- .../src/DomainContentBackupManager.h | 1 + domain-server/src/EntitiesBackupHandler.h | 2 +- 7 files changed, 24 insertions(+), 10 deletions(-) diff --git a/domain-server/src/AssetsBackupHandler.cpp b/domain-server/src/AssetsBackupHandler.cpp index c365b942af..694277910f 100644 --- a/domain-server/src/AssetsBackupHandler.cpp +++ b/domain-server/src/AssetsBackupHandler.cpp @@ -306,7 +306,7 @@ void AssetsBackupHandler::recoverBackup(QuaZip& zip) { restoreAllAssets(); } -void AssetsBackupHandler::deleteBackup(QuaZip& zip) { +void AssetsBackupHandler::deleteBackup(const QString& absoluteFilePath) { Q_ASSERT(QThread::currentThread() == thread()); if (operationInProgress()) { @@ -315,10 +315,10 @@ void AssetsBackupHandler::deleteBackup(QuaZip& zip) { } auto it = find_if(begin(_backups), end(_backups), [&](const std::vector::value_type& value) { - return value.filePath == zip.getZipName(); + return value.filePath == absoluteFilePath; }); if (it == end(_backups)) { - qCDebug(asset_backup) << "Could not find backup" << zip.getZipName() << "to delete."; + qCDebug(asset_backup) << "Could not find backup" << absoluteFilePath << "to delete."; return; } diff --git a/domain-server/src/AssetsBackupHandler.h b/domain-server/src/AssetsBackupHandler.h index 1421ddd400..a4b62f563d 100644 --- a/domain-server/src/AssetsBackupHandler.h +++ b/domain-server/src/AssetsBackupHandler.h @@ -37,7 +37,7 @@ public: void loadBackup(QuaZip& zip) override; void createBackup(QuaZip& zip) override; void recoverBackup(QuaZip& zip) override; - void deleteBackup(QuaZip& zip) override; + void deleteBackup(const QString& absoluteFilePath) override; void consolidateBackup(QuaZip& zip) override; bool operationInProgress() { return getRecoveryStatus().first; } diff --git a/domain-server/src/BackupHandler.h b/domain-server/src/BackupHandler.h index 1bd40cd9e4..7d876cec01 100644 --- a/domain-server/src/BackupHandler.h +++ b/domain-server/src/BackupHandler.h @@ -30,7 +30,7 @@ public: virtual void loadBackup(QuaZip& zip) = 0; virtual void createBackup(QuaZip& zip) = 0; virtual void recoverBackup(QuaZip& zip) = 0; - virtual void deleteBackup(QuaZip& zip) = 0; + virtual void deleteBackup(const QString& absoluteFilePath) = 0; virtual void consolidateBackup(QuaZip& zip) = 0; }; using BackupHandlerPointer = std::unique_ptr; diff --git a/domain-server/src/ContentSettingsBackupHandler.h b/domain-server/src/ContentSettingsBackupHandler.h index 8a81392513..ba252c862c 100644 --- a/domain-server/src/ContentSettingsBackupHandler.h +++ b/domain-server/src/ContentSettingsBackupHandler.h @@ -28,7 +28,7 @@ public: void recoverBackup(QuaZip& zip) override; - void deleteBackup(QuaZip& zip) override {} + void deleteBackup(const QString& absoluteFilePath) override {} void consolidateBackup(QuaZip& zip) override {} private: diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 5eb4e7627f..bf388ce63e 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -89,8 +89,7 @@ void DomainContentBackupManager::parseSettings(const QJsonObject& settings) { } auto name = obj["Name"].toString(); - auto format = obj["format"].toString(); - format = name.replace(" ", "_").toLower(); + auto format = name.replace(" ", "_").toLower(); qCDebug(domain_server) << " Name:" << name; qCDebug(domain_server) << " format:" << format; @@ -116,6 +115,12 @@ void DomainContentBackupManager::parseSettings(const QJsonObject& settings) { } } +void DomainContentBackupManager::refreshBackupRules() { + for (auto& backup : _backupRules) { + backup.lastBackupSeconds = getMostRecentBackupTimeInSecs(backup.extensionFormat); + } +} + int64_t DomainContentBackupManager::getMostRecentBackupTimeInSecs(const QString& format) { int64_t mostRecentBackupInSecs = 0; @@ -235,8 +240,16 @@ void DomainContentBackupManager::deleteBackup(MiniPromise::Promise promise, cons } QDir backupDir { _backupDirectory }; - QFile backupFile { backupDir.filePath(backupName) }; + auto absoluteFilePath { backupDir.filePath(backupName) }; + QFile backupFile { absoluteFilePath }; auto success = backupFile.remove(); + + refreshBackupRules(); + + for (auto& handler : _backupHandlers) { + handler->deleteBackup(absoluteFilePath); + } + promise->resolve({ { "success", success } }); diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h index 790dff0fb4..4ec3d7bcc7 100644 --- a/domain-server/src/DomainContentBackupManager.h +++ b/domain-server/src/DomainContentBackupManager.h @@ -68,6 +68,7 @@ protected: void load(); void backup(); void removeOldBackupVersions(const BackupRule& rule); + void refreshBackupRules(); bool getMostRecentBackup(const QString& format, QString& mostRecentBackupFileName, QDateTime& mostRecentBackupTime); int64_t getMostRecentBackupTimeInSecs(const QString& format); void parseSettings(const QJsonObject& settings); diff --git a/domain-server/src/EntitiesBackupHandler.h b/domain-server/src/EntitiesBackupHandler.h index 1a6110f1cd..c143fe5774 100644 --- a/domain-server/src/EntitiesBackupHandler.h +++ b/domain-server/src/EntitiesBackupHandler.h @@ -30,7 +30,7 @@ public: void recoverBackup(QuaZip& zip) override; // Delete a skeleton backup - void deleteBackup(QuaZip& zip) override {} + void deleteBackup(const QString& absoluteFilePath) override {} // Create a full backup void consolidateBackup(QuaZip& zip) override {} From bb8caa0ce3edccec0ed6d6d31c06f20643d4e508 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 16 Feb 2018 13:07:45 -0800 Subject: [PATCH 328/569] Update HTTPConnection to not use QIODevice when given a QByteArray --- .../embedded-webserver/src/HTTPConnection.cpp | 72 ++++++++++--------- .../embedded-webserver/src/HTTPConnection.h | 1 + 2 files changed, 38 insertions(+), 35 deletions(-) diff --git a/libraries/embedded-webserver/src/HTTPConnection.cpp b/libraries/embedded-webserver/src/HTTPConnection.cpp index 6d0126b3d1..3f6f8d64ee 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.cpp +++ b/libraries/embedded-webserver/src/HTTPConnection.cpp @@ -133,57 +133,33 @@ QList HTTPConnection::parseFormData() const { } void HTTPConnection::respond(const char* code, const QByteArray& content, const char* contentType, const Headers& headers) { - QByteArray data(content); - auto device { std::unique_ptr(new QBuffer()) }; - device->setBuffer(new QByteArray(content)); - if (device->open(QIODevice::ReadOnly)) { - respond(code, std::move(device), contentType, headers); - } else { - qCritical() << "Error opening QBuffer to respond to " << _requestUrl.path(); - } + respondWithStatusAndHeaders(code, contentType, headers, content.size()); + + _socket->write(content); + + _socket->disconnectFromHost(); + + // make sure we receive no further read notifications + disconnect(_socket, &QTcpSocket::readyRead, this, nullptr); } void HTTPConnection::respond(const char* code, std::unique_ptr device, const char* contentType, const Headers& headers) { _responseDevice = std::move(device); - _socket->write("HTTP/1.1 "); - if (_responseDevice->isSequential()) { qWarning() << "Error responding to HTTPConnection: sequential IO devices not supported"; - _socket->write(StatusCode500); - _socket->write("\r\n"); + respondWithStatusAndHeaders(StatusCode500, contentType, headers, 0); _socket->disconnect(SIGNAL(readyRead()), this); _socket->disconnectFromHost(); return; } - _socket->write(code); - _socket->write("\r\n"); - - for (Headers::const_iterator it = headers.constBegin(), end = headers.constEnd(); - it != end; it++) { - _socket->write(it.key()); - _socket->write(": "); - _socket->write(it.value()); - _socket->write("\r\n"); - } - - int csize = _responseDevice->size(); - if (csize > 0) { - _socket->write("Content-Length: "); - _socket->write(QByteArray::number(csize)); - _socket->write("\r\n"); - - _socket->write("Content-Type: "); - _socket->write(contentType); - _socket->write("\r\n"); - } - _socket->write("Connection: close\r\n\r\n"); + int totalToBeWritten = _responseDevice->size(); + respondWithStatusAndHeaders(code, contentType, headers, totalToBeWritten); if (_responseDevice->atEnd()) { _socket->disconnectFromHost(); } else { - int totalToBeWritten = csize; connect(_socket, &QTcpSocket::bytesWritten, this, [this, totalToBeWritten](size_t bytes) mutable { constexpr size_t HTTP_RESPONSE_CHUNK_SIZE = 1024 * 10; if (!_responseDevice->atEnd()) { @@ -201,6 +177,32 @@ void HTTPConnection::respond(const char* code, std::unique_ptr device disconnect(_socket, &QTcpSocket::readyRead, this, nullptr); } +void HTTPConnection::respondWithStatusAndHeaders(const char* code, const char* contentType, const Headers& headers, int64_t contentLength) { + _socket->write("HTTP/1.1 "); + + _socket->write(code); + _socket->write("\r\n"); + + for (Headers::const_iterator it = headers.constBegin(), end = headers.constEnd(); + it != end; it++) { + _socket->write(it.key()); + _socket->write(": "); + _socket->write(it.value()); + _socket->write("\r\n"); + } + + if (contentLength > 0) { + _socket->write("Content-Length: "); + _socket->write(QByteArray::number(contentLength)); + _socket->write("\r\n"); + + _socket->write("Content-Type: "); + _socket->write(contentType); + _socket->write("\r\n"); + } + _socket->write("Connection: close\r\n\r\n"); +} + void HTTPConnection::readRequest() { if (!_socket->canReadLine()) { return; diff --git a/libraries/embedded-webserver/src/HTTPConnection.h b/libraries/embedded-webserver/src/HTTPConnection.h index a020dfdca9..ec00864514 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.h +++ b/libraries/embedded-webserver/src/HTTPConnection.h @@ -105,6 +105,7 @@ protected slots: void readContent (); protected: + void respondWithStatusAndHeaders(const char* code, const char* contentType, const Headers& headers, int64_t size); /// The parent HTTP manager HTTPManager* _parentManager; From ec3580f5964187fd898ce735e029544d57856970 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 16 Feb 2018 13:08:57 -0800 Subject: [PATCH 329/569] Fix recovery ID not being recorded --- domain-server/src/DomainContentBackupManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index bf388ce63e..8ea3d2ba90 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -281,6 +281,7 @@ void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise, success = false; } else { _isRecovering = true; + _recoveryFilename = backupName; for (auto& handler : _backupHandlers) { handler->recoverBackup(zip); } From 0230abea790ddb7917ec366ae9b7b87c9038d3c4 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 16 Feb 2018 13:54:35 -0800 Subject: [PATCH 330/569] Fix backup loading not getting auto backups --- .../src/DomainContentBackupManager.cpp | 108 +++++++++++------- .../src/DomainContentBackupManager.h | 13 ++- domain-server/src/DomainServer.cpp | 4 +- 3 files changed, 79 insertions(+), 46 deletions(-) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 8ea3d2ba90..db924c4e4f 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -300,12 +300,7 @@ void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise, }); } -void DomainContentBackupManager::getAllBackupInformation(MiniPromise::Promise promise) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "getAllBackupInformation", Q_ARG(MiniPromise::Promise, promise)); - return; - } - +std::vector DomainContentBackupManager::getAllBackups() { QDir backupDir { _backupDirectory }; auto matchingFiles = backupDir.entryInfoList({ AUTOMATIC_BACKUP_PREFIX + "*.zip", MANUAL_BACKUP_PREFIX + "*.zip" }, @@ -315,7 +310,7 @@ void DomainContentBackupManager::getAllBackupInformation(MiniPromise::Promise pr QString dateTimeFormat = "(" + DATETIME_FORMAT_RE + ")"; QRegExp backupNameFormat { prefixFormat + nameFormat + "-" + dateTimeFormat + "\\.zip" }; - QVariantList backups; + std::vector backups; for (const auto& fileInfo : matchingFiles) { auto fileName = fileInfo.fileName(); @@ -338,17 +333,53 @@ void DomainContentBackupManager::getAllBackupInformation(MiniPromise::Promise pr availabilityProgress += progress / _backupHandlers.size(); } - backups.push_back(QVariantMap({ - { "id", fileInfo.fileName() }, - { "name", name }, - { "createdAtMillis", createdAt.toMSecsSinceEpoch() }, - { "isAvailable", isAvailable }, - { "availabilityProgress", availabilityProgress }, - { "isManualBackup", type == MANUAL_BACKUP_PREFIX } - })); + backups.push_back( + { fileInfo.fileName(), name, fileInfo.absoluteFilePath(), createdAt, type == MANUAL_BACKUP_PREFIX }); } } + return backups; +} + +void DomainContentBackupManager::getAllBackupsAndStatus(MiniPromise::Promise promise) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "getAllBackupInformation", Q_ARG(MiniPromise::Promise, promise)); + return; + } + + QDir backupDir { _backupDirectory }; + auto matchingFiles = + backupDir.entryInfoList({ AUTOMATIC_BACKUP_PREFIX + "*.zip", MANUAL_BACKUP_PREFIX + "*.zip" }, + QDir::Files | QDir::NoSymLinks, QDir::Name); + QString prefixFormat = "(" + QRegExp::escape(AUTOMATIC_BACKUP_PREFIX) + "|" + QRegExp::escape(MANUAL_BACKUP_PREFIX) + ")"; + QString nameFormat = "(.+)"; + QString dateTimeFormat = "(" + DATETIME_FORMAT_RE + ")"; + QRegExp backupNameFormat { prefixFormat + nameFormat + "-" + dateTimeFormat + "\\.zip" }; + + auto backups = getAllBackups(); + + QVariantList variantBackups; + + for (auto& backup : backups) { + bool isAvailable { true }; + float availabilityProgress { 0.0f }; + for (auto& handler : _backupHandlers) { + bool handlerIsAvailable { true }; + float progress { 0.0f }; + std::tie(handlerIsAvailable, progress) = handler->isAvailable(backup.absolutePath); + isAvailable &= handlerIsAvailable; + availabilityProgress += progress / _backupHandlers.size(); + } + variantBackups.push_back(QVariantMap({ + { "id", backup.id }, + { "name", backup.name }, + { "createdAtMillis", backup.createdAt.toMSecsSinceEpoch() }, + { "isAvailable", isAvailable }, + { "availabilityProgress", availabilityProgress }, + { "isManualBackup", backup.isManualBackup } + })); + } + float recoveryProgress = 0.0f; bool isRecovering = _isRecovering.load(); if (_isRecovering) { @@ -365,7 +396,7 @@ void DomainContentBackupManager::getAllBackupInformation(MiniPromise::Promise pr }; QVariantMap info { - { "backups", backups }, + { "backups", variantBackups }, { "status", status } }; @@ -404,32 +435,27 @@ void DomainContentBackupManager::removeOldBackupVersions(const BackupRule& rule) } void DomainContentBackupManager::load() { - QDir backupDir { _backupDirectory }; - if (backupDir.exists()) { - - auto matchingFiles = backupDir.entryInfoList({ "backup-*.zip" }, QDir::Files | QDir::NoSymLinks, QDir::Name); - - for (const auto& file : matchingFiles) { - QFile backupFile { file.absoluteFilePath() }; - if (!backupFile.open(QIODevice::ReadOnly)) { - qCritical() << "Could not open file:" << file.absoluteFilePath(); - qCritical() << " ERROR:" << backupFile.errorString(); - continue; - } - - QuaZip zip { &backupFile }; - if (!zip.open(QuaZip::mdUnzip)) { - qCritical() << "Could not open backup archive:" << file.absoluteFilePath(); - qCritical() << " ERROR:" << zip.getZipError(); - continue; - } - - for (auto& handler : _backupHandlers) { - handler->loadBackup(zip); - } - - zip.close(); + auto backups = getAllBackups(); + for (auto& backup : backups) { + QFile backupFile{ backup.absolutePath }; + if (!backupFile.open(QIODevice::ReadOnly)) { + qCritical() << "Could not open file:" << backup.absolutePath; + qCritical() << " ERROR:" << backupFile.errorString(); + continue; } + + QuaZip zip{ &backupFile }; + if (!zip.open(QuaZip::mdUnzip)) { + qCritical() << "Could not open backup archive:" << backup.absolutePath; + qCritical() << " ERROR:" << zip.getZipError(); + continue; + } + + for (auto& handler : _backupHandlers) { + handler->loadBackup(zip); + } + + zip.close(); } } diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h index 4ec3d7bcc7..2cfda4b650 100644 --- a/domain-server/src/DomainContentBackupManager.h +++ b/domain-server/src/DomainContentBackupManager.h @@ -24,6 +24,14 @@ #include +struct BackupItemInfo { + QString id; + QString name; + QString absolutePath; + QDateTime createdAt; + bool isManualBackup; +}; + class DomainContentBackupManager : public GenericThread { Q_OBJECT public: @@ -43,14 +51,13 @@ public: int persistInterval = DEFAULT_PERSIST_INTERVAL, bool debugTimestampNow = false); + std::vector getAllBackups(); void addBackupHandler(BackupHandlerPointer handler); - void aboutToFinish(); /// call this to inform the persist thread that the owner is about to finish to support final persist - void replaceData(QByteArray data); public slots: - void getAllBackupInformation(MiniPromise::Promise promise); + void getAllBackupsAndStatus(MiniPromise::Promise promise); void createManualBackup(MiniPromise::Promise promise, const QString& name); void recoverFromBackup(MiniPromise::Promise promise, const QString& backupName); void deleteBackup(MiniPromise::Promise promise, const QString& backupName); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index fe145b341b..157eaa483f 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2128,13 +2128,13 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return true; } else if (url.path() == URI_API_BACKUPS) { - auto deferred = makePromise("getAllBackupInformation"); + auto deferred = makePromise("getAllBackupsAndStatus"); deferred->then([connection, JSON_MIME_TYPE](QString error, QVariantMap result) { QJsonDocument docJSON(QJsonObject::fromVariantMap(result)); connection->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); }); - _contentManager->getAllBackupInformation(deferred); + _contentManager->getAllBackupsAndStatus(deferred); return true; } else if (url.path().startsWith(URI_API_BACKUPS_ID)) { auto id = url.path().mid(QString(URI_API_BACKUPS_ID).length()); From 27c26bab8659b965229d451efb79edb6e4290615 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 16 Feb 2018 13:57:38 -0800 Subject: [PATCH 331/569] Fix ambiguous int64_t in HTTPConnection --- libraries/embedded-webserver/src/HTTPConnection.cpp | 2 +- libraries/embedded-webserver/src/HTTPConnection.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/embedded-webserver/src/HTTPConnection.cpp b/libraries/embedded-webserver/src/HTTPConnection.cpp index 3f6f8d64ee..00879e1380 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.cpp +++ b/libraries/embedded-webserver/src/HTTPConnection.cpp @@ -177,7 +177,7 @@ void HTTPConnection::respond(const char* code, std::unique_ptr device disconnect(_socket, &QTcpSocket::readyRead, this, nullptr); } -void HTTPConnection::respondWithStatusAndHeaders(const char* code, const char* contentType, const Headers& headers, int64_t contentLength) { +void HTTPConnection::respondWithStatusAndHeaders(const char* code, const char* contentType, const Headers& headers, qint64 contentLength) { _socket->write("HTTP/1.1 "); _socket->write(code); diff --git a/libraries/embedded-webserver/src/HTTPConnection.h b/libraries/embedded-webserver/src/HTTPConnection.h index ec00864514..60408d4325 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.h +++ b/libraries/embedded-webserver/src/HTTPConnection.h @@ -105,7 +105,7 @@ protected slots: void readContent (); protected: - void respondWithStatusAndHeaders(const char* code, const char* contentType, const Headers& headers, int64_t size); + void respondWithStatusAndHeaders(const char* code, const char* contentType, const Headers& headers, qint64 size); /// The parent HTTP manager HTTPManager* _parentManager; From 4c1f22f84e62213b3de4e7cb658a0473809a37d3 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Fri, 16 Feb 2018 14:01:33 -0800 Subject: [PATCH 332/569] Models and Avatar cast shadows (box doesn't, yet). --- interface/src/avatar/MyAvatar.cpp | 5 +++- .../src/RenderableEntityItem.cpp | 1 + libraries/entities/src/EntityItem.cpp | 28 +++++++++++++++++++ libraries/entities/src/EntityItem.h | 5 ++++ libraries/entities/src/ModelEntityItem.cpp | 28 ------------------- libraries/entities/src/ModelEntityItem.h | 5 ---- .../render-utils/src/MeshPartPayload.cpp | 2 +- 7 files changed, 39 insertions(+), 35 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 97b8c71a32..c70dc9df98 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1112,7 +1112,7 @@ void MyAvatar::setEnableDebugDrawIKChains(bool isEnabled) { void MyAvatar::setEnableMeshVisible(bool isEnabled) { _skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); - _skeletonModel->setCanCastShadow(true, qApp->getMain3DScene()); + _skeletonModel->setCanCastShadow(isEnabled, qApp->getMain3DScene()); } void MyAvatar::setEnableInverseKinematics(bool isEnabled) { @@ -2010,8 +2010,11 @@ void MyAvatar::preDisplaySide(RenderArgs* renderArgs) { _attachmentData[i].jointName.compare("RightEye", Qt::CaseInsensitive) == 0 || _attachmentData[i].jointName.compare("HeadTop_End", Qt::CaseInsensitive) == 0 || _attachmentData[i].jointName.compare("Face", Qt::CaseInsensitive) == 0) { + _attachmentModels[i]->setVisibleInScene(shouldDrawHead, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); + + _attachmentModels[i]->setCanCastShadow(shouldDrawHead, qApp->getMain3DScene()); } } } diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index aca2f4d35b..7f2f57d1ac 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -371,6 +371,7 @@ void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transa _moving = entity->isMovingRelativeToParent(); _visible = entity->getVisible(); + _canCastShadow = entity->getCanCastShadow(); _cauterized = entity->getCauterized(); _needsRenderUpdate = false; }); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index ed13a46414..e14d0b6757 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -99,6 +99,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_REGISTRATION_POINT; requestedProperties += PROP_ANGULAR_DAMPING; requestedProperties += PROP_VISIBLE; + requestedProperties += PROP_CAN_CAST_SHADOW; requestedProperties += PROP_COLLISIONLESS; requestedProperties += PROP_COLLISION_MASK; requestedProperties += PROP_DYNAMIC; @@ -257,6 +258,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, getRegistrationPoint()); APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, getAngularDamping()); APPEND_ENTITY_PROPERTY(PROP_VISIBLE, getVisible()); + APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, getCanCastShadow()); APPEND_ENTITY_PROPERTY(PROP_COLLISIONLESS, getCollisionless()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_MASK, getCollisionMask()); APPEND_ENTITY_PROPERTY(PROP_DYNAMIC, getDynamic()); @@ -807,6 +809,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, setAngularDamping); READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible); + READ_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow); READ_ENTITY_PROPERTY(PROP_COLLISIONLESS, bool, setCollisionless); READ_ENTITY_PROPERTY(PROP_COLLISION_MASK, uint8_t, setCollisionMask); READ_ENTITY_PROPERTY(PROP_DYNAMIC, bool, setDynamic); @@ -900,6 +903,7 @@ void EntityItem::debugDump() const { qCDebug(entities, " edited ago:%f", (double)getEditedAgo()); qCDebug(entities, " position:%f,%f,%f", (double)position.x, (double)position.y, (double)position.z); qCDebug(entities) << " dimensions:" << getScaledDimensions(); + qCDebug(entities) << " can cast shadow" << getCanCastShadow(); } // adjust any internal timestamps to fix clock skew for this server @@ -1242,6 +1246,7 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularDamping, getAngularDamping); COPY_ENTITY_PROPERTY_TO_PROPERTIES(localRenderAlpha, getLocalRenderAlpha); COPY_ENTITY_PROPERTY_TO_PROPERTIES(visible, getVisible); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(canCastShadow, getCanCastShadow); COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionless, getCollisionless); COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionMask, getCollisionMask); COPY_ENTITY_PROPERTY_TO_PROPERTIES(dynamic, getDynamic); @@ -1354,6 +1359,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionSoundURL, setCollisionSoundURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(localRenderAlpha, setLocalRenderAlpha); SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(canCastShadow, setCanCastShadow); SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData); // Certifiable Properties @@ -2731,6 +2737,28 @@ void EntityItem::setVisible(bool value) { } } +bool EntityItem::getCanCastShadow() const { + bool result; + withReadLock([&] { + result = _canCastShadow; + }); + return result; +} + +void EntityItem::setCanCastShadow(bool value) { + bool changed = false; + withWriteLock([&] { + if (_canCastShadow != value) { + changed = true; + _canCastShadow = value; + } + }); + + if (changed) { + emit requestRenderUpdate(); + } +} + bool EntityItem::isChildOfMyAvatar() const { QUuid ancestorID = findAncestorOfType(NestableType::Avatar); return !ancestorID.isNull() && (ancestorID == Physics::getSessionUUID() || ancestorID == AVATAR_SELF_ID); diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 5f84bcc311..19a1a9c88e 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -273,6 +273,10 @@ public: bool getVisible() const; void setVisible(bool value); + + bool getCanCastShadow() const; + void setCanCastShadow(bool value); + inline bool isVisible() const { return getVisible(); } inline bool isInvisible() const { return !getVisible(); } @@ -543,6 +547,7 @@ protected: glm::vec3 _registrationPoint { ENTITY_ITEM_DEFAULT_REGISTRATION_POINT }; float _angularDamping { ENTITY_ITEM_DEFAULT_ANGULAR_DAMPING }; bool _visible { ENTITY_ITEM_DEFAULT_VISIBLE }; + bool _canCastShadow{ ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW }; bool _collisionless { ENTITY_ITEM_DEFAULT_COLLISIONLESS }; uint8_t _collisionMask { ENTITY_COLLISION_MASK_DEFAULT }; bool _dynamic { ENTITY_ITEM_DEFAULT_DYNAMIC }; diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index b1edd47a67..a4fe8e6b1e 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -54,7 +54,6 @@ void ModelEntityItem::setTextures(const QString& textures) { EntityItemProperties ModelEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class - COPY_ENTITY_PROPERTY_TO_PROPERTIES(canCastShadow, getCanCastShadow); COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getXColor); COPY_ENTITY_PROPERTY_TO_PROPERTIES(modelURL, getModelURL); COPY_ENTITY_PROPERTY_TO_PROPERTIES(compoundShapeURL, getCompoundShapeURL); @@ -74,7 +73,6 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = false; somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class - SET_ENTITY_PROPERTY_FROM_PROPERTIES(canCastShadow, setCanCastShadow); SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(modelURL, setModelURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); @@ -116,7 +114,6 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, const unsigned char* dataAt = data; bool animationPropertiesChanged = false; - READ_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow); READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor); READ_ENTITY_PROPERTY(PROP_MODEL_URL, QString, setModelURL); READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); @@ -153,7 +150,6 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); - requestedProperties += PROP_CAN_CAST_SHADOW; requestedProperties += PROP_MODEL_URL; requestedProperties += PROP_COMPOUND_SHAPE_URL; requestedProperties += PROP_TEXTURES; @@ -178,7 +174,6 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit bool successPropertyFits = true; - APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, getCanCastShadow()); APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_MODEL_URL, getModelURL()); APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL()); @@ -295,7 +290,6 @@ void ModelEntityItem::updateFrameCount() { } void ModelEntityItem::debugDump() const { - qCDebug(entities) << " can cast shadow" << getCanCastShadow(); qCDebug(entities) << "ModelEntityItem id:" << getEntityItemID(); qCDebug(entities) << " edited ago:" << getEditedAgo(); qCDebug(entities) << " position:" << getWorldPosition(); @@ -728,25 +722,3 @@ bool ModelEntityItem::isAnimatingSomething() const { (_animationProperties.getFPS() != 0.0f); }); } - -bool ModelEntityItem::getCanCastShadow() const { - bool result; - withReadLock([&] { - result = _canCastShadow; - }); - return result; -} - -void ModelEntityItem::setCanCastShadow(bool value) { - bool changed = false; - withWriteLock([&] { - if (_canCastShadow != value) { - changed = true; - _canCastShadow = value; - } - }); - - if (changed) { - emit requestRenderUpdate(); - } -} diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 791eebb7d9..c2109ba51f 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -131,9 +131,6 @@ public: QVector getJointTranslations() const; QVector getJointTranslationsSet() const; - bool getCanCastShadow() const; - void setCanCastShadow(bool value); - private: void setAnimationSettings(const QString& value); // only called for old bitstream format ShapeType computeTrueShapeType() const; @@ -174,8 +171,6 @@ protected: ShapeType _shapeType = SHAPE_TYPE_NONE; - bool _canCastShadow{ ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW }; - private: uint64_t _lastAnimated{ 0 }; AnimationPropertyGroup _previousAnimationProperties; diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 84f6c6aca3..178f5782dd 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -411,7 +411,7 @@ void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& render _worldBound.transform(boundTransform); } -void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled) { +void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, bool canCastShadow, uint8_t tagBits, bool isGroupCulled) { ItemKey::Builder builder; builder.withTypeShape(); From 936629ec1a41247b8ab47548b65921bd9ec7b081 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 16 Feb 2018 14:05:40 -0800 Subject: [PATCH 333/569] Update DomainContentBackupManager to use emplace_back where available --- domain-server/src/DomainContentBackupManager.cpp | 6 +++--- domain-server/src/DomainContentBackupManager.h | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index db924c4e4f..618af15e60 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -333,8 +333,8 @@ std::vector DomainContentBackupManager::getAllBackups() { availabilityProgress += progress / _backupHandlers.size(); } - backups.push_back( - { fileInfo.fileName(), name, fileInfo.absoluteFilePath(), createdAt, type == MANUAL_BACKUP_PREFIX }); + backups.emplace_back(fileInfo.fileName(), name, fileInfo.absoluteFilePath(), createdAt, + type == MANUAL_BACKUP_PREFIX); } } @@ -343,7 +343,7 @@ std::vector DomainContentBackupManager::getAllBackups() { void DomainContentBackupManager::getAllBackupsAndStatus(MiniPromise::Promise promise) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "getAllBackupInformation", Q_ARG(MiniPromise::Promise, promise)); + QMetaObject::invokeMethod(this, "getAllBackupsAndStatus", Q_ARG(MiniPromise::Promise, promise)); return; } diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h index 2cfda4b650..43e4cb16da 100644 --- a/domain-server/src/DomainContentBackupManager.h +++ b/domain-server/src/DomainContentBackupManager.h @@ -25,6 +25,13 @@ #include struct BackupItemInfo { + BackupItemInfo(QString pId, QString pName, QString pAbsolutePath, QDateTime pCreatedAt, bool pIsManualBackup) + : id(pId) + , name(pName) + , absolutePath(pAbsolutePath) + , createdAt(pCreatedAt) + , isManualBackup(pIsManualBackup){}; + QString id; QString name; QString absolutePath; From 41d7d7efbbf4ec675f04a85e5a5f9f8e083f98a2 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 16 Feb 2018 14:33:19 -0800 Subject: [PATCH 334/569] Fix initializer list style --- domain-server/src/DomainContentBackupManager.h | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h index 43e4cb16da..f1aa4acab2 100644 --- a/domain-server/src/DomainContentBackupManager.h +++ b/domain-server/src/DomainContentBackupManager.h @@ -25,12 +25,8 @@ #include struct BackupItemInfo { - BackupItemInfo(QString pId, QString pName, QString pAbsolutePath, QDateTime pCreatedAt, bool pIsManualBackup) - : id(pId) - , name(pName) - , absolutePath(pAbsolutePath) - , createdAt(pCreatedAt) - , isManualBackup(pIsManualBackup){}; + BackupItemInfo(QString pId, QString pName, QString pAbsolutePath, QDateTime pCreatedAt, bool pIsManualBackup) : + id(pId), name(pName), absolutePath(pAbsolutePath), createdAt(pCreatedAt), isManualBackup(pIsManualBackup) { }; QString id; QString name; From a2072062f18841f47d2ba31927d9cdaf96c23050 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 16 Feb 2018 14:33:33 -0800 Subject: [PATCH 335/569] Fix recovery filename not being reset when recovery complete --- domain-server/src/DomainContentBackupManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 618af15e60..dc749ad182 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -160,6 +160,7 @@ bool DomainContentBackupManager::process() { if (!isStillRecovering) { _isRecovering = false; + _recoveryFilename = ""; emit recoveryCompleted(); } } From f4cde44e6af1a4863d771ef6fc4ade79e4c04c32 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 16 Feb 2018 15:25:29 -0800 Subject: [PATCH 336/569] Fix indentation of brace initialization --- domain-server/src/DomainContentBackupManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index dc749ad182..a711d2112d 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -438,14 +438,14 @@ void DomainContentBackupManager::removeOldBackupVersions(const BackupRule& rule) void DomainContentBackupManager::load() { auto backups = getAllBackups(); for (auto& backup : backups) { - QFile backupFile{ backup.absolutePath }; + QFile backupFile { backup.absolutePath }; if (!backupFile.open(QIODevice::ReadOnly)) { qCritical() << "Could not open file:" << backup.absolutePath; qCritical() << " ERROR:" << backupFile.errorString(); continue; } - QuaZip zip{ &backupFile }; + QuaZip zip { &backupFile }; if (!zip.open(QuaZip::mdUnzip)) { qCritical() << "Could not open backup archive:" << backup.absolutePath; qCritical() << " ERROR:" << zip.getZipError(); From 29ceffd7cce43ba0c79e15a5ba2889df2b50671d Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 14 Feb 2018 12:05:23 -0800 Subject: [PATCH 337/569] add sections to content page for backup/restore --- .../resources/web/content/index.shtml | 2 + .../web/content/js/bootstrap-sortable.min.js | 1 + .../resources/web/content/js/content.js | 193 +++++++++++++++--- .../web/content/js/moment-locale.min.js | 1 + .../resources/web/css/bootstrap-sortable.css | 110 ++++++++++ domain-server/resources/web/css/style.css | 52 ++++- domain-server/resources/web/header.html | 1 + .../resources/web/js/base-settings.js | 8 +- .../resources/web/js/domain-server.js | 58 ++++-- domain-server/resources/web/js/shared.js | 6 +- .../resources/web/settings/js/settings.js | 18 +- 11 files changed, 387 insertions(+), 63 deletions(-) create mode 100755 domain-server/resources/web/content/js/bootstrap-sortable.min.js create mode 100644 domain-server/resources/web/content/js/moment-locale.min.js create mode 100755 domain-server/resources/web/css/bootstrap-sortable.css diff --git a/domain-server/resources/web/content/index.shtml b/domain-server/resources/web/content/index.shtml index 9b507f7826..f934faa976 100644 --- a/domain-server/resources/web/content/index.shtml +++ b/domain-server/resources/web/content/index.shtml @@ -14,6 +14,8 @@ + + diff --git a/domain-server/resources/web/content/js/bootstrap-sortable.min.js b/domain-server/resources/web/content/js/bootstrap-sortable.min.js new file mode 100755 index 0000000000..ac21ebe969 --- /dev/null +++ b/domain-server/resources/web/content/js/bootstrap-sortable.min.js @@ -0,0 +1 @@ +!function(t,e){"use strict";"function"==typeof define&&define.amd?define("tinysort",function(){return e}):t.tinysort=e}(this,function(){"use strict";function t(t,e){for(var r,a=t.length,o=a;o--;)e(t[r=a-o-1],r)}function e(t,e,r){for(var o in e)(r||t[o]===a)&&(t[o]=e[o]);return t}function r(t,e,r){f.push({prepare:t,sort:e,sortBy:r})}var a,o,n=!1,s=null,i=window,d=i.document,l=parseFloat,c=/(-?\d+\.?\d*)\s*$/g,u=/(\d+\.?\d*)\s*$/g,f=[],h=0,p=0,v=String.fromCharCode(4095),m={selector:s,order:"asc",attr:s,data:s,useVal:n,place:"org",returns:n,cases:n,natural:n,forceStrings:n,ignoreDashes:n,sortFunction:s,useFlex:n,emptyEnd:n};return i.Element&&((o=Element.prototype).matchesSelector=o.matchesSelector||o.mozMatchesSelector||o.msMatchesSelector||o.oMatchesSelector||o.webkitMatchesSelector||function(t){for(var e=(this.parentNode||this.document).querySelectorAll(t),r=-1;e[++r]&&e[r]!=this;);return!!e[r]}),e(r,{loop:t}),e(function(r,o){function i(t){var r=!!t.selector,a=r&&":"===t.selector[0],o=e(t||{},m);k.push(e({hasSelector:r,hasAttr:!(o.attr===s||""===o.attr),hasData:o.data!==s,hasFilter:a,sortReturnNumber:"asc"===o.order?1:-1},o))}function g(t,e,r){for(var a=r(t.toString()),o=r(e.toString()),n=0;a[n]&&o[n];n++)if(a[n]!==o[n]){var s=Number(a[n]),i=Number(o[n]);return s==a[n]&&i==o[n]?s-i:a[n]>o[n]?1:-1}return a.length-o.length}function b(t){for(var e,r,a=[],o=0,n=-1,s=0;e=(r=t.charAt(o++)).charCodeAt(0);){var i=46==e||e>=48&&57>=e;i!==s&&(a[++n]="",s=i),a[n]+=r}return a}function w(){return q.forEach(function(t){E.appendChild(t.elm)}),E}function y(t){var e=t.elm,r=d.createElement("div");return t.ghost=r,e.parentNode.insertBefore(r,e),t}function S(t,e){var r=t.ghost,a=r.parentNode;a.insertBefore(e,r),a.removeChild(r),delete t.ghost}function x(t,e){var r,a=t.elm;return e.selector&&(e.hasFilter?a.matchesSelector(e.selector)||(a=s):a=a.querySelector(e.selector)),e.hasAttr?r=a.getAttribute(e.attr):e.useVal?r=a.value||a.getAttribute("value"):e.hasData?r=a.getAttribute("data-"+e.data):a&&(r=a.textContent),C(r)&&(e.cases||(r=r.toLowerCase()),r=r.replace(/\s+/g," ")),null===r&&(r=v),r}function C(t){return"string"==typeof t}C(r)&&(r=d.querySelectorAll(r)),0===r.length&&console.warn("No elements to sort");var F,N,E=d.createDocumentFragment(),A=[],q=[],M=[],k=[],D=!0,z=r.length&&r[0].parentNode,Y=z.rootNode!==document,H=r.length&&(o===a||!1!==o.useFlex)&&!Y&&-1!==getComputedStyle(z,null).display.indexOf("flex");return function(){0===arguments.length?i({}):t(arguments,function(t){i(C(t)?{selector:t}:t)}),h=k.length}.apply(s,Array.prototype.slice.call(arguments,1)),t(r,function(t,e){N?N!==t.parentNode&&(D=!1):N=t.parentNode;var r=k[0],a=r.hasFilter,o=r.selector,n=!o||a&&t.matchesSelector(o)||o&&t.querySelector(o)?q:M,s={elm:t,pos:e,posn:n.length};A.push(s),n.push(s)}),F=q.slice(0),q.sort(function(e,r){var o=0;for(0!==p&&(p=0);0===o&&h>p;){var s=k[p],i=s.ignoreDashes?u:c;if(t(f,function(t){var e=t.prepare;e&&e(s)}),s.sortFunction)o=s.sortFunction(e,r);else if("rand"==s.order)o=Math.random()<.5?1:-1;else{var d=n,v=x(e,s),m=x(r,s),w=""===v||v===a,y=""===m||m===a;if(v===m)o=0;else if(s.emptyEnd&&(w||y))o=w&&y?0:w?1:-1;else{if(!s.forceStrings){var S=C(v)?v&&v.match(i):n,F=C(m)?m&&m.match(i):n;S&&F&&v.substr(0,v.length-S[0].length)==m.substr(0,m.length-F[0].length)&&(d=!n,v=l(S[0]),m=l(F[0]))}o=v===a||m===a?0:s.natural&&(isNaN(v)||isNaN(m))?g(v,m,b):m>v?-1:v>m?1:0}}t(f,function(t){var e=t.sort;e&&(o=e(s,d,v,m,o))}),0==(o*=s.sortReturnNumber)&&p++}return 0===o&&(o=e.pos>r.pos?1:-1),o}),function(){var t=q.length===A.length;if(D&&t)H?q.forEach(function(t,e){t.elm.style.order=e}):N?N.appendChild(w()):console.warn("parentNode has been removed");else{var e=k[0].place,r="start"===e,a="end"===e,o="first"===e,n="last"===e;if("org"===e)q.forEach(y),q.forEach(function(t,e){S(F[e],t.elm)});else if(r||a){var s=F[r?0:F.length-1],i=s&&s.elm.parentNode,d=i&&(r&&i.firstChild||i.lastChild);d&&(d!==s.elm&&(s={elm:d}),y(s),a&&i.appendChild(s.ghost),S(s,w()))}else(o||n)&&S(y(F[o?0:F.length-1]),w())}}(),q.map(function(t){return t.elm})},{plugin:r,defaults:m})}()),function(t,e){"function"==typeof define&&define.amd?define(["jquery","tinysort","moment"],e):e(t.jQuery,t.tinysort,t.moment||void 0)}(this,function(t,e,r){var a,o,n,s=t(document);function i(e){var s=void 0!==r;a=e.sign?e.sign:"arrow","default"==e.customSort&&(e.customSort=u),o=e.customSort||o||u,n=e.emptyEnd,t("table.sortable").each(function(){var a=t(this),o=!0===e.applyLast;a.find("span.sign").remove(),a.find("> thead [colspan]").each(function(){for(var e=parseFloat(t(this).attr("colspan")),r=1;r')}),a.find("> thead [rowspan]").each(function(){for(var e=t(this),r=parseFloat(e.attr("rowspan")),a=1;a')}}),a.find("> thead tr").each(function(e){t(this).find("th").each(function(r){var a=t(this);a.addClass("nosort").removeClass("up down"),a.attr("data-sortcolumn",r),a.attr("data-sortkey",r+"-"+e)})}),a.find("> thead .rowspan-compensate, .colspan-compensate").remove(),a.find("th").each(function(){var e=t(this);if(void 0!==e.attr("data-dateformat")&&s){var o=parseFloat(e.attr("data-sortcolumn"));a.find("td:nth-child("+(o+1)+")").each(function(){var a=t(this);a.attr("data-value",r(a.text(),e.attr("data-dateformat")).format("YYYY/MM/DD/HH/mm/ss"))})}else if(void 0!==e.attr("data-valueprovider")){o=parseFloat(e.attr("data-sortcolumn"));a.find("td:nth-child("+(o+1)+")").each(function(){var r=t(this);r.attr("data-value",new RegExp(e.attr("data-valueprovider")).exec(r.text())[0])})}}),a.find("td").each(function(){var e=t(this);void 0!==e.attr("data-dateformat")&&s?e.attr("data-value",r(e.text(),e.attr("data-dateformat")).format("YYYY/MM/DD/HH/mm/ss")):void 0!==e.attr("data-valueprovider")?e.attr("data-value",new RegExp(e.attr("data-valueprovider")).exec(e.text())[0]):void 0===e.attr("data-value")&&e.attr("data-value",e.text())});var n=c(a),i=n.bsSort;a.find('> thead th[data-defaultsort!="disabled"]').each(function(e){var r=t(this),a=r.closest("table.sortable");r.data("sortTable",a);var s=r.attr("data-sortkey"),d=o?n.lastSort:-1;i[s]=o?i[s]:r.attr("data-defaultsort"),void 0!==i[s]&&o===(s===d)&&(i[s]="asc"===i[s]?"desc":"asc",f(r,a))})})}function d(e){e.find("> tbody [rowspan]").each(function(){var e=t(this),r=parseFloat(e.attr("rowspan"));e.removeAttr("rowspan");var a=e.attr("rowspan-group")||function(){function t(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)}return t()+t()+"-"+t()+"-"+t()+"-"+t()+"-"+t()+t()+t()}();e.attr("rowspan-group",a),e.attr("rowspan-value",r);for(var o=e.parent("tr"),n=o.children().index(e),s=1;s thead th[data-defaultsort!="disabled"]').each(function(e){var a=t(this),o=a.attr("data-sortkey");r.bsSort[o]=a.attr("data-defaultsort"),void 0!==r.bsSort[o]&&(r.lastSort=o)}),e.data("bootstrap-sortable-context",r)),r}function u(t,r){e(t,r)}function f(e,r){r.trigger("before-sort"),d(r);var s=parseFloat(e.attr("data-sortcolumn")),i=c(r),l=i.bsSort;if(e.attr("colspan")){var u=parseFloat(e.data("mainsort"))||0,h=parseFloat(e.data("sortkey").split("-").pop());if(r.find("> thead tr").length-1>h)return void f(r.find('[data-sortkey="'+(s+u)+"-"+(h+1)+'"]'),r);s+=u}var p=e.attr("data-defaultsign")||a;if(r.find("> thead th").each(function(){t(this).removeClass("up").removeClass("down").addClass("nosort")}),t.browser.mozilla){var v=r.find("> thead div.mozilla");void 0!==v&&(v.find(".sign").remove(),v.parent().html(v.html())),e.wrapInner('
    '),e.children().eq(0).append('')}else r.find("> thead span.sign").remove(),e.append('');var m=e.attr("data-sortkey"),g="desc"!==e.attr("data-firstsort")?"desc":"asc",b=l[m]||g;i.lastSort!==m&&void 0!==l[m]||(b="asc"===b?"desc":"asc"),l[m]=b,i.lastSort=m,"desc"===l[m]?(e.find("span.sign").addClass("up"),e.addClass("up").removeClass("down nosort")):e.addClass("down").removeClass("up nosort");var w=r.children("tbody").children("tr"),y=[];t(w.filter('[data-disablesort="true"]').get().reverse()).each(function(e,r){var a=t(r);y.push({index:w.index(a),row:a}),a.remove()});var S=w.not('[data-disablesort="true"]');if(0!=S.length){var x="asc"===l[m]&&n;o(S,{emptyEnd:x,selector:"td:nth-child("+(s+1)+")",order:l[m],data:"value"})}t(y.reverse()).each(function(t,e){0===e.index?r.children("tbody").prepend(e.row):r.children("tbody").children("tr").eq(e.index-1).after(e.row)}),r.find("> tbody > tr > td.sorted,> thead th.sorted").removeClass("sorted"),S.find("td:eq("+s+")").addClass("sorted"),e.addClass("sorted"),r.find("> tbody [rowspan-group]").each(function(){for(var e=t(this),r=e.attr("rowspan-group"),a=e.parent("tr"),o=a.children().index(e);;){var n=a.next("tr");if(!n.is("tr"))break;var s=n.children().eq(o);if(s.attr("rowspan-group")!==r)break;var i=parseFloat(e.attr("rowspan"))||1;e.attr("rowspan",i+1),s.remove(),a=n}}),r.trigger("sorted")}if(t.bootstrapSortable=function(t){null==t?i({}):t.constructor===Boolean?i({applyLast:t}):void 0!==t.sortingHeader?l(t.sortingHeader):i(t)},s.on("click",'table.sortable>thead th[data-defaultsort!="disabled"]',function(t){l(this)}),!t.browser){t.browser={chrome:!1,mozilla:!1,opera:!1,msie:!1,safari:!1};var h=navigator.userAgent;t.each(t.browser,function(e){t.browser[e]=!!new RegExp(e,"i").test(h),t.browser.mozilla&&"mozilla"===e&&(t.browser.mozilla=!!new RegExp("firefox","i").test(h)),t.browser.chrome&&"safari"===e&&(t.browser.safari=!1)})}t(t.bootstrapSortable)}); \ No newline at end of file diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js index e448952c65..e2b653995f 100644 --- a/domain-server/resources/web/content/js/content.js +++ b/domain-server/resources/web/content/js/content.js @@ -1,37 +1,174 @@ $(document).ready(function(){ - Settings.afterReloadActions = function() {}; + var RESTORE_SETTINGS_UPLOAD_ID = 'restore-settings-button'; + var RESTORE_SETTINGS_FILE_ID = 'restore-settings-file'; - var frm = $('#upload-form'); - frm.submit(function (ev) { - $.ajax({ - type: frm.attr('method'), - url: frm.attr('action'), - data: new FormData($(this)[0]), - cache: false, - contentType: false, - processData: false, - success: function (data) { - swal({ - title: 'Uploaded', - type: 'success', - text: 'Your Entity Server is restarting to replace its local content with the uploaded file.', - confirmButtonText: 'OK' - }) - }, - error: function (data) { - swal({ - title: '', - type: 'error', - text: 'Your entities file could not be transferred to the Entity Server.
    Verify that the file is a .json or .json.gz entities file and try again.', - html: true, - confirmButtonText: 'OK', - }); + function setupBackupUpload() { + // construct the HTML needed for the settings backup panel + var html = "
    "; + + html += "Upload a Content Backup to replace the content of this domain"; + html += "
    Note: Your domain's content will be replaced by the content you upload, but the existing backup files of your domain's content will not immediately be changed.
    "; + + html += ""; + html += ""; + + html += "
    "; + + $('#' + Settings.UPLOAD_CONTENT_BACKUP_PANEL_ID + ' .panel-body').html(html); + } + + var GENERATE_ARCHIVE_BUTTON_ID = 'generate-archive-button'; + var AUTOMATIC_ARCHIVES_TABLE_ID = 'automatic-archives-table'; + var AUTOMATIC_ARCHIVES_TBODY_ID = 'automatic-archives-tbody'; + var MANUAL_ARCHIVES_TABLE_ID = 'manual-archives-table'; + var MANUAL_ARCHIVES_TBODY_ID = 'manual-archives-tbody'; + var automaticBackups = []; + var manualBackups = []; + + function setupContentArchives() { + + // construct the HTML needed for the content archives panel + var html = "
    "; + html += ""; + html += "Your domain server makes regular archives of the content in your domain. In the list below, you can see and download all of your domain content and settings backups." + html += "
    "; + html += ""; + + var backups_table_head = ""; + + html += backups_table_head; + html += "
    Archive NameArchive DateActions
    "; + html += "
    "; + html += ""; + html += "You can generate and download an archive of your domain content right now. You can also download, delete and restore any archive listed."; + html += ""; + html += "
    "; + html += ""; + html += backups_table_head; + html += "
    "; + + // put the base HTML in the content archives panel + $('#' + Settings.CONTENT_ARCHIVES_PANEL_ID + ' .panel-body').html(html); + } + + function reloadLatestBackups() { + // make a GET request to get backup information to populate the table + $.get('/api/backups', function(data) { + // split the returned data into manual and automatic manual backups + var splitBackups = _.partition(data.backups, function(value, index) { + return value.isManualBackup; + }); + + manualBackups = splitBackups[0]; + automaticBackups = splitBackups[1]; + + // populate the backups tables with the backups + function createBackupTableRow(backup) { + return "" + backup.name + "" + + moment(backup.createdAtMillis).format('lll') + + "" + + "" + + ""; } + + var automaticRows = ""; + + if (automaticBackups.length > 0) { + for (var backupIndex in automaticBackups) { + // create a table row for this backup and add it to the rows we'll put in the table body + automaticRows += createBackupTableRow(automaticBackups[backupIndex]); + } + } + + $('#' + AUTOMATIC_ARCHIVES_TBODY_ID).html(automaticRows); + + var manualRows = ""; + + if (manualBackups.length > 0) { + for (var backupIndex in manualBackups) { + // create a table row for this backup and add it to the rows we'll put in the table body + manualRows += createBackupTableRow(manualBackups[backupIndex]); + } + } + + $('#' + MANUAL_ARCHIVES_TBODY_ID).html(manualRows); + + // tell bootstrap sortable to update for the new rows + $.bootstrapSortable({ applyLast: true }); + + }).fail(function(){ + // we've hit the very rare case where we couldn't load the list of backups from the domain server + + // set our backups to empty + automaticBackups = []; + manualBackups = []; + + // replace the content archives panel with a simple error message + // stating that the user should reload the page + $('#' + Settings.CONTENT_ARCHIVES_PANEL_ID + ' .panel-body').html( + "
    " + + "There was a problem loading your list of automatic and manual content archives. Please reload the page to try again." + + "
    " + ); + + }).always(function(){ + // toggle showing or hiding the tables depending on if they have entries + $('#' + AUTOMATIC_ARCHIVES_TABLE_ID).toggle(automaticBackups.length > 0); + $('#' + MANUAL_ARCHIVES_TABLE_ID).toggle(manualBackups.length > 0); }); + } - ev.preventDefault(); + // handle click on manual archive creation button + $('body').on('click', '#' + GENERATE_ARCHIVE_BUTTON_ID, function(e) { + e.preventDefault(); - showSpinnerAlert("Uploading Entities File"); + // show a sweet alert to ask the user to provide a name for their content archive + swal({ + title: "Generate a Content Archive", + type: "input", + text: "This will capture the state of all the content in your domain right now, which you can save as a backup and restore from later.", + confirmButtonText: "Generate Archive", + showCancelButton: true, + closeOnConfirm: false, + inputPlaceholder: 'Archive Name' + }, function(inputValue){ + if (inputValue === false) { + return false; + } + + if (inputValue === "") { + swal.showInputError("Please give the content archive a name.") + return false; + } + + // post the provided archive name to ask the server to kick off a manual backup + $.ajax({ + type: 'POST', + url: '/api/backup', + data: { + 'name': inputValue + } + }).done(function(data) { + // since we successfully setup a new content archive, reload the table of archives + // which should show that this archive is pending creation + reloadContentArchives(); + }).fail(function(jqXHR, textStatus, errorThrown) { + + }); + + swal.close(); + }); }); + + Settings.extraGroupsAtIndex = Settings.extraContentGroupsAtIndex; + + Settings.afterReloadActions = function() { + setupBackupUpload(); + setupContentArchives(); + + // load the latest backups immediately + reloadLatestBackups(); + }; }); diff --git a/domain-server/resources/web/content/js/moment-locale.min.js b/domain-server/resources/web/content/js/moment-locale.min.js new file mode 100644 index 0000000000..fabea0c841 --- /dev/null +++ b/domain-server/resources/web/content/js/moment-locale.min.js @@ -0,0 +1 @@ +!function(e,a){"object"==typeof exports&&"undefined"!=typeof module?module.exports=a():"function"==typeof define&&define.amd?define(a):e.moment=a()}(this,function(){"use strict";function e(){return Ea.apply(null,arguments)}function a(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function t(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function s(e){return void 0===e}function n(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function d(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function r(e,a){var t,s=[];for(t=0;t0)for(t=0;t=0?t?"+":"":"-")+Math.pow(10,Math.max(0,n)).toString().substr(1)+s}function j(e,a,t,s){var n=s;"string"==typeof s&&(n=function(){return this[s]()}),e&&(Va[e]=n),a&&(Va[a[0]]=function(){return b(n.apply(this,arguments),a[1],a[2])}),t&&(Va[t]=function(){return this.localeData().ordinal(n.apply(this,arguments),e)})}function x(e){return e.match(/\[[\s\S]/)?e.replace(/^\[|\]$/g,""):e.replace(/\\/g,"")}function P(e,a){return e.isValid()?(a=O(a,e.localeData()),Ua[a]=Ua[a]||function(e){var a,t,s=e.match(Ca);for(a=0,t=s.length;a=0&&Ga.test(e);)e=e.replace(Ga,t),Ga.lastIndex=0,s-=1;return e}function W(e,a,t){ot[e]=D(a)?a:function(e,s){return e&&t?t:a}}function E(e,a){return _(ot,e)?ot[e](a._strict,a._locale):new RegExp(function(e){return A(e.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(e,a,t,s,n){return a||t||s||n}))}(e))}function A(e){return e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function F(e,a){var t,s=a;for("string"==typeof e&&(e=[e]),n(a)&&(s=function(e,t){t[a]=Y(e)}),t=0;t=0&&isFinite(a.getUTCFullYear())&&a.setUTCFullYear(e),a}function B(e,a,t){var s=7+a-t;return-((7+$(e,0,s).getUTCDay()-a)%7)+s-1}function q(e,a,t,s,n){var d,r,_=1+7*(a-1)+(7+t-s)%7+B(e,s,n);return _<=0?r=N(d=e-1)+_:_>N(e)?(d=e+1,r=_-N(e)):(d=e,r=_),{year:d,dayOfYear:r}}function Q(e,a,t){var s,n,d=B(e.year(),a,t),r=Math.floor((e.dayOfYear()-d-1)/7)+1;return r<1?s=r+X(n=e.year()-1,a,t):r>X(e.year(),a,t)?(s=r-X(e.year(),a,t),n=e.year()+1):(n=e.year(),s=r),{week:s,year:n}}function X(e,a,t){var s=B(e,a,t),n=B(e+1,a,t);return(N(e)-s+n)/7}function ee(){function e(e,a){return a.length-e.length}var a,t,s,n,d,r=[],_=[],i=[],m=[];for(a=0;a<7;a++)t=o([2e3,1]).day(a),s=this.weekdaysMin(t,""),n=this.weekdaysShort(t,""),d=this.weekdays(t,""),r.push(s),_.push(n),i.push(d),m.push(s),m.push(n),m.push(d);for(r.sort(e),_.sort(e),i.sort(e),m.sort(e),a=0;a<7;a++)_[a]=A(_[a]),i[a]=A(i[a]),m[a]=A(m[a]);this._weekdaysRegex=new RegExp("^("+m.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+i.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+_.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+r.join("|")+")","i")}function ae(){return this.hours()%12||12}function te(e,a){j(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),a)})}function se(e,a){return a._meridiemParse}function ne(e){return e?e.toLowerCase().replace("_","-"):e}function de(e){var a=null;if(!At[e]&&"undefined"!=typeof module&&module&&module.exports)try{a=Ot._abbr;require("./locale/"+e),re(a)}catch(e){}return At[e]}function re(e,a){var t;return e&&(t=s(a)?ie(e):_e(e,a))&&(Ot=t),Ot._abbr}function _e(e,a){if(null!==a){var t=Et;if(a.abbr=e,null!=At[e])p("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),t=At[e]._config;else if(null!=a.parentLocale){if(null==At[a.parentLocale])return Ft[a.parentLocale]||(Ft[a.parentLocale]=[]),Ft[a.parentLocale].push({name:e,config:a}),null;t=At[a.parentLocale]._config}return At[e]=new g(T(t,a)),Ft[e]&&Ft[e].forEach(function(e){_e(e.name,e.config)}),re(e),At[e]}return delete At[e],null}function ie(e){var t;if(e&&e._locale&&e._locale._abbr&&(e=e._locale._abbr),!e)return Ot;if(!a(e)){if(t=de(e))return t;e=[e]}return function(e){for(var a,t,s,n,d=0;d0;){if(s=de(n.slice(0,a).join("-")))return s;if(t&&t.length>=a&&y(n,t,!0)>=a-1)break;a--}d++}return null}(e)}function oe(e){var a,t=e._a;return t&&-2===m(e).overflow&&(a=t[lt]<0||t[lt]>11?lt:t[Mt]<1||t[Mt]>U(t[ut],t[lt])?Mt:t[ht]<0||t[ht]>24||24===t[ht]&&(0!==t[Lt]||0!==t[ct]||0!==t[Yt])?ht:t[Lt]<0||t[Lt]>59?Lt:t[ct]<0||t[ct]>59?ct:t[Yt]<0||t[Yt]>999?Yt:-1,m(e)._overflowDayOfYear&&(aMt)&&(a=Mt),m(e)._overflowWeeks&&-1===a&&(a=yt),m(e)._overflowWeekday&&-1===a&&(a=ft),m(e).overflow=a),e}function me(e,a,t){return null!=e?e:null!=a?a:t}function ue(a){var t,s,n,d,r,_=[];if(!a._d){for(n=function(a){var t=new Date(e.now());return a._useUTC?[t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate()]:[t.getFullYear(),t.getMonth(),t.getDate()]}(a),a._w&&null==a._a[Mt]&&null==a._a[lt]&&function(e){var a,t,s,n,d,r,_,i;if(null!=(a=e._w).GG||null!=a.W||null!=a.E)d=1,r=4,t=me(a.GG,e._a[ut],Q(ye(),1,4).year),s=me(a.W,1),((n=me(a.E,1))<1||n>7)&&(i=!0);else{d=e._locale._week.dow,r=e._locale._week.doy;var o=Q(ye(),d,r);t=me(a.gg,e._a[ut],o.year),s=me(a.w,o.week),null!=a.d?((n=a.d)<0||n>6)&&(i=!0):null!=a.e?(n=a.e+d,(a.e<0||a.e>6)&&(i=!0)):n=d}s<1||s>X(t,d,r)?m(e)._overflowWeeks=!0:null!=i?m(e)._overflowWeekday=!0:(_=q(t,s,n,d,r),e._a[ut]=_.year,e._dayOfYear=_.dayOfYear)}(a),null!=a._dayOfYear&&(r=me(a._a[ut],n[ut]),(a._dayOfYear>N(r)||0===a._dayOfYear)&&(m(a)._overflowDayOfYear=!0),s=$(r,0,a._dayOfYear),a._a[lt]=s.getUTCMonth(),a._a[Mt]=s.getUTCDate()),t=0;t<3&&null==a._a[t];++t)a._a[t]=_[t]=n[t];for(;t<7;t++)a._a[t]=_[t]=null==a._a[t]?2===t?1:0:a._a[t];24===a._a[ht]&&0===a._a[Lt]&&0===a._a[ct]&&0===a._a[Yt]&&(a._nextDay=!0,a._a[ht]=0),a._d=(a._useUTC?$:function(e,a,t,s,n,d,r){var _=new Date(e,a,t,s,n,d,r);return e<100&&e>=0&&isFinite(_.getFullYear())&&_.setFullYear(e),_}).apply(null,_),d=a._useUTC?a._d.getUTCDay():a._d.getDay(),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[ht]=24),a._w&&void 0!==a._w.d&&a._w.d!==d&&(m(a).weekdayMismatch=!0)}}function le(e){var a,t,s,n,d,r,_=e._i,i=zt.exec(_)||Jt.exec(_);if(i){for(m(e).iso=!0,a=0,t=Rt.length;a0&&m(a).unusedInput.push(r),_=_.slice(_.indexOf(s)+s.length),o+=s.length),Va[d]?(s?m(a).empty=!1:m(a).unusedTokens.push(d),J(d,s,a)):a._strict&&!s&&m(a).unusedTokens.push(d);m(a).charsLeftOver=i-o,_.length>0&&m(a).unusedInput.push(_),a._a[ht]<=12&&!0===m(a).bigHour&&a._a[ht]>0&&(m(a).bigHour=void 0),m(a).parsedDateParts=a._a.slice(0),m(a).meridiem=a._meridiem,a._a[ht]=function(e,a,t){var s;if(null==t)return a;return null!=e.meridiemHour?e.meridiemHour(a,t):null!=e.isPM?((s=e.isPM(t))&&a<12&&(a+=12),s||12!==a||(a=0),a):a}(a._locale,a._a[ht],a._meridiem),ue(a),oe(a)}else he(a);else le(a)}function ce(_){var o=_._i,c=_._f;return _._locale=_._locale||ie(_._l),null===o||void 0===c&&""===o?l({nullInput:!0}):("string"==typeof o&&(_._i=o=_._locale.preparse(o)),L(o)?new h(oe(o)):(d(o)?_._d=o:a(c)?function(e){var a,t,s,n,d;if(0===e._f.length)return m(e).invalidFormat=!0,void(e._d=new Date(NaN));for(n=0;nd&&(a=d),function(e,a,t,s,n){var d=q(e,a,t,s,n),r=$(d.year,0,d.dayOfYear);return this.year(r.getUTCFullYear()),this.month(r.getUTCMonth()),this.date(r.getUTCDate()),this}.call(this,e,a,t,s,n))}function ze(e,a){a[Yt]=Y(1e3*("0."+e))}function Je(e){return e}function Ne(e,a,t,s){var n=ie(),d=o().set(s,a);return n[t](d,e)}function Re(e,a,t){if(n(e)&&(a=e,e=void 0),e=e||"",null!=a)return Ne(e,a,t,"month");var s,d=[];for(s=0;s<12;s++)d[s]=Ne(e,s,t,"month");return d}function Ie(e,a,t,s){"boolean"==typeof e?(n(a)&&(t=a,a=void 0),a=a||""):(t=a=e,e=!1,n(a)&&(t=a,a=void 0),a=a||"");var d=ie(),r=e?d._week.dow:0;if(null!=t)return Ne(a,(t+r)%7,s,"day");var _,i=[];for(_=0;_<7;_++)i[_]=Ne(a,(_+r)%7,s,"day");return i}function Ce(e,a,t,s){var n=He(a,t);return e._milliseconds+=s*n._milliseconds,e._days+=s*n._days,e._months+=s*n._months,e._bubble()}function Ge(e){return e<0?Math.floor(e):Math.ceil(e)}function Ue(e){return 4800*e/146097}function Ve(e){return 146097*e/4800}function Ke(e){return function(){return this.as(e)}}function Ze(e){return function(){return this.isValid()?this._data[e]:NaN}}function $e(e){return(e>0)-(e<0)||+e}function Be(){if(!this.isValid())return this.localeData().invalidDate();var e,a,t=vs(this._milliseconds)/1e3,s=vs(this._days),n=vs(this._months);a=c((e=c(t/60))/60),t%=60,e%=60;var d=c(n/12),r=n%=12,_=s,i=a,o=e,m=t?t.toFixed(3).replace(/\.?0+$/,""):"",u=this.asSeconds();if(!u)return"P0D";var l=u<0?"-":"",M=$e(this._months)!==$e(u)?"-":"",h=$e(this._days)!==$e(u)?"-":"",L=$e(this._milliseconds)!==$e(u)?"-":"";return l+"P"+(d?M+d+"Y":"")+(r?M+r+"M":"")+(_?h+_+"D":"")+(i||o||m?"T":"")+(i?L+i+"H":"")+(o?L+o+"M":"")+(m?L+m+"S":"")}function qe(e,a,t){return"m"===t?a?"\u0445\u0432\u0456\u043b\u0456\u043d\u0430":"\u0445\u0432\u0456\u043b\u0456\u043d\u0443":"h"===t?a?"\u0433\u0430\u0434\u0437\u0456\u043d\u0430":"\u0433\u0430\u0434\u0437\u0456\u043d\u0443":e+" "+function(e,a){var t=e.split("_");return a%10==1&&a%100!=11?t[0]:a%10>=2&&a%10<=4&&(a%100<10||a%100>=20)?t[1]:t[2]}({ss:a?"\u0441\u0435\u043a\u0443\u043d\u0434\u0430_\u0441\u0435\u043a\u0443\u043d\u0434\u044b_\u0441\u0435\u043a\u0443\u043d\u0434":"\u0441\u0435\u043a\u0443\u043d\u0434\u0443_\u0441\u0435\u043a\u0443\u043d\u0434\u044b_\u0441\u0435\u043a\u0443\u043d\u0434",mm:a?"\u0445\u0432\u0456\u043b\u0456\u043d\u0430_\u0445\u0432\u0456\u043b\u0456\u043d\u044b_\u0445\u0432\u0456\u043b\u0456\u043d":"\u0445\u0432\u0456\u043b\u0456\u043d\u0443_\u0445\u0432\u0456\u043b\u0456\u043d\u044b_\u0445\u0432\u0456\u043b\u0456\u043d",hh:a?"\u0433\u0430\u0434\u0437\u0456\u043d\u0430_\u0433\u0430\u0434\u0437\u0456\u043d\u044b_\u0433\u0430\u0434\u0437\u0456\u043d":"\u0433\u0430\u0434\u0437\u0456\u043d\u0443_\u0433\u0430\u0434\u0437\u0456\u043d\u044b_\u0433\u0430\u0434\u0437\u0456\u043d",dd:"\u0434\u0437\u0435\u043d\u044c_\u0434\u043d\u0456_\u0434\u0437\u0451\u043d",MM:"\u043c\u0435\u0441\u044f\u0446_\u043c\u0435\u0441\u044f\u0446\u044b_\u043c\u0435\u0441\u044f\u0446\u0430\u045e",yy:"\u0433\u043e\u0434_\u0433\u0430\u0434\u044b_\u0433\u0430\u0434\u043e\u045e"}[t],+e)}function Qe(e,a,t){return e+" "+function(e,a){if(2===a)return function(e){var a={m:"v",b:"v",d:"z"};if(void 0===a[e.charAt(0)])return e;return a[e.charAt(0)]+e.substring(1)}(e);return e}({mm:"munutenn",MM:"miz",dd:"devezh"}[t],e)}function Xe(e){return e>9?Xe(e%10):e}function ea(e,a,t){var s=e+" ";switch(t){case"ss":return s+=1===e?"sekunda":2===e||3===e||4===e?"sekunde":"sekundi";case"m":return a?"jedna minuta":"jedne minute";case"mm":return s+=1===e?"minuta":2===e||3===e||4===e?"minute":"minuta";case"h":return a?"jedan sat":"jednog sata";case"hh":return s+=1===e?"sat":2===e||3===e||4===e?"sata":"sati";case"dd":return s+=1===e?"dan":"dana";case"MM":return s+=1===e?"mjesec":2===e||3===e||4===e?"mjeseca":"mjeseci";case"yy":return s+=1===e?"godina":2===e||3===e||4===e?"godine":"godina"}}function aa(e){return e>1&&e<5&&1!=~~(e/10)}function ta(e,a,t,s){var n=e+" ";switch(t){case"s":return a||s?"p\xe1r sekund":"p\xe1r sekundami";case"ss":return a||s?n+(aa(e)?"sekundy":"sekund"):n+"sekundami";break;case"m":return a?"minuta":s?"minutu":"minutou";case"mm":return a||s?n+(aa(e)?"minuty":"minut"):n+"minutami";break;case"h":return a?"hodina":s?"hodinu":"hodinou";case"hh":return a||s?n+(aa(e)?"hodiny":"hodin"):n+"hodinami";break;case"d":return a||s?"den":"dnem";case"dd":return a||s?n+(aa(e)?"dny":"dn\xed"):n+"dny";break;case"M":return a||s?"m\u011bs\xedc":"m\u011bs\xedcem";case"MM":return a||s?n+(aa(e)?"m\u011bs\xedce":"m\u011bs\xedc\u016f"):n+"m\u011bs\xedci";break;case"y":return a||s?"rok":"rokem";case"yy":return a||s?n+(aa(e)?"roky":"let"):n+"lety";break}}function sa(e,a,t,s){var n={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[e+" Tage",e+" Tagen"],M:["ein Monat","einem Monat"],MM:[e+" Monate",e+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[e+" Jahre",e+" Jahren"]};return a?n[t][0]:n[t][1]}function na(e,a,t,s){var n={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[e+" Tage",e+" Tagen"],M:["ein Monat","einem Monat"],MM:[e+" Monate",e+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[e+" Jahre",e+" Jahren"]};return a?n[t][0]:n[t][1]}function da(e,a,t,s){var n={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[e+" Tage",e+" Tagen"],M:["ein Monat","einem Monat"],MM:[e+" Monate",e+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[e+" Jahre",e+" Jahren"]};return a?n[t][0]:n[t][1]}function ra(e,a,t,s){var n={s:["m\xf5ne sekundi","m\xf5ni sekund","paar sekundit"],ss:[e+"sekundi",e+"sekundit"],m:["\xfche minuti","\xfcks minut"],mm:[e+" minuti",e+" minutit"],h:["\xfche tunni","tund aega","\xfcks tund"],hh:[e+" tunni",e+" tundi"],d:["\xfche p\xe4eva","\xfcks p\xe4ev"],M:["kuu aja","kuu aega","\xfcks kuu"],MM:[e+" kuu",e+" kuud"],y:["\xfche aasta","aasta","\xfcks aasta"],yy:[e+" aasta",e+" aastat"]};return a?n[t][2]?n[t][2]:n[t][1]:s?n[t][0]:n[t][1]}function _a(e,a,t,s){var n="";switch(t){case"s":return s?"muutaman sekunnin":"muutama sekunti";case"ss":return s?"sekunnin":"sekuntia";case"m":return s?"minuutin":"minuutti";case"mm":n=s?"minuutin":"minuuttia";break;case"h":return s?"tunnin":"tunti";case"hh":n=s?"tunnin":"tuntia";break;case"d":return s?"p\xe4iv\xe4n":"p\xe4iv\xe4";case"dd":n=s?"p\xe4iv\xe4n":"p\xe4iv\xe4\xe4";break;case"M":return s?"kuukauden":"kuukausi";case"MM":n=s?"kuukauden":"kuukautta";break;case"y":return s?"vuoden":"vuosi";case"yy":n=s?"vuoden":"vuotta";break}return n=function(e,a){return e<10?a?mn[e]:on[e]:e}(e,s)+" "+n}function ia(e,a,t,s){var n={s:["thodde secondanim","thodde second"],ss:[e+" secondanim",e+" second"],m:["eka mintan","ek minute"],mm:[e+" mintanim",e+" mintam"],h:["eka horan","ek hor"],hh:[e+" horanim",e+" hor"],d:["eka disan","ek dis"],dd:[e+" disanim",e+" dis"],M:["eka mhoinean","ek mhoino"],MM:[e+" mhoineanim",e+" mhoine"],y:["eka vorsan","ek voros"],yy:[e+" vorsanim",e+" vorsam"]};return a?n[t][0]:n[t][1]}function oa(e,a,t){var s=e+" ";switch(t){case"ss":return s+=1===e?"sekunda":2===e||3===e||4===e?"sekunde":"sekundi";case"m":return a?"jedna minuta":"jedne minute";case"mm":return s+=1===e?"minuta":2===e||3===e||4===e?"minute":"minuta";case"h":return a?"jedan sat":"jednog sata";case"hh":return s+=1===e?"sat":2===e||3===e||4===e?"sata":"sati";case"dd":return s+=1===e?"dan":"dana";case"MM":return s+=1===e?"mjesec":2===e||3===e||4===e?"mjeseca":"mjeseci";case"yy":return s+=1===e?"godina":2===e||3===e||4===e?"godine":"godina"}}function ma(e,a,t,s){var n=e;switch(t){case"s":return s||a?"n\xe9h\xe1ny m\xe1sodperc":"n\xe9h\xe1ny m\xe1sodperce";case"ss":return n+(s||a)?" m\xe1sodperc":" m\xe1sodperce";case"m":return"egy"+(s||a?" perc":" perce");case"mm":return n+(s||a?" perc":" perce");case"h":return"egy"+(s||a?" \xf3ra":" \xf3r\xe1ja");case"hh":return n+(s||a?" \xf3ra":" \xf3r\xe1ja");case"d":return"egy"+(s||a?" nap":" napja");case"dd":return n+(s||a?" nap":" napja");case"M":return"egy"+(s||a?" h\xf3nap":" h\xf3napja");case"MM":return n+(s||a?" h\xf3nap":" h\xf3napja");case"y":return"egy"+(s||a?" \xe9v":" \xe9ve");case"yy":return n+(s||a?" \xe9v":" \xe9ve")}return""}function ua(e){return(e?"":"[m\xfalt] ")+"["+Yn[this.day()]+"] LT[-kor]"}function la(e){return e%100==11||e%10!=1}function Ma(e,a,t,s){var n=e+" ";switch(t){case"s":return a||s?"nokkrar sek\xfandur":"nokkrum sek\xfandum";case"ss":return la(e)?n+(a||s?"sek\xfandur":"sek\xfandum"):n+"sek\xfanda";case"m":return a?"m\xedn\xfata":"m\xedn\xfatu";case"mm":return la(e)?n+(a||s?"m\xedn\xfatur":"m\xedn\xfatum"):a?n+"m\xedn\xfata":n+"m\xedn\xfatu";case"hh":return la(e)?n+(a||s?"klukkustundir":"klukkustundum"):n+"klukkustund";case"d":return a?"dagur":s?"dag":"degi";case"dd":return la(e)?a?n+"dagar":n+(s?"daga":"d\xf6gum"):a?n+"dagur":n+(s?"dag":"degi");case"M":return a?"m\xe1nu\xf0ur":s?"m\xe1nu\xf0":"m\xe1nu\xf0i";case"MM":return la(e)?a?n+"m\xe1nu\xf0ir":n+(s?"m\xe1nu\xf0i":"m\xe1nu\xf0um"):a?n+"m\xe1nu\xf0ur":n+(s?"m\xe1nu\xf0":"m\xe1nu\xf0i");case"y":return a||s?"\xe1r":"\xe1ri";case"yy":return la(e)?n+(a||s?"\xe1r":"\xe1rum"):n+(a||s?"\xe1r":"\xe1ri")}}function ha(e,a,t,s){var n={m:["eng Minutt","enger Minutt"],h:["eng Stonn","enger Stonn"],d:["een Dag","engem Dag"],M:["ee Mount","engem Mount"],y:["ee Joer","engem Joer"]};return a?n[t][0]:n[t][1]}function La(e){if(e=parseInt(e,10),isNaN(e))return!1;if(e<0)return!0;if(e<10)return 4<=e&&e<=7;if(e<100){var a=e%10;return La(0===a?e/10:a)}if(e<1e4){for(;e>=10;)e/=10;return La(e)}return e/=1e3,La(e)}function ca(e,a,t,s){return a?ya(t)[0]:s?ya(t)[1]:ya(t)[2]}function Ya(e){return e%10==0||e>10&&e<20}function ya(e){return Dn[e].split("_")}function fa(e,a,t,s){var n=e+" ";return 1===e?n+ca(0,a,t[0],s):a?n+(Ya(e)?ya(t)[1]:ya(t)[0]):s?n+ya(t)[1]:n+(Ya(e)?ya(t)[1]:ya(t)[2])}function ka(e,a,t){return t?a%10==1&&a%100!=11?e[2]:e[3]:a%10==1&&a%100!=11?e[0]:e[1]}function pa(e,a,t){return e+" "+ka(Tn[t],e,a)}function Da(e,a,t){return ka(Tn[t],e,a)}function Ta(e,a,t,s){var n="";if(a)switch(t){case"s":n="\u0915\u093e\u0939\u0940 \u0938\u0947\u0915\u0902\u0926";break;case"ss":n="%d \u0938\u0947\u0915\u0902\u0926";break;case"m":n="\u090f\u0915 \u092e\u093f\u0928\u093f\u091f";break;case"mm":n="%d \u092e\u093f\u0928\u093f\u091f\u0947";break;case"h":n="\u090f\u0915 \u0924\u093e\u0938";break;case"hh":n="%d \u0924\u093e\u0938";break;case"d":n="\u090f\u0915 \u0926\u093f\u0935\u0938";break;case"dd":n="%d \u0926\u093f\u0935\u0938";break;case"M":n="\u090f\u0915 \u092e\u0939\u093f\u0928\u093e";break;case"MM":n="%d \u092e\u0939\u093f\u0928\u0947";break;case"y":n="\u090f\u0915 \u0935\u0930\u094d\u0937";break;case"yy":n="%d \u0935\u0930\u094d\u0937\u0947";break}else switch(t){case"s":n="\u0915\u093e\u0939\u0940 \u0938\u0947\u0915\u0902\u0926\u093e\u0902";break;case"ss":n="%d \u0938\u0947\u0915\u0902\u0926\u093e\u0902";break;case"m":n="\u090f\u0915\u093e \u092e\u093f\u0928\u093f\u091f\u093e";break;case"mm":n="%d \u092e\u093f\u0928\u093f\u091f\u093e\u0902";break;case"h":n="\u090f\u0915\u093e \u0924\u093e\u0938\u093e";break;case"hh":n="%d \u0924\u093e\u0938\u093e\u0902";break;case"d":n="\u090f\u0915\u093e \u0926\u093f\u0935\u0938\u093e";break;case"dd":n="%d \u0926\u093f\u0935\u0938\u093e\u0902";break;case"M":n="\u090f\u0915\u093e \u092e\u0939\u093f\u0928\u094d\u092f\u093e";break;case"MM":n="%d \u092e\u0939\u093f\u0928\u094d\u092f\u093e\u0902";break;case"y":n="\u090f\u0915\u093e \u0935\u0930\u094d\u0937\u093e";break;case"yy":n="%d \u0935\u0930\u094d\u0937\u093e\u0902";break}return n.replace(/%d/i,e)}function ga(e){return e%10<5&&e%10>1&&~~(e/10)%10!=1}function wa(e,a,t){var s=e+" ";switch(t){case"ss":return s+(ga(e)?"sekundy":"sekund");case"m":return a?"minuta":"minut\u0119";case"mm":return s+(ga(e)?"minuty":"minut");case"h":return a?"godzina":"godzin\u0119";case"hh":return s+(ga(e)?"godziny":"godzin");case"MM":return s+(ga(e)?"miesi\u0105ce":"miesi\u0119cy");case"yy":return s+(ga(e)?"lata":"lat")}}function va(e,a,t){var s=" ";return(e%100>=20||e>=100&&e%100==0)&&(s=" de "),e+s+{ss:"secunde",mm:"minute",hh:"ore",dd:"zile",MM:"luni",yy:"ani"}[t]}function Sa(e,a,t){return"m"===t?a?"\u043c\u0438\u043d\u0443\u0442\u0430":"\u043c\u0438\u043d\u0443\u0442\u0443":e+" "+function(e,a){var t=e.split("_");return a%10==1&&a%100!=11?t[0]:a%10>=2&&a%10<=4&&(a%100<10||a%100>=20)?t[1]:t[2]}({ss:a?"\u0441\u0435\u043a\u0443\u043d\u0434\u0430_\u0441\u0435\u043a\u0443\u043d\u0434\u044b_\u0441\u0435\u043a\u0443\u043d\u0434":"\u0441\u0435\u043a\u0443\u043d\u0434\u0443_\u0441\u0435\u043a\u0443\u043d\u0434\u044b_\u0441\u0435\u043a\u0443\u043d\u0434",mm:a?"\u043c\u0438\u043d\u0443\u0442\u0430_\u043c\u0438\u043d\u0443\u0442\u044b_\u043c\u0438\u043d\u0443\u0442":"\u043c\u0438\u043d\u0443\u0442\u0443_\u043c\u0438\u043d\u0443\u0442\u044b_\u043c\u0438\u043d\u0443\u0442",hh:"\u0447\u0430\u0441_\u0447\u0430\u0441\u0430_\u0447\u0430\u0441\u043e\u0432",dd:"\u0434\u0435\u043d\u044c_\u0434\u043d\u044f_\u0434\u043d\u0435\u0439",MM:"\u043c\u0435\u0441\u044f\u0446_\u043c\u0435\u0441\u044f\u0446\u0430_\u043c\u0435\u0441\u044f\u0446\u0435\u0432",yy:"\u0433\u043e\u0434_\u0433\u043e\u0434\u0430_\u043b\u0435\u0442"}[t],+e)}function Ha(e){return e>1&&e<5}function ba(e,a,t,s){var n=e+" ";switch(t){case"s":return a||s?"p\xe1r sek\xfand":"p\xe1r sekundami";case"ss":return a||s?n+(Ha(e)?"sekundy":"sek\xfand"):n+"sekundami";break;case"m":return a?"min\xfata":s?"min\xfatu":"min\xfatou";case"mm":return a||s?n+(Ha(e)?"min\xfaty":"min\xfat"):n+"min\xfatami";break;case"h":return a?"hodina":s?"hodinu":"hodinou";case"hh":return a||s?n+(Ha(e)?"hodiny":"hod\xedn"):n+"hodinami";break;case"d":return a||s?"de\u0148":"d\u0148om";case"dd":return a||s?n+(Ha(e)?"dni":"dn\xed"):n+"d\u0148ami";break;case"M":return a||s?"mesiac":"mesiacom";case"MM":return a||s?n+(Ha(e)?"mesiace":"mesiacov"):n+"mesiacmi";break;case"y":return a||s?"rok":"rokom";case"yy":return a||s?n+(Ha(e)?"roky":"rokov"):n+"rokmi";break}}function ja(e,a,t,s){var n=e+" ";switch(t){case"s":return a||s?"nekaj sekund":"nekaj sekundami";case"ss":return n+=1===e?a?"sekundo":"sekundi":2===e?a||s?"sekundi":"sekundah":e<5?a||s?"sekunde":"sekundah":"sekund";case"m":return a?"ena minuta":"eno minuto";case"mm":return n+=1===e?a?"minuta":"minuto":2===e?a||s?"minuti":"minutama":e<5?a||s?"minute":"minutami":a||s?"minut":"minutami";case"h":return a?"ena ura":"eno uro";case"hh":return n+=1===e?a?"ura":"uro":2===e?a||s?"uri":"urama":e<5?a||s?"ure":"urami":a||s?"ur":"urami";case"d":return a||s?"en dan":"enim dnem";case"dd":return n+=1===e?a||s?"dan":"dnem":2===e?a||s?"dni":"dnevoma":a||s?"dni":"dnevi";case"M":return a||s?"en mesec":"enim mesecem";case"MM":return n+=1===e?a||s?"mesec":"mesecem":2===e?a||s?"meseca":"mesecema":e<5?a||s?"mesece":"meseci":a||s?"mesecev":"meseci";case"y":return a||s?"eno leto":"enim letom";case"yy":return n+=1===e?a||s?"leto":"letom":2===e?a||s?"leti":"letoma":e<5?a||s?"leta":"leti":a||s?"let":"leti"}}function xa(e,a,t,s){var n=function(e){var a=Math.floor(e%1e3/100),t=Math.floor(e%100/10),s=e%10,n="";a>0&&(n+=Qn[a]+"vatlh");t>0&&(n+=(""!==n?" ":"")+Qn[t]+"maH");s>0&&(n+=(""!==n?" ":"")+Qn[s]);return""===n?"pagh":n}(e);switch(t){case"ss":return n+" lup";case"mm":return n+" tup";case"hh":return n+" rep";case"dd":return n+" jaj";case"MM":return n+" jar";case"yy":return n+" DIS"}}function Pa(e,a,t,s){var n={s:["viensas secunds","'iensas secunds"],ss:[e+" secunds",e+" secunds"],m:["'n m\xedut","'iens m\xedut"],mm:[e+" m\xeduts",e+" m\xeduts"],h:["'n \xfeora","'iensa \xfeora"],hh:[e+" \xfeoras",e+" \xfeoras"],d:["'n ziua","'iensa ziua"],dd:[e+" ziuas",e+" ziuas"],M:["'n mes","'iens mes"],MM:[e+" mesen",e+" mesen"],y:["'n ar","'iens ar"],yy:[e+" ars",e+" ars"]};return s?n[t][0]:a?n[t][0]:n[t][1]}function Oa(e,a,t){return"m"===t?a?"\u0445\u0432\u0438\u043b\u0438\u043d\u0430":"\u0445\u0432\u0438\u043b\u0438\u043d\u0443":"h"===t?a?"\u0433\u043e\u0434\u0438\u043d\u0430":"\u0433\u043e\u0434\u0438\u043d\u0443":e+" "+function(e,a){var t=e.split("_");return a%10==1&&a%100!=11?t[0]:a%10>=2&&a%10<=4&&(a%100<10||a%100>=20)?t[1]:t[2]}({ss:a?"\u0441\u0435\u043a\u0443\u043d\u0434\u0430_\u0441\u0435\u043a\u0443\u043d\u0434\u0438_\u0441\u0435\u043a\u0443\u043d\u0434":"\u0441\u0435\u043a\u0443\u043d\u0434\u0443_\u0441\u0435\u043a\u0443\u043d\u0434\u0438_\u0441\u0435\u043a\u0443\u043d\u0434",mm:a?"\u0445\u0432\u0438\u043b\u0438\u043d\u0430_\u0445\u0432\u0438\u043b\u0438\u043d\u0438_\u0445\u0432\u0438\u043b\u0438\u043d":"\u0445\u0432\u0438\u043b\u0438\u043d\u0443_\u0445\u0432\u0438\u043b\u0438\u043d\u0438_\u0445\u0432\u0438\u043b\u0438\u043d",hh:a?"\u0433\u043e\u0434\u0438\u043d\u0430_\u0433\u043e\u0434\u0438\u043d\u0438_\u0433\u043e\u0434\u0438\u043d":"\u0433\u043e\u0434\u0438\u043d\u0443_\u0433\u043e\u0434\u0438\u043d\u0438_\u0433\u043e\u0434\u0438\u043d",dd:"\u0434\u0435\u043d\u044c_\u0434\u043d\u0456_\u0434\u043d\u0456\u0432",MM:"\u043c\u0456\u0441\u044f\u0446\u044c_\u043c\u0456\u0441\u044f\u0446\u0456_\u043c\u0456\u0441\u044f\u0446\u0456\u0432",yy:"\u0440\u0456\u043a_\u0440\u043e\u043a\u0438_\u0440\u043e\u043a\u0456\u0432"}[t],+e)}function Wa(e){return function(){return e+"\u043e"+(11===this.hours()?"\u0431":"")+"] LT"}}var Ea,Aa;Aa=Array.prototype.some?Array.prototype.some:function(e){for(var a=Object(this),t=a.length>>>0,s=0;s68?1900:2e3)};var kt,pt=I("FullYear",!0);kt=Array.prototype.indexOf?Array.prototype.indexOf:function(e){var a;for(a=0;athis?this:e:l()}),Zt=["year","quarter","month","week","day","hour","minute","second","millisecond"];Te("Z",":"),Te("ZZ",""),W("Z",_t),W("ZZ",_t),F(["Z","ZZ"],function(e,a,t){t._useUTC=!0,t._tzm=ge(_t,e)});var $t=/([\+\-]|\d\d)/gi;e.updateOffset=function(){};var Bt=/^(\-|\+)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/,qt=/^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;He.fn=ke.prototype,He.invalid=function(){return He(NaN)};var Qt=xe(1,"add"),Xt=xe(-1,"subtract");e.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",e.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var es=k("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(e){return void 0===e?this.localeData():this.locale(e)});j(0,["gg",2],0,function(){return this.weekYear()%100}),j(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Ae("gggg","weekYear"),Ae("ggggg","weekYear"),Ae("GGGG","isoWeekYear"),Ae("GGGGG","isoWeekYear"),w("weekYear","gg"),w("isoWeekYear","GG"),H("weekYear",1),H("isoWeekYear",1),W("G",dt),W("g",dt),W("GG",Qa,Za),W("gg",Qa,Za),W("GGGG",tt,Ba),W("gggg",tt,Ba),W("GGGGG",st,qa),W("ggggg",st,qa),z(["gggg","ggggg","GGGG","GGGGG"],function(e,a,t,s){a[s.substr(0,2)]=Y(e)}),z(["gg","GG"],function(a,t,s,n){t[n]=e.parseTwoDigitYear(a)}),j("Q",0,"Qo","quarter"),w("quarter","Q"),H("quarter",7),W("Q",Ka),F("Q",function(e,a){a[lt]=3*(Y(e)-1)}),j("D",["DD",2],"Do","date"),w("date","D"),H("date",9),W("D",Qa),W("DD",Qa,Za),W("Do",function(e,a){return e?a._dayOfMonthOrdinalParse||a._ordinalParse:a._dayOfMonthOrdinalParseLenient}),F(["D","DD"],Mt),F("Do",function(e,a){a[Mt]=Y(e.match(Qa)[0])});var as=I("Date",!0);j("DDD",["DDDD",3],"DDDo","dayOfYear"),w("dayOfYear","DDD"),H("dayOfYear",4),W("DDD",at),W("DDDD",$a),F(["DDD","DDDD"],function(e,a,t){t._dayOfYear=Y(e)}),j("m",["mm",2],0,"minute"),w("minute","m"),H("minute",14),W("m",Qa),W("mm",Qa,Za),F(["m","mm"],Lt);var ts=I("Minutes",!1);j("s",["ss",2],0,"second"),w("second","s"),H("second",15),W("s",Qa),W("ss",Qa,Za),F(["s","ss"],ct);var ss=I("Seconds",!1);j("S",0,0,function(){return~~(this.millisecond()/100)}),j(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),j(0,["SSS",3],0,"millisecond"),j(0,["SSSS",4],0,function(){return 10*this.millisecond()}),j(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),j(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),j(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),j(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),j(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),w("millisecond","ms"),H("millisecond",16),W("S",at,Ka),W("SS",at,Za),W("SSS",at,$a);var ns;for(ns="SSSS";ns.length<=9;ns+="S")W(ns,nt);for(ns="S";ns.length<=9;ns+="S")F(ns,ze);var ds=I("Milliseconds",!1);j("z",0,0,"zoneAbbr"),j("zz",0,0,"zoneName");var rs=h.prototype;rs.add=Qt,rs.calendar=function(a,t){var s=a||ye(),n=we(s,this).startOf("day"),d=e.calendarFormat(this,n)||"sameElse",r=t&&(D(t[d])?t[d].call(this,s):t[d]);return this.format(r||this.localeData().calendar(d,this,ye(s)))},rs.clone=function(){return new h(this)},rs.diff=function(e,a,t){var s,n,d;if(!this.isValid())return NaN;if(!(s=we(e,this)).isValid())return NaN;switch(n=6e4*(s.utcOffset()-this.utcOffset()),a=v(a)){case"year":d=Oe(this,s)/12;break;case"month":d=Oe(this,s);break;case"quarter":d=Oe(this,s)/3;break;case"second":d=(this-s)/1e3;break;case"minute":d=(this-s)/6e4;break;case"hour":d=(this-s)/36e5;break;case"day":d=(this-s-n)/864e5;break;case"week":d=(this-s-n)/6048e5;break;default:d=this-s}return t?d:c(d)},rs.endOf=function(e){return void 0===(e=v(e))||"millisecond"===e?this:("date"===e&&(e="day"),this.startOf(e).add(1,"isoWeek"===e?"week":e).subtract(1,"ms"))},rs.format=function(a){a||(a=this.isUtc()?e.defaultFormatUtc:e.defaultFormat);var t=P(this,a);return this.localeData().postformat(t)},rs.from=function(e,a){return this.isValid()&&(L(e)&&e.isValid()||ye(e).isValid())?He({to:this,from:e}).locale(this.locale()).humanize(!a):this.localeData().invalidDate()},rs.fromNow=function(e){return this.from(ye(),e)},rs.to=function(e,a){return this.isValid()&&(L(e)&&e.isValid()||ye(e).isValid())?He({from:this,to:e}).locale(this.locale()).humanize(!a):this.localeData().invalidDate()},rs.toNow=function(e){return this.to(ye(),e)},rs.get=function(e){return e=v(e),D(this[e])?this[e]():this},rs.invalidAt=function(){return m(this).overflow},rs.isAfter=function(e,a){var t=L(e)?e:ye(e);return!(!this.isValid()||!t.isValid())&&("millisecond"===(a=v(s(a)?"millisecond":a))?this.valueOf()>t.valueOf():t.valueOf()9999?P(t,a?"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYYYY-MM-DD[T]HH:mm:ss.SSSZ"):D(Date.prototype.toISOString)?a?this.toDate().toISOString():new Date(this._d.valueOf()).toISOString().replace("Z",P(t,"Z")):P(t,a?"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYY-MM-DD[T]HH:mm:ss.SSSZ")},rs.inspect=function(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var e="moment",a="";this.isLocal()||(e=0===this.utcOffset()?"moment.utc":"moment.parseZone",a="Z");var t="["+e+'("]',s=0<=this.year()&&this.year()<=9999?"YYYY":"YYYYYY",n=a+'[")]';return this.format(t+s+"-MM-DD[T]HH:mm:ss.SSS"+n)},rs.toJSON=function(){return this.isValid()?this.toISOString():null},rs.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},rs.unix=function(){return Math.floor(this.valueOf()/1e3)},rs.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},rs.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},rs.year=pt,rs.isLeapYear=function(){return R(this.year())},rs.weekYear=function(e){return Fe.call(this,e,this.week(),this.weekday(),this.localeData()._week.dow,this.localeData()._week.doy)},rs.isoWeekYear=function(e){return Fe.call(this,e,this.isoWeek(),this.isoWeekday(),1,4)},rs.quarter=rs.quarters=function(e){return null==e?Math.ceil((this.month()+1)/3):this.month(3*(e-1)+this.month()%3)},rs.month=K,rs.daysInMonth=function(){return U(this.year(),this.month())},rs.week=rs.weeks=function(e){var a=this.localeData().week(this);return null==e?a:this.add(7*(e-a),"d")},rs.isoWeek=rs.isoWeeks=function(e){var a=Q(this,1,4).week;return null==e?a:this.add(7*(e-a),"d")},rs.weeksInYear=function(){var e=this.localeData()._week;return X(this.year(),e.dow,e.doy)},rs.isoWeeksInYear=function(){return X(this.year(),1,4)},rs.date=as,rs.day=rs.days=function(e){if(!this.isValid())return null!=e?this:NaN;var a=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=e?(e=function(e,a){return"string"!=typeof e?e:isNaN(e)?"number"==typeof(e=a.weekdaysParse(e))?e:null:parseInt(e,10)}(e,this.localeData()),this.add(e-a,"d")):a},rs.weekday=function(e){if(!this.isValid())return null!=e?this:NaN;var a=(this.day()+7-this.localeData()._week.dow)%7;return null==e?a:this.add(e-a,"d")},rs.isoWeekday=function(e){if(!this.isValid())return null!=e?this:NaN;if(null!=e){var a=function(e,a){return"string"==typeof e?a.weekdaysParse(e)%7||7:isNaN(e)?null:e}(e,this.localeData());return this.day(this.day()%7?a:a-7)}return this.day()||7},rs.dayOfYear=function(e){var a=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==e?a:this.add(e-a,"d")},rs.hour=rs.hours=Wt,rs.minute=rs.minutes=ts,rs.second=rs.seconds=ss,rs.millisecond=rs.milliseconds=ds,rs.utcOffset=function(a,t,s){var n,d=this._offset||0;if(!this.isValid())return null!=a?this:NaN;if(null!=a){if("string"==typeof a){if(null===(a=ge(_t,a)))return this}else Math.abs(a)<16&&!s&&(a*=60);return!this._isUTC&&t&&(n=ve(this)),this._offset=a,this._isUTC=!0,null!=n&&this.add(n,"m"),d!==a&&(!t||this._changeInProgress?Pe(this,He(a-d,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,e.updateOffset(this,!0),this._changeInProgress=null)),this}return this._isUTC?d:ve(this)},rs.utc=function(e){return this.utcOffset(0,e)},rs.local=function(e){return this._isUTC&&(this.utcOffset(0,e),this._isUTC=!1,e&&this.subtract(ve(this),"m")),this},rs.parseZone=function(){if(null!=this._tzm)this.utcOffset(this._tzm,!1,!0);else if("string"==typeof this._i){var e=ge(rt,this._i);null!=e?this.utcOffset(e):this.utcOffset(0,!0)}return this},rs.hasAlignedHourOffset=function(e){return!!this.isValid()&&(e=e?ye(e).utcOffset():0,(this.utcOffset()-e)%60==0)},rs.isDST=function(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},rs.isLocal=function(){return!!this.isValid()&&!this._isUTC},rs.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},rs.isUtc=Se,rs.isUTC=Se,rs.zoneAbbr=function(){return this._isUTC?"UTC":""},rs.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},rs.dates=k("dates accessor is deprecated. Use date instead.",as),rs.months=k("months accessor is deprecated. Use month instead",K),rs.years=k("years accessor is deprecated. Use year instead",pt),rs.zone=k("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,a){return null!=e?("string"!=typeof e&&(e=-e),this.utcOffset(e,a),this):-this.utcOffset()}),rs.isDSTShifted=k("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!s(this._isDSTShifted))return this._isDSTShifted;var e={};if(M(e,this),(e=ce(e))._a){var a=e._isUTC?o(e._a):ye(e._a);this._isDSTShifted=this.isValid()&&y(e._a,a.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted});var _s=g.prototype;_s.calendar=function(e,a,t){var s=this._calendar[e]||this._calendar.sameElse;return D(s)?s.call(a,t):s},_s.longDateFormat=function(e){var a=this._longDateFormat[e],t=this._longDateFormat[e.toUpperCase()];return a||!t?a:(this._longDateFormat[e]=t.replace(/MMMM|MM|DD|dddd/g,function(e){return e.slice(1)}),this._longDateFormat[e])},_s.invalidDate=function(){return this._invalidDate},_s.ordinal=function(e){return this._ordinal.replace("%d",e)},_s.preparse=Je,_s.postformat=Je,_s.relativeTime=function(e,a,t,s){var n=this._relativeTime[t];return D(n)?n(e,a,t,s):n.replace(/%d/i,e)},_s.pastFuture=function(e,a){var t=this._relativeTime[e>0?"future":"past"];return D(t)?t(a):t.replace(/%s/i,a)},_s.set=function(e){var a,t;for(t in e)D(a=e[t])?this[t]=a:this["_"+t]=a;this._config=e,this._dayOfMonthOrdinalParseLenient=new RegExp((this._dayOfMonthOrdinalParse.source||this._ordinalParse.source)+"|"+/\d{1,2}/.source)},_s.months=function(e,t){return e?a(this._months)?this._months[e.month()]:this._months[(this._months.isFormat||Dt).test(t)?"format":"standalone"][e.month()]:a(this._months)?this._months:this._months.standalone},_s.monthsShort=function(e,t){return e?a(this._monthsShort)?this._monthsShort[e.month()]:this._monthsShort[Dt.test(t)?"format":"standalone"][e.month()]:a(this._monthsShort)?this._monthsShort:this._monthsShort.standalone},_s.monthsParse=function(e,a,t){var s,n,d;if(this._monthsParseExact)return function(e,a,t){var s,n,d,r=e.toLocaleLowerCase();if(!this._monthsParse)for(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[],s=0;s<12;++s)d=o([2e3,s]),this._shortMonthsParse[s]=this.monthsShort(d,"").toLocaleLowerCase(),this._longMonthsParse[s]=this.months(d,"").toLocaleLowerCase();return t?"MMM"===a?-1!==(n=kt.call(this._shortMonthsParse,r))?n:null:-1!==(n=kt.call(this._longMonthsParse,r))?n:null:"MMM"===a?-1!==(n=kt.call(this._shortMonthsParse,r))?n:-1!==(n=kt.call(this._longMonthsParse,r))?n:null:-1!==(n=kt.call(this._longMonthsParse,r))?n:-1!==(n=kt.call(this._shortMonthsParse,r))?n:null}.call(this,e,a,t);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),s=0;s<12;s++){if(n=o([2e3,s]),t&&!this._longMonthsParse[s]&&(this._longMonthsParse[s]=new RegExp("^"+this.months(n,"").replace(".","")+"$","i"),this._shortMonthsParse[s]=new RegExp("^"+this.monthsShort(n,"").replace(".","")+"$","i")),t||this._monthsParse[s]||(d="^"+this.months(n,"")+"|^"+this.monthsShort(n,""),this._monthsParse[s]=new RegExp(d.replace(".",""),"i")),t&&"MMMM"===a&&this._longMonthsParse[s].test(e))return s;if(t&&"MMM"===a&&this._shortMonthsParse[s].test(e))return s;if(!t&&this._monthsParse[s].test(e))return s}},_s.monthsRegex=function(e){return this._monthsParseExact?(_(this,"_monthsRegex")||Z.call(this),e?this._monthsStrictRegex:this._monthsRegex):(_(this,"_monthsRegex")||(this._monthsRegex=vt),this._monthsStrictRegex&&e?this._monthsStrictRegex:this._monthsRegex)},_s.monthsShortRegex=function(e){return this._monthsParseExact?(_(this,"_monthsRegex")||Z.call(this),e?this._monthsShortStrictRegex:this._monthsShortRegex):(_(this,"_monthsShortRegex")||(this._monthsShortRegex=wt),this._monthsShortStrictRegex&&e?this._monthsShortStrictRegex:this._monthsShortRegex)},_s.week=function(e){return Q(e,this._week.dow,this._week.doy).week},_s.firstDayOfYear=function(){return this._week.doy},_s.firstDayOfWeek=function(){return this._week.dow},_s.weekdays=function(e,t){return e?a(this._weekdays)?this._weekdays[e.day()]:this._weekdays[this._weekdays.isFormat.test(t)?"format":"standalone"][e.day()]:a(this._weekdays)?this._weekdays:this._weekdays.standalone},_s.weekdaysMin=function(e){return e?this._weekdaysMin[e.day()]:this._weekdaysMin},_s.weekdaysShort=function(e){return e?this._weekdaysShort[e.day()]:this._weekdaysShort},_s.weekdaysParse=function(e,a,t){var s,n,d;if(this._weekdaysParseExact)return function(e,a,t){var s,n,d,r=e.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],s=0;s<7;++s)d=o([2e3,1]).day(s),this._minWeekdaysParse[s]=this.weekdaysMin(d,"").toLocaleLowerCase(),this._shortWeekdaysParse[s]=this.weekdaysShort(d,"").toLocaleLowerCase(),this._weekdaysParse[s]=this.weekdays(d,"").toLocaleLowerCase();return t?"dddd"===a?-1!==(n=kt.call(this._weekdaysParse,r))?n:null:"ddd"===a?-1!==(n=kt.call(this._shortWeekdaysParse,r))?n:null:-1!==(n=kt.call(this._minWeekdaysParse,r))?n:null:"dddd"===a?-1!==(n=kt.call(this._weekdaysParse,r))?n:-1!==(n=kt.call(this._shortWeekdaysParse,r))?n:-1!==(n=kt.call(this._minWeekdaysParse,r))?n:null:"ddd"===a?-1!==(n=kt.call(this._shortWeekdaysParse,r))?n:-1!==(n=kt.call(this._weekdaysParse,r))?n:-1!==(n=kt.call(this._minWeekdaysParse,r))?n:null:-1!==(n=kt.call(this._minWeekdaysParse,r))?n:-1!==(n=kt.call(this._weekdaysParse,r))?n:-1!==(n=kt.call(this._shortWeekdaysParse,r))?n:null}.call(this,e,a,t);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),s=0;s<7;s++){if(n=o([2e3,1]).day(s),t&&!this._fullWeekdaysParse[s]&&(this._fullWeekdaysParse[s]=new RegExp("^"+this.weekdays(n,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[s]=new RegExp("^"+this.weekdaysShort(n,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[s]=new RegExp("^"+this.weekdaysMin(n,"").replace(".",".?")+"$","i")),this._weekdaysParse[s]||(d="^"+this.weekdays(n,"")+"|^"+this.weekdaysShort(n,"")+"|^"+this.weekdaysMin(n,""),this._weekdaysParse[s]=new RegExp(d.replace(".",""),"i")),t&&"dddd"===a&&this._fullWeekdaysParse[s].test(e))return s;if(t&&"ddd"===a&&this._shortWeekdaysParse[s].test(e))return s;if(t&&"dd"===a&&this._minWeekdaysParse[s].test(e))return s;if(!t&&this._weekdaysParse[s].test(e))return s}},_s.weekdaysRegex=function(e){return this._weekdaysParseExact?(_(this,"_weekdaysRegex")||ee.call(this),e?this._weekdaysStrictRegex:this._weekdaysRegex):(_(this,"_weekdaysRegex")||(this._weekdaysRegex=jt),this._weekdaysStrictRegex&&e?this._weekdaysStrictRegex:this._weekdaysRegex)},_s.weekdaysShortRegex=function(e){return this._weekdaysParseExact?(_(this,"_weekdaysRegex")||ee.call(this),e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(_(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=xt),this._weekdaysShortStrictRegex&&e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)},_s.weekdaysMinRegex=function(e){return this._weekdaysParseExact?(_(this,"_weekdaysRegex")||ee.call(this),e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(_(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=Pt),this._weekdaysMinStrictRegex&&e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)},_s.isPM=function(e){return"p"===(e+"").toLowerCase().charAt(0)},_s.meridiem=function(e,a,t){return e>11?t?"pm":"PM":t?"am":"AM"},re("en",{dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var a=e%10;return e+(1===Y(e%100/10)?"th":1===a?"st":2===a?"nd":3===a?"rd":"th")}}),e.lang=k("moment.lang is deprecated. Use moment.locale instead.",re),e.langData=k("moment.langData is deprecated. Use moment.localeData instead.",ie);var is=Math.abs,os=Ke("ms"),ms=Ke("s"),us=Ke("m"),ls=Ke("h"),Ms=Ke("d"),hs=Ke("w"),Ls=Ke("M"),cs=Ke("y"),Ys=Ze("milliseconds"),ys=Ze("seconds"),fs=Ze("minutes"),ks=Ze("hours"),ps=Ze("days"),Ds=Ze("months"),Ts=Ze("years"),gs=Math.round,ws={ss:44,s:45,m:45,h:22,d:26,M:11},vs=Math.abs,Ss=ke.prototype;Ss.isValid=function(){return this._isValid},Ss.abs=function(){var e=this._data;return this._milliseconds=is(this._milliseconds),this._days=is(this._days),this._months=is(this._months),e.milliseconds=is(e.milliseconds),e.seconds=is(e.seconds),e.minutes=is(e.minutes),e.hours=is(e.hours),e.months=is(e.months),e.years=is(e.years),this},Ss.add=function(e,a){return Ce(this,e,a,1)},Ss.subtract=function(e,a){return Ce(this,e,a,-1)},Ss.as=function(e){if(!this.isValid())return NaN;var a,t,s=this._milliseconds;if("month"===(e=v(e))||"year"===e)return a=this._days+s/864e5,t=this._months+Ue(a),"month"===e?t:t/12;switch(a=this._days+Math.round(Ve(this._months)),e){case"week":return a/7+s/6048e5;case"day":return a+s/864e5;case"hour":return 24*a+s/36e5;case"minute":return 1440*a+s/6e4;case"second":return 86400*a+s/1e3;case"millisecond":return Math.floor(864e5*a)+s;default:throw new Error("Unknown unit "+e)}},Ss.asMilliseconds=os,Ss.asSeconds=ms,Ss.asMinutes=us,Ss.asHours=ls,Ss.asDays=Ms,Ss.asWeeks=hs,Ss.asMonths=Ls,Ss.asYears=cs,Ss.valueOf=function(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*Y(this._months/12):NaN},Ss._bubble=function(){var e,a,t,s,n,d=this._milliseconds,r=this._days,_=this._months,i=this._data;return d>=0&&r>=0&&_>=0||d<=0&&r<=0&&_<=0||(d+=864e5*Ge(Ve(_)+r),r=0,_=0),i.milliseconds=d%1e3,e=c(d/1e3),i.seconds=e%60,a=c(e/60),i.minutes=a%60,t=c(a/60),i.hours=t%24,r+=c(t/24),n=c(Ue(r)),_+=n,r-=Ge(Ve(n)),s=c(_/12),_%=12,i.days=r,i.months=_,i.years=s,this},Ss.clone=function(){return He(this)},Ss.get=function(e){return e=v(e),this.isValid()?this[e+"s"]():NaN},Ss.milliseconds=Ys,Ss.seconds=ys,Ss.minutes=fs,Ss.hours=ks,Ss.days=ps,Ss.weeks=function(){return c(this.days()/7)},Ss.months=Ds,Ss.years=Ts,Ss.humanize=function(e){if(!this.isValid())return this.localeData().invalidDate();var a=this.localeData(),t=function(e,a,t){var s=He(e).abs(),n=gs(s.as("s")),d=gs(s.as("m")),r=gs(s.as("h")),_=gs(s.as("d")),i=gs(s.as("M")),o=gs(s.as("y")),m=n<=ws.ss&&["s",n]||n0,m[4]=t,function(e,a,t,s,n){return n.relativeTime(a||1,!!t,e,s)}.apply(null,m)}(this,!e,a);return e&&(t=a.pastFuture(+this,t)),a.postformat(t)},Ss.toISOString=Be,Ss.toString=Be,Ss.toJSON=Be,Ss.locale=We,Ss.localeData=Ee,Ss.toIsoString=k("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Be),Ss.lang=es,j("X",0,0,"unix"),j("x",0,0,"valueOf"),W("x",dt),W("X",/[+-]?\d+(\.\d{1,3})?/),F("X",function(e,a,t){t._d=new Date(1e3*parseFloat(e,10))}),F("x",function(e,a,t){t._d=new Date(Y(e))}),e.version="2.20.1",function(e){Ea=e}(ye),e.fn=rs,e.min=function(){return fe("isBefore",[].slice.call(arguments,0))},e.max=function(){return fe("isAfter",[].slice.call(arguments,0))},e.now=function(){return Date.now?Date.now():+new Date},e.utc=o,e.unix=function(e){return ye(1e3*e)},e.months=function(e,a){return Re(e,a,"months")},e.isDate=d,e.locale=re,e.invalid=l,e.duration=He,e.isMoment=L,e.weekdays=function(e,a,t){return Ie(e,a,t,"weekdays")},e.parseZone=function(){return ye.apply(null,arguments).parseZone()},e.localeData=ie,e.isDuration=pe,e.monthsShort=function(e,a){return Re(e,a,"monthsShort")},e.weekdaysMin=function(e,a,t){return Ie(e,a,t,"weekdaysMin")},e.defineLocale=_e,e.updateLocale=function(e,a){if(null!=a){var t,s,n=Et;null!=(s=de(e))&&(n=s._config),(t=new g(a=T(n,a))).parentLocale=At[e],At[e]=t,re(e)}else null!=At[e]&&(null!=At[e].parentLocale?At[e]=At[e].parentLocale:null!=At[e]&&delete At[e]);return At[e]},e.locales=function(){return Na(At)},e.weekdaysShort=function(e,a,t){return Ie(e,a,t,"weekdaysShort")},e.normalizeUnits=v,e.relativeTimeRounding=function(e){return void 0===e?gs:"function"==typeof e&&(gs=e,!0)},e.relativeTimeThreshold=function(e,a){return void 0!==ws[e]&&(void 0===a?ws[e]:(ws[e]=a,"s"===e&&(ws.ss=a-1),!0))},e.calendarFormat=function(e,a){var t=e.diff(a,"days",!0);return t<-6?"sameElse":t<-1?"lastWeek":t<0?"lastDay":t<1?"sameDay":t<2?"nextDay":t<7?"nextWeek":"sameElse"},e.prototype=rs,e.HTML5_FMT={DATETIME_LOCAL:"YYYY-MM-DDTHH:mm",DATETIME_LOCAL_SECONDS:"YYYY-MM-DDTHH:mm:ss",DATETIME_LOCAL_MS:"YYYY-MM-DDTHH:mm:ss.SSS",DATE:"YYYY-MM-DD",TIME:"HH:mm",TIME_SECONDS:"HH:mm:ss",TIME_MS:"HH:mm:ss.SSS",WEEK:"YYYY-[W]WW",MONTH:"YYYY-MM"},e.defineLocale("af",{months:"Januarie_Februarie_Maart_April_Mei_Junie_Julie_Augustus_September_Oktober_November_Desember".split("_"),monthsShort:"Jan_Feb_Mrt_Apr_Mei_Jun_Jul_Aug_Sep_Okt_Nov_Des".split("_"),weekdays:"Sondag_Maandag_Dinsdag_Woensdag_Donderdag_Vrydag_Saterdag".split("_"),weekdaysShort:"Son_Maa_Din_Woe_Don_Vry_Sat".split("_"),weekdaysMin:"So_Ma_Di_Wo_Do_Vr_Sa".split("_"),meridiemParse:/vm|nm/i,isPM:function(e){return/^nm$/i.test(e)},meridiem:function(e,a,t){return e<12?t?"vm":"VM":t?"nm":"NM"},longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Vandag om] LT",nextDay:"[M\xf4re om] LT",nextWeek:"dddd [om] LT",lastDay:"[Gister om] LT",lastWeek:"[Laas] dddd [om] LT",sameElse:"L"},relativeTime:{future:"oor %s",past:"%s gelede",s:"'n paar sekondes",ss:"%d sekondes",m:"'n minuut",mm:"%d minute",h:"'n uur",hh:"%d ure",d:"'n dag",dd:"%d dae",M:"'n maand",MM:"%d maande",y:"'n jaar",yy:"%d jaar"},dayOfMonthOrdinalParse:/\d{1,2}(ste|de)/,ordinal:function(e){return e+(1===e||8===e||e>=20?"ste":"de")},week:{dow:1,doy:4}}),e.defineLocale("ar-dz",{months:"\u062c\u0627\u0646\u0641\u064a_\u0641\u064a\u0641\u0631\u064a_\u0645\u0627\u0631\u0633_\u0623\u0641\u0631\u064a\u0644_\u0645\u0627\u064a_\u062c\u0648\u0627\u0646_\u062c\u0648\u064a\u0644\u064a\u0629_\u0623\u0648\u062a_\u0633\u0628\u062a\u0645\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0641\u0645\u0628\u0631_\u062f\u064a\u0633\u0645\u0628\u0631".split("_"),monthsShort:"\u062c\u0627\u0646\u0641\u064a_\u0641\u064a\u0641\u0631\u064a_\u0645\u0627\u0631\u0633_\u0623\u0641\u0631\u064a\u0644_\u0645\u0627\u064a_\u062c\u0648\u0627\u0646_\u062c\u0648\u064a\u0644\u064a\u0629_\u0623\u0648\u062a_\u0633\u0628\u062a\u0645\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0641\u0645\u0628\u0631_\u062f\u064a\u0633\u0645\u0628\u0631".split("_"),weekdays:"\u0627\u0644\u0623\u062d\u062f_\u0627\u0644\u0625\u062b\u0646\u064a\u0646_\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621_\u0627\u0644\u062e\u0645\u064a\u0633_\u0627\u0644\u062c\u0645\u0639\u0629_\u0627\u0644\u0633\u0628\u062a".split("_"),weekdaysShort:"\u0627\u062d\u062f_\u0627\u062b\u0646\u064a\u0646_\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0631\u0628\u0639\u0627\u0621_\u062e\u0645\u064a\u0633_\u062c\u0645\u0639\u0629_\u0633\u0628\u062a".split("_"),weekdaysMin:"\u0623\u062d_\u0625\u062b_\u062b\u0644\u0627_\u0623\u0631_\u062e\u0645_\u062c\u0645_\u0633\u0628".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[\u0627\u0644\u064a\u0648\u0645 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextDay:"[\u063a\u062f\u0627 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastDay:"[\u0623\u0645\u0633 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",sameElse:"L"},relativeTime:{future:"\u0641\u064a %s",past:"\u0645\u0646\u0630 %s",s:"\u062b\u0648\u0627\u0646",ss:"%d \u062b\u0627\u0646\u064a\u0629",m:"\u062f\u0642\u064a\u0642\u0629",mm:"%d \u062f\u0642\u0627\u0626\u0642",h:"\u0633\u0627\u0639\u0629",hh:"%d \u0633\u0627\u0639\u0627\u062a",d:"\u064a\u0648\u0645",dd:"%d \u0623\u064a\u0627\u0645",M:"\u0634\u0647\u0631",MM:"%d \u0623\u0634\u0647\u0631",y:"\u0633\u0646\u0629",yy:"%d \u0633\u0646\u0648\u0627\u062a"},week:{dow:0,doy:4}}),e.defineLocale("ar-kw",{months:"\u064a\u0646\u0627\u064a\u0631_\u0641\u0628\u0631\u0627\u064a\u0631_\u0645\u0627\u0631\u0633_\u0623\u0628\u0631\u064a\u0644_\u0645\u0627\u064a_\u064a\u0648\u0646\u064a\u0648_\u064a\u0648\u0644\u064a\u0648\u0632_\u063a\u0634\u062a_\u0634\u062a\u0646\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0646\u0628\u0631_\u062f\u062c\u0646\u0628\u0631".split("_"),monthsShort:"\u064a\u0646\u0627\u064a\u0631_\u0641\u0628\u0631\u0627\u064a\u0631_\u0645\u0627\u0631\u0633_\u0623\u0628\u0631\u064a\u0644_\u0645\u0627\u064a_\u064a\u0648\u0646\u064a\u0648_\u064a\u0648\u0644\u064a\u0648\u0632_\u063a\u0634\u062a_\u0634\u062a\u0646\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0646\u0628\u0631_\u062f\u062c\u0646\u0628\u0631".split("_"),weekdays:"\u0627\u0644\u0623\u062d\u062f_\u0627\u0644\u0625\u062a\u0646\u064a\u0646_\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621_\u0627\u0644\u062e\u0645\u064a\u0633_\u0627\u0644\u062c\u0645\u0639\u0629_\u0627\u0644\u0633\u0628\u062a".split("_"),weekdaysShort:"\u0627\u062d\u062f_\u0627\u062a\u0646\u064a\u0646_\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0631\u0628\u0639\u0627\u0621_\u062e\u0645\u064a\u0633_\u062c\u0645\u0639\u0629_\u0633\u0628\u062a".split("_"),weekdaysMin:"\u062d_\u0646_\u062b_\u0631_\u062e_\u062c_\u0633".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[\u0627\u0644\u064a\u0648\u0645 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextDay:"[\u063a\u062f\u0627 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastDay:"[\u0623\u0645\u0633 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",sameElse:"L"},relativeTime:{future:"\u0641\u064a %s",past:"\u0645\u0646\u0630 %s",s:"\u062b\u0648\u0627\u0646",ss:"%d \u062b\u0627\u0646\u064a\u0629",m:"\u062f\u0642\u064a\u0642\u0629",mm:"%d \u062f\u0642\u0627\u0626\u0642",h:"\u0633\u0627\u0639\u0629",hh:"%d \u0633\u0627\u0639\u0627\u062a",d:"\u064a\u0648\u0645",dd:"%d \u0623\u064a\u0627\u0645",M:"\u0634\u0647\u0631",MM:"%d \u0623\u0634\u0647\u0631",y:"\u0633\u0646\u0629",yy:"%d \u0633\u0646\u0648\u0627\u062a"},week:{dow:0,doy:12}});var Hs={1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",0:"0"},bs=function(e){return 0===e?0:1===e?1:2===e?2:e%100>=3&&e%100<=10?3:e%100>=11?4:5},js={s:["\u0623\u0642\u0644 \u0645\u0646 \u062b\u0627\u0646\u064a\u0629","\u062b\u0627\u0646\u064a\u0629 \u0648\u0627\u062d\u062f\u0629",["\u062b\u0627\u0646\u064a\u062a\u0627\u0646","\u062b\u0627\u0646\u064a\u062a\u064a\u0646"],"%d \u062b\u0648\u0627\u0646","%d \u062b\u0627\u0646\u064a\u0629","%d \u062b\u0627\u0646\u064a\u0629"],m:["\u0623\u0642\u0644 \u0645\u0646 \u062f\u0642\u064a\u0642\u0629","\u062f\u0642\u064a\u0642\u0629 \u0648\u0627\u062d\u062f\u0629",["\u062f\u0642\u064a\u0642\u062a\u0627\u0646","\u062f\u0642\u064a\u0642\u062a\u064a\u0646"],"%d \u062f\u0642\u0627\u0626\u0642","%d \u062f\u0642\u064a\u0642\u0629","%d \u062f\u0642\u064a\u0642\u0629"],h:["\u0623\u0642\u0644 \u0645\u0646 \u0633\u0627\u0639\u0629","\u0633\u0627\u0639\u0629 \u0648\u0627\u062d\u062f\u0629",["\u0633\u0627\u0639\u062a\u0627\u0646","\u0633\u0627\u0639\u062a\u064a\u0646"],"%d \u0633\u0627\u0639\u0627\u062a","%d \u0633\u0627\u0639\u0629","%d \u0633\u0627\u0639\u0629"],d:["\u0623\u0642\u0644 \u0645\u0646 \u064a\u0648\u0645","\u064a\u0648\u0645 \u0648\u0627\u062d\u062f",["\u064a\u0648\u0645\u0627\u0646","\u064a\u0648\u0645\u064a\u0646"],"%d \u0623\u064a\u0627\u0645","%d \u064a\u0648\u0645\u064b\u0627","%d \u064a\u0648\u0645"],M:["\u0623\u0642\u0644 \u0645\u0646 \u0634\u0647\u0631","\u0634\u0647\u0631 \u0648\u0627\u062d\u062f",["\u0634\u0647\u0631\u0627\u0646","\u0634\u0647\u0631\u064a\u0646"],"%d \u0623\u0634\u0647\u0631","%d \u0634\u0647\u0631\u0627","%d \u0634\u0647\u0631"],y:["\u0623\u0642\u0644 \u0645\u0646 \u0639\u0627\u0645","\u0639\u0627\u0645 \u0648\u0627\u062d\u062f",["\u0639\u0627\u0645\u0627\u0646","\u0639\u0627\u0645\u064a\u0646"],"%d \u0623\u0639\u0648\u0627\u0645","%d \u0639\u0627\u0645\u064b\u0627","%d \u0639\u0627\u0645"]},xs=function(e){return function(a,t,s,n){var d=bs(a),r=js[e][bs(a)];return 2===d&&(r=r[t?0:1]),r.replace(/%d/i,a)}},Ps=["\u064a\u0646\u0627\u064a\u0631","\u0641\u0628\u0631\u0627\u064a\u0631","\u0645\u0627\u0631\u0633","\u0623\u0628\u0631\u064a\u0644","\u0645\u0627\u064a\u0648","\u064a\u0648\u0646\u064a\u0648","\u064a\u0648\u0644\u064a\u0648","\u0623\u063a\u0633\u0637\u0633","\u0633\u0628\u062a\u0645\u0628\u0631","\u0623\u0643\u062a\u0648\u0628\u0631","\u0646\u0648\u0641\u0645\u0628\u0631","\u062f\u064a\u0633\u0645\u0628\u0631"];e.defineLocale("ar-ly",{months:Ps,monthsShort:Ps,weekdays:"\u0627\u0644\u0623\u062d\u062f_\u0627\u0644\u0625\u062b\u0646\u064a\u0646_\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621_\u0627\u0644\u062e\u0645\u064a\u0633_\u0627\u0644\u062c\u0645\u0639\u0629_\u0627\u0644\u0633\u0628\u062a".split("_"),weekdaysShort:"\u0623\u062d\u062f_\u0625\u062b\u0646\u064a\u0646_\u062b\u0644\u0627\u062b\u0627\u0621_\u0623\u0631\u0628\u0639\u0627\u0621_\u062e\u0645\u064a\u0633_\u062c\u0645\u0639\u0629_\u0633\u0628\u062a".split("_"),weekdaysMin:"\u062d_\u0646_\u062b_\u0631_\u062e_\u062c_\u0633".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"D/\u200fM/\u200fYYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},meridiemParse:/\u0635|\u0645/,isPM:function(e){return"\u0645"===e},meridiem:function(e,a,t){return e<12?"\u0635":"\u0645"},calendar:{sameDay:"[\u0627\u0644\u064a\u0648\u0645 \u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextDay:"[\u063a\u062f\u064b\u0627 \u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextWeek:"dddd [\u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastDay:"[\u0623\u0645\u0633 \u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastWeek:"dddd [\u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",sameElse:"L"},relativeTime:{future:"\u0628\u0639\u062f %s",past:"\u0645\u0646\u0630 %s",s:xs("s"),ss:xs("s"),m:xs("m"),mm:xs("m"),h:xs("h"),hh:xs("h"),d:xs("d"),dd:xs("d"),M:xs("M"),MM:xs("M"),y:xs("y"),yy:xs("y")},preparse:function(e){return e.replace(/\u060c/g,",")},postformat:function(e){return e.replace(/\d/g,function(e){return Hs[e]}).replace(/,/g,"\u060c")},week:{dow:6,doy:12}}),e.defineLocale("ar-ma",{months:"\u064a\u0646\u0627\u064a\u0631_\u0641\u0628\u0631\u0627\u064a\u0631_\u0645\u0627\u0631\u0633_\u0623\u0628\u0631\u064a\u0644_\u0645\u0627\u064a_\u064a\u0648\u0646\u064a\u0648_\u064a\u0648\u0644\u064a\u0648\u0632_\u063a\u0634\u062a_\u0634\u062a\u0646\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0646\u0628\u0631_\u062f\u062c\u0646\u0628\u0631".split("_"),monthsShort:"\u064a\u0646\u0627\u064a\u0631_\u0641\u0628\u0631\u0627\u064a\u0631_\u0645\u0627\u0631\u0633_\u0623\u0628\u0631\u064a\u0644_\u0645\u0627\u064a_\u064a\u0648\u0646\u064a\u0648_\u064a\u0648\u0644\u064a\u0648\u0632_\u063a\u0634\u062a_\u0634\u062a\u0646\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0646\u0628\u0631_\u062f\u062c\u0646\u0628\u0631".split("_"),weekdays:"\u0627\u0644\u0623\u062d\u062f_\u0627\u0644\u0625\u062a\u0646\u064a\u0646_\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621_\u0627\u0644\u062e\u0645\u064a\u0633_\u0627\u0644\u062c\u0645\u0639\u0629_\u0627\u0644\u0633\u0628\u062a".split("_"),weekdaysShort:"\u0627\u062d\u062f_\u0627\u062a\u0646\u064a\u0646_\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0631\u0628\u0639\u0627\u0621_\u062e\u0645\u064a\u0633_\u062c\u0645\u0639\u0629_\u0633\u0628\u062a".split("_"),weekdaysMin:"\u062d_\u0646_\u062b_\u0631_\u062e_\u062c_\u0633".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[\u0627\u0644\u064a\u0648\u0645 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextDay:"[\u063a\u062f\u0627 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastDay:"[\u0623\u0645\u0633 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",sameElse:"L"},relativeTime:{future:"\u0641\u064a %s",past:"\u0645\u0646\u0630 %s",s:"\u062b\u0648\u0627\u0646",ss:"%d \u062b\u0627\u0646\u064a\u0629",m:"\u062f\u0642\u064a\u0642\u0629",mm:"%d \u062f\u0642\u0627\u0626\u0642",h:"\u0633\u0627\u0639\u0629",hh:"%d \u0633\u0627\u0639\u0627\u062a",d:"\u064a\u0648\u0645",dd:"%d \u0623\u064a\u0627\u0645",M:"\u0634\u0647\u0631",MM:"%d \u0623\u0634\u0647\u0631",y:"\u0633\u0646\u0629",yy:"%d \u0633\u0646\u0648\u0627\u062a"},week:{dow:6,doy:12}});var Os={1:"\u0661",2:"\u0662",3:"\u0663",4:"\u0664",5:"\u0665",6:"\u0666",7:"\u0667",8:"\u0668",9:"\u0669",0:"\u0660"},Ws={"\u0661":"1","\u0662":"2","\u0663":"3","\u0664":"4","\u0665":"5","\u0666":"6","\u0667":"7","\u0668":"8","\u0669":"9","\u0660":"0"};e.defineLocale("ar-sa",{months:"\u064a\u0646\u0627\u064a\u0631_\u0641\u0628\u0631\u0627\u064a\u0631_\u0645\u0627\u0631\u0633_\u0623\u0628\u0631\u064a\u0644_\u0645\u0627\u064a\u0648_\u064a\u0648\u0646\u064a\u0648_\u064a\u0648\u0644\u064a\u0648_\u0623\u063a\u0633\u0637\u0633_\u0633\u0628\u062a\u0645\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0641\u0645\u0628\u0631_\u062f\u064a\u0633\u0645\u0628\u0631".split("_"),monthsShort:"\u064a\u0646\u0627\u064a\u0631_\u0641\u0628\u0631\u0627\u064a\u0631_\u0645\u0627\u0631\u0633_\u0623\u0628\u0631\u064a\u0644_\u0645\u0627\u064a\u0648_\u064a\u0648\u0646\u064a\u0648_\u064a\u0648\u0644\u064a\u0648_\u0623\u063a\u0633\u0637\u0633_\u0633\u0628\u062a\u0645\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0641\u0645\u0628\u0631_\u062f\u064a\u0633\u0645\u0628\u0631".split("_"),weekdays:"\u0627\u0644\u0623\u062d\u062f_\u0627\u0644\u0625\u062b\u0646\u064a\u0646_\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621_\u0627\u0644\u062e\u0645\u064a\u0633_\u0627\u0644\u062c\u0645\u0639\u0629_\u0627\u0644\u0633\u0628\u062a".split("_"),weekdaysShort:"\u0623\u062d\u062f_\u0625\u062b\u0646\u064a\u0646_\u062b\u0644\u0627\u062b\u0627\u0621_\u0623\u0631\u0628\u0639\u0627\u0621_\u062e\u0645\u064a\u0633_\u062c\u0645\u0639\u0629_\u0633\u0628\u062a".split("_"),weekdaysMin:"\u062d_\u0646_\u062b_\u0631_\u062e_\u062c_\u0633".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},meridiemParse:/\u0635|\u0645/,isPM:function(e){return"\u0645"===e},meridiem:function(e,a,t){return e<12?"\u0635":"\u0645"},calendar:{sameDay:"[\u0627\u0644\u064a\u0648\u0645 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextDay:"[\u063a\u062f\u0627 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastDay:"[\u0623\u0645\u0633 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",sameElse:"L"},relativeTime:{future:"\u0641\u064a %s",past:"\u0645\u0646\u0630 %s",s:"\u062b\u0648\u0627\u0646",ss:"%d \u062b\u0627\u0646\u064a\u0629",m:"\u062f\u0642\u064a\u0642\u0629",mm:"%d \u062f\u0642\u0627\u0626\u0642",h:"\u0633\u0627\u0639\u0629",hh:"%d \u0633\u0627\u0639\u0627\u062a",d:"\u064a\u0648\u0645",dd:"%d \u0623\u064a\u0627\u0645",M:"\u0634\u0647\u0631",MM:"%d \u0623\u0634\u0647\u0631",y:"\u0633\u0646\u0629",yy:"%d \u0633\u0646\u0648\u0627\u062a"},preparse:function(e){return e.replace(/[\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u0660]/g,function(e){return Ws[e]}).replace(/\u060c/g,",")},postformat:function(e){return e.replace(/\d/g,function(e){return Os[e]}).replace(/,/g,"\u060c")},week:{dow:0,doy:6}}),e.defineLocale("ar-tn",{months:"\u062c\u0627\u0646\u0641\u064a_\u0641\u064a\u0641\u0631\u064a_\u0645\u0627\u0631\u0633_\u0623\u0641\u0631\u064a\u0644_\u0645\u0627\u064a_\u062c\u0648\u0627\u0646_\u062c\u0648\u064a\u0644\u064a\u0629_\u0623\u0648\u062a_\u0633\u0628\u062a\u0645\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0641\u0645\u0628\u0631_\u062f\u064a\u0633\u0645\u0628\u0631".split("_"),monthsShort:"\u062c\u0627\u0646\u0641\u064a_\u0641\u064a\u0641\u0631\u064a_\u0645\u0627\u0631\u0633_\u0623\u0641\u0631\u064a\u0644_\u0645\u0627\u064a_\u062c\u0648\u0627\u0646_\u062c\u0648\u064a\u0644\u064a\u0629_\u0623\u0648\u062a_\u0633\u0628\u062a\u0645\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0641\u0645\u0628\u0631_\u062f\u064a\u0633\u0645\u0628\u0631".split("_"),weekdays:"\u0627\u0644\u0623\u062d\u062f_\u0627\u0644\u0625\u062b\u0646\u064a\u0646_\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621_\u0627\u0644\u062e\u0645\u064a\u0633_\u0627\u0644\u062c\u0645\u0639\u0629_\u0627\u0644\u0633\u0628\u062a".split("_"),weekdaysShort:"\u0623\u062d\u062f_\u0625\u062b\u0646\u064a\u0646_\u062b\u0644\u0627\u062b\u0627\u0621_\u0623\u0631\u0628\u0639\u0627\u0621_\u062e\u0645\u064a\u0633_\u062c\u0645\u0639\u0629_\u0633\u0628\u062a".split("_"),weekdaysMin:"\u062d_\u0646_\u062b_\u0631_\u062e_\u062c_\u0633".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[\u0627\u0644\u064a\u0648\u0645 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextDay:"[\u063a\u062f\u0627 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastDay:"[\u0623\u0645\u0633 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",sameElse:"L"},relativeTime:{future:"\u0641\u064a %s",past:"\u0645\u0646\u0630 %s",s:"\u062b\u0648\u0627\u0646",ss:"%d \u062b\u0627\u0646\u064a\u0629",m:"\u062f\u0642\u064a\u0642\u0629",mm:"%d \u062f\u0642\u0627\u0626\u0642",h:"\u0633\u0627\u0639\u0629",hh:"%d \u0633\u0627\u0639\u0627\u062a",d:"\u064a\u0648\u0645",dd:"%d \u0623\u064a\u0627\u0645",M:"\u0634\u0647\u0631",MM:"%d \u0623\u0634\u0647\u0631",y:"\u0633\u0646\u0629",yy:"%d \u0633\u0646\u0648\u0627\u062a"},week:{dow:1,doy:4}});var Es={1:"\u0661",2:"\u0662",3:"\u0663",4:"\u0664",5:"\u0665",6:"\u0666",7:"\u0667",8:"\u0668",9:"\u0669",0:"\u0660"},As={"\u0661":"1","\u0662":"2","\u0663":"3","\u0664":"4","\u0665":"5","\u0666":"6","\u0667":"7","\u0668":"8","\u0669":"9","\u0660":"0"},Fs=function(e){return 0===e?0:1===e?1:2===e?2:e%100>=3&&e%100<=10?3:e%100>=11?4:5},zs={s:["\u0623\u0642\u0644 \u0645\u0646 \u062b\u0627\u0646\u064a\u0629","\u062b\u0627\u0646\u064a\u0629 \u0648\u0627\u062d\u062f\u0629",["\u062b\u0627\u0646\u064a\u062a\u0627\u0646","\u062b\u0627\u0646\u064a\u062a\u064a\u0646"],"%d \u062b\u0648\u0627\u0646","%d \u062b\u0627\u0646\u064a\u0629","%d \u062b\u0627\u0646\u064a\u0629"],m:["\u0623\u0642\u0644 \u0645\u0646 \u062f\u0642\u064a\u0642\u0629","\u062f\u0642\u064a\u0642\u0629 \u0648\u0627\u062d\u062f\u0629",["\u062f\u0642\u064a\u0642\u062a\u0627\u0646","\u062f\u0642\u064a\u0642\u062a\u064a\u0646"],"%d \u062f\u0642\u0627\u0626\u0642","%d \u062f\u0642\u064a\u0642\u0629","%d \u062f\u0642\u064a\u0642\u0629"],h:["\u0623\u0642\u0644 \u0645\u0646 \u0633\u0627\u0639\u0629","\u0633\u0627\u0639\u0629 \u0648\u0627\u062d\u062f\u0629",["\u0633\u0627\u0639\u062a\u0627\u0646","\u0633\u0627\u0639\u062a\u064a\u0646"],"%d \u0633\u0627\u0639\u0627\u062a","%d \u0633\u0627\u0639\u0629","%d \u0633\u0627\u0639\u0629"],d:["\u0623\u0642\u0644 \u0645\u0646 \u064a\u0648\u0645","\u064a\u0648\u0645 \u0648\u0627\u062d\u062f",["\u064a\u0648\u0645\u0627\u0646","\u064a\u0648\u0645\u064a\u0646"],"%d \u0623\u064a\u0627\u0645","%d \u064a\u0648\u0645\u064b\u0627","%d \u064a\u0648\u0645"],M:["\u0623\u0642\u0644 \u0645\u0646 \u0634\u0647\u0631","\u0634\u0647\u0631 \u0648\u0627\u062d\u062f",["\u0634\u0647\u0631\u0627\u0646","\u0634\u0647\u0631\u064a\u0646"],"%d \u0623\u0634\u0647\u0631","%d \u0634\u0647\u0631\u0627","%d \u0634\u0647\u0631"],y:["\u0623\u0642\u0644 \u0645\u0646 \u0639\u0627\u0645","\u0639\u0627\u0645 \u0648\u0627\u062d\u062f",["\u0639\u0627\u0645\u0627\u0646","\u0639\u0627\u0645\u064a\u0646"],"%d \u0623\u0639\u0648\u0627\u0645","%d \u0639\u0627\u0645\u064b\u0627","%d \u0639\u0627\u0645"]},Js=function(e){return function(a,t,s,n){var d=Fs(a),r=zs[e][Fs(a)];return 2===d&&(r=r[t?0:1]),r.replace(/%d/i,a)}},Ns=["\u064a\u0646\u0627\u064a\u0631","\u0641\u0628\u0631\u0627\u064a\u0631","\u0645\u0627\u0631\u0633","\u0623\u0628\u0631\u064a\u0644","\u0645\u0627\u064a\u0648","\u064a\u0648\u0646\u064a\u0648","\u064a\u0648\u0644\u064a\u0648","\u0623\u063a\u0633\u0637\u0633","\u0633\u0628\u062a\u0645\u0628\u0631","\u0623\u0643\u062a\u0648\u0628\u0631","\u0646\u0648\u0641\u0645\u0628\u0631","\u062f\u064a\u0633\u0645\u0628\u0631"];e.defineLocale("ar",{months:Ns,monthsShort:Ns,weekdays:"\u0627\u0644\u0623\u062d\u062f_\u0627\u0644\u0625\u062b\u0646\u064a\u0646_\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621_\u0627\u0644\u062e\u0645\u064a\u0633_\u0627\u0644\u062c\u0645\u0639\u0629_\u0627\u0644\u0633\u0628\u062a".split("_"),weekdaysShort:"\u0623\u062d\u062f_\u0625\u062b\u0646\u064a\u0646_\u062b\u0644\u0627\u062b\u0627\u0621_\u0623\u0631\u0628\u0639\u0627\u0621_\u062e\u0645\u064a\u0633_\u062c\u0645\u0639\u0629_\u0633\u0628\u062a".split("_"),weekdaysMin:"\u062d_\u0646_\u062b_\u0631_\u062e_\u062c_\u0633".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"D/\u200fM/\u200fYYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},meridiemParse:/\u0635|\u0645/,isPM:function(e){return"\u0645"===e},meridiem:function(e,a,t){return e<12?"\u0635":"\u0645"},calendar:{sameDay:"[\u0627\u0644\u064a\u0648\u0645 \u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextDay:"[\u063a\u062f\u064b\u0627 \u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextWeek:"dddd [\u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastDay:"[\u0623\u0645\u0633 \u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastWeek:"dddd [\u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",sameElse:"L"},relativeTime:{future:"\u0628\u0639\u062f %s",past:"\u0645\u0646\u0630 %s",s:Js("s"),ss:Js("s"),m:Js("m"),mm:Js("m"),h:Js("h"),hh:Js("h"),d:Js("d"),dd:Js("d"),M:Js("M"),MM:Js("M"),y:Js("y"),yy:Js("y")},preparse:function(e){return e.replace(/[\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u0660]/g,function(e){return As[e]}).replace(/\u060c/g,",")},postformat:function(e){return e.replace(/\d/g,function(e){return Es[e]}).replace(/,/g,"\u060c")},week:{dow:6,doy:12}});var Rs={1:"-inci",5:"-inci",8:"-inci",70:"-inci",80:"-inci",2:"-nci",7:"-nci",20:"-nci",50:"-nci",3:"-\xfcnc\xfc",4:"-\xfcnc\xfc",100:"-\xfcnc\xfc",6:"-nc\u0131",9:"-uncu",10:"-uncu",30:"-uncu",60:"-\u0131nc\u0131",90:"-\u0131nc\u0131"};e.defineLocale("az",{months:"yanvar_fevral_mart_aprel_may_iyun_iyul_avqust_sentyabr_oktyabr_noyabr_dekabr".split("_"),monthsShort:"yan_fev_mar_apr_may_iyn_iyl_avq_sen_okt_noy_dek".split("_"),weekdays:"Bazar_Bazar ert\u0259si_\xc7\u0259r\u015f\u0259nb\u0259 ax\u015fam\u0131_\xc7\u0259r\u015f\u0259nb\u0259_C\xfcm\u0259 ax\u015fam\u0131_C\xfcm\u0259_\u015e\u0259nb\u0259".split("_"),weekdaysShort:"Baz_BzE_\xc7Ax_\xc7\u0259r_CAx_C\xfcm_\u015e\u0259n".split("_"),weekdaysMin:"Bz_BE_\xc7A_\xc7\u0259_CA_C\xfc_\u015e\u0259".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[bug\xfcn saat] LT",nextDay:"[sabah saat] LT",nextWeek:"[g\u0259l\u0259n h\u0259ft\u0259] dddd [saat] LT",lastDay:"[d\xfcn\u0259n] LT",lastWeek:"[ke\xe7\u0259n h\u0259ft\u0259] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s sonra",past:"%s \u0259vv\u0259l",s:"birne\xe7\u0259 saniyy\u0259",ss:"%d saniy\u0259",m:"bir d\u0259qiq\u0259",mm:"%d d\u0259qiq\u0259",h:"bir saat",hh:"%d saat",d:"bir g\xfcn",dd:"%d g\xfcn",M:"bir ay",MM:"%d ay",y:"bir il",yy:"%d il"},meridiemParse:/gec\u0259|s\u0259h\u0259r|g\xfcnd\xfcz|ax\u015fam/,isPM:function(e){return/^(g\xfcnd\xfcz|ax\u015fam)$/.test(e)},meridiem:function(e,a,t){return e<4?"gec\u0259":e<12?"s\u0259h\u0259r":e<17?"g\xfcnd\xfcz":"ax\u015fam"},dayOfMonthOrdinalParse:/\d{1,2}-(\u0131nc\u0131|inci|nci|\xfcnc\xfc|nc\u0131|uncu)/,ordinal:function(e){if(0===e)return e+"-\u0131nc\u0131";var a=e%10;return e+(Rs[a]||Rs[e%100-a]||Rs[e>=100?100:null])},week:{dow:1,doy:7}}),e.defineLocale("be",{months:{format:"\u0441\u0442\u0443\u0434\u0437\u0435\u043d\u044f_\u043b\u044e\u0442\u0430\u0433\u0430_\u0441\u0430\u043a\u0430\u0432\u0456\u043a\u0430_\u043a\u0440\u0430\u0441\u0430\u0432\u0456\u043a\u0430_\u0442\u0440\u0430\u045e\u043d\u044f_\u0447\u044d\u0440\u0432\u0435\u043d\u044f_\u043b\u0456\u043f\u0435\u043d\u044f_\u0436\u043d\u0456\u045e\u043d\u044f_\u0432\u0435\u0440\u0430\u0441\u043d\u044f_\u043a\u0430\u0441\u0442\u0440\u044b\u0447\u043d\u0456\u043a\u0430_\u043b\u0456\u0441\u0442\u0430\u043f\u0430\u0434\u0430_\u0441\u043d\u0435\u0436\u043d\u044f".split("_"),standalone:"\u0441\u0442\u0443\u0434\u0437\u0435\u043d\u044c_\u043b\u044e\u0442\u044b_\u0441\u0430\u043a\u0430\u0432\u0456\u043a_\u043a\u0440\u0430\u0441\u0430\u0432\u0456\u043a_\u0442\u0440\u0430\u0432\u0435\u043d\u044c_\u0447\u044d\u0440\u0432\u0435\u043d\u044c_\u043b\u0456\u043f\u0435\u043d\u044c_\u0436\u043d\u0456\u0432\u0435\u043d\u044c_\u0432\u0435\u0440\u0430\u0441\u0435\u043d\u044c_\u043a\u0430\u0441\u0442\u0440\u044b\u0447\u043d\u0456\u043a_\u043b\u0456\u0441\u0442\u0430\u043f\u0430\u0434_\u0441\u043d\u0435\u0436\u0430\u043d\u044c".split("_")},monthsShort:"\u0441\u0442\u0443\u0434_\u043b\u044e\u0442_\u0441\u0430\u043a_\u043a\u0440\u0430\u0441_\u0442\u0440\u0430\u0432_\u0447\u044d\u0440\u0432_\u043b\u0456\u043f_\u0436\u043d\u0456\u0432_\u0432\u0435\u0440_\u043a\u0430\u0441\u0442_\u043b\u0456\u0441\u0442_\u0441\u043d\u0435\u0436".split("_"),weekdays:{format:"\u043d\u044f\u0434\u0437\u0435\u043b\u044e_\u043f\u0430\u043d\u044f\u0434\u0437\u0435\u043b\u0430\u043a_\u0430\u045e\u0442\u043e\u0440\u0430\u043a_\u0441\u0435\u0440\u0430\u0434\u0443_\u0447\u0430\u0446\u0432\u0435\u0440_\u043f\u044f\u0442\u043d\u0456\u0446\u0443_\u0441\u0443\u0431\u043e\u0442\u0443".split("_"),standalone:"\u043d\u044f\u0434\u0437\u0435\u043b\u044f_\u043f\u0430\u043d\u044f\u0434\u0437\u0435\u043b\u0430\u043a_\u0430\u045e\u0442\u043e\u0440\u0430\u043a_\u0441\u0435\u0440\u0430\u0434\u0430_\u0447\u0430\u0446\u0432\u0435\u0440_\u043f\u044f\u0442\u043d\u0456\u0446\u0430_\u0441\u0443\u0431\u043e\u0442\u0430".split("_"),isFormat:/\[ ?[\u0412\u0432] ?(?:\u043c\u0456\u043d\u0443\u043b\u0443\u044e|\u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0443\u044e)? ?\] ?dddd/},weekdaysShort:"\u043d\u0434_\u043f\u043d_\u0430\u0442_\u0441\u0440_\u0447\u0446_\u043f\u0442_\u0441\u0431".split("_"),weekdaysMin:"\u043d\u0434_\u043f\u043d_\u0430\u0442_\u0441\u0440_\u0447\u0446_\u043f\u0442_\u0441\u0431".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY \u0433.",LLL:"D MMMM YYYY \u0433., HH:mm",LLLL:"dddd, D MMMM YYYY \u0433., HH:mm"},calendar:{sameDay:"[\u0421\u0451\u043d\u043d\u044f \u045e] LT",nextDay:"[\u0417\u0430\u045e\u0442\u0440\u0430 \u045e] LT",lastDay:"[\u0423\u0447\u043e\u0440\u0430 \u045e] LT",nextWeek:function(){return"[\u0423] dddd [\u045e] LT"},lastWeek:function(){switch(this.day()){case 0:case 3:case 5:case 6:return"[\u0423 \u043c\u0456\u043d\u0443\u043b\u0443\u044e] dddd [\u045e] LT";case 1:case 2:case 4:return"[\u0423 \u043c\u0456\u043d\u0443\u043b\u044b] dddd [\u045e] LT"}},sameElse:"L"},relativeTime:{future:"\u043f\u0440\u0430\u0437 %s",past:"%s \u0442\u0430\u043c\u0443",s:"\u043d\u0435\u043a\u0430\u043b\u044c\u043a\u0456 \u0441\u0435\u043a\u0443\u043d\u0434",m:qe,mm:qe,h:qe,hh:qe,d:"\u0434\u0437\u0435\u043d\u044c",dd:qe,M:"\u043c\u0435\u0441\u044f\u0446",MM:qe,y:"\u0433\u043e\u0434",yy:qe},meridiemParse:/\u043d\u043e\u0447\u044b|\u0440\u0430\u043d\u0456\u0446\u044b|\u0434\u043d\u044f|\u0432\u0435\u0447\u0430\u0440\u0430/,isPM:function(e){return/^(\u0434\u043d\u044f|\u0432\u0435\u0447\u0430\u0440\u0430)$/.test(e)},meridiem:function(e,a,t){return e<4?"\u043d\u043e\u0447\u044b":e<12?"\u0440\u0430\u043d\u0456\u0446\u044b":e<17?"\u0434\u043d\u044f":"\u0432\u0435\u0447\u0430\u0440\u0430"},dayOfMonthOrdinalParse:/\d{1,2}-(\u0456|\u044b|\u0433\u0430)/,ordinal:function(e,a){switch(a){case"M":case"d":case"DDD":case"w":case"W":return e%10!=2&&e%10!=3||e%100==12||e%100==13?e+"-\u044b":e+"-\u0456";case"D":return e+"-\u0433\u0430";default:return e}},week:{dow:1,doy:7}}),e.defineLocale("bg",{months:"\u044f\u043d\u0443\u0430\u0440\u0438_\u0444\u0435\u0432\u0440\u0443\u0430\u0440\u0438_\u043c\u0430\u0440\u0442_\u0430\u043f\u0440\u0438\u043b_\u043c\u0430\u0439_\u044e\u043d\u0438_\u044e\u043b\u0438_\u0430\u0432\u0433\u0443\u0441\u0442_\u0441\u0435\u043f\u0442\u0435\u043c\u0432\u0440\u0438_\u043e\u043a\u0442\u043e\u043c\u0432\u0440\u0438_\u043d\u043e\u0435\u043c\u0432\u0440\u0438_\u0434\u0435\u043a\u0435\u043c\u0432\u0440\u0438".split("_"),monthsShort:"\u044f\u043d\u0440_\u0444\u0435\u0432_\u043c\u0430\u0440_\u0430\u043f\u0440_\u043c\u0430\u0439_\u044e\u043d\u0438_\u044e\u043b\u0438_\u0430\u0432\u0433_\u0441\u0435\u043f_\u043e\u043a\u0442_\u043d\u043e\u0435_\u0434\u0435\u043a".split("_"),weekdays:"\u043d\u0435\u0434\u0435\u043b\u044f_\u043f\u043e\u043d\u0435\u0434\u0435\u043b\u043d\u0438\u043a_\u0432\u0442\u043e\u0440\u043d\u0438\u043a_\u0441\u0440\u044f\u0434\u0430_\u0447\u0435\u0442\u0432\u044a\u0440\u0442\u044a\u043a_\u043f\u0435\u0442\u044a\u043a_\u0441\u044a\u0431\u043e\u0442\u0430".split("_"),weekdaysShort:"\u043d\u0435\u0434_\u043f\u043e\u043d_\u0432\u0442\u043e_\u0441\u0440\u044f_\u0447\u0435\u0442_\u043f\u0435\u0442_\u0441\u044a\u0431".split("_"),weekdaysMin:"\u043d\u0434_\u043f\u043d_\u0432\u0442_\u0441\u0440_\u0447\u0442_\u043f\u0442_\u0441\u0431".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"D.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY H:mm",LLLL:"dddd, D MMMM YYYY H:mm"},calendar:{sameDay:"[\u0414\u043d\u0435\u0441 \u0432] LT",nextDay:"[\u0423\u0442\u0440\u0435 \u0432] LT",nextWeek:"dddd [\u0432] LT",lastDay:"[\u0412\u0447\u0435\u0440\u0430 \u0432] LT",lastWeek:function(){switch(this.day()){case 0:case 3:case 6:return"[\u0412 \u0438\u0437\u043c\u0438\u043d\u0430\u043b\u0430\u0442\u0430] dddd [\u0432] LT";case 1:case 2:case 4:case 5:return"[\u0412 \u0438\u0437\u043c\u0438\u043d\u0430\u043b\u0438\u044f] dddd [\u0432] LT"}},sameElse:"L"},relativeTime:{future:"\u0441\u043b\u0435\u0434 %s",past:"\u043f\u0440\u0435\u0434\u0438 %s",s:"\u043d\u044f\u043a\u043e\u043b\u043a\u043e \u0441\u0435\u043a\u0443\u043d\u0434\u0438",ss:"%d \u0441\u0435\u043a\u0443\u043d\u0434\u0438",m:"\u043c\u0438\u043d\u0443\u0442\u0430",mm:"%d \u043c\u0438\u043d\u0443\u0442\u0438",h:"\u0447\u0430\u0441",hh:"%d \u0447\u0430\u0441\u0430",d:"\u0434\u0435\u043d",dd:"%d \u0434\u043d\u0438",M:"\u043c\u0435\u0441\u0435\u0446",MM:"%d \u043c\u0435\u0441\u0435\u0446\u0430",y:"\u0433\u043e\u0434\u0438\u043d\u0430",yy:"%d \u0433\u043e\u0434\u0438\u043d\u0438"},dayOfMonthOrdinalParse:/\d{1,2}-(\u0435\u0432|\u0435\u043d|\u0442\u0438|\u0432\u0438|\u0440\u0438|\u043c\u0438)/,ordinal:function(e){var a=e%10,t=e%100;return 0===e?e+"-\u0435\u0432":0===t?e+"-\u0435\u043d":t>10&&t<20?e+"-\u0442\u0438":1===a?e+"-\u0432\u0438":2===a?e+"-\u0440\u0438":7===a||8===a?e+"-\u043c\u0438":e+"-\u0442\u0438"},week:{dow:1,doy:7}}),e.defineLocale("bm",{months:"Zanwuyekalo_Fewuruyekalo_Marisikalo_Awirilikalo_M\u025bkalo_Zuw\u025bnkalo_Zuluyekalo_Utikalo_S\u025btanburukalo_\u0254kut\u0254burukalo_Nowanburukalo_Desanburukalo".split("_"),monthsShort:"Zan_Few_Mar_Awi_M\u025b_Zuw_Zul_Uti_S\u025bt_\u0254ku_Now_Des".split("_"),weekdays:"Kari_Nt\u025bn\u025bn_Tarata_Araba_Alamisa_Juma_Sibiri".split("_"),weekdaysShort:"Kar_Nt\u025b_Tar_Ara_Ala_Jum_Sib".split("_"),weekdaysMin:"Ka_Nt_Ta_Ar_Al_Ju_Si".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"MMMM [tile] D [san] YYYY",LLL:"MMMM [tile] D [san] YYYY [l\u025br\u025b] HH:mm",LLLL:"dddd MMMM [tile] D [san] YYYY [l\u025br\u025b] HH:mm"},calendar:{sameDay:"[Bi l\u025br\u025b] LT",nextDay:"[Sini l\u025br\u025b] LT",nextWeek:"dddd [don l\u025br\u025b] LT",lastDay:"[Kunu l\u025br\u025b] LT",lastWeek:"dddd [t\u025bm\u025bnen l\u025br\u025b] LT",sameElse:"L"},relativeTime:{future:"%s k\u0254n\u0254",past:"a b\u025b %s b\u0254",s:"sanga dama dama",ss:"sekondi %d",m:"miniti kelen",mm:"miniti %d",h:"l\u025br\u025b kelen",hh:"l\u025br\u025b %d",d:"tile kelen",dd:"tile %d",M:"kalo kelen",MM:"kalo %d",y:"san kelen",yy:"san %d"},week:{dow:1,doy:4}});var Is={1:"\u09e7",2:"\u09e8",3:"\u09e9",4:"\u09ea",5:"\u09eb",6:"\u09ec",7:"\u09ed",8:"\u09ee",9:"\u09ef",0:"\u09e6"},Cs={"\u09e7":"1","\u09e8":"2","\u09e9":"3","\u09ea":"4","\u09eb":"5","\u09ec":"6","\u09ed":"7","\u09ee":"8","\u09ef":"9","\u09e6":"0"};e.defineLocale("bn",{months:"\u099c\u09be\u09a8\u09c1\u09df\u09be\u09b0\u09c0_\u09ab\u09c7\u09ac\u09cd\u09b0\u09c1\u09df\u09be\u09b0\u09bf_\u09ae\u09be\u09b0\u09cd\u099a_\u098f\u09aa\u09cd\u09b0\u09bf\u09b2_\u09ae\u09c7_\u099c\u09c1\u09a8_\u099c\u09c1\u09b2\u09be\u0987_\u0986\u0997\u09b8\u09cd\u099f_\u09b8\u09c7\u09aa\u09cd\u099f\u09c7\u09ae\u09cd\u09ac\u09b0_\u0985\u0995\u09cd\u099f\u09cb\u09ac\u09b0_\u09a8\u09ad\u09c7\u09ae\u09cd\u09ac\u09b0_\u09a1\u09bf\u09b8\u09c7\u09ae\u09cd\u09ac\u09b0".split("_"),monthsShort:"\u099c\u09be\u09a8\u09c1_\u09ab\u09c7\u09ac_\u09ae\u09be\u09b0\u09cd\u099a_\u098f\u09aa\u09cd\u09b0_\u09ae\u09c7_\u099c\u09c1\u09a8_\u099c\u09c1\u09b2_\u0986\u0997_\u09b8\u09c7\u09aa\u09cd\u099f_\u0985\u0995\u09cd\u099f\u09cb_\u09a8\u09ad\u09c7_\u09a1\u09bf\u09b8\u09c7".split("_"),weekdays:"\u09b0\u09ac\u09bf\u09ac\u09be\u09b0_\u09b8\u09cb\u09ae\u09ac\u09be\u09b0_\u09ae\u0999\u09cd\u0997\u09b2\u09ac\u09be\u09b0_\u09ac\u09c1\u09a7\u09ac\u09be\u09b0_\u09ac\u09c3\u09b9\u09b8\u09cd\u09aa\u09a4\u09bf\u09ac\u09be\u09b0_\u09b6\u09c1\u0995\u09cd\u09b0\u09ac\u09be\u09b0_\u09b6\u09a8\u09bf\u09ac\u09be\u09b0".split("_"),weekdaysShort:"\u09b0\u09ac\u09bf_\u09b8\u09cb\u09ae_\u09ae\u0999\u09cd\u0997\u09b2_\u09ac\u09c1\u09a7_\u09ac\u09c3\u09b9\u09b8\u09cd\u09aa\u09a4\u09bf_\u09b6\u09c1\u0995\u09cd\u09b0_\u09b6\u09a8\u09bf".split("_"),weekdaysMin:"\u09b0\u09ac\u09bf_\u09b8\u09cb\u09ae_\u09ae\u0999\u09cd\u0997_\u09ac\u09c1\u09a7_\u09ac\u09c3\u09b9\u0983_\u09b6\u09c1\u0995\u09cd\u09b0_\u09b6\u09a8\u09bf".split("_"),longDateFormat:{LT:"A h:mm \u09b8\u09ae\u09df",LTS:"A h:mm:ss \u09b8\u09ae\u09df",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm \u09b8\u09ae\u09df",LLLL:"dddd, D MMMM YYYY, A h:mm \u09b8\u09ae\u09df"},calendar:{sameDay:"[\u0986\u099c] LT",nextDay:"[\u0986\u0997\u09be\u09ae\u09c0\u0995\u09be\u09b2] LT",nextWeek:"dddd, LT",lastDay:"[\u0997\u09a4\u0995\u09be\u09b2] LT",lastWeek:"[\u0997\u09a4] dddd, LT",sameElse:"L"},relativeTime:{future:"%s \u09aa\u09b0\u09c7",past:"%s \u0986\u0997\u09c7",s:"\u0995\u09df\u09c7\u0995 \u09b8\u09c7\u0995\u09c7\u09a8\u09cd\u09a1",ss:"%d \u09b8\u09c7\u0995\u09c7\u09a8\u09cd\u09a1",m:"\u098f\u0995 \u09ae\u09bf\u09a8\u09bf\u099f",mm:"%d \u09ae\u09bf\u09a8\u09bf\u099f",h:"\u098f\u0995 \u0998\u09a8\u09cd\u099f\u09be",hh:"%d \u0998\u09a8\u09cd\u099f\u09be",d:"\u098f\u0995 \u09a6\u09bf\u09a8",dd:"%d \u09a6\u09bf\u09a8",M:"\u098f\u0995 \u09ae\u09be\u09b8",MM:"%d \u09ae\u09be\u09b8",y:"\u098f\u0995 \u09ac\u099b\u09b0",yy:"%d \u09ac\u099b\u09b0"},preparse:function(e){return e.replace(/[\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u09e6]/g,function(e){return Cs[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return Is[e]})},meridiemParse:/\u09b0\u09be\u09a4|\u09b8\u0995\u09be\u09b2|\u09a6\u09c1\u09aa\u09c1\u09b0|\u09ac\u09bf\u0995\u09be\u09b2|\u09b0\u09be\u09a4/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u09b0\u09be\u09a4"===a&&e>=4||"\u09a6\u09c1\u09aa\u09c1\u09b0"===a&&e<5||"\u09ac\u09bf\u0995\u09be\u09b2"===a?e+12:e},meridiem:function(e,a,t){return e<4?"\u09b0\u09be\u09a4":e<10?"\u09b8\u0995\u09be\u09b2":e<17?"\u09a6\u09c1\u09aa\u09c1\u09b0":e<20?"\u09ac\u09bf\u0995\u09be\u09b2":"\u09b0\u09be\u09a4"},week:{dow:0,doy:6}});var Gs={1:"\u0f21",2:"\u0f22",3:"\u0f23",4:"\u0f24",5:"\u0f25",6:"\u0f26",7:"\u0f27",8:"\u0f28",9:"\u0f29",0:"\u0f20"},Us={"\u0f21":"1","\u0f22":"2","\u0f23":"3","\u0f24":"4","\u0f25":"5","\u0f26":"6","\u0f27":"7","\u0f28":"8","\u0f29":"9","\u0f20":"0"};e.defineLocale("bo",{months:"\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f51\u0f44\u0f0b\u0f54\u0f7c_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f42\u0f49\u0f72\u0f66\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f42\u0f66\u0f74\u0f58\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f5e\u0f72\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f63\u0f94\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f51\u0fb2\u0f74\u0f42\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f51\u0f74\u0f53\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f62\u0f92\u0fb1\u0f51\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f51\u0f42\u0f74\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f45\u0f74\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f45\u0f74\u0f0b\u0f42\u0f45\u0f72\u0f42\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f45\u0f74\u0f0b\u0f42\u0f49\u0f72\u0f66\u0f0b\u0f54".split("_"),monthsShort:"\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f51\u0f44\u0f0b\u0f54\u0f7c_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f42\u0f49\u0f72\u0f66\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f42\u0f66\u0f74\u0f58\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f5e\u0f72\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f63\u0f94\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f51\u0fb2\u0f74\u0f42\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f51\u0f74\u0f53\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f62\u0f92\u0fb1\u0f51\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f51\u0f42\u0f74\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f45\u0f74\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f45\u0f74\u0f0b\u0f42\u0f45\u0f72\u0f42\u0f0b\u0f54_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f45\u0f74\u0f0b\u0f42\u0f49\u0f72\u0f66\u0f0b\u0f54".split("_"),weekdays:"\u0f42\u0f5f\u0f60\u0f0b\u0f49\u0f72\u0f0b\u0f58\u0f0b_\u0f42\u0f5f\u0f60\u0f0b\u0f5f\u0fb3\u0f0b\u0f56\u0f0b_\u0f42\u0f5f\u0f60\u0f0b\u0f58\u0f72\u0f42\u0f0b\u0f51\u0f58\u0f62\u0f0b_\u0f42\u0f5f\u0f60\u0f0b\u0f63\u0fb7\u0f42\u0f0b\u0f54\u0f0b_\u0f42\u0f5f\u0f60\u0f0b\u0f55\u0f74\u0f62\u0f0b\u0f56\u0f74_\u0f42\u0f5f\u0f60\u0f0b\u0f54\u0f0b\u0f66\u0f44\u0f66\u0f0b_\u0f42\u0f5f\u0f60\u0f0b\u0f66\u0fa4\u0f7a\u0f53\u0f0b\u0f54\u0f0b".split("_"),weekdaysShort:"\u0f49\u0f72\u0f0b\u0f58\u0f0b_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b_\u0f58\u0f72\u0f42\u0f0b\u0f51\u0f58\u0f62\u0f0b_\u0f63\u0fb7\u0f42\u0f0b\u0f54\u0f0b_\u0f55\u0f74\u0f62\u0f0b\u0f56\u0f74_\u0f54\u0f0b\u0f66\u0f44\u0f66\u0f0b_\u0f66\u0fa4\u0f7a\u0f53\u0f0b\u0f54\u0f0b".split("_"),weekdaysMin:"\u0f49\u0f72\u0f0b\u0f58\u0f0b_\u0f5f\u0fb3\u0f0b\u0f56\u0f0b_\u0f58\u0f72\u0f42\u0f0b\u0f51\u0f58\u0f62\u0f0b_\u0f63\u0fb7\u0f42\u0f0b\u0f54\u0f0b_\u0f55\u0f74\u0f62\u0f0b\u0f56\u0f74_\u0f54\u0f0b\u0f66\u0f44\u0f66\u0f0b_\u0f66\u0fa4\u0f7a\u0f53\u0f0b\u0f54\u0f0b".split("_"),longDateFormat:{LT:"A h:mm",LTS:"A h:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm",LLLL:"dddd, D MMMM YYYY, A h:mm"},calendar:{sameDay:"[\u0f51\u0f72\u0f0b\u0f62\u0f72\u0f44] LT",nextDay:"[\u0f66\u0f44\u0f0b\u0f49\u0f72\u0f53] LT",nextWeek:"[\u0f56\u0f51\u0f74\u0f53\u0f0b\u0f55\u0fb2\u0f42\u0f0b\u0f62\u0f97\u0f7a\u0f66\u0f0b\u0f58], LT",lastDay:"[\u0f41\u0f0b\u0f66\u0f44] LT",lastWeek:"[\u0f56\u0f51\u0f74\u0f53\u0f0b\u0f55\u0fb2\u0f42\u0f0b\u0f58\u0f50\u0f60\u0f0b\u0f58] dddd, LT",sameElse:"L"},relativeTime:{future:"%s \u0f63\u0f0b",past:"%s \u0f66\u0f94\u0f53\u0f0b\u0f63",s:"\u0f63\u0f58\u0f0b\u0f66\u0f44",ss:"%d \u0f66\u0f90\u0f62\u0f0b\u0f46\u0f0d",m:"\u0f66\u0f90\u0f62\u0f0b\u0f58\u0f0b\u0f42\u0f45\u0f72\u0f42",mm:"%d \u0f66\u0f90\u0f62\u0f0b\u0f58",h:"\u0f46\u0f74\u0f0b\u0f5a\u0f7c\u0f51\u0f0b\u0f42\u0f45\u0f72\u0f42",hh:"%d \u0f46\u0f74\u0f0b\u0f5a\u0f7c\u0f51",d:"\u0f49\u0f72\u0f53\u0f0b\u0f42\u0f45\u0f72\u0f42",dd:"%d \u0f49\u0f72\u0f53\u0f0b",M:"\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f42\u0f45\u0f72\u0f42",MM:"%d \u0f5f\u0fb3\u0f0b\u0f56",y:"\u0f63\u0f7c\u0f0b\u0f42\u0f45\u0f72\u0f42",yy:"%d \u0f63\u0f7c"},preparse:function(e){return e.replace(/[\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u0f20]/g,function(e){return Us[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return Gs[e]})},meridiemParse:/\u0f58\u0f5a\u0f53\u0f0b\u0f58\u0f7c|\u0f5e\u0f7c\u0f42\u0f66\u0f0b\u0f40\u0f66|\u0f49\u0f72\u0f53\u0f0b\u0f42\u0f74\u0f44|\u0f51\u0f42\u0f7c\u0f44\u0f0b\u0f51\u0f42|\u0f58\u0f5a\u0f53\u0f0b\u0f58\u0f7c/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u0f58\u0f5a\u0f53\u0f0b\u0f58\u0f7c"===a&&e>=4||"\u0f49\u0f72\u0f53\u0f0b\u0f42\u0f74\u0f44"===a&&e<5||"\u0f51\u0f42\u0f7c\u0f44\u0f0b\u0f51\u0f42"===a?e+12:e},meridiem:function(e,a,t){return e<4?"\u0f58\u0f5a\u0f53\u0f0b\u0f58\u0f7c":e<10?"\u0f5e\u0f7c\u0f42\u0f66\u0f0b\u0f40\u0f66":e<17?"\u0f49\u0f72\u0f53\u0f0b\u0f42\u0f74\u0f44":e<20?"\u0f51\u0f42\u0f7c\u0f44\u0f0b\u0f51\u0f42":"\u0f58\u0f5a\u0f53\u0f0b\u0f58\u0f7c"},week:{dow:0,doy:6}}),e.defineLocale("br",{months:"Genver_C'hwevrer_Meurzh_Ebrel_Mae_Mezheven_Gouere_Eost_Gwengolo_Here_Du_Kerzu".split("_"),monthsShort:"Gen_C'hwe_Meu_Ebr_Mae_Eve_Gou_Eos_Gwe_Her_Du_Ker".split("_"),weekdays:"Sul_Lun_Meurzh_Merc'her_Yaou_Gwener_Sadorn".split("_"),weekdaysShort:"Sul_Lun_Meu_Mer_Yao_Gwe_Sad".split("_"),weekdaysMin:"Su_Lu_Me_Mer_Ya_Gw_Sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"h[e]mm A",LTS:"h[e]mm:ss A",L:"DD/MM/YYYY",LL:"D [a viz] MMMM YYYY",LLL:"D [a viz] MMMM YYYY h[e]mm A",LLLL:"dddd, D [a viz] MMMM YYYY h[e]mm A"},calendar:{sameDay:"[Hiziv da] LT",nextDay:"[Warc'hoazh da] LT",nextWeek:"dddd [da] LT",lastDay:"[Dec'h da] LT",lastWeek:"dddd [paset da] LT",sameElse:"L"},relativeTime:{future:"a-benn %s",past:"%s 'zo",s:"un nebeud segondenno\xf9",ss:"%d eilenn",m:"ur vunutenn",mm:Qe,h:"un eur",hh:"%d eur",d:"un devezh",dd:Qe,M:"ur miz",MM:Qe,y:"ur bloaz",yy:function(e){switch(Xe(e)){case 1:case 3:case 4:case 5:case 9:return e+" bloaz";default:return e+" vloaz"}}},dayOfMonthOrdinalParse:/\d{1,2}(a\xf1|vet)/,ordinal:function(e){return e+(1===e?"a\xf1":"vet")},week:{dow:1,doy:4}}),e.defineLocale("bs",{months:"januar_februar_mart_april_maj_juni_juli_august_septembar_oktobar_novembar_decembar".split("_"),monthsShort:"jan._feb._mar._apr._maj._jun._jul._aug._sep._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"nedjelja_ponedjeljak_utorak_srijeda_\u010detvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sri._\u010det._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_\u010de_pe_su".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedjelju] [u] LT";case 3:return"[u] [srijedu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[ju\u010der u] LT",lastWeek:function(){switch(this.day()){case 0:case 3:return"[pro\u0161lu] dddd [u] LT";case 6:return"[pro\u0161le] [subote] [u] LT";case 1:case 2:case 4:case 5:return"[pro\u0161li] dddd [u] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"prije %s",s:"par sekundi",ss:ea,m:ea,mm:ea,h:ea,hh:ea,d:"dan",dd:ea,M:"mjesec",MM:ea,y:"godinu",yy:ea},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),e.defineLocale("ca",{months:{standalone:"gener_febrer_mar\xe7_abril_maig_juny_juliol_agost_setembre_octubre_novembre_desembre".split("_"),format:"de gener_de febrer_de mar\xe7_d'abril_de maig_de juny_de juliol_d'agost_de setembre_d'octubre_de novembre_de desembre".split("_"),isFormat:/D[oD]?(\s)+MMMM/},monthsShort:"gen._febr._mar\xe7_abr._maig_juny_jul._ag._set._oct._nov._des.".split("_"),monthsParseExact:!0,weekdays:"diumenge_dilluns_dimarts_dimecres_dijous_divendres_dissabte".split("_"),weekdaysShort:"dg._dl._dt._dc._dj._dv._ds.".split("_"),weekdaysMin:"dg_dl_dt_dc_dj_dv_ds".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM [de] YYYY",ll:"D MMM YYYY",LLL:"D MMMM [de] YYYY [a les] H:mm",lll:"D MMM YYYY, H:mm",LLLL:"dddd D MMMM [de] YYYY [a les] H:mm",llll:"ddd D MMM YYYY, H:mm"},calendar:{sameDay:function(){return"[avui a "+(1!==this.hours()?"les":"la")+"] LT"},nextDay:function(){return"[dem\xe0 a "+(1!==this.hours()?"les":"la")+"] LT"},nextWeek:function(){return"dddd [a "+(1!==this.hours()?"les":"la")+"] LT"},lastDay:function(){return"[ahir a "+(1!==this.hours()?"les":"la")+"] LT"},lastWeek:function(){return"[el] dddd [passat a "+(1!==this.hours()?"les":"la")+"] LT"},sameElse:"L"},relativeTime:{future:"d'aqu\xed %s",past:"fa %s",s:"uns segons",ss:"%d segons",m:"un minut",mm:"%d minuts",h:"una hora",hh:"%d hores",d:"un dia",dd:"%d dies",M:"un mes",MM:"%d mesos",y:"un any",yy:"%d anys"},dayOfMonthOrdinalParse:/\d{1,2}(r|n|t|\xe8|a)/,ordinal:function(e,a){var t=1===e?"r":2===e?"n":3===e?"r":4===e?"t":"\xe8";return"w"!==a&&"W"!==a||(t="a"),e+t},week:{dow:1,doy:4}});var Vs="leden_\xfanor_b\u0159ezen_duben_kv\u011bten_\u010derven_\u010dervenec_srpen_z\xe1\u0159\xed_\u0159\xedjen_listopad_prosinec".split("_"),Ks="led_\xfano_b\u0159e_dub_kv\u011b_\u010dvn_\u010dvc_srp_z\xe1\u0159_\u0159\xedj_lis_pro".split("_");e.defineLocale("cs",{months:Vs,monthsShort:Ks,monthsParse:function(e,a){var t,s=[];for(t=0;t<12;t++)s[t]=new RegExp("^"+e[t]+"$|^"+a[t]+"$","i");return s}(Vs,Ks),shortMonthsParse:function(e){var a,t=[];for(a=0;a<12;a++)t[a]=new RegExp("^"+e[a]+"$","i");return t}(Ks),longMonthsParse:function(e){var a,t=[];for(a=0;a<12;a++)t[a]=new RegExp("^"+e[a]+"$","i");return t}(Vs),weekdays:"ned\u011ble_pond\u011bl\xed_\xfater\xfd_st\u0159eda_\u010dtvrtek_p\xe1tek_sobota".split("_"),weekdaysShort:"ne_po_\xfat_st_\u010dt_p\xe1_so".split("_"),weekdaysMin:"ne_po_\xfat_st_\u010dt_p\xe1_so".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd D. MMMM YYYY H:mm",l:"D. M. YYYY"},calendar:{sameDay:"[dnes v] LT",nextDay:"[z\xedtra v] LT",nextWeek:function(){switch(this.day()){case 0:return"[v ned\u011bli v] LT";case 1:case 2:return"[v] dddd [v] LT";case 3:return"[ve st\u0159edu v] LT";case 4:return"[ve \u010dtvrtek v] LT";case 5:return"[v p\xe1tek v] LT";case 6:return"[v sobotu v] LT"}},lastDay:"[v\u010dera v] LT",lastWeek:function(){switch(this.day()){case 0:return"[minulou ned\u011bli v] LT";case 1:case 2:return"[minul\xe9] dddd [v] LT";case 3:return"[minulou st\u0159edu v] LT";case 4:case 5:return"[minul\xfd] dddd [v] LT";case 6:return"[minulou sobotu v] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"p\u0159ed %s",s:ta,ss:ta,m:ta,mm:ta,h:ta,hh:ta,d:ta,dd:ta,M:ta,MM:ta,y:ta,yy:ta},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),e.defineLocale("cv",{months:"\u043a\u04d1\u0440\u043b\u0430\u0447_\u043d\u0430\u0440\u04d1\u0441_\u043f\u0443\u0448_\u0430\u043a\u0430_\u043c\u0430\u0439_\u04ab\u04d7\u0440\u0442\u043c\u0435_\u0443\u0442\u04d1_\u04ab\u0443\u0440\u043b\u0430_\u0430\u0432\u04d1\u043d_\u044e\u043f\u0430_\u0447\u04f3\u043a_\u0440\u0430\u0448\u0442\u0430\u0432".split("_"),monthsShort:"\u043a\u04d1\u0440_\u043d\u0430\u0440_\u043f\u0443\u0448_\u0430\u043a\u0430_\u043c\u0430\u0439_\u04ab\u04d7\u0440_\u0443\u0442\u04d1_\u04ab\u0443\u0440_\u0430\u0432\u043d_\u044e\u043f\u0430_\u0447\u04f3\u043a_\u0440\u0430\u0448".split("_"),weekdays:"\u0432\u044b\u0440\u0441\u0430\u0440\u043d\u0438\u043a\u0443\u043d_\u0442\u0443\u043d\u0442\u0438\u043a\u0443\u043d_\u044b\u0442\u043b\u0430\u0440\u0438\u043a\u0443\u043d_\u044e\u043d\u043a\u0443\u043d_\u043a\u04d7\u04ab\u043d\u0435\u0440\u043d\u0438\u043a\u0443\u043d_\u044d\u0440\u043d\u0435\u043a\u0443\u043d_\u0448\u04d1\u043c\u0430\u0442\u043a\u0443\u043d".split("_"),weekdaysShort:"\u0432\u044b\u0440_\u0442\u0443\u043d_\u044b\u0442\u043b_\u044e\u043d_\u043a\u04d7\u04ab_\u044d\u0440\u043d_\u0448\u04d1\u043c".split("_"),weekdaysMin:"\u0432\u0440_\u0442\u043d_\u044b\u0442_\u044e\u043d_\u043a\u04ab_\u044d\u0440_\u0448\u043c".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD-MM-YYYY",LL:"YYYY [\u04ab\u0443\u043b\u0445\u0438] MMMM [\u0443\u0439\u04d1\u0445\u04d7\u043d] D[-\u043c\u04d7\u0448\u04d7]",LLL:"YYYY [\u04ab\u0443\u043b\u0445\u0438] MMMM [\u0443\u0439\u04d1\u0445\u04d7\u043d] D[-\u043c\u04d7\u0448\u04d7], HH:mm",LLLL:"dddd, YYYY [\u04ab\u0443\u043b\u0445\u0438] MMMM [\u0443\u0439\u04d1\u0445\u04d7\u043d] D[-\u043c\u04d7\u0448\u04d7], HH:mm"},calendar:{sameDay:"[\u041f\u0430\u044f\u043d] LT [\u0441\u0435\u0445\u0435\u0442\u0440\u0435]",nextDay:"[\u042b\u0440\u0430\u043d] LT [\u0441\u0435\u0445\u0435\u0442\u0440\u0435]",lastDay:"[\u04d6\u043d\u0435\u0440] LT [\u0441\u0435\u0445\u0435\u0442\u0440\u0435]",nextWeek:"[\u04aa\u0438\u0442\u0435\u0441] dddd LT [\u0441\u0435\u0445\u0435\u0442\u0440\u0435]",lastWeek:"[\u0418\u0440\u0442\u043d\u04d7] dddd LT [\u0441\u0435\u0445\u0435\u0442\u0440\u0435]",sameElse:"L"},relativeTime:{future:function(e){return e+(/\u0441\u0435\u0445\u0435\u0442$/i.exec(e)?"\u0440\u0435\u043d":/\u04ab\u0443\u043b$/i.exec(e)?"\u0442\u0430\u043d":"\u0440\u0430\u043d")},past:"%s \u043a\u0430\u044f\u043b\u043b\u0430",s:"\u043f\u04d7\u0440-\u0438\u043a \u04ab\u0435\u043a\u043a\u0443\u043d\u0442",ss:"%d \u04ab\u0435\u043a\u043a\u0443\u043d\u0442",m:"\u043f\u04d7\u0440 \u043c\u0438\u043d\u0443\u0442",mm:"%d \u043c\u0438\u043d\u0443\u0442",h:"\u043f\u04d7\u0440 \u0441\u0435\u0445\u0435\u0442",hh:"%d \u0441\u0435\u0445\u0435\u0442",d:"\u043f\u04d7\u0440 \u043a\u0443\u043d",dd:"%d \u043a\u0443\u043d",M:"\u043f\u04d7\u0440 \u0443\u0439\u04d1\u0445",MM:"%d \u0443\u0439\u04d1\u0445",y:"\u043f\u04d7\u0440 \u04ab\u0443\u043b",yy:"%d \u04ab\u0443\u043b"},dayOfMonthOrdinalParse:/\d{1,2}-\u043c\u04d7\u0448/,ordinal:"%d-\u043c\u04d7\u0448",week:{dow:1,doy:7}}),e.defineLocale("cy",{months:"Ionawr_Chwefror_Mawrth_Ebrill_Mai_Mehefin_Gorffennaf_Awst_Medi_Hydref_Tachwedd_Rhagfyr".split("_"),monthsShort:"Ion_Chwe_Maw_Ebr_Mai_Meh_Gor_Aws_Med_Hyd_Tach_Rhag".split("_"),weekdays:"Dydd Sul_Dydd Llun_Dydd Mawrth_Dydd Mercher_Dydd Iau_Dydd Gwener_Dydd Sadwrn".split("_"),weekdaysShort:"Sul_Llun_Maw_Mer_Iau_Gwe_Sad".split("_"),weekdaysMin:"Su_Ll_Ma_Me_Ia_Gw_Sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Heddiw am] LT",nextDay:"[Yfory am] LT",nextWeek:"dddd [am] LT",lastDay:"[Ddoe am] LT",lastWeek:"dddd [diwethaf am] LT",sameElse:"L"},relativeTime:{future:"mewn %s",past:"%s yn \xf4l",s:"ychydig eiliadau",ss:"%d eiliad",m:"munud",mm:"%d munud",h:"awr",hh:"%d awr",d:"diwrnod",dd:"%d diwrnod",M:"mis",MM:"%d mis",y:"blwyddyn",yy:"%d flynedd"},dayOfMonthOrdinalParse:/\d{1,2}(fed|ain|af|il|ydd|ed|eg)/,ordinal:function(e){var a="";return e>20?a=40===e||50===e||60===e||80===e||100===e?"fed":"ain":e>0&&(a=["","af","il","ydd","ydd","ed","ed","ed","fed","fed","fed","eg","fed","eg","eg","fed","eg","eg","fed","eg","fed"][e]),e+a},week:{dow:1,doy:4}}),e.defineLocale("da",{months:"januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"s\xf8ndag_mandag_tirsdag_onsdag_torsdag_fredag_l\xf8rdag".split("_"),weekdaysShort:"s\xf8n_man_tir_ons_tor_fre_l\xf8r".split("_"),weekdaysMin:"s\xf8_ma_ti_on_to_fr_l\xf8".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY HH:mm",LLLL:"dddd [d.] D. MMMM YYYY [kl.] HH:mm"},calendar:{sameDay:"[i dag kl.] LT",nextDay:"[i morgen kl.] LT",nextWeek:"p\xe5 dddd [kl.] LT",lastDay:"[i g\xe5r kl.] LT",lastWeek:"[i] dddd[s kl.] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s siden",s:"f\xe5 sekunder",ss:"%d sekunder",m:"et minut",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dage",M:"en m\xe5ned",MM:"%d m\xe5neder",y:"et \xe5r",yy:"%d \xe5r"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),e.defineLocale("de-at",{months:"J\xe4nner_Februar_M\xe4rz_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"J\xe4n._Feb._M\xe4rz_Apr._Mai_Juni_Juli_Aug._Sep._Okt._Nov._Dez.".split("_"),monthsParseExact:!0,weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY HH:mm",LLLL:"dddd, D. MMMM YYYY HH:mm"},calendar:{sameDay:"[heute um] LT [Uhr]",sameElse:"L",nextDay:"[morgen um] LT [Uhr]",nextWeek:"dddd [um] LT [Uhr]",lastDay:"[gestern um] LT [Uhr]",lastWeek:"[letzten] dddd [um] LT [Uhr]"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",ss:"%d Sekunden",m:sa,mm:"%d Minuten",h:sa,hh:"%d Stunden",d:sa,dd:sa,M:sa,MM:sa,y:sa,yy:sa},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),e.defineLocale("de-ch",{months:"Januar_Februar_M\xe4rz_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Feb._M\xe4rz_Apr._Mai_Juni_Juli_Aug._Sep._Okt._Nov._Dez.".split("_"),monthsParseExact:!0,weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY HH:mm",LLLL:"dddd, D. MMMM YYYY HH:mm"},calendar:{sameDay:"[heute um] LT [Uhr]",sameElse:"L",nextDay:"[morgen um] LT [Uhr]",nextWeek:"dddd [um] LT [Uhr]",lastDay:"[gestern um] LT [Uhr]",lastWeek:"[letzten] dddd [um] LT [Uhr]"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",ss:"%d Sekunden",m:na,mm:"%d Minuten",h:na,hh:"%d Stunden",d:na,dd:na,M:na,MM:na,y:na,yy:na},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),e.defineLocale("de",{months:"Januar_Februar_M\xe4rz_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Feb._M\xe4rz_Apr._Mai_Juni_Juli_Aug._Sep._Okt._Nov._Dez.".split("_"),monthsParseExact:!0,weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY HH:mm",LLLL:"dddd, D. MMMM YYYY HH:mm"},calendar:{sameDay:"[heute um] LT [Uhr]",sameElse:"L",nextDay:"[morgen um] LT [Uhr]",nextWeek:"dddd [um] LT [Uhr]",lastDay:"[gestern um] LT [Uhr]",lastWeek:"[letzten] dddd [um] LT [Uhr]"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",ss:"%d Sekunden",m:da,mm:"%d Minuten",h:da,hh:"%d Stunden",d:da,dd:da,M:da,MM:da,y:da,yy:da},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}});var Zs=["\u0796\u07ac\u0782\u07aa\u0787\u07a6\u0783\u07a9","\u078a\u07ac\u0784\u07b0\u0783\u07aa\u0787\u07a6\u0783\u07a9","\u0789\u07a7\u0783\u07a8\u0797\u07aa","\u0787\u07ad\u0795\u07b0\u0783\u07a9\u078d\u07aa","\u0789\u07ad","\u0796\u07ab\u0782\u07b0","\u0796\u07aa\u078d\u07a6\u0787\u07a8","\u0787\u07af\u078e\u07a6\u0790\u07b0\u0793\u07aa","\u0790\u07ac\u0795\u07b0\u0793\u07ac\u0789\u07b0\u0784\u07a6\u0783\u07aa","\u0787\u07ae\u0786\u07b0\u0793\u07af\u0784\u07a6\u0783\u07aa","\u0782\u07ae\u0788\u07ac\u0789\u07b0\u0784\u07a6\u0783\u07aa","\u0791\u07a8\u0790\u07ac\u0789\u07b0\u0784\u07a6\u0783\u07aa"],$s=["\u0787\u07a7\u078b\u07a8\u0787\u07b0\u078c\u07a6","\u0780\u07af\u0789\u07a6","\u0787\u07a6\u0782\u07b0\u078e\u07a7\u0783\u07a6","\u0784\u07aa\u078b\u07a6","\u0784\u07aa\u0783\u07a7\u0790\u07b0\u078a\u07a6\u078c\u07a8","\u0780\u07aa\u0786\u07aa\u0783\u07aa","\u0780\u07ae\u0782\u07a8\u0780\u07a8\u0783\u07aa"];e.defineLocale("dv",{months:Zs,monthsShort:Zs,weekdays:$s,weekdaysShort:$s,weekdaysMin:"\u0787\u07a7\u078b\u07a8_\u0780\u07af\u0789\u07a6_\u0787\u07a6\u0782\u07b0_\u0784\u07aa\u078b\u07a6_\u0784\u07aa\u0783\u07a7_\u0780\u07aa\u0786\u07aa_\u0780\u07ae\u0782\u07a8".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"D/M/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},meridiemParse:/\u0789\u0786|\u0789\u078a/,isPM:function(e){return"\u0789\u078a"===e},meridiem:function(e,a,t){return e<12?"\u0789\u0786":"\u0789\u078a"},calendar:{sameDay:"[\u0789\u07a8\u0787\u07a6\u078b\u07aa] LT",nextDay:"[\u0789\u07a7\u078b\u07a6\u0789\u07a7] LT",nextWeek:"dddd LT",lastDay:"[\u0787\u07a8\u0787\u07b0\u0794\u07ac] LT",lastWeek:"[\u078a\u07a7\u0787\u07a8\u078c\u07aa\u0788\u07a8] dddd LT",sameElse:"L"},relativeTime:{future:"\u078c\u07ac\u0783\u07ad\u078e\u07a6\u0787\u07a8 %s",past:"\u0786\u07aa\u0783\u07a8\u0782\u07b0 %s",s:"\u0790\u07a8\u0786\u07aa\u0782\u07b0\u078c\u07aa\u0786\u07ae\u0785\u07ac\u0787\u07b0",ss:"d% \u0790\u07a8\u0786\u07aa\u0782\u07b0\u078c\u07aa",m:"\u0789\u07a8\u0782\u07a8\u0793\u07ac\u0787\u07b0",mm:"\u0789\u07a8\u0782\u07a8\u0793\u07aa %d",h:"\u078e\u07a6\u0791\u07a8\u0787\u07a8\u0783\u07ac\u0787\u07b0",hh:"\u078e\u07a6\u0791\u07a8\u0787\u07a8\u0783\u07aa %d",d:"\u078b\u07aa\u0788\u07a6\u0780\u07ac\u0787\u07b0",dd:"\u078b\u07aa\u0788\u07a6\u0790\u07b0 %d",M:"\u0789\u07a6\u0780\u07ac\u0787\u07b0",MM:"\u0789\u07a6\u0790\u07b0 %d",y:"\u0787\u07a6\u0780\u07a6\u0783\u07ac\u0787\u07b0",yy:"\u0787\u07a6\u0780\u07a6\u0783\u07aa %d"},preparse:function(e){return e.replace(/\u060c/g,",")},postformat:function(e){return e.replace(/,/g,"\u060c")},week:{dow:7,doy:12}}),e.defineLocale("el",{monthsNominativeEl:"\u0399\u03b1\u03bd\u03bf\u03c5\u03ac\u03c1\u03b9\u03bf\u03c2_\u03a6\u03b5\u03b2\u03c1\u03bf\u03c5\u03ac\u03c1\u03b9\u03bf\u03c2_\u039c\u03ac\u03c1\u03c4\u03b9\u03bf\u03c2_\u0391\u03c0\u03c1\u03af\u03bb\u03b9\u03bf\u03c2_\u039c\u03ac\u03b9\u03bf\u03c2_\u0399\u03bf\u03cd\u03bd\u03b9\u03bf\u03c2_\u0399\u03bf\u03cd\u03bb\u03b9\u03bf\u03c2_\u0391\u03cd\u03b3\u03bf\u03c5\u03c3\u03c4\u03bf\u03c2_\u03a3\u03b5\u03c0\u03c4\u03ad\u03bc\u03b2\u03c1\u03b9\u03bf\u03c2_\u039f\u03ba\u03c4\u03ce\u03b2\u03c1\u03b9\u03bf\u03c2_\u039d\u03bf\u03ad\u03bc\u03b2\u03c1\u03b9\u03bf\u03c2_\u0394\u03b5\u03ba\u03ad\u03bc\u03b2\u03c1\u03b9\u03bf\u03c2".split("_"),monthsGenitiveEl:"\u0399\u03b1\u03bd\u03bf\u03c5\u03b1\u03c1\u03af\u03bf\u03c5_\u03a6\u03b5\u03b2\u03c1\u03bf\u03c5\u03b1\u03c1\u03af\u03bf\u03c5_\u039c\u03b1\u03c1\u03c4\u03af\u03bf\u03c5_\u0391\u03c0\u03c1\u03b9\u03bb\u03af\u03bf\u03c5_\u039c\u03b1\u0390\u03bf\u03c5_\u0399\u03bf\u03c5\u03bd\u03af\u03bf\u03c5_\u0399\u03bf\u03c5\u03bb\u03af\u03bf\u03c5_\u0391\u03c5\u03b3\u03bf\u03cd\u03c3\u03c4\u03bf\u03c5_\u03a3\u03b5\u03c0\u03c4\u03b5\u03bc\u03b2\u03c1\u03af\u03bf\u03c5_\u039f\u03ba\u03c4\u03c9\u03b2\u03c1\u03af\u03bf\u03c5_\u039d\u03bf\u03b5\u03bc\u03b2\u03c1\u03af\u03bf\u03c5_\u0394\u03b5\u03ba\u03b5\u03bc\u03b2\u03c1\u03af\u03bf\u03c5".split("_"),months:function(e,a){return e?"string"==typeof a&&/D/.test(a.substring(0,a.indexOf("MMMM")))?this._monthsGenitiveEl[e.month()]:this._monthsNominativeEl[e.month()]:this._monthsNominativeEl},monthsShort:"\u0399\u03b1\u03bd_\u03a6\u03b5\u03b2_\u039c\u03b1\u03c1_\u0391\u03c0\u03c1_\u039c\u03b1\u03ca_\u0399\u03bf\u03c5\u03bd_\u0399\u03bf\u03c5\u03bb_\u0391\u03c5\u03b3_\u03a3\u03b5\u03c0_\u039f\u03ba\u03c4_\u039d\u03bf\u03b5_\u0394\u03b5\u03ba".split("_"),weekdays:"\u039a\u03c5\u03c1\u03b9\u03b1\u03ba\u03ae_\u0394\u03b5\u03c5\u03c4\u03ad\u03c1\u03b1_\u03a4\u03c1\u03af\u03c4\u03b7_\u03a4\u03b5\u03c4\u03ac\u03c1\u03c4\u03b7_\u03a0\u03ad\u03bc\u03c0\u03c4\u03b7_\u03a0\u03b1\u03c1\u03b1\u03c3\u03ba\u03b5\u03c5\u03ae_\u03a3\u03ac\u03b2\u03b2\u03b1\u03c4\u03bf".split("_"),weekdaysShort:"\u039a\u03c5\u03c1_\u0394\u03b5\u03c5_\u03a4\u03c1\u03b9_\u03a4\u03b5\u03c4_\u03a0\u03b5\u03bc_\u03a0\u03b1\u03c1_\u03a3\u03b1\u03b2".split("_"),weekdaysMin:"\u039a\u03c5_\u0394\u03b5_\u03a4\u03c1_\u03a4\u03b5_\u03a0\u03b5_\u03a0\u03b1_\u03a3\u03b1".split("_"),meridiem:function(e,a,t){return e>11?t?"\u03bc\u03bc":"\u039c\u039c":t?"\u03c0\u03bc":"\u03a0\u039c"},isPM:function(e){return"\u03bc"===(e+"").toLowerCase()[0]},meridiemParse:/[\u03a0\u039c]\.?\u039c?\.?/i,longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendarEl:{sameDay:"[\u03a3\u03ae\u03bc\u03b5\u03c1\u03b1 {}] LT",nextDay:"[\u0391\u03cd\u03c1\u03b9\u03bf {}] LT",nextWeek:"dddd [{}] LT",lastDay:"[\u03a7\u03b8\u03b5\u03c2 {}] LT",lastWeek:function(){switch(this.day()){case 6:return"[\u03c4\u03bf \u03c0\u03c1\u03bf\u03b7\u03b3\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf] dddd [{}] LT";default:return"[\u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03b7\u03b3\u03bf\u03cd\u03bc\u03b5\u03bd\u03b7] dddd [{}] LT"}},sameElse:"L"},calendar:function(e,a){var t=this._calendarEl[e],s=a&&a.hours();return D(t)&&(t=t.apply(a)),t.replace("{}",s%12==1?"\u03c3\u03c4\u03b7":"\u03c3\u03c4\u03b9\u03c2")},relativeTime:{future:"\u03c3\u03b5 %s",past:"%s \u03c0\u03c1\u03b9\u03bd",s:"\u03bb\u03af\u03b3\u03b1 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1",ss:"%d \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1",m:"\u03ad\u03bd\u03b1 \u03bb\u03b5\u03c0\u03c4\u03cc",mm:"%d \u03bb\u03b5\u03c0\u03c4\u03ac",h:"\u03bc\u03af\u03b1 \u03ce\u03c1\u03b1",hh:"%d \u03ce\u03c1\u03b5\u03c2",d:"\u03bc\u03af\u03b1 \u03bc\u03ad\u03c1\u03b1",dd:"%d \u03bc\u03ad\u03c1\u03b5\u03c2",M:"\u03ad\u03bd\u03b1\u03c2 \u03bc\u03ae\u03bd\u03b1\u03c2",MM:"%d \u03bc\u03ae\u03bd\u03b5\u03c2",y:"\u03ad\u03bd\u03b1\u03c2 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c2",yy:"%d \u03c7\u03c1\u03cc\u03bd\u03b9\u03b1"},dayOfMonthOrdinalParse:/\d{1,2}\u03b7/,ordinal:"%d\u03b7",week:{dow:1,doy:4}}),e.defineLocale("en-au",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var a=e%10;return e+(1==~~(e%100/10)?"th":1===a?"st":2===a?"nd":3===a?"rd":"th")},week:{dow:1,doy:4}}),e.defineLocale("en-ca",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"YYYY-MM-DD",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var a=e%10;return e+(1==~~(e%100/10)?"th":1===a?"st":2===a?"nd":3===a?"rd":"th")}}),e.defineLocale("en-gb",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var a=e%10;return e+(1==~~(e%100/10)?"th":1===a?"st":2===a?"nd":3===a?"rd":"th")},week:{dow:1,doy:4}}),e.defineLocale("en-ie",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var a=e%10;return e+(1==~~(e%100/10)?"th":1===a?"st":2===a?"nd":3===a?"rd":"th")},week:{dow:1,doy:4}}),e.defineLocale("en-nz",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var a=e%10;return e+(1==~~(e%100/10)?"th":1===a?"st":2===a?"nd":3===a?"rd":"th")},week:{dow:1,doy:4}}),e.defineLocale("eo",{months:"januaro_februaro_marto_aprilo_majo_junio_julio_a\u016dgusto_septembro_oktobro_novembro_decembro".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_a\u016dg_sep_okt_nov_dec".split("_"),weekdays:"diman\u0109o_lundo_mardo_merkredo_\u0135a\u016ddo_vendredo_sabato".split("_"),weekdaysShort:"dim_lun_mard_merk_\u0135a\u016d_ven_sab".split("_"),weekdaysMin:"di_lu_ma_me_\u0135a_ve_sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"D[-a de] MMMM, YYYY",LLL:"D[-a de] MMMM, YYYY HH:mm",LLLL:"dddd, [la] D[-a de] MMMM, YYYY HH:mm"},meridiemParse:/[ap]\.t\.m/i,isPM:function(e){return"p"===e.charAt(0).toLowerCase()},meridiem:function(e,a,t){return e>11?t?"p.t.m.":"P.T.M.":t?"a.t.m.":"A.T.M."},calendar:{sameDay:"[Hodia\u016d je] LT",nextDay:"[Morga\u016d je] LT",nextWeek:"dddd [je] LT",lastDay:"[Hiera\u016d je] LT",lastWeek:"[pasinta] dddd [je] LT",sameElse:"L"},relativeTime:{future:"post %s",past:"anta\u016d %s",s:"sekundoj",ss:"%d sekundoj",m:"minuto",mm:"%d minutoj",h:"horo",hh:"%d horoj",d:"tago",dd:"%d tagoj",M:"monato",MM:"%d monatoj",y:"jaro",yy:"%d jaroj"},dayOfMonthOrdinalParse:/\d{1,2}a/,ordinal:"%da",week:{dow:1,doy:7}});var Bs="ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_"),qs="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_"),Qs=[/^ene/i,/^feb/i,/^mar/i,/^abr/i,/^may/i,/^jun/i,/^jul/i,/^ago/i,/^sep/i,/^oct/i,/^nov/i,/^dic/i],Xs=/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre|ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i;e.defineLocale("es-do",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(e,a){return e?/-MMM-/.test(a)?qs[e.month()]:Bs[e.month()]:Bs},monthsRegex:Xs,monthsShortRegex:Xs,monthsStrictRegex:/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre)/i,monthsShortStrictRegex:/^(ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i,monthsParse:Qs,longMonthsParse:Qs,shortMonthsParse:Qs,weekdays:"domingo_lunes_martes_mi\xe9rcoles_jueves_viernes_s\xe1bado".split("_"),weekdaysShort:"dom._lun._mar._mi\xe9._jue._vie._s\xe1b.".split("_"),weekdaysMin:"do_lu_ma_mi_ju_vi_s\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY h:mm A",LLLL:"dddd, D [de] MMMM [de] YYYY h:mm A"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[ma\xf1ana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",ss:"%d segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un d\xeda",dd:"%d d\xedas",M:"un mes",MM:"%d meses",y:"un a\xf1o",yy:"%d a\xf1os"},dayOfMonthOrdinalParse:/\d{1,2}\xba/,ordinal:"%d\xba",week:{dow:1,doy:4}});var en="ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_"),an="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_");e.defineLocale("es-us",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(e,a){return e?/-MMM-/.test(a)?an[e.month()]:en[e.month()]:en},monthsParseExact:!0,weekdays:"domingo_lunes_martes_mi\xe9rcoles_jueves_viernes_s\xe1bado".split("_"),weekdaysShort:"dom._lun._mar._mi\xe9._jue._vie._s\xe1b.".split("_"),weekdaysMin:"do_lu_ma_mi_ju_vi_s\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"MM/DD/YYYY",LL:"MMMM [de] D [de] YYYY",LLL:"MMMM [de] D [de] YYYY h:mm A",LLLL:"dddd, MMMM [de] D [de] YYYY h:mm A"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[ma\xf1ana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",ss:"%d segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un d\xeda",dd:"%d d\xedas",M:"un mes",MM:"%d meses",y:"un a\xf1o",yy:"%d a\xf1os"},dayOfMonthOrdinalParse:/\d{1,2}\xba/,ordinal:"%d\xba",week:{dow:0,doy:6}});var tn="ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_"),sn="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_"),nn=[/^ene/i,/^feb/i,/^mar/i,/^abr/i,/^may/i,/^jun/i,/^jul/i,/^ago/i,/^sep/i,/^oct/i,/^nov/i,/^dic/i],dn=/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre|ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i;e.defineLocale("es",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(e,a){return e?/-MMM-/.test(a)?sn[e.month()]:tn[e.month()]:tn},monthsRegex:dn,monthsShortRegex:dn,monthsStrictRegex:/^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre)/i,monthsShortStrictRegex:/^(ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i,monthsParse:nn,longMonthsParse:nn,shortMonthsParse:nn,weekdays:"domingo_lunes_martes_mi\xe9rcoles_jueves_viernes_s\xe1bado".split("_"),weekdaysShort:"dom._lun._mar._mi\xe9._jue._vie._s\xe1b.".split("_"),weekdaysMin:"do_lu_ma_mi_ju_vi_s\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY H:mm",LLLL:"dddd, D [de] MMMM [de] YYYY H:mm"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[ma\xf1ana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",ss:"%d segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un d\xeda",dd:"%d d\xedas",M:"un mes",MM:"%d meses",y:"un a\xf1o",yy:"%d a\xf1os"},dayOfMonthOrdinalParse:/\d{1,2}\xba/,ordinal:"%d\xba",week:{dow:1,doy:4}}),e.defineLocale("et",{months:"jaanuar_veebruar_m\xe4rts_aprill_mai_juuni_juuli_august_september_oktoober_november_detsember".split("_"),monthsShort:"jaan_veebr_m\xe4rts_apr_mai_juuni_juuli_aug_sept_okt_nov_dets".split("_"),weekdays:"p\xfchap\xe4ev_esmasp\xe4ev_teisip\xe4ev_kolmap\xe4ev_neljap\xe4ev_reede_laup\xe4ev".split("_"),weekdaysShort:"P_E_T_K_N_R_L".split("_"),weekdaysMin:"P_E_T_K_N_R_L".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[T\xe4na,] LT",nextDay:"[Homme,] LT",nextWeek:"[J\xe4rgmine] dddd LT",lastDay:"[Eile,] LT",lastWeek:"[Eelmine] dddd LT",sameElse:"L"},relativeTime:{future:"%s p\xe4rast",past:"%s tagasi",s:ra,ss:ra,m:ra,mm:ra,h:ra,hh:ra,d:ra,dd:"%d p\xe4eva",M:ra,MM:ra,y:ra,yy:ra},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),e.defineLocale("eu",{months:"urtarrila_otsaila_martxoa_apirila_maiatza_ekaina_uztaila_abuztua_iraila_urria_azaroa_abendua".split("_"),monthsShort:"urt._ots._mar._api._mai._eka._uzt._abu._ira._urr._aza._abe.".split("_"),monthsParseExact:!0,weekdays:"igandea_astelehena_asteartea_asteazkena_osteguna_ostirala_larunbata".split("_"),weekdaysShort:"ig._al._ar._az._og._ol._lr.".split("_"),weekdaysMin:"ig_al_ar_az_og_ol_lr".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"YYYY[ko] MMMM[ren] D[a]",LLL:"YYYY[ko] MMMM[ren] D[a] HH:mm",LLLL:"dddd, YYYY[ko] MMMM[ren] D[a] HH:mm",l:"YYYY-M-D",ll:"YYYY[ko] MMM D[a]",lll:"YYYY[ko] MMM D[a] HH:mm",llll:"ddd, YYYY[ko] MMM D[a] HH:mm"},calendar:{sameDay:"[gaur] LT[etan]",nextDay:"[bihar] LT[etan]",nextWeek:"dddd LT[etan]",lastDay:"[atzo] LT[etan]",lastWeek:"[aurreko] dddd LT[etan]",sameElse:"L"},relativeTime:{future:"%s barru",past:"duela %s",s:"segundo batzuk",ss:"%d segundo",m:"minutu bat",mm:"%d minutu",h:"ordu bat",hh:"%d ordu",d:"egun bat",dd:"%d egun",M:"hilabete bat",MM:"%d hilabete",y:"urte bat",yy:"%d urte"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}});var rn={1:"\u06f1",2:"\u06f2",3:"\u06f3",4:"\u06f4",5:"\u06f5",6:"\u06f6",7:"\u06f7",8:"\u06f8",9:"\u06f9",0:"\u06f0"},_n={"\u06f1":"1","\u06f2":"2","\u06f3":"3","\u06f4":"4","\u06f5":"5","\u06f6":"6","\u06f7":"7","\u06f8":"8","\u06f9":"9","\u06f0":"0"};e.defineLocale("fa",{months:"\u0698\u0627\u0646\u0648\u06cc\u0647_\u0641\u0648\u0631\u06cc\u0647_\u0645\u0627\u0631\u0633_\u0622\u0648\u0631\u06cc\u0644_\u0645\u0647_\u0698\u0648\u0626\u0646_\u0698\u0648\u0626\u06cc\u0647_\u0627\u0648\u062a_\u0633\u067e\u062a\u0627\u0645\u0628\u0631_\u0627\u06a9\u062a\u0628\u0631_\u0646\u0648\u0627\u0645\u0628\u0631_\u062f\u0633\u0627\u0645\u0628\u0631".split("_"),monthsShort:"\u0698\u0627\u0646\u0648\u06cc\u0647_\u0641\u0648\u0631\u06cc\u0647_\u0645\u0627\u0631\u0633_\u0622\u0648\u0631\u06cc\u0644_\u0645\u0647_\u0698\u0648\u0626\u0646_\u0698\u0648\u0626\u06cc\u0647_\u0627\u0648\u062a_\u0633\u067e\u062a\u0627\u0645\u0628\u0631_\u0627\u06a9\u062a\u0628\u0631_\u0646\u0648\u0627\u0645\u0628\u0631_\u062f\u0633\u0627\u0645\u0628\u0631".split("_"),weekdays:"\u06cc\u06a9\u200c\u0634\u0646\u0628\u0647_\u062f\u0648\u0634\u0646\u0628\u0647_\u0633\u0647\u200c\u0634\u0646\u0628\u0647_\u0686\u0647\u0627\u0631\u0634\u0646\u0628\u0647_\u067e\u0646\u062c\u200c\u0634\u0646\u0628\u0647_\u062c\u0645\u0639\u0647_\u0634\u0646\u0628\u0647".split("_"),weekdaysShort:"\u06cc\u06a9\u200c\u0634\u0646\u0628\u0647_\u062f\u0648\u0634\u0646\u0628\u0647_\u0633\u0647\u200c\u0634\u0646\u0628\u0647_\u0686\u0647\u0627\u0631\u0634\u0646\u0628\u0647_\u067e\u0646\u062c\u200c\u0634\u0646\u0628\u0647_\u062c\u0645\u0639\u0647_\u0634\u0646\u0628\u0647".split("_"),weekdaysMin:"\u06cc_\u062f_\u0633_\u0686_\u067e_\u062c_\u0634".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},meridiemParse:/\u0642\u0628\u0644 \u0627\u0632 \u0638\u0647\u0631|\u0628\u0639\u062f \u0627\u0632 \u0638\u0647\u0631/,isPM:function(e){return/\u0628\u0639\u062f \u0627\u0632 \u0638\u0647\u0631/.test(e)},meridiem:function(e,a,t){return e<12?"\u0642\u0628\u0644 \u0627\u0632 \u0638\u0647\u0631":"\u0628\u0639\u062f \u0627\u0632 \u0638\u0647\u0631"},calendar:{sameDay:"[\u0627\u0645\u0631\u0648\u0632 \u0633\u0627\u0639\u062a] LT",nextDay:"[\u0641\u0631\u062f\u0627 \u0633\u0627\u0639\u062a] LT",nextWeek:"dddd [\u0633\u0627\u0639\u062a] LT",lastDay:"[\u062f\u06cc\u0631\u0648\u0632 \u0633\u0627\u0639\u062a] LT",lastWeek:"dddd [\u067e\u06cc\u0634] [\u0633\u0627\u0639\u062a] LT",sameElse:"L"},relativeTime:{future:"\u062f\u0631 %s",past:"%s \u067e\u06cc\u0634",s:"\u0686\u0646\u062f \u062b\u0627\u0646\u06cc\u0647",ss:"\u062b\u0627\u0646\u06cc\u0647 d%",m:"\u06cc\u06a9 \u062f\u0642\u06cc\u0642\u0647",mm:"%d \u062f\u0642\u06cc\u0642\u0647",h:"\u06cc\u06a9 \u0633\u0627\u0639\u062a",hh:"%d \u0633\u0627\u0639\u062a",d:"\u06cc\u06a9 \u0631\u0648\u0632",dd:"%d \u0631\u0648\u0632",M:"\u06cc\u06a9 \u0645\u0627\u0647",MM:"%d \u0645\u0627\u0647",y:"\u06cc\u06a9 \u0633\u0627\u0644",yy:"%d \u0633\u0627\u0644"},preparse:function(e){return e.replace(/[\u06f0-\u06f9]/g,function(e){return _n[e]}).replace(/\u060c/g,",")},postformat:function(e){return e.replace(/\d/g,function(e){return rn[e]}).replace(/,/g,"\u060c")},dayOfMonthOrdinalParse:/\d{1,2}\u0645/,ordinal:"%d\u0645",week:{dow:6,doy:12}});var on="nolla yksi kaksi kolme nelj\xe4 viisi kuusi seitsem\xe4n kahdeksan yhdeks\xe4n".split(" "),mn=["nolla","yhden","kahden","kolmen","nelj\xe4n","viiden","kuuden",on[7],on[8],on[9]];e.defineLocale("fi",{months:"tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kes\xe4kuu_hein\xe4kuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu".split("_"),monthsShort:"tammi_helmi_maalis_huhti_touko_kes\xe4_hein\xe4_elo_syys_loka_marras_joulu".split("_"),weekdays:"sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai".split("_"),weekdaysShort:"su_ma_ti_ke_to_pe_la".split("_"),weekdaysMin:"su_ma_ti_ke_to_pe_la".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD.MM.YYYY",LL:"Do MMMM[ta] YYYY",LLL:"Do MMMM[ta] YYYY, [klo] HH.mm",LLLL:"dddd, Do MMMM[ta] YYYY, [klo] HH.mm",l:"D.M.YYYY",ll:"Do MMM YYYY",lll:"Do MMM YYYY, [klo] HH.mm",llll:"ddd, Do MMM YYYY, [klo] HH.mm"},calendar:{sameDay:"[t\xe4n\xe4\xe4n] [klo] LT",nextDay:"[huomenna] [klo] LT",nextWeek:"dddd [klo] LT",lastDay:"[eilen] [klo] LT",lastWeek:"[viime] dddd[na] [klo] LT",sameElse:"L"},relativeTime:{future:"%s p\xe4\xe4st\xe4",past:"%s sitten",s:_a,ss:_a,m:_a,mm:_a,h:_a,hh:_a,d:_a,dd:_a,M:_a,MM:_a,y:_a,yy:_a},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),e.defineLocale("fo",{months:"januar_februar_mars_apr\xedl_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_"),weekdays:"sunnudagur_m\xe1nadagur_t\xfdsdagur_mikudagur_h\xf3sdagur_fr\xedggjadagur_leygardagur".split("_"),weekdaysShort:"sun_m\xe1n_t\xfds_mik_h\xf3s_fr\xed_ley".split("_"),weekdaysMin:"su_m\xe1_t\xfd_mi_h\xf3_fr_le".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D. MMMM, YYYY HH:mm"},calendar:{sameDay:"[\xcd dag kl.] LT",nextDay:"[\xcd morgin kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[\xcd gj\xe1r kl.] LT",lastWeek:"[s\xed\xf0stu] dddd [kl] LT",sameElse:"L"},relativeTime:{future:"um %s",past:"%s s\xed\xf0ani",s:"f\xe1 sekund",ss:"%d sekundir",m:"ein minutt",mm:"%d minuttir",h:"ein t\xedmi",hh:"%d t\xedmar",d:"ein dagur",dd:"%d dagar",M:"ein m\xe1na\xf0i",MM:"%d m\xe1na\xf0ir",y:"eitt \xe1r",yy:"%d \xe1r"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),e.defineLocale("fr-ca",{months:"janvier_f\xe9vrier_mars_avril_mai_juin_juillet_ao\xfbt_septembre_octobre_novembre_d\xe9cembre".split("_"),monthsShort:"janv._f\xe9vr._mars_avr._mai_juin_juil._ao\xfbt_sept._oct._nov._d\xe9c.".split("_"),monthsParseExact:!0,weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"di_lu_ma_me_je_ve_sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[Aujourd\u2019hui \xe0] LT",nextDay:"[Demain \xe0] LT",nextWeek:"dddd [\xe0] LT",lastDay:"[Hier \xe0] LT",lastWeek:"dddd [dernier \xe0] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",ss:"%d secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},dayOfMonthOrdinalParse:/\d{1,2}(er|e)/,ordinal:function(e,a){switch(a){default:case"M":case"Q":case"D":case"DDD":case"d":return e+(1===e?"er":"e");case"w":case"W":return e+(1===e?"re":"e")}}}),e.defineLocale("fr-ch",{months:"janvier_f\xe9vrier_mars_avril_mai_juin_juillet_ao\xfbt_septembre_octobre_novembre_d\xe9cembre".split("_"),monthsShort:"janv._f\xe9vr._mars_avr._mai_juin_juil._ao\xfbt_sept._oct._nov._d\xe9c.".split("_"),monthsParseExact:!0,weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"di_lu_ma_me_je_ve_sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[Aujourd\u2019hui \xe0] LT",nextDay:"[Demain \xe0] LT",nextWeek:"dddd [\xe0] LT",lastDay:"[Hier \xe0] LT",lastWeek:"dddd [dernier \xe0] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",ss:"%d secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},dayOfMonthOrdinalParse:/\d{1,2}(er|e)/,ordinal:function(e,a){switch(a){default:case"M":case"Q":case"D":case"DDD":case"d":return e+(1===e?"er":"e");case"w":case"W":return e+(1===e?"re":"e")}},week:{dow:1,doy:4}}),e.defineLocale("fr",{months:"janvier_f\xe9vrier_mars_avril_mai_juin_juillet_ao\xfbt_septembre_octobre_novembre_d\xe9cembre".split("_"),monthsShort:"janv._f\xe9vr._mars_avr._mai_juin_juil._ao\xfbt_sept._oct._nov._d\xe9c.".split("_"),monthsParseExact:!0,weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"di_lu_ma_me_je_ve_sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[Aujourd\u2019hui \xe0] LT",nextDay:"[Demain \xe0] LT",nextWeek:"dddd [\xe0] LT",lastDay:"[Hier \xe0] LT",lastWeek:"dddd [dernier \xe0] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",ss:"%d secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},dayOfMonthOrdinalParse:/\d{1,2}(er|)/,ordinal:function(e,a){switch(a){case"D":return e+(1===e?"er":"");default:case"M":case"Q":case"DDD":case"d":return e+(1===e?"er":"e");case"w":case"W":return e+(1===e?"re":"e")}},week:{dow:1,doy:4}});var un="jan._feb._mrt._apr._mai_jun._jul._aug._sep._okt._nov._des.".split("_"),ln="jan_feb_mrt_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_");e.defineLocale("fy",{months:"jannewaris_febrewaris_maart_april_maaie_juny_july_augustus_septimber_oktober_novimber_desimber".split("_"),monthsShort:function(e,a){return e?/-MMM-/.test(a)?ln[e.month()]:un[e.month()]:un},monthsParseExact:!0,weekdays:"snein_moandei_tiisdei_woansdei_tongersdei_freed_sneon".split("_"),weekdaysShort:"si._mo._ti._wo._to._fr._so.".split("_"),weekdaysMin:"Si_Mo_Ti_Wo_To_Fr_So".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[hjoed om] LT",nextDay:"[moarn om] LT",nextWeek:"dddd [om] LT",lastDay:"[juster om] LT",lastWeek:"[\xf4fr\xfbne] dddd [om] LT",sameElse:"L"},relativeTime:{future:"oer %s",past:"%s lyn",s:"in pear sekonden",ss:"%d sekonden",m:"ien min\xfat",mm:"%d minuten",h:"ien oere",hh:"%d oeren",d:"ien dei",dd:"%d dagen",M:"ien moanne",MM:"%d moannen",y:"ien jier",yy:"%d jierren"},dayOfMonthOrdinalParse:/\d{1,2}(ste|de)/,ordinal:function(e){return e+(1===e||8===e||e>=20?"ste":"de")},week:{dow:1,doy:4}});e.defineLocale("gd",{months:["Am Faoilleach","An Gearran","Am M\xe0rt","An Giblean","An C\xe8itean","An t-\xd2gmhios","An t-Iuchar","An L\xf9nastal","An t-Sultain","An D\xe0mhair","An t-Samhain","An D\xf9bhlachd"],monthsShort:["Faoi","Gear","M\xe0rt","Gibl","C\xe8it","\xd2gmh","Iuch","L\xf9n","Sult","D\xe0mh","Samh","D\xf9bh"],monthsParseExact:!0,weekdays:["Did\xf2mhnaich","Diluain","Dim\xe0irt","Diciadain","Diardaoin","Dihaoine","Disathairne"],weekdaysShort:["Did","Dil","Dim","Dic","Dia","Dih","Dis"],weekdaysMin:["D\xf2","Lu","M\xe0","Ci","Ar","Ha","Sa"],longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[An-diugh aig] LT",nextDay:"[A-m\xe0ireach aig] LT",nextWeek:"dddd [aig] LT",lastDay:"[An-d\xe8 aig] LT",lastWeek:"dddd [seo chaidh] [aig] LT",sameElse:"L"},relativeTime:{future:"ann an %s",past:"bho chionn %s",s:"beagan diogan",ss:"%d diogan",m:"mionaid",mm:"%d mionaidean",h:"uair",hh:"%d uairean",d:"latha",dd:"%d latha",M:"m\xecos",MM:"%d m\xecosan",y:"bliadhna",yy:"%d bliadhna"},dayOfMonthOrdinalParse:/\d{1,2}(d|na|mh)/,ordinal:function(e){return e+(1===e?"d":e%10==2?"na":"mh")},week:{dow:1,doy:4}}),e.defineLocale("gl",{months:"xaneiro_febreiro_marzo_abril_maio_xu\xf1o_xullo_agosto_setembro_outubro_novembro_decembro".split("_"),monthsShort:"xan._feb._mar._abr._mai._xu\xf1._xul._ago._set._out._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"domingo_luns_martes_m\xe9rcores_xoves_venres_s\xe1bado".split("_"),weekdaysShort:"dom._lun._mar._m\xe9r._xov._ven._s\xe1b.".split("_"),weekdaysMin:"do_lu_ma_m\xe9_xo_ve_s\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY H:mm",LLLL:"dddd, D [de] MMMM [de] YYYY H:mm"},calendar:{sameDay:function(){return"[hoxe "+(1!==this.hours()?"\xe1s":"\xe1")+"] LT"},nextDay:function(){return"[ma\xf1\xe1 "+(1!==this.hours()?"\xe1s":"\xe1")+"] LT"},nextWeek:function(){return"dddd ["+(1!==this.hours()?"\xe1s":"a")+"] LT"},lastDay:function(){return"[onte "+(1!==this.hours()?"\xe1":"a")+"] LT"},lastWeek:function(){return"[o] dddd [pasado "+(1!==this.hours()?"\xe1s":"a")+"] LT"},sameElse:"L"},relativeTime:{future:function(e){return 0===e.indexOf("un")?"n"+e:"en "+e},past:"hai %s",s:"uns segundos",ss:"%d segundos",m:"un minuto",mm:"%d minutos",h:"unha hora",hh:"%d horas",d:"un d\xeda",dd:"%d d\xedas",M:"un mes",MM:"%d meses",y:"un ano",yy:"%d anos"},dayOfMonthOrdinalParse:/\d{1,2}\xba/,ordinal:"%d\xba",week:{dow:1,doy:4}}),e.defineLocale("gom-latn",{months:"Janer_Febrer_Mars_Abril_Mai_Jun_Julai_Agost_Setembr_Otubr_Novembr_Dezembr".split("_"),monthsShort:"Jan._Feb._Mars_Abr._Mai_Jun_Jul._Ago._Set._Otu._Nov._Dez.".split("_"),monthsParseExact:!0,weekdays:"Aitar_Somar_Mongllar_Budvar_Brestar_Sukrar_Son'var".split("_"),weekdaysShort:"Ait._Som._Mon._Bud._Bre._Suk._Son.".split("_"),weekdaysMin:"Ai_Sm_Mo_Bu_Br_Su_Sn".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"A h:mm [vazta]",LTS:"A h:mm:ss [vazta]",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY A h:mm [vazta]",LLLL:"dddd, MMMM[achea] Do, YYYY, A h:mm [vazta]",llll:"ddd, D MMM YYYY, A h:mm [vazta]"},calendar:{sameDay:"[Aiz] LT",nextDay:"[Faleam] LT",nextWeek:"[Ieta to] dddd[,] LT",lastDay:"[Kal] LT",lastWeek:"[Fatlo] dddd[,] LT",sameElse:"L"},relativeTime:{future:"%s",past:"%s adim",s:ia,ss:ia,m:ia,mm:ia,h:ia,hh:ia,d:ia,dd:ia,M:ia,MM:ia,y:ia,yy:ia},dayOfMonthOrdinalParse:/\d{1,2}(er)/,ordinal:function(e,a){switch(a){case"D":return e+"er";default:case"M":case"Q":case"DDD":case"d":case"w":case"W":return e}},week:{dow:1,doy:4},meridiemParse:/rati|sokalli|donparam|sanje/,meridiemHour:function(e,a){return 12===e&&(e=0),"rati"===a?e<4?e:e+12:"sokalli"===a?e:"donparam"===a?e>12?e:e+12:"sanje"===a?e+12:void 0},meridiem:function(e,a,t){return e<4?"rati":e<12?"sokalli":e<16?"donparam":e<20?"sanje":"rati"}});var Mn={1:"\u0ae7",2:"\u0ae8",3:"\u0ae9",4:"\u0aea",5:"\u0aeb",6:"\u0aec",7:"\u0aed",8:"\u0aee",9:"\u0aef",0:"\u0ae6"},hn={"\u0ae7":"1","\u0ae8":"2","\u0ae9":"3","\u0aea":"4","\u0aeb":"5","\u0aec":"6","\u0aed":"7","\u0aee":"8","\u0aef":"9","\u0ae6":"0"};e.defineLocale("gu",{months:"\u0a9c\u0abe\u0aa8\u0acd\u0aaf\u0ac1\u0a86\u0ab0\u0ac0_\u0aab\u0ac7\u0aac\u0acd\u0ab0\u0ac1\u0a86\u0ab0\u0ac0_\u0aae\u0abe\u0ab0\u0acd\u0a9a_\u0a8f\u0aaa\u0acd\u0ab0\u0abf\u0ab2_\u0aae\u0ac7_\u0a9c\u0ac2\u0aa8_\u0a9c\u0ac1\u0ab2\u0abe\u0a88_\u0a91\u0a97\u0ab8\u0acd\u0a9f_\u0ab8\u0aaa\u0acd\u0a9f\u0ac7\u0aae\u0acd\u0aac\u0ab0_\u0a91\u0a95\u0acd\u0a9f\u0acd\u0aac\u0ab0_\u0aa8\u0ab5\u0ac7\u0aae\u0acd\u0aac\u0ab0_\u0aa1\u0abf\u0ab8\u0ac7\u0aae\u0acd\u0aac\u0ab0".split("_"),monthsShort:"\u0a9c\u0abe\u0aa8\u0acd\u0aaf\u0ac1._\u0aab\u0ac7\u0aac\u0acd\u0ab0\u0ac1._\u0aae\u0abe\u0ab0\u0acd\u0a9a_\u0a8f\u0aaa\u0acd\u0ab0\u0abf._\u0aae\u0ac7_\u0a9c\u0ac2\u0aa8_\u0a9c\u0ac1\u0ab2\u0abe._\u0a91\u0a97._\u0ab8\u0aaa\u0acd\u0a9f\u0ac7._\u0a91\u0a95\u0acd\u0a9f\u0acd._\u0aa8\u0ab5\u0ac7._\u0aa1\u0abf\u0ab8\u0ac7.".split("_"),monthsParseExact:!0,weekdays:"\u0ab0\u0ab5\u0abf\u0ab5\u0abe\u0ab0_\u0ab8\u0acb\u0aae\u0ab5\u0abe\u0ab0_\u0aae\u0a82\u0a97\u0ab3\u0ab5\u0abe\u0ab0_\u0aac\u0ac1\u0aa7\u0acd\u0ab5\u0abe\u0ab0_\u0a97\u0ac1\u0ab0\u0ac1\u0ab5\u0abe\u0ab0_\u0ab6\u0ac1\u0a95\u0acd\u0ab0\u0ab5\u0abe\u0ab0_\u0ab6\u0aa8\u0abf\u0ab5\u0abe\u0ab0".split("_"),weekdaysShort:"\u0ab0\u0ab5\u0abf_\u0ab8\u0acb\u0aae_\u0aae\u0a82\u0a97\u0ab3_\u0aac\u0ac1\u0aa7\u0acd_\u0a97\u0ac1\u0ab0\u0ac1_\u0ab6\u0ac1\u0a95\u0acd\u0ab0_\u0ab6\u0aa8\u0abf".split("_"),weekdaysMin:"\u0ab0_\u0ab8\u0acb_\u0aae\u0a82_\u0aac\u0ac1_\u0a97\u0ac1_\u0ab6\u0ac1_\u0ab6".split("_"),longDateFormat:{LT:"A h:mm \u0ab5\u0abe\u0a97\u0acd\u0aaf\u0ac7",LTS:"A h:mm:ss \u0ab5\u0abe\u0a97\u0acd\u0aaf\u0ac7",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm \u0ab5\u0abe\u0a97\u0acd\u0aaf\u0ac7",LLLL:"dddd, D MMMM YYYY, A h:mm \u0ab5\u0abe\u0a97\u0acd\u0aaf\u0ac7"},calendar:{sameDay:"[\u0a86\u0a9c] LT",nextDay:"[\u0a95\u0abe\u0ab2\u0ac7] LT",nextWeek:"dddd, LT",lastDay:"[\u0a97\u0a87\u0a95\u0abe\u0ab2\u0ac7] LT",lastWeek:"[\u0aaa\u0abe\u0a9b\u0ab2\u0abe] dddd, LT",sameElse:"L"},relativeTime:{future:"%s \u0aae\u0abe",past:"%s \u0aaa\u0ac7\u0ab9\u0ab2\u0abe",s:"\u0a85\u0aae\u0ac1\u0a95 \u0aaa\u0ab3\u0acb",ss:"%d \u0ab8\u0ac7\u0a95\u0a82\u0aa1",m:"\u0a8f\u0a95 \u0aae\u0abf\u0aa8\u0abf\u0a9f",mm:"%d \u0aae\u0abf\u0aa8\u0abf\u0a9f",h:"\u0a8f\u0a95 \u0a95\u0ab2\u0abe\u0a95",hh:"%d \u0a95\u0ab2\u0abe\u0a95",d:"\u0a8f\u0a95 \u0aa6\u0abf\u0ab5\u0ab8",dd:"%d \u0aa6\u0abf\u0ab5\u0ab8",M:"\u0a8f\u0a95 \u0aae\u0ab9\u0abf\u0aa8\u0acb",MM:"%d \u0aae\u0ab9\u0abf\u0aa8\u0acb",y:"\u0a8f\u0a95 \u0ab5\u0ab0\u0acd\u0ab7",yy:"%d \u0ab5\u0ab0\u0acd\u0ab7"},preparse:function(e){return e.replace(/[\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0ae6]/g,function(e){return hn[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return Mn[e]})},meridiemParse:/\u0ab0\u0abe\u0aa4|\u0aac\u0aaa\u0acb\u0ab0|\u0ab8\u0ab5\u0abe\u0ab0|\u0ab8\u0abe\u0a82\u0a9c/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u0ab0\u0abe\u0aa4"===a?e<4?e:e+12:"\u0ab8\u0ab5\u0abe\u0ab0"===a?e:"\u0aac\u0aaa\u0acb\u0ab0"===a?e>=10?e:e+12:"\u0ab8\u0abe\u0a82\u0a9c"===a?e+12:void 0},meridiem:function(e,a,t){return e<4?"\u0ab0\u0abe\u0aa4":e<10?"\u0ab8\u0ab5\u0abe\u0ab0":e<17?"\u0aac\u0aaa\u0acb\u0ab0":e<20?"\u0ab8\u0abe\u0a82\u0a9c":"\u0ab0\u0abe\u0aa4"},week:{dow:0,doy:6}}),e.defineLocale("he",{months:"\u05d9\u05e0\u05d5\u05d0\u05e8_\u05e4\u05d1\u05e8\u05d5\u05d0\u05e8_\u05de\u05e8\u05e5_\u05d0\u05e4\u05e8\u05d9\u05dc_\u05de\u05d0\u05d9_\u05d9\u05d5\u05e0\u05d9_\u05d9\u05d5\u05dc\u05d9_\u05d0\u05d5\u05d2\u05d5\u05e1\u05d8_\u05e1\u05e4\u05d8\u05de\u05d1\u05e8_\u05d0\u05d5\u05e7\u05d8\u05d5\u05d1\u05e8_\u05e0\u05d5\u05d1\u05de\u05d1\u05e8_\u05d3\u05e6\u05de\u05d1\u05e8".split("_"),monthsShort:"\u05d9\u05e0\u05d5\u05f3_\u05e4\u05d1\u05e8\u05f3_\u05de\u05e8\u05e5_\u05d0\u05e4\u05e8\u05f3_\u05de\u05d0\u05d9_\u05d9\u05d5\u05e0\u05d9_\u05d9\u05d5\u05dc\u05d9_\u05d0\u05d5\u05d2\u05f3_\u05e1\u05e4\u05d8\u05f3_\u05d0\u05d5\u05e7\u05f3_\u05e0\u05d5\u05d1\u05f3_\u05d3\u05e6\u05de\u05f3".split("_"),weekdays:"\u05e8\u05d0\u05e9\u05d5\u05df_\u05e9\u05e0\u05d9_\u05e9\u05dc\u05d9\u05e9\u05d9_\u05e8\u05d1\u05d9\u05e2\u05d9_\u05d7\u05de\u05d9\u05e9\u05d9_\u05e9\u05d9\u05e9\u05d9_\u05e9\u05d1\u05ea".split("_"),weekdaysShort:"\u05d0\u05f3_\u05d1\u05f3_\u05d2\u05f3_\u05d3\u05f3_\u05d4\u05f3_\u05d5\u05f3_\u05e9\u05f3".split("_"),weekdaysMin:"\u05d0_\u05d1_\u05d2_\u05d3_\u05d4_\u05d5_\u05e9".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D [\u05d1]MMMM YYYY",LLL:"D [\u05d1]MMMM YYYY HH:mm",LLLL:"dddd, D [\u05d1]MMMM YYYY HH:mm",l:"D/M/YYYY",ll:"D MMM YYYY",lll:"D MMM YYYY HH:mm",llll:"ddd, D MMM YYYY HH:mm"},calendar:{sameDay:"[\u05d4\u05d9\u05d5\u05dd \u05d1\u05be]LT",nextDay:"[\u05de\u05d7\u05e8 \u05d1\u05be]LT",nextWeek:"dddd [\u05d1\u05e9\u05e2\u05d4] LT",lastDay:"[\u05d0\u05ea\u05de\u05d5\u05dc \u05d1\u05be]LT",lastWeek:"[\u05d1\u05d9\u05d5\u05dd] dddd [\u05d4\u05d0\u05d7\u05e8\u05d5\u05df \u05d1\u05e9\u05e2\u05d4] LT",sameElse:"L"},relativeTime:{future:"\u05d1\u05e2\u05d5\u05d3 %s",past:"\u05dc\u05e4\u05e0\u05d9 %s",s:"\u05de\u05e1\u05e4\u05e8 \u05e9\u05e0\u05d9\u05d5\u05ea",ss:"%d \u05e9\u05e0\u05d9\u05d5\u05ea",m:"\u05d3\u05e7\u05d4",mm:"%d \u05d3\u05e7\u05d5\u05ea",h:"\u05e9\u05e2\u05d4",hh:function(e){return 2===e?"\u05e9\u05e2\u05ea\u05d9\u05d9\u05dd":e+" \u05e9\u05e2\u05d5\u05ea"},d:"\u05d9\u05d5\u05dd",dd:function(e){return 2===e?"\u05d9\u05d5\u05de\u05d9\u05d9\u05dd":e+" \u05d9\u05de\u05d9\u05dd"},M:"\u05d7\u05d5\u05d3\u05e9",MM:function(e){return 2===e?"\u05d7\u05d5\u05d3\u05e9\u05d9\u05d9\u05dd":e+" \u05d7\u05d5\u05d3\u05e9\u05d9\u05dd"},y:"\u05e9\u05e0\u05d4",yy:function(e){return 2===e?"\u05e9\u05e0\u05ea\u05d9\u05d9\u05dd":e%10==0&&10!==e?e+" \u05e9\u05e0\u05d4":e+" \u05e9\u05e0\u05d9\u05dd"}},meridiemParse:/\u05d0\u05d7\u05d4"\u05e6|\u05dc\u05e4\u05e0\u05d4"\u05e6|\u05d0\u05d7\u05e8\u05d9 \u05d4\u05e6\u05d4\u05e8\u05d9\u05d9\u05dd|\u05dc\u05e4\u05e0\u05d9 \u05d4\u05e6\u05d4\u05e8\u05d9\u05d9\u05dd|\u05dc\u05e4\u05e0\u05d5\u05ea \u05d1\u05d5\u05e7\u05e8|\u05d1\u05d1\u05d5\u05e7\u05e8|\u05d1\u05e2\u05e8\u05d1/i,isPM:function(e){return/^(\u05d0\u05d7\u05d4"\u05e6|\u05d0\u05d7\u05e8\u05d9 \u05d4\u05e6\u05d4\u05e8\u05d9\u05d9\u05dd|\u05d1\u05e2\u05e8\u05d1)$/.test(e)},meridiem:function(e,a,t){return e<5?"\u05dc\u05e4\u05e0\u05d5\u05ea \u05d1\u05d5\u05e7\u05e8":e<10?"\u05d1\u05d1\u05d5\u05e7\u05e8":e<12?t?'\u05dc\u05e4\u05e0\u05d4"\u05e6':"\u05dc\u05e4\u05e0\u05d9 \u05d4\u05e6\u05d4\u05e8\u05d9\u05d9\u05dd":e<18?t?'\u05d0\u05d7\u05d4"\u05e6':"\u05d0\u05d7\u05e8\u05d9 \u05d4\u05e6\u05d4\u05e8\u05d9\u05d9\u05dd":"\u05d1\u05e2\u05e8\u05d1"}});var Ln={1:"\u0967",2:"\u0968",3:"\u0969",4:"\u096a",5:"\u096b",6:"\u096c",7:"\u096d",8:"\u096e",9:"\u096f",0:"\u0966"},cn={"\u0967":"1","\u0968":"2","\u0969":"3","\u096a":"4","\u096b":"5","\u096c":"6","\u096d":"7","\u096e":"8","\u096f":"9","\u0966":"0"};e.defineLocale("hi",{months:"\u091c\u0928\u0935\u0930\u0940_\u092b\u093c\u0930\u0935\u0930\u0940_\u092e\u093e\u0930\u094d\u091a_\u0905\u092a\u094d\u0930\u0948\u0932_\u092e\u0908_\u091c\u0942\u0928_\u091c\u0941\u0932\u093e\u0908_\u0905\u0917\u0938\u094d\u0924_\u0938\u093f\u0924\u092e\u094d\u092c\u0930_\u0905\u0915\u094d\u091f\u0942\u092c\u0930_\u0928\u0935\u092e\u094d\u092c\u0930_\u0926\u093f\u0938\u092e\u094d\u092c\u0930".split("_"),monthsShort:"\u091c\u0928._\u092b\u093c\u0930._\u092e\u093e\u0930\u094d\u091a_\u0905\u092a\u094d\u0930\u0948._\u092e\u0908_\u091c\u0942\u0928_\u091c\u0941\u0932._\u0905\u0917._\u0938\u093f\u0924._\u0905\u0915\u094d\u091f\u0942._\u0928\u0935._\u0926\u093f\u0938.".split("_"),monthsParseExact:!0,weekdays:"\u0930\u0935\u093f\u0935\u093e\u0930_\u0938\u094b\u092e\u0935\u093e\u0930_\u092e\u0902\u0917\u0932\u0935\u093e\u0930_\u092c\u0941\u0927\u0935\u093e\u0930_\u0917\u0941\u0930\u0942\u0935\u093e\u0930_\u0936\u0941\u0915\u094d\u0930\u0935\u093e\u0930_\u0936\u0928\u093f\u0935\u093e\u0930".split("_"),weekdaysShort:"\u0930\u0935\u093f_\u0938\u094b\u092e_\u092e\u0902\u0917\u0932_\u092c\u0941\u0927_\u0917\u0941\u0930\u0942_\u0936\u0941\u0915\u094d\u0930_\u0936\u0928\u093f".split("_"),weekdaysMin:"\u0930_\u0938\u094b_\u092e\u0902_\u092c\u0941_\u0917\u0941_\u0936\u0941_\u0936".split("_"),longDateFormat:{LT:"A h:mm \u092c\u091c\u0947",LTS:"A h:mm:ss \u092c\u091c\u0947",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm \u092c\u091c\u0947",LLLL:"dddd, D MMMM YYYY, A h:mm \u092c\u091c\u0947"},calendar:{sameDay:"[\u0906\u091c] LT",nextDay:"[\u0915\u0932] LT",nextWeek:"dddd, LT",lastDay:"[\u0915\u0932] LT",lastWeek:"[\u092a\u093f\u091b\u0932\u0947] dddd, LT",sameElse:"L"},relativeTime:{future:"%s \u092e\u0947\u0902",past:"%s \u092a\u0939\u0932\u0947",s:"\u0915\u0941\u091b \u0939\u0940 \u0915\u094d\u0937\u0923",ss:"%d \u0938\u0947\u0915\u0902\u0921",m:"\u090f\u0915 \u092e\u093f\u0928\u091f",mm:"%d \u092e\u093f\u0928\u091f",h:"\u090f\u0915 \u0918\u0902\u091f\u093e",hh:"%d \u0918\u0902\u091f\u0947",d:"\u090f\u0915 \u0926\u093f\u0928",dd:"%d \u0926\u093f\u0928",M:"\u090f\u0915 \u092e\u0939\u0940\u0928\u0947",MM:"%d \u092e\u0939\u0940\u0928\u0947",y:"\u090f\u0915 \u0935\u0930\u094d\u0937",yy:"%d \u0935\u0930\u094d\u0937"},preparse:function(e){return e.replace(/[\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f\u0966]/g,function(e){return cn[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return Ln[e]})},meridiemParse:/\u0930\u093e\u0924|\u0938\u0941\u092c\u0939|\u0926\u094b\u092a\u0939\u0930|\u0936\u093e\u092e/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u0930\u093e\u0924"===a?e<4?e:e+12:"\u0938\u0941\u092c\u0939"===a?e:"\u0926\u094b\u092a\u0939\u0930"===a?e>=10?e:e+12:"\u0936\u093e\u092e"===a?e+12:void 0},meridiem:function(e,a,t){return e<4?"\u0930\u093e\u0924":e<10?"\u0938\u0941\u092c\u0939":e<17?"\u0926\u094b\u092a\u0939\u0930":e<20?"\u0936\u093e\u092e":"\u0930\u093e\u0924"},week:{dow:0,doy:6}}),e.defineLocale("hr",{months:{format:"sije\u010dnja_velja\u010de_o\u017eujka_travnja_svibnja_lipnja_srpnja_kolovoza_rujna_listopada_studenoga_prosinca".split("_"),standalone:"sije\u010danj_velja\u010da_o\u017eujak_travanj_svibanj_lipanj_srpanj_kolovoz_rujan_listopad_studeni_prosinac".split("_")},monthsShort:"sij._velj._o\u017eu._tra._svi._lip._srp._kol._ruj._lis._stu._pro.".split("_"),monthsParseExact:!0,weekdays:"nedjelja_ponedjeljak_utorak_srijeda_\u010detvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sri._\u010det._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_\u010de_pe_su".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedjelju] [u] LT";case 3:return"[u] [srijedu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[ju\u010der u] LT",lastWeek:function(){switch(this.day()){case 0:case 3:return"[pro\u0161lu] dddd [u] LT";case 6:return"[pro\u0161le] [subote] [u] LT";case 1:case 2:case 4:case 5:return"[pro\u0161li] dddd [u] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"prije %s",s:"par sekundi",ss:oa,m:oa,mm:oa,h:oa,hh:oa,d:"dan",dd:oa,M:"mjesec",MM:oa,y:"godinu",yy:oa},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}});var Yn="vas\xe1rnap h\xe9tf\u0151n kedden szerd\xe1n cs\xfct\xf6rt\xf6k\xf6n p\xe9nteken szombaton".split(" ");e.defineLocale("hu",{months:"janu\xe1r_febru\xe1r_m\xe1rcius_\xe1prilis_m\xe1jus_j\xfanius_j\xfalius_augusztus_szeptember_okt\xf3ber_november_december".split("_"),monthsShort:"jan_feb_m\xe1rc_\xe1pr_m\xe1j_j\xfan_j\xfal_aug_szept_okt_nov_dec".split("_"),weekdays:"vas\xe1rnap_h\xe9tf\u0151_kedd_szerda_cs\xfct\xf6rt\xf6k_p\xe9ntek_szombat".split("_"),weekdaysShort:"vas_h\xe9t_kedd_sze_cs\xfct_p\xe9n_szo".split("_"),weekdaysMin:"v_h_k_sze_cs_p_szo".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"YYYY.MM.DD.",LL:"YYYY. MMMM D.",LLL:"YYYY. MMMM D. H:mm",LLLL:"YYYY. MMMM D., dddd H:mm"},meridiemParse:/de|du/i,isPM:function(e){return"u"===e.charAt(1).toLowerCase()},meridiem:function(e,a,t){return e<12?!0===t?"de":"DE":!0===t?"du":"DU"},calendar:{sameDay:"[ma] LT[-kor]",nextDay:"[holnap] LT[-kor]",nextWeek:function(){return ua.call(this,!0)},lastDay:"[tegnap] LT[-kor]",lastWeek:function(){return ua.call(this,!1)},sameElse:"L"},relativeTime:{future:"%s m\xfalva",past:"%s",s:ma,ss:ma,m:ma,mm:ma,h:ma,hh:ma,d:ma,dd:ma,M:ma,MM:ma,y:ma,yy:ma},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),e.defineLocale("hy-am",{months:{format:"\u0570\u0578\u0582\u0576\u057e\u0561\u0580\u056b_\u0583\u0565\u057f\u0580\u057e\u0561\u0580\u056b_\u0574\u0561\u0580\u057f\u056b_\u0561\u057a\u0580\u056b\u056c\u056b_\u0574\u0561\u0575\u056b\u057d\u056b_\u0570\u0578\u0582\u0576\u056b\u057d\u056b_\u0570\u0578\u0582\u056c\u056b\u057d\u056b_\u0585\u0563\u0578\u057d\u057f\u0578\u057d\u056b_\u057d\u0565\u057a\u057f\u0565\u0574\u0562\u0565\u0580\u056b_\u0570\u0578\u056f\u057f\u0565\u0574\u0562\u0565\u0580\u056b_\u0576\u0578\u0575\u0565\u0574\u0562\u0565\u0580\u056b_\u0564\u0565\u056f\u057f\u0565\u0574\u0562\u0565\u0580\u056b".split("_"),standalone:"\u0570\u0578\u0582\u0576\u057e\u0561\u0580_\u0583\u0565\u057f\u0580\u057e\u0561\u0580_\u0574\u0561\u0580\u057f_\u0561\u057a\u0580\u056b\u056c_\u0574\u0561\u0575\u056b\u057d_\u0570\u0578\u0582\u0576\u056b\u057d_\u0570\u0578\u0582\u056c\u056b\u057d_\u0585\u0563\u0578\u057d\u057f\u0578\u057d_\u057d\u0565\u057a\u057f\u0565\u0574\u0562\u0565\u0580_\u0570\u0578\u056f\u057f\u0565\u0574\u0562\u0565\u0580_\u0576\u0578\u0575\u0565\u0574\u0562\u0565\u0580_\u0564\u0565\u056f\u057f\u0565\u0574\u0562\u0565\u0580".split("_")},monthsShort:"\u0570\u0576\u057e_\u0583\u057f\u0580_\u0574\u0580\u057f_\u0561\u057a\u0580_\u0574\u0575\u057d_\u0570\u0576\u057d_\u0570\u056c\u057d_\u0585\u0563\u057d_\u057d\u057a\u057f_\u0570\u056f\u057f_\u0576\u0574\u0562_\u0564\u056f\u057f".split("_"),weekdays:"\u056f\u056b\u0580\u0561\u056f\u056b_\u0565\u0580\u056f\u0578\u0582\u0577\u0561\u0562\u0569\u056b_\u0565\u0580\u0565\u0584\u0577\u0561\u0562\u0569\u056b_\u0579\u0578\u0580\u0565\u0584\u0577\u0561\u0562\u0569\u056b_\u0570\u056b\u0576\u0563\u0577\u0561\u0562\u0569\u056b_\u0578\u0582\u0580\u0562\u0561\u0569_\u0577\u0561\u0562\u0561\u0569".split("_"),weekdaysShort:"\u056f\u0580\u056f_\u0565\u0580\u056f_\u0565\u0580\u0584_\u0579\u0580\u0584_\u0570\u0576\u0563_\u0578\u0582\u0580\u0562_\u0577\u0562\u0569".split("_"),weekdaysMin:"\u056f\u0580\u056f_\u0565\u0580\u056f_\u0565\u0580\u0584_\u0579\u0580\u0584_\u0570\u0576\u0563_\u0578\u0582\u0580\u0562_\u0577\u0562\u0569".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY \u0569.",LLL:"D MMMM YYYY \u0569., HH:mm",LLLL:"dddd, D MMMM YYYY \u0569., HH:mm"},calendar:{sameDay:"[\u0561\u0575\u057d\u0585\u0580] LT",nextDay:"[\u057e\u0561\u0572\u0568] LT",lastDay:"[\u0565\u0580\u0565\u056f] LT",nextWeek:function(){return"dddd [\u0585\u0580\u0568 \u056a\u0561\u0574\u0568] LT"},lastWeek:function(){return"[\u0561\u0576\u0581\u0561\u056e] dddd [\u0585\u0580\u0568 \u056a\u0561\u0574\u0568] LT"},sameElse:"L"},relativeTime:{future:"%s \u0570\u0565\u057f\u0578",past:"%s \u0561\u057c\u0561\u057b",s:"\u0574\u056b \u0584\u0561\u0576\u056b \u057e\u0561\u0575\u0580\u056f\u0575\u0561\u0576",ss:"%d \u057e\u0561\u0575\u0580\u056f\u0575\u0561\u0576",m:"\u0580\u0578\u057a\u0565",mm:"%d \u0580\u0578\u057a\u0565",h:"\u056a\u0561\u0574",hh:"%d \u056a\u0561\u0574",d:"\u0585\u0580",dd:"%d \u0585\u0580",M:"\u0561\u0574\u056b\u057d",MM:"%d \u0561\u0574\u056b\u057d",y:"\u057f\u0561\u0580\u056b",yy:"%d \u057f\u0561\u0580\u056b"},meridiemParse:/\u0563\u056b\u0577\u0565\u0580\u057e\u0561|\u0561\u057c\u0561\u057e\u0578\u057f\u057e\u0561|\u0581\u0565\u0580\u0565\u056f\u057e\u0561|\u0565\u0580\u0565\u056f\u0578\u0575\u0561\u0576/,isPM:function(e){return/^(\u0581\u0565\u0580\u0565\u056f\u057e\u0561|\u0565\u0580\u0565\u056f\u0578\u0575\u0561\u0576)$/.test(e)},meridiem:function(e){return e<4?"\u0563\u056b\u0577\u0565\u0580\u057e\u0561":e<12?"\u0561\u057c\u0561\u057e\u0578\u057f\u057e\u0561":e<17?"\u0581\u0565\u0580\u0565\u056f\u057e\u0561":"\u0565\u0580\u0565\u056f\u0578\u0575\u0561\u0576"},dayOfMonthOrdinalParse:/\d{1,2}|\d{1,2}-(\u056b\u0576|\u0580\u0564)/,ordinal:function(e,a){switch(a){case"DDD":case"w":case"W":case"DDDo":return 1===e?e+"-\u056b\u0576":e+"-\u0580\u0564";default:return e}},week:{dow:1,doy:7}}),e.defineLocale("id",{months:"Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_November_Desember".split("_"),monthsShort:"Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nov_Des".split("_"),weekdays:"Minggu_Senin_Selasa_Rabu_Kamis_Jumat_Sabtu".split("_"),weekdaysShort:"Min_Sen_Sel_Rab_Kam_Jum_Sab".split("_"),weekdaysMin:"Mg_Sn_Sl_Rb_Km_Jm_Sb".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] HH.mm",LLLL:"dddd, D MMMM YYYY [pukul] HH.mm"},meridiemParse:/pagi|siang|sore|malam/,meridiemHour:function(e,a){return 12===e&&(e=0),"pagi"===a?e:"siang"===a?e>=11?e:e+12:"sore"===a||"malam"===a?e+12:void 0},meridiem:function(e,a,t){return e<11?"pagi":e<15?"siang":e<19?"sore":"malam"},calendar:{sameDay:"[Hari ini pukul] LT",nextDay:"[Besok pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kemarin pukul] LT",lastWeek:"dddd [lalu pukul] LT",sameElse:"L"},relativeTime:{future:"dalam %s",past:"%s yang lalu",s:"beberapa detik",ss:"%d detik",m:"semenit",mm:"%d menit",h:"sejam",hh:"%d jam",d:"sehari",dd:"%d hari",M:"sebulan",MM:"%d bulan",y:"setahun",yy:"%d tahun"},week:{dow:1,doy:7}}),e.defineLocale("is",{months:"jan\xfaar_febr\xfaar_mars_apr\xedl_ma\xed_j\xfan\xed_j\xfal\xed_\xe1g\xfast_september_okt\xf3ber_n\xf3vember_desember".split("_"),monthsShort:"jan_feb_mar_apr_ma\xed_j\xfan_j\xfal_\xe1g\xfa_sep_okt_n\xf3v_des".split("_"),weekdays:"sunnudagur_m\xe1nudagur_\xferi\xf0judagur_mi\xf0vikudagur_fimmtudagur_f\xf6studagur_laugardagur".split("_"),weekdaysShort:"sun_m\xe1n_\xferi_mi\xf0_fim_f\xf6s_lau".split("_"),weekdaysMin:"Su_M\xe1_\xder_Mi_Fi_F\xf6_La".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] H:mm",LLLL:"dddd, D. MMMM YYYY [kl.] H:mm"},calendar:{sameDay:"[\xed dag kl.] LT",nextDay:"[\xe1 morgun kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[\xed g\xe6r kl.] LT",lastWeek:"[s\xed\xf0asta] dddd [kl.] LT",sameElse:"L"},relativeTime:{future:"eftir %s",past:"fyrir %s s\xed\xf0an",s:Ma,ss:Ma,m:Ma,mm:Ma,h:"klukkustund",hh:Ma,d:Ma,dd:Ma,M:Ma,MM:Ma,y:Ma,yy:Ma},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),e.defineLocale("it",{months:"gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre".split("_"),monthsShort:"gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic".split("_"),weekdays:"domenica_luned\xec_marted\xec_mercoled\xec_gioved\xec_venerd\xec_sabato".split("_"),weekdaysShort:"dom_lun_mar_mer_gio_ven_sab".split("_"),weekdaysMin:"do_lu_ma_me_gi_ve_sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[Oggi alle] LT",nextDay:"[Domani alle] LT",nextWeek:"dddd [alle] LT",lastDay:"[Ieri alle] LT",lastWeek:function(){switch(this.day()){case 0:return"[la scorsa] dddd [alle] LT";default:return"[lo scorso] dddd [alle] LT"}},sameElse:"L"},relativeTime:{future:function(e){return(/^[0-9].+$/.test(e)?"tra":"in")+" "+e},past:"%s fa",s:"alcuni secondi",ss:"%d secondi",m:"un minuto",mm:"%d minuti",h:"un'ora",hh:"%d ore",d:"un giorno",dd:"%d giorni",M:"un mese",MM:"%d mesi",y:"un anno",yy:"%d anni"},dayOfMonthOrdinalParse:/\d{1,2}\xba/,ordinal:"%d\xba",week:{dow:1,doy:4}}),e.defineLocale("ja",{months:"1\u6708_2\u6708_3\u6708_4\u6708_5\u6708_6\u6708_7\u6708_8\u6708_9\u6708_10\u6708_11\u6708_12\u6708".split("_"),monthsShort:"1\u6708_2\u6708_3\u6708_4\u6708_5\u6708_6\u6708_7\u6708_8\u6708_9\u6708_10\u6708_11\u6708_12\u6708".split("_"),weekdays:"\u65e5\u66dc\u65e5_\u6708\u66dc\u65e5_\u706b\u66dc\u65e5_\u6c34\u66dc\u65e5_\u6728\u66dc\u65e5_\u91d1\u66dc\u65e5_\u571f\u66dc\u65e5".split("_"),weekdaysShort:"\u65e5_\u6708_\u706b_\u6c34_\u6728_\u91d1_\u571f".split("_"),weekdaysMin:"\u65e5_\u6708_\u706b_\u6c34_\u6728_\u91d1_\u571f".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY/MM/DD",LL:"YYYY\u5e74M\u6708D\u65e5",LLL:"YYYY\u5e74M\u6708D\u65e5 HH:mm",LLLL:"YYYY\u5e74M\u6708D\u65e5 HH:mm dddd",l:"YYYY/MM/DD",ll:"YYYY\u5e74M\u6708D\u65e5",lll:"YYYY\u5e74M\u6708D\u65e5 HH:mm",llll:"YYYY\u5e74M\u6708D\u65e5 HH:mm dddd"},meridiemParse:/\u5348\u524d|\u5348\u5f8c/i,isPM:function(e){return"\u5348\u5f8c"===e},meridiem:function(e,a,t){return e<12?"\u5348\u524d":"\u5348\u5f8c"},calendar:{sameDay:"[\u4eca\u65e5] LT",nextDay:"[\u660e\u65e5] LT",nextWeek:"[\u6765\u9031]dddd LT",lastDay:"[\u6628\u65e5] LT",lastWeek:"[\u524d\u9031]dddd LT",sameElse:"L"},dayOfMonthOrdinalParse:/\d{1,2}\u65e5/,ordinal:function(e,a){switch(a){case"d":case"D":case"DDD":return e+"\u65e5";default:return e}},relativeTime:{future:"%s\u5f8c",past:"%s\u524d",s:"\u6570\u79d2",ss:"%d\u79d2",m:"1\u5206",mm:"%d\u5206",h:"1\u6642\u9593",hh:"%d\u6642\u9593",d:"1\u65e5",dd:"%d\u65e5",M:"1\u30f6\u6708",MM:"%d\u30f6\u6708",y:"1\u5e74",yy:"%d\u5e74"}}),e.defineLocale("jv",{months:"Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_Nopember_Desember".split("_"),monthsShort:"Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nop_Des".split("_"),weekdays:"Minggu_Senen_Seloso_Rebu_Kemis_Jemuwah_Septu".split("_"),weekdaysShort:"Min_Sen_Sel_Reb_Kem_Jem_Sep".split("_"),weekdaysMin:"Mg_Sn_Sl_Rb_Km_Jm_Sp".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] HH.mm",LLLL:"dddd, D MMMM YYYY [pukul] HH.mm"},meridiemParse:/enjing|siyang|sonten|ndalu/,meridiemHour:function(e,a){return 12===e&&(e=0),"enjing"===a?e:"siyang"===a?e>=11?e:e+12:"sonten"===a||"ndalu"===a?e+12:void 0},meridiem:function(e,a,t){return e<11?"enjing":e<15?"siyang":e<19?"sonten":"ndalu"},calendar:{sameDay:"[Dinten puniko pukul] LT",nextDay:"[Mbenjang pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kala wingi pukul] LT",lastWeek:"dddd [kepengker pukul] LT",sameElse:"L"},relativeTime:{future:"wonten ing %s",past:"%s ingkang kepengker",s:"sawetawis detik",ss:"%d detik",m:"setunggal menit",mm:"%d menit",h:"setunggal jam",hh:"%d jam",d:"sedinten",dd:"%d dinten",M:"sewulan",MM:"%d wulan",y:"setaun",yy:"%d taun"},week:{dow:1,doy:7}}),e.defineLocale("ka",{months:{standalone:"\u10d8\u10d0\u10dc\u10d5\u10d0\u10e0\u10d8_\u10d7\u10d4\u10d1\u10d4\u10e0\u10d5\u10d0\u10da\u10d8_\u10db\u10d0\u10e0\u10e2\u10d8_\u10d0\u10de\u10e0\u10d8\u10da\u10d8_\u10db\u10d0\u10d8\u10e1\u10d8_\u10d8\u10d5\u10dc\u10d8\u10e1\u10d8_\u10d8\u10d5\u10da\u10d8\u10e1\u10d8_\u10d0\u10d2\u10d5\u10d8\u10e1\u10e2\u10dd_\u10e1\u10d4\u10e5\u10e2\u10d4\u10db\u10d1\u10d4\u10e0\u10d8_\u10dd\u10e5\u10e2\u10dd\u10db\u10d1\u10d4\u10e0\u10d8_\u10dc\u10dd\u10d4\u10db\u10d1\u10d4\u10e0\u10d8_\u10d3\u10d4\u10d9\u10d4\u10db\u10d1\u10d4\u10e0\u10d8".split("_"),format:"\u10d8\u10d0\u10dc\u10d5\u10d0\u10e0\u10e1_\u10d7\u10d4\u10d1\u10d4\u10e0\u10d5\u10d0\u10da\u10e1_\u10db\u10d0\u10e0\u10e2\u10e1_\u10d0\u10de\u10e0\u10d8\u10da\u10d8\u10e1_\u10db\u10d0\u10d8\u10e1\u10e1_\u10d8\u10d5\u10dc\u10d8\u10e1\u10e1_\u10d8\u10d5\u10da\u10d8\u10e1\u10e1_\u10d0\u10d2\u10d5\u10d8\u10e1\u10e2\u10e1_\u10e1\u10d4\u10e5\u10e2\u10d4\u10db\u10d1\u10d4\u10e0\u10e1_\u10dd\u10e5\u10e2\u10dd\u10db\u10d1\u10d4\u10e0\u10e1_\u10dc\u10dd\u10d4\u10db\u10d1\u10d4\u10e0\u10e1_\u10d3\u10d4\u10d9\u10d4\u10db\u10d1\u10d4\u10e0\u10e1".split("_")},monthsShort:"\u10d8\u10d0\u10dc_\u10d7\u10d4\u10d1_\u10db\u10d0\u10e0_\u10d0\u10de\u10e0_\u10db\u10d0\u10d8_\u10d8\u10d5\u10dc_\u10d8\u10d5\u10da_\u10d0\u10d2\u10d5_\u10e1\u10d4\u10e5_\u10dd\u10e5\u10e2_\u10dc\u10dd\u10d4_\u10d3\u10d4\u10d9".split("_"),weekdays:{standalone:"\u10d9\u10d5\u10d8\u10e0\u10d0_\u10dd\u10e0\u10e8\u10d0\u10d1\u10d0\u10d7\u10d8_\u10e1\u10d0\u10db\u10e8\u10d0\u10d1\u10d0\u10d7\u10d8_\u10dd\u10d7\u10ee\u10e8\u10d0\u10d1\u10d0\u10d7\u10d8_\u10ee\u10e3\u10d7\u10e8\u10d0\u10d1\u10d0\u10d7\u10d8_\u10de\u10d0\u10e0\u10d0\u10e1\u10d9\u10d4\u10d5\u10d8_\u10e8\u10d0\u10d1\u10d0\u10d7\u10d8".split("_"),format:"\u10d9\u10d5\u10d8\u10e0\u10d0\u10e1_\u10dd\u10e0\u10e8\u10d0\u10d1\u10d0\u10d7\u10e1_\u10e1\u10d0\u10db\u10e8\u10d0\u10d1\u10d0\u10d7\u10e1_\u10dd\u10d7\u10ee\u10e8\u10d0\u10d1\u10d0\u10d7\u10e1_\u10ee\u10e3\u10d7\u10e8\u10d0\u10d1\u10d0\u10d7\u10e1_\u10de\u10d0\u10e0\u10d0\u10e1\u10d9\u10d4\u10d5\u10e1_\u10e8\u10d0\u10d1\u10d0\u10d7\u10e1".split("_"),isFormat:/(\u10ec\u10d8\u10dc\u10d0|\u10e8\u10d4\u10db\u10d3\u10d4\u10d2)/},weekdaysShort:"\u10d9\u10d5\u10d8_\u10dd\u10e0\u10e8_\u10e1\u10d0\u10db_\u10dd\u10d7\u10ee_\u10ee\u10e3\u10d7_\u10de\u10d0\u10e0_\u10e8\u10d0\u10d1".split("_"),weekdaysMin:"\u10d9\u10d5_\u10dd\u10e0_\u10e1\u10d0_\u10dd\u10d7_\u10ee\u10e3_\u10de\u10d0_\u10e8\u10d0".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[\u10d3\u10e6\u10d4\u10e1] LT[-\u10d6\u10d4]",nextDay:"[\u10ee\u10d5\u10d0\u10da] LT[-\u10d6\u10d4]",lastDay:"[\u10d2\u10e3\u10e8\u10d8\u10dc] LT[-\u10d6\u10d4]",nextWeek:"[\u10e8\u10d4\u10db\u10d3\u10d4\u10d2] dddd LT[-\u10d6\u10d4]",lastWeek:"[\u10ec\u10d8\u10dc\u10d0] dddd LT-\u10d6\u10d4",sameElse:"L"},relativeTime:{future:function(e){return/(\u10ec\u10d0\u10db\u10d8|\u10ec\u10e3\u10d7\u10d8|\u10e1\u10d0\u10d0\u10d7\u10d8|\u10ec\u10d4\u10da\u10d8)/.test(e)?e.replace(/\u10d8$/,"\u10e8\u10d8"):e+"\u10e8\u10d8"},past:function(e){return/(\u10ec\u10d0\u10db\u10d8|\u10ec\u10e3\u10d7\u10d8|\u10e1\u10d0\u10d0\u10d7\u10d8|\u10d3\u10e6\u10d4|\u10d7\u10d5\u10d4)/.test(e)?e.replace(/(\u10d8|\u10d4)$/,"\u10d8\u10e1 \u10e3\u10d9\u10d0\u10dc"):/\u10ec\u10d4\u10da\u10d8/.test(e)?e.replace(/\u10ec\u10d4\u10da\u10d8$/,"\u10ec\u10da\u10d8\u10e1 \u10e3\u10d9\u10d0\u10dc"):void 0},s:"\u10e0\u10d0\u10db\u10d3\u10d4\u10dc\u10d8\u10db\u10d4 \u10ec\u10d0\u10db\u10d8",ss:"%d \u10ec\u10d0\u10db\u10d8",m:"\u10ec\u10e3\u10d7\u10d8",mm:"%d \u10ec\u10e3\u10d7\u10d8",h:"\u10e1\u10d0\u10d0\u10d7\u10d8",hh:"%d \u10e1\u10d0\u10d0\u10d7\u10d8",d:"\u10d3\u10e6\u10d4",dd:"%d \u10d3\u10e6\u10d4",M:"\u10d7\u10d5\u10d4",MM:"%d \u10d7\u10d5\u10d4",y:"\u10ec\u10d4\u10da\u10d8",yy:"%d \u10ec\u10d4\u10da\u10d8"},dayOfMonthOrdinalParse:/0|1-\u10da\u10d8|\u10db\u10d4-\d{1,2}|\d{1,2}-\u10d4/,ordinal:function(e){return 0===e?e:1===e?e+"-\u10da\u10d8":e<20||e<=100&&e%20==0||e%100==0?"\u10db\u10d4-"+e:e+"-\u10d4"},week:{dow:1,doy:7}});var yn={0:"-\u0448\u0456",1:"-\u0448\u0456",2:"-\u0448\u0456",3:"-\u0448\u0456",4:"-\u0448\u0456",5:"-\u0448\u0456",6:"-\u0448\u044b",7:"-\u0448\u0456",8:"-\u0448\u0456",9:"-\u0448\u044b",10:"-\u0448\u044b",20:"-\u0448\u044b",30:"-\u0448\u044b",40:"-\u0448\u044b",50:"-\u0448\u0456",60:"-\u0448\u044b",70:"-\u0448\u0456",80:"-\u0448\u0456",90:"-\u0448\u044b",100:"-\u0448\u0456"};e.defineLocale("kk",{months:"\u049b\u0430\u04a3\u0442\u0430\u0440_\u0430\u049b\u043f\u0430\u043d_\u043d\u0430\u0443\u0440\u044b\u0437_\u0441\u04d9\u0443\u0456\u0440_\u043c\u0430\u043c\u044b\u0440_\u043c\u0430\u0443\u0441\u044b\u043c_\u0448\u0456\u043b\u0434\u0435_\u0442\u0430\u043c\u044b\u0437_\u049b\u044b\u0440\u043a\u04af\u0439\u0435\u043a_\u049b\u0430\u0437\u0430\u043d_\u049b\u0430\u0440\u0430\u0448\u0430_\u0436\u0435\u043b\u0442\u043e\u049b\u0441\u0430\u043d".split("_"),monthsShort:"\u049b\u0430\u04a3_\u0430\u049b\u043f_\u043d\u0430\u0443_\u0441\u04d9\u0443_\u043c\u0430\u043c_\u043c\u0430\u0443_\u0448\u0456\u043b_\u0442\u0430\u043c_\u049b\u044b\u0440_\u049b\u0430\u0437_\u049b\u0430\u0440_\u0436\u0435\u043b".split("_"),weekdays:"\u0436\u0435\u043a\u0441\u0435\u043d\u0431\u0456_\u0434\u04af\u0439\u0441\u0435\u043d\u0431\u0456_\u0441\u0435\u0439\u0441\u0435\u043d\u0431\u0456_\u0441\u04d9\u0440\u0441\u0435\u043d\u0431\u0456_\u0431\u0435\u0439\u0441\u0435\u043d\u0431\u0456_\u0436\u04b1\u043c\u0430_\u0441\u0435\u043d\u0431\u0456".split("_"),weekdaysShort:"\u0436\u0435\u043a_\u0434\u04af\u0439_\u0441\u0435\u0439_\u0441\u04d9\u0440_\u0431\u0435\u0439_\u0436\u04b1\u043c_\u0441\u0435\u043d".split("_"),weekdaysMin:"\u0436\u043a_\u0434\u0439_\u0441\u0439_\u0441\u0440_\u0431\u0439_\u0436\u043c_\u0441\u043d".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[\u0411\u04af\u0433\u0456\u043d \u0441\u0430\u0493\u0430\u0442] LT",nextDay:"[\u0415\u0440\u0442\u0435\u04a3 \u0441\u0430\u0493\u0430\u0442] LT",nextWeek:"dddd [\u0441\u0430\u0493\u0430\u0442] LT",lastDay:"[\u041a\u0435\u0448\u0435 \u0441\u0430\u0493\u0430\u0442] LT",lastWeek:"[\u04e8\u0442\u043a\u0435\u043d \u0430\u043f\u0442\u0430\u043d\u044b\u04a3] dddd [\u0441\u0430\u0493\u0430\u0442] LT",sameElse:"L"},relativeTime:{future:"%s \u0456\u0448\u0456\u043d\u0434\u0435",past:"%s \u0431\u04b1\u0440\u044b\u043d",s:"\u0431\u0456\u0440\u043d\u0435\u0448\u0435 \u0441\u0435\u043a\u0443\u043d\u0434",ss:"%d \u0441\u0435\u043a\u0443\u043d\u0434",m:"\u0431\u0456\u0440 \u043c\u0438\u043d\u0443\u0442",mm:"%d \u043c\u0438\u043d\u0443\u0442",h:"\u0431\u0456\u0440 \u0441\u0430\u0493\u0430\u0442",hh:"%d \u0441\u0430\u0493\u0430\u0442",d:"\u0431\u0456\u0440 \u043a\u04af\u043d",dd:"%d \u043a\u04af\u043d",M:"\u0431\u0456\u0440 \u0430\u0439",MM:"%d \u0430\u0439",y:"\u0431\u0456\u0440 \u0436\u044b\u043b",yy:"%d \u0436\u044b\u043b"},dayOfMonthOrdinalParse:/\d{1,2}-(\u0448\u0456|\u0448\u044b)/,ordinal:function(e){return e+(yn[e]||yn[e%10]||yn[e>=100?100:null])},week:{dow:1,doy:7}}),e.defineLocale("km",{months:"\u1798\u1780\u179a\u17b6_\u1780\u17bb\u1798\u17d2\u1797\u17c8_\u1798\u17b8\u1793\u17b6_\u1798\u17c1\u179f\u17b6_\u17a7\u179f\u1797\u17b6_\u1798\u17b7\u1790\u17bb\u1793\u17b6_\u1780\u1780\u17d2\u1780\u178a\u17b6_\u179f\u17b8\u17a0\u17b6_\u1780\u1789\u17d2\u1789\u17b6_\u178f\u17bb\u179b\u17b6_\u179c\u17b7\u1785\u17d2\u1786\u17b7\u1780\u17b6_\u1792\u17d2\u1793\u17bc".split("_"),monthsShort:"\u1798\u1780\u179a\u17b6_\u1780\u17bb\u1798\u17d2\u1797\u17c8_\u1798\u17b8\u1793\u17b6_\u1798\u17c1\u179f\u17b6_\u17a7\u179f\u1797\u17b6_\u1798\u17b7\u1790\u17bb\u1793\u17b6_\u1780\u1780\u17d2\u1780\u178a\u17b6_\u179f\u17b8\u17a0\u17b6_\u1780\u1789\u17d2\u1789\u17b6_\u178f\u17bb\u179b\u17b6_\u179c\u17b7\u1785\u17d2\u1786\u17b7\u1780\u17b6_\u1792\u17d2\u1793\u17bc".split("_"),weekdays:"\u17a2\u17b6\u1791\u17b7\u178f\u17d2\u1799_\u1785\u17d0\u1793\u17d2\u1791_\u17a2\u1784\u17d2\u1782\u17b6\u179a_\u1796\u17bb\u1792_\u1796\u17d2\u179a\u17a0\u179f\u17d2\u1794\u178f\u17b7\u17cd_\u179f\u17bb\u1780\u17d2\u179a_\u179f\u17c5\u179a\u17cd".split("_"),weekdaysShort:"\u17a2\u17b6\u1791\u17b7\u178f\u17d2\u1799_\u1785\u17d0\u1793\u17d2\u1791_\u17a2\u1784\u17d2\u1782\u17b6\u179a_\u1796\u17bb\u1792_\u1796\u17d2\u179a\u17a0\u179f\u17d2\u1794\u178f\u17b7\u17cd_\u179f\u17bb\u1780\u17d2\u179a_\u179f\u17c5\u179a\u17cd".split("_"),weekdaysMin:"\u17a2\u17b6\u1791\u17b7\u178f\u17d2\u1799_\u1785\u17d0\u1793\u17d2\u1791_\u17a2\u1784\u17d2\u1782\u17b6\u179a_\u1796\u17bb\u1792_\u1796\u17d2\u179a\u17a0\u179f\u17d2\u1794\u178f\u17b7\u17cd_\u179f\u17bb\u1780\u17d2\u179a_\u179f\u17c5\u179a\u17cd".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[\u1790\u17d2\u1784\u17c3\u1793\u17c1\u17c7 \u1798\u17c9\u17c4\u1784] LT",nextDay:"[\u179f\u17d2\u17a2\u17c2\u1780 \u1798\u17c9\u17c4\u1784] LT",nextWeek:"dddd [\u1798\u17c9\u17c4\u1784] LT",lastDay:"[\u1798\u17d2\u179f\u17b7\u179b\u1798\u17b7\u1789 \u1798\u17c9\u17c4\u1784] LT",lastWeek:"dddd [\u179f\u1794\u17d2\u178f\u17b6\u17a0\u17cd\u1798\u17bb\u1793] [\u1798\u17c9\u17c4\u1784] LT",sameElse:"L"},relativeTime:{future:"%s\u1791\u17c0\u178f",past:"%s\u1798\u17bb\u1793",s:"\u1794\u17c9\u17bb\u1793\u17d2\u1798\u17b6\u1793\u179c\u17b7\u1793\u17b6\u1791\u17b8",ss:"%d \u179c\u17b7\u1793\u17b6\u1791\u17b8",m:"\u1798\u17bd\u1799\u1793\u17b6\u1791\u17b8",mm:"%d \u1793\u17b6\u1791\u17b8",h:"\u1798\u17bd\u1799\u1798\u17c9\u17c4\u1784",hh:"%d \u1798\u17c9\u17c4\u1784",d:"\u1798\u17bd\u1799\u1790\u17d2\u1784\u17c3",dd:"%d \u1790\u17d2\u1784\u17c3",M:"\u1798\u17bd\u1799\u1781\u17c2",MM:"%d \u1781\u17c2",y:"\u1798\u17bd\u1799\u1786\u17d2\u1793\u17b6\u17c6",yy:"%d \u1786\u17d2\u1793\u17b6\u17c6"},week:{dow:1,doy:4}});var fn={1:"\u0ce7",2:"\u0ce8",3:"\u0ce9",4:"\u0cea",5:"\u0ceb",6:"\u0cec",7:"\u0ced",8:"\u0cee",9:"\u0cef",0:"\u0ce6"},kn={"\u0ce7":"1","\u0ce8":"2","\u0ce9":"3","\u0cea":"4","\u0ceb":"5","\u0cec":"6","\u0ced":"7","\u0cee":"8","\u0cef":"9","\u0ce6":"0"};e.defineLocale("kn",{months:"\u0c9c\u0ca8\u0cb5\u0cb0\u0cbf_\u0cab\u0cc6\u0cac\u0ccd\u0cb0\u0cb5\u0cb0\u0cbf_\u0cae\u0cbe\u0cb0\u0ccd\u0c9a\u0ccd_\u0c8f\u0caa\u0ccd\u0cb0\u0cbf\u0cb2\u0ccd_\u0cae\u0cc6\u0cd5_\u0c9c\u0cc2\u0ca8\u0ccd_\u0c9c\u0cc1\u0cb2\u0cc6\u0cd6_\u0c86\u0c97\u0cb8\u0ccd\u0c9f\u0ccd_\u0cb8\u0cc6\u0caa\u0ccd\u0c9f\u0cc6\u0c82\u0cac\u0cb0\u0ccd_\u0c85\u0c95\u0ccd\u0c9f\u0cc6\u0cc2\u0cd5\u0cac\u0cb0\u0ccd_\u0ca8\u0cb5\u0cc6\u0c82\u0cac\u0cb0\u0ccd_\u0ca1\u0cbf\u0cb8\u0cc6\u0c82\u0cac\u0cb0\u0ccd".split("_"),monthsShort:"\u0c9c\u0ca8_\u0cab\u0cc6\u0cac\u0ccd\u0cb0_\u0cae\u0cbe\u0cb0\u0ccd\u0c9a\u0ccd_\u0c8f\u0caa\u0ccd\u0cb0\u0cbf\u0cb2\u0ccd_\u0cae\u0cc6\u0cd5_\u0c9c\u0cc2\u0ca8\u0ccd_\u0c9c\u0cc1\u0cb2\u0cc6\u0cd6_\u0c86\u0c97\u0cb8\u0ccd\u0c9f\u0ccd_\u0cb8\u0cc6\u0caa\u0ccd\u0c9f\u0cc6\u0c82\u0cac_\u0c85\u0c95\u0ccd\u0c9f\u0cc6\u0cc2\u0cd5\u0cac_\u0ca8\u0cb5\u0cc6\u0c82\u0cac_\u0ca1\u0cbf\u0cb8\u0cc6\u0c82\u0cac".split("_"),monthsParseExact:!0,weekdays:"\u0cad\u0cbe\u0ca8\u0cc1\u0cb5\u0cbe\u0cb0_\u0cb8\u0cc6\u0cc2\u0cd5\u0cae\u0cb5\u0cbe\u0cb0_\u0cae\u0c82\u0c97\u0cb3\u0cb5\u0cbe\u0cb0_\u0cac\u0cc1\u0ca7\u0cb5\u0cbe\u0cb0_\u0c97\u0cc1\u0cb0\u0cc1\u0cb5\u0cbe\u0cb0_\u0cb6\u0cc1\u0c95\u0ccd\u0cb0\u0cb5\u0cbe\u0cb0_\u0cb6\u0ca8\u0cbf\u0cb5\u0cbe\u0cb0".split("_"),weekdaysShort:"\u0cad\u0cbe\u0ca8\u0cc1_\u0cb8\u0cc6\u0cc2\u0cd5\u0cae_\u0cae\u0c82\u0c97\u0cb3_\u0cac\u0cc1\u0ca7_\u0c97\u0cc1\u0cb0\u0cc1_\u0cb6\u0cc1\u0c95\u0ccd\u0cb0_\u0cb6\u0ca8\u0cbf".split("_"),weekdaysMin:"\u0cad\u0cbe_\u0cb8\u0cc6\u0cc2\u0cd5_\u0cae\u0c82_\u0cac\u0cc1_\u0c97\u0cc1_\u0cb6\u0cc1_\u0cb6".split("_"),longDateFormat:{LT:"A h:mm",LTS:"A h:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm",LLLL:"dddd, D MMMM YYYY, A h:mm"},calendar:{sameDay:"[\u0c87\u0c82\u0ca6\u0cc1] LT",nextDay:"[\u0ca8\u0cbe\u0cb3\u0cc6] LT",nextWeek:"dddd, LT",lastDay:"[\u0ca8\u0cbf\u0ca8\u0ccd\u0ca8\u0cc6] LT",lastWeek:"[\u0c95\u0cc6\u0cc2\u0ca8\u0cc6\u0caf] dddd, LT",sameElse:"L"},relativeTime:{future:"%s \u0ca8\u0c82\u0ca4\u0cb0",past:"%s \u0cb9\u0cbf\u0c82\u0ca6\u0cc6",s:"\u0c95\u0cc6\u0cb2\u0cb5\u0cc1 \u0c95\u0ccd\u0cb7\u0ca3\u0c97\u0cb3\u0cc1",ss:"%d \u0cb8\u0cc6\u0c95\u0cc6\u0c82\u0ca1\u0cc1\u0c97\u0cb3\u0cc1",m:"\u0c92\u0c82\u0ca6\u0cc1 \u0ca8\u0cbf\u0cae\u0cbf\u0cb7",mm:"%d \u0ca8\u0cbf\u0cae\u0cbf\u0cb7",h:"\u0c92\u0c82\u0ca6\u0cc1 \u0c97\u0c82\u0c9f\u0cc6",hh:"%d \u0c97\u0c82\u0c9f\u0cc6",d:"\u0c92\u0c82\u0ca6\u0cc1 \u0ca6\u0cbf\u0ca8",dd:"%d \u0ca6\u0cbf\u0ca8",M:"\u0c92\u0c82\u0ca6\u0cc1 \u0ca4\u0cbf\u0c82\u0c97\u0cb3\u0cc1",MM:"%d \u0ca4\u0cbf\u0c82\u0c97\u0cb3\u0cc1",y:"\u0c92\u0c82\u0ca6\u0cc1 \u0cb5\u0cb0\u0ccd\u0cb7",yy:"%d \u0cb5\u0cb0\u0ccd\u0cb7"},preparse:function(e){return e.replace(/[\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee\u0cef\u0ce6]/g,function(e){return kn[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return fn[e]})},meridiemParse:/\u0cb0\u0cbe\u0ca4\u0ccd\u0cb0\u0cbf|\u0cac\u0cc6\u0cb3\u0cbf\u0c97\u0ccd\u0c97\u0cc6|\u0cae\u0ca7\u0ccd\u0caf\u0cbe\u0cb9\u0ccd\u0ca8|\u0cb8\u0c82\u0c9c\u0cc6/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u0cb0\u0cbe\u0ca4\u0ccd\u0cb0\u0cbf"===a?e<4?e:e+12:"\u0cac\u0cc6\u0cb3\u0cbf\u0c97\u0ccd\u0c97\u0cc6"===a?e:"\u0cae\u0ca7\u0ccd\u0caf\u0cbe\u0cb9\u0ccd\u0ca8"===a?e>=10?e:e+12:"\u0cb8\u0c82\u0c9c\u0cc6"===a?e+12:void 0},meridiem:function(e,a,t){return e<4?"\u0cb0\u0cbe\u0ca4\u0ccd\u0cb0\u0cbf":e<10?"\u0cac\u0cc6\u0cb3\u0cbf\u0c97\u0ccd\u0c97\u0cc6":e<17?"\u0cae\u0ca7\u0ccd\u0caf\u0cbe\u0cb9\u0ccd\u0ca8":e<20?"\u0cb8\u0c82\u0c9c\u0cc6":"\u0cb0\u0cbe\u0ca4\u0ccd\u0cb0\u0cbf"},dayOfMonthOrdinalParse:/\d{1,2}(\u0ca8\u0cc6\u0cd5)/,ordinal:function(e){return e+"\u0ca8\u0cc6\u0cd5"},week:{dow:0,doy:6}}),e.defineLocale("ko",{months:"1\uc6d4_2\uc6d4_3\uc6d4_4\uc6d4_5\uc6d4_6\uc6d4_7\uc6d4_8\uc6d4_9\uc6d4_10\uc6d4_11\uc6d4_12\uc6d4".split("_"),monthsShort:"1\uc6d4_2\uc6d4_3\uc6d4_4\uc6d4_5\uc6d4_6\uc6d4_7\uc6d4_8\uc6d4_9\uc6d4_10\uc6d4_11\uc6d4_12\uc6d4".split("_"),weekdays:"\uc77c\uc694\uc77c_\uc6d4\uc694\uc77c_\ud654\uc694\uc77c_\uc218\uc694\uc77c_\ubaa9\uc694\uc77c_\uae08\uc694\uc77c_\ud1a0\uc694\uc77c".split("_"),weekdaysShort:"\uc77c_\uc6d4_\ud654_\uc218_\ubaa9_\uae08_\ud1a0".split("_"),weekdaysMin:"\uc77c_\uc6d4_\ud654_\uc218_\ubaa9_\uae08_\ud1a0".split("_"),longDateFormat:{LT:"A h:mm",LTS:"A h:mm:ss",L:"YYYY.MM.DD",LL:"YYYY\ub144 MMMM D\uc77c",LLL:"YYYY\ub144 MMMM D\uc77c A h:mm",LLLL:"YYYY\ub144 MMMM D\uc77c dddd A h:mm",l:"YYYY.MM.DD",ll:"YYYY\ub144 MMMM D\uc77c",lll:"YYYY\ub144 MMMM D\uc77c A h:mm",llll:"YYYY\ub144 MMMM D\uc77c dddd A h:mm"},calendar:{sameDay:"\uc624\ub298 LT",nextDay:"\ub0b4\uc77c LT",nextWeek:"dddd LT",lastDay:"\uc5b4\uc81c LT",lastWeek:"\uc9c0\ub09c\uc8fc dddd LT",sameElse:"L"},relativeTime:{future:"%s \ud6c4",past:"%s \uc804",s:"\uba87 \ucd08",ss:"%d\ucd08",m:"1\ubd84",mm:"%d\ubd84",h:"\ud55c \uc2dc\uac04",hh:"%d\uc2dc\uac04",d:"\ud558\ub8e8",dd:"%d\uc77c",M:"\ud55c \ub2ec",MM:"%d\ub2ec",y:"\uc77c \ub144",yy:"%d\ub144"},dayOfMonthOrdinalParse:/\d{1,2}(\uc77c|\uc6d4|\uc8fc)/,ordinal:function(e,a){switch(a){case"d":case"D":case"DDD":return e+"\uc77c";case"M":return e+"\uc6d4";case"w":case"W":return e+"\uc8fc";default:return e}},meridiemParse:/\uc624\uc804|\uc624\ud6c4/,isPM:function(e){return"\uc624\ud6c4"===e},meridiem:function(e,a,t){return e<12?"\uc624\uc804":"\uc624\ud6c4"}});var pn={0:"-\u0447\u04af",1:"-\u0447\u0438",2:"-\u0447\u0438",3:"-\u0447\u04af",4:"-\u0447\u04af",5:"-\u0447\u0438",6:"-\u0447\u044b",7:"-\u0447\u0438",8:"-\u0447\u0438",9:"-\u0447\u0443",10:"-\u0447\u0443",20:"-\u0447\u044b",30:"-\u0447\u0443",40:"-\u0447\u044b",50:"-\u0447\u04af",60:"-\u0447\u044b",70:"-\u0447\u0438",80:"-\u0447\u0438",90:"-\u0447\u0443",100:"-\u0447\u04af"};e.defineLocale("ky",{months:"\u044f\u043d\u0432\u0430\u0440\u044c_\u0444\u0435\u0432\u0440\u0430\u043b\u044c_\u043c\u0430\u0440\u0442_\u0430\u043f\u0440\u0435\u043b\u044c_\u043c\u0430\u0439_\u0438\u044e\u043d\u044c_\u0438\u044e\u043b\u044c_\u0430\u0432\u0433\u0443\u0441\u0442_\u0441\u0435\u043d\u0442\u044f\u0431\u0440\u044c_\u043e\u043a\u0442\u044f\u0431\u0440\u044c_\u043d\u043e\u044f\u0431\u0440\u044c_\u0434\u0435\u043a\u0430\u0431\u0440\u044c".split("_"),monthsShort:"\u044f\u043d\u0432_\u0444\u0435\u0432_\u043c\u0430\u0440\u0442_\u0430\u043f\u0440_\u043c\u0430\u0439_\u0438\u044e\u043d\u044c_\u0438\u044e\u043b\u044c_\u0430\u0432\u0433_\u0441\u0435\u043d_\u043e\u043a\u0442_\u043d\u043e\u044f_\u0434\u0435\u043a".split("_"),weekdays:"\u0416\u0435\u043a\u0448\u0435\u043c\u0431\u0438_\u0414\u04af\u0439\u0448\u04e9\u043c\u0431\u04af_\u0428\u0435\u0439\u0448\u0435\u043c\u0431\u0438_\u0428\u0430\u0440\u0448\u0435\u043c\u0431\u0438_\u0411\u0435\u0439\u0448\u0435\u043c\u0431\u0438_\u0416\u0443\u043c\u0430_\u0418\u0448\u0435\u043c\u0431\u0438".split("_"),weekdaysShort:"\u0416\u0435\u043a_\u0414\u04af\u0439_\u0428\u0435\u0439_\u0428\u0430\u0440_\u0411\u0435\u0439_\u0416\u0443\u043c_\u0418\u0448\u0435".split("_"),weekdaysMin:"\u0416\u043a_\u0414\u0439_\u0428\u0439_\u0428\u0440_\u0411\u0439_\u0416\u043c_\u0418\u0448".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[\u0411\u04af\u0433\u04af\u043d \u0441\u0430\u0430\u0442] LT",nextDay:"[\u042d\u0440\u0442\u0435\u04a3 \u0441\u0430\u0430\u0442] LT",nextWeek:"dddd [\u0441\u0430\u0430\u0442] LT",lastDay:"[\u041a\u0435\u0447\u0435 \u0441\u0430\u0430\u0442] LT",lastWeek:"[\u04e8\u0442\u043a\u0435\u043d \u0430\u043f\u0442\u0430\u043d\u044b\u043d] dddd [\u043a\u04af\u043d\u04af] [\u0441\u0430\u0430\u0442] LT",sameElse:"L"},relativeTime:{future:"%s \u0438\u0447\u0438\u043d\u0434\u0435",past:"%s \u043c\u0443\u0440\u0443\u043d",s:"\u0431\u0438\u0440\u043d\u0435\u0447\u0435 \u0441\u0435\u043a\u0443\u043d\u0434",ss:"%d \u0441\u0435\u043a\u0443\u043d\u0434",m:"\u0431\u0438\u0440 \u043c\u04af\u043d\u04e9\u0442",mm:"%d \u043c\u04af\u043d\u04e9\u0442",h:"\u0431\u0438\u0440 \u0441\u0430\u0430\u0442",hh:"%d \u0441\u0430\u0430\u0442",d:"\u0431\u0438\u0440 \u043a\u04af\u043d",dd:"%d \u043a\u04af\u043d",M:"\u0431\u0438\u0440 \u0430\u0439",MM:"%d \u0430\u0439",y:"\u0431\u0438\u0440 \u0436\u044b\u043b",yy:"%d \u0436\u044b\u043b"},dayOfMonthOrdinalParse:/\d{1,2}-(\u0447\u0438|\u0447\u044b|\u0447\u04af|\u0447\u0443)/,ordinal:function(e){return e+(pn[e]||pn[e%10]||pn[e>=100?100:null])},week:{dow:1,doy:7}}),e.defineLocale("lb",{months:"Januar_Februar_M\xe4erz_Abr\xebll_Mee_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Febr._Mrz._Abr._Mee_Jun._Jul._Aug._Sept._Okt._Nov._Dez.".split("_"),monthsParseExact:!0,weekdays:"Sonndeg_M\xe9indeg_D\xebnschdeg_M\xebttwoch_Donneschdeg_Freideg_Samschdeg".split("_"),weekdaysShort:"So._M\xe9._D\xeb._M\xeb._Do._Fr._Sa.".split("_"),weekdaysMin:"So_M\xe9_D\xeb_M\xeb_Do_Fr_Sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm [Auer]",LTS:"H:mm:ss [Auer]",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm [Auer]",LLLL:"dddd, D. MMMM YYYY H:mm [Auer]"},calendar:{sameDay:"[Haut um] LT",sameElse:"L",nextDay:"[Muer um] LT",nextWeek:"dddd [um] LT",lastDay:"[G\xebschter um] LT",lastWeek:function(){switch(this.day()){case 2:case 4:return"[Leschten] dddd [um] LT";default:return"[Leschte] dddd [um] LT"}}},relativeTime:{future:function(e){return La(e.substr(0,e.indexOf(" ")))?"a "+e:"an "+e},past:function(e){return La(e.substr(0,e.indexOf(" ")))?"viru "+e:"virun "+e},s:"e puer Sekonnen",ss:"%d Sekonnen",m:ha,mm:"%d Minutten",h:ha,hh:"%d Stonnen",d:ha,dd:"%d Deeg",M:ha,MM:"%d M\xe9int",y:ha,yy:"%d Joer"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),e.defineLocale("lo",{months:"\u0ea1\u0eb1\u0e87\u0e81\u0ead\u0e99_\u0e81\u0eb8\u0ea1\u0e9e\u0eb2_\u0ea1\u0eb5\u0e99\u0eb2_\u0ec0\u0ea1\u0eaa\u0eb2_\u0e9e\u0eb6\u0e94\u0eaa\u0eb0\u0e9e\u0eb2_\u0ea1\u0eb4\u0e96\u0eb8\u0e99\u0eb2_\u0e81\u0ecd\u0ea5\u0eb0\u0e81\u0ebb\u0e94_\u0eaa\u0eb4\u0e87\u0eab\u0eb2_\u0e81\u0eb1\u0e99\u0e8d\u0eb2_\u0e95\u0eb8\u0ea5\u0eb2_\u0e9e\u0eb0\u0e88\u0eb4\u0e81_\u0e97\u0eb1\u0e99\u0ea7\u0eb2".split("_"),monthsShort:"\u0ea1\u0eb1\u0e87\u0e81\u0ead\u0e99_\u0e81\u0eb8\u0ea1\u0e9e\u0eb2_\u0ea1\u0eb5\u0e99\u0eb2_\u0ec0\u0ea1\u0eaa\u0eb2_\u0e9e\u0eb6\u0e94\u0eaa\u0eb0\u0e9e\u0eb2_\u0ea1\u0eb4\u0e96\u0eb8\u0e99\u0eb2_\u0e81\u0ecd\u0ea5\u0eb0\u0e81\u0ebb\u0e94_\u0eaa\u0eb4\u0e87\u0eab\u0eb2_\u0e81\u0eb1\u0e99\u0e8d\u0eb2_\u0e95\u0eb8\u0ea5\u0eb2_\u0e9e\u0eb0\u0e88\u0eb4\u0e81_\u0e97\u0eb1\u0e99\u0ea7\u0eb2".split("_"),weekdays:"\u0ead\u0eb2\u0e97\u0eb4\u0e94_\u0e88\u0eb1\u0e99_\u0ead\u0eb1\u0e87\u0e84\u0eb2\u0e99_\u0e9e\u0eb8\u0e94_\u0e9e\u0eb0\u0eab\u0eb1\u0e94_\u0eaa\u0eb8\u0e81_\u0ec0\u0eaa\u0ebb\u0eb2".split("_"),weekdaysShort:"\u0e97\u0eb4\u0e94_\u0e88\u0eb1\u0e99_\u0ead\u0eb1\u0e87\u0e84\u0eb2\u0e99_\u0e9e\u0eb8\u0e94_\u0e9e\u0eb0\u0eab\u0eb1\u0e94_\u0eaa\u0eb8\u0e81_\u0ec0\u0eaa\u0ebb\u0eb2".split("_"),weekdaysMin:"\u0e97_\u0e88_\u0ead\u0e84_\u0e9e_\u0e9e\u0eab_\u0eaa\u0e81_\u0eaa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"\u0ea7\u0eb1\u0e99dddd D MMMM YYYY HH:mm"},meridiemParse:/\u0e95\u0ead\u0e99\u0ec0\u0e8a\u0ebb\u0ec9\u0eb2|\u0e95\u0ead\u0e99\u0ec1\u0ea5\u0e87/,isPM:function(e){return"\u0e95\u0ead\u0e99\u0ec1\u0ea5\u0e87"===e},meridiem:function(e,a,t){return e<12?"\u0e95\u0ead\u0e99\u0ec0\u0e8a\u0ebb\u0ec9\u0eb2":"\u0e95\u0ead\u0e99\u0ec1\u0ea5\u0e87"},calendar:{sameDay:"[\u0ea1\u0eb7\u0ec9\u0e99\u0eb5\u0ec9\u0ec0\u0ea7\u0ea5\u0eb2] LT",nextDay:"[\u0ea1\u0eb7\u0ec9\u0ead\u0eb7\u0ec8\u0e99\u0ec0\u0ea7\u0ea5\u0eb2] LT",nextWeek:"[\u0ea7\u0eb1\u0e99]dddd[\u0edc\u0ec9\u0eb2\u0ec0\u0ea7\u0ea5\u0eb2] LT",lastDay:"[\u0ea1\u0eb7\u0ec9\u0ea7\u0eb2\u0e99\u0e99\u0eb5\u0ec9\u0ec0\u0ea7\u0ea5\u0eb2] LT",lastWeek:"[\u0ea7\u0eb1\u0e99]dddd[\u0ec1\u0ea5\u0ec9\u0ea7\u0e99\u0eb5\u0ec9\u0ec0\u0ea7\u0ea5\u0eb2] LT",sameElse:"L"},relativeTime:{future:"\u0ead\u0eb5\u0e81 %s",past:"%s\u0e9c\u0ec8\u0eb2\u0e99\u0ea1\u0eb2",s:"\u0e9a\u0ecd\u0ec8\u0ec0\u0e97\u0ebb\u0ec8\u0eb2\u0ec3\u0e94\u0ea7\u0eb4\u0e99\u0eb2\u0e97\u0eb5",ss:"%d \u0ea7\u0eb4\u0e99\u0eb2\u0e97\u0eb5",m:"1 \u0e99\u0eb2\u0e97\u0eb5",mm:"%d \u0e99\u0eb2\u0e97\u0eb5",h:"1 \u0e8a\u0ebb\u0ec8\u0ea7\u0ec2\u0ea1\u0e87",hh:"%d \u0e8a\u0ebb\u0ec8\u0ea7\u0ec2\u0ea1\u0e87",d:"1 \u0ea1\u0eb7\u0ec9",dd:"%d \u0ea1\u0eb7\u0ec9",M:"1 \u0ec0\u0e94\u0eb7\u0ead\u0e99",MM:"%d \u0ec0\u0e94\u0eb7\u0ead\u0e99",y:"1 \u0e9b\u0eb5",yy:"%d \u0e9b\u0eb5"},dayOfMonthOrdinalParse:/(\u0e97\u0eb5\u0ec8)\d{1,2}/,ordinal:function(e){return"\u0e97\u0eb5\u0ec8"+e}});var Dn={ss:"sekund\u0117_sekund\u017ei\u0173_sekundes",m:"minut\u0117_minut\u0117s_minut\u0119",mm:"minut\u0117s_minu\u010di\u0173_minutes",h:"valanda_valandos_valand\u0105",hh:"valandos_valand\u0173_valandas",d:"diena_dienos_dien\u0105",dd:"dienos_dien\u0173_dienas",M:"m\u0117nuo_m\u0117nesio_m\u0117nes\u012f",MM:"m\u0117nesiai_m\u0117nesi\u0173_m\u0117nesius",y:"metai_met\u0173_metus",yy:"metai_met\u0173_metus"};e.defineLocale("lt",{months:{format:"sausio_vasario_kovo_baland\u017eio_gegu\u017e\u0117s_bir\u017eelio_liepos_rugpj\u016b\u010dio_rugs\u0117jo_spalio_lapkri\u010dio_gruod\u017eio".split("_"),standalone:"sausis_vasaris_kovas_balandis_gegu\u017e\u0117_bir\u017eelis_liepa_rugpj\u016btis_rugs\u0117jis_spalis_lapkritis_gruodis".split("_"),isFormat:/D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|MMMM?(\[[^\[\]]*\]|\s)+D[oD]?/},monthsShort:"sau_vas_kov_bal_geg_bir_lie_rgp_rgs_spa_lap_grd".split("_"),weekdays:{format:"sekmadien\u012f_pirmadien\u012f_antradien\u012f_tre\u010diadien\u012f_ketvirtadien\u012f_penktadien\u012f_\u0161e\u0161tadien\u012f".split("_"),standalone:"sekmadienis_pirmadienis_antradienis_tre\u010diadienis_ketvirtadienis_penktadienis_\u0161e\u0161tadienis".split("_"),isFormat:/dddd HH:mm/},weekdaysShort:"Sek_Pir_Ant_Tre_Ket_Pen_\u0160e\u0161".split("_"),weekdaysMin:"S_P_A_T_K_Pn_\u0160".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"YYYY [m.] MMMM D [d.]",LLL:"YYYY [m.] MMMM D [d.], HH:mm [val.]",LLLL:"YYYY [m.] MMMM D [d.], dddd, HH:mm [val.]",l:"YYYY-MM-DD",ll:"YYYY [m.] MMMM D [d.]",lll:"YYYY [m.] MMMM D [d.], HH:mm [val.]",llll:"YYYY [m.] MMMM D [d.], ddd, HH:mm [val.]"},calendar:{sameDay:"[\u0160iandien] LT",nextDay:"[Rytoj] LT",nextWeek:"dddd LT",lastDay:"[Vakar] LT",lastWeek:"[Pra\u0117jus\u012f] dddd LT",sameElse:"L"},relativeTime:{future:"po %s",past:"prie\u0161 %s",s:function(e,a,t,s){return a?"kelios sekund\u0117s":s?"keli\u0173 sekund\u017ei\u0173":"kelias sekundes"},ss:fa,m:ca,mm:fa,h:ca,hh:fa,d:ca,dd:fa,M:ca,MM:fa,y:ca,yy:fa},dayOfMonthOrdinalParse:/\d{1,2}-oji/,ordinal:function(e){return e+"-oji"},week:{dow:1,doy:4}});var Tn={ss:"sekundes_sekund\u0113m_sekunde_sekundes".split("_"),m:"min\u016btes_min\u016bt\u0113m_min\u016bte_min\u016btes".split("_"),mm:"min\u016btes_min\u016bt\u0113m_min\u016bte_min\u016btes".split("_"),h:"stundas_stund\u0101m_stunda_stundas".split("_"),hh:"stundas_stund\u0101m_stunda_stundas".split("_"),d:"dienas_dien\u0101m_diena_dienas".split("_"),dd:"dienas_dien\u0101m_diena_dienas".split("_"),M:"m\u0113ne\u0161a_m\u0113ne\u0161iem_m\u0113nesis_m\u0113ne\u0161i".split("_"),MM:"m\u0113ne\u0161a_m\u0113ne\u0161iem_m\u0113nesis_m\u0113ne\u0161i".split("_"),y:"gada_gadiem_gads_gadi".split("_"),yy:"gada_gadiem_gads_gadi".split("_")};e.defineLocale("lv",{months:"janv\u0101ris_febru\u0101ris_marts_apr\u012blis_maijs_j\u016bnijs_j\u016blijs_augusts_septembris_oktobris_novembris_decembris".split("_"),monthsShort:"jan_feb_mar_apr_mai_j\u016bn_j\u016bl_aug_sep_okt_nov_dec".split("_"),weekdays:"sv\u0113tdiena_pirmdiena_otrdiena_tre\u0161diena_ceturtdiena_piektdiena_sestdiena".split("_"),weekdaysShort:"Sv_P_O_T_C_Pk_S".split("_"),weekdaysMin:"Sv_P_O_T_C_Pk_S".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY.",LL:"YYYY. [gada] D. MMMM",LLL:"YYYY. [gada] D. MMMM, HH:mm",LLLL:"YYYY. [gada] D. MMMM, dddd, HH:mm"},calendar:{sameDay:"[\u0160odien pulksten] LT",nextDay:"[R\u012bt pulksten] LT",nextWeek:"dddd [pulksten] LT",lastDay:"[Vakar pulksten] LT",lastWeek:"[Pag\u0101ju\u0161\u0101] dddd [pulksten] LT",sameElse:"L"},relativeTime:{future:"p\u0113c %s",past:"pirms %s",s:function(e,a){return a?"da\u017eas sekundes":"da\u017e\u0101m sekund\u0113m"},ss:pa,m:Da,mm:pa,h:Da,hh:pa,d:Da,dd:pa,M:Da,MM:pa,y:Da,yy:pa},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}});var gn={words:{ss:["sekund","sekunda","sekundi"],m:["jedan minut","jednog minuta"],mm:["minut","minuta","minuta"],h:["jedan sat","jednog sata"],hh:["sat","sata","sati"],dd:["dan","dana","dana"],MM:["mjesec","mjeseca","mjeseci"],yy:["godina","godine","godina"]},correctGrammaticalCase:function(e,a){return 1===e?a[0]:e>=2&&e<=4?a[1]:a[2]},translate:function(e,a,t){var s=gn.words[t];return 1===t.length?a?s[0]:s[1]:e+" "+gn.correctGrammaticalCase(e,s)}};e.defineLocale("me",{months:"januar_februar_mart_april_maj_jun_jul_avgust_septembar_oktobar_novembar_decembar".split("_"),monthsShort:"jan._feb._mar._apr._maj_jun_jul_avg._sep._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"nedjelja_ponedjeljak_utorak_srijeda_\u010detvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sri._\u010det._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_\u010de_pe_su".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[danas u] LT",nextDay:"[sjutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedjelju] [u] LT";case 3:return"[u] [srijedu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[ju\u010de u] LT",lastWeek:function(){return["[pro\u0161le] [nedjelje] [u] LT","[pro\u0161log] [ponedjeljka] [u] LT","[pro\u0161log] [utorka] [u] LT","[pro\u0161le] [srijede] [u] LT","[pro\u0161log] [\u010detvrtka] [u] LT","[pro\u0161log] [petka] [u] LT","[pro\u0161le] [subote] [u] LT"][this.day()]},sameElse:"L"},relativeTime:{future:"za %s",past:"prije %s",s:"nekoliko sekundi",ss:gn.translate,m:gn.translate,mm:gn.translate,h:gn.translate,hh:gn.translate,d:"dan",dd:gn.translate,M:"mjesec",MM:gn.translate,y:"godinu",yy:gn.translate},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),e.defineLocale("mi",{months:"Kohi-t\u0101te_Hui-tanguru_Pout\u016b-te-rangi_Paenga-wh\u0101wh\u0101_Haratua_Pipiri_H\u014dngoingoi_Here-turi-k\u014dk\u0101_Mahuru_Whiringa-\u0101-nuku_Whiringa-\u0101-rangi_Hakihea".split("_"),monthsShort:"Kohi_Hui_Pou_Pae_Hara_Pipi_H\u014dngoi_Here_Mahu_Whi-nu_Whi-ra_Haki".split("_"),monthsRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,monthsStrictRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,monthsShortRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,monthsShortStrictRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,2}/i,weekdays:"R\u0101tapu_Mane_T\u016brei_Wenerei_T\u0101ite_Paraire_H\u0101tarei".split("_"),weekdaysShort:"Ta_Ma_T\u016b_We_T\u0101i_Pa_H\u0101".split("_"),weekdaysMin:"Ta_Ma_T\u016b_We_T\u0101i_Pa_H\u0101".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [i] HH:mm",LLLL:"dddd, D MMMM YYYY [i] HH:mm"},calendar:{sameDay:"[i teie mahana, i] LT",nextDay:"[apopo i] LT",nextWeek:"dddd [i] LT",lastDay:"[inanahi i] LT",lastWeek:"dddd [whakamutunga i] LT",sameElse:"L"},relativeTime:{future:"i roto i %s",past:"%s i mua",s:"te h\u0113kona ruarua",ss:"%d h\u0113kona",m:"he meneti",mm:"%d meneti",h:"te haora",hh:"%d haora",d:"he ra",dd:"%d ra",M:"he marama",MM:"%d marama",y:"he tau",yy:"%d tau"},dayOfMonthOrdinalParse:/\d{1,2}\xba/,ordinal:"%d\xba",week:{dow:1,doy:4}}),e.defineLocale("mk",{months:"\u0458\u0430\u043d\u0443\u0430\u0440\u0438_\u0444\u0435\u0432\u0440\u0443\u0430\u0440\u0438_\u043c\u0430\u0440\u0442_\u0430\u043f\u0440\u0438\u043b_\u043c\u0430\u0458_\u0458\u0443\u043d\u0438_\u0458\u0443\u043b\u0438_\u0430\u0432\u0433\u0443\u0441\u0442_\u0441\u0435\u043f\u0442\u0435\u043c\u0432\u0440\u0438_\u043e\u043a\u0442\u043e\u043c\u0432\u0440\u0438_\u043d\u043e\u0435\u043c\u0432\u0440\u0438_\u0434\u0435\u043a\u0435\u043c\u0432\u0440\u0438".split("_"),monthsShort:"\u0458\u0430\u043d_\u0444\u0435\u0432_\u043c\u0430\u0440_\u0430\u043f\u0440_\u043c\u0430\u0458_\u0458\u0443\u043d_\u0458\u0443\u043b_\u0430\u0432\u0433_\u0441\u0435\u043f_\u043e\u043a\u0442_\u043d\u043e\u0435_\u0434\u0435\u043a".split("_"),weekdays:"\u043d\u0435\u0434\u0435\u043b\u0430_\u043f\u043e\u043d\u0435\u0434\u0435\u043b\u043d\u0438\u043a_\u0432\u0442\u043e\u0440\u043d\u0438\u043a_\u0441\u0440\u0435\u0434\u0430_\u0447\u0435\u0442\u0432\u0440\u0442\u043e\u043a_\u043f\u0435\u0442\u043e\u043a_\u0441\u0430\u0431\u043e\u0442\u0430".split("_"),weekdaysShort:"\u043d\u0435\u0434_\u043f\u043e\u043d_\u0432\u0442\u043e_\u0441\u0440\u0435_\u0447\u0435\u0442_\u043f\u0435\u0442_\u0441\u0430\u0431".split("_"),weekdaysMin:"\u043de_\u043fo_\u0432\u0442_\u0441\u0440_\u0447\u0435_\u043f\u0435_\u0441a".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"D.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY H:mm",LLLL:"dddd, D MMMM YYYY H:mm"},calendar:{sameDay:"[\u0414\u0435\u043d\u0435\u0441 \u0432\u043e] LT",nextDay:"[\u0423\u0442\u0440\u0435 \u0432\u043e] LT",nextWeek:"[\u0412\u043e] dddd [\u0432\u043e] LT",lastDay:"[\u0412\u0447\u0435\u0440\u0430 \u0432\u043e] LT",lastWeek:function(){switch(this.day()){case 0:case 3:case 6:return"[\u0418\u0437\u043c\u0438\u043d\u0430\u0442\u0430\u0442\u0430] dddd [\u0432\u043e] LT";case 1:case 2:case 4:case 5:return"[\u0418\u0437\u043c\u0438\u043d\u0430\u0442\u0438\u043e\u0442] dddd [\u0432\u043e] LT"}},sameElse:"L"},relativeTime:{future:"\u043f\u043e\u0441\u043b\u0435 %s",past:"\u043f\u0440\u0435\u0434 %s",s:"\u043d\u0435\u043a\u043e\u043b\u043a\u0443 \u0441\u0435\u043a\u0443\u043d\u0434\u0438",ss:"%d \u0441\u0435\u043a\u0443\u043d\u0434\u0438",m:"\u043c\u0438\u043d\u0443\u0442\u0430",mm:"%d \u043c\u0438\u043d\u0443\u0442\u0438",h:"\u0447\u0430\u0441",hh:"%d \u0447\u0430\u0441\u0430",d:"\u0434\u0435\u043d",dd:"%d \u0434\u0435\u043d\u0430",M:"\u043c\u0435\u0441\u0435\u0446",MM:"%d \u043c\u0435\u0441\u0435\u0446\u0438",y:"\u0433\u043e\u0434\u0438\u043d\u0430",yy:"%d \u0433\u043e\u0434\u0438\u043d\u0438"},dayOfMonthOrdinalParse:/\d{1,2}-(\u0435\u0432|\u0435\u043d|\u0442\u0438|\u0432\u0438|\u0440\u0438|\u043c\u0438)/,ordinal:function(e){var a=e%10,t=e%100;return 0===e?e+"-\u0435\u0432":0===t?e+"-\u0435\u043d":t>10&&t<20?e+"-\u0442\u0438":1===a?e+"-\u0432\u0438":2===a?e+"-\u0440\u0438":7===a||8===a?e+"-\u043c\u0438":e+"-\u0442\u0438"},week:{dow:1,doy:7}}),e.defineLocale("ml",{months:"\u0d1c\u0d28\u0d41\u0d35\u0d30\u0d3f_\u0d2b\u0d46\u0d2c\u0d4d\u0d30\u0d41\u0d35\u0d30\u0d3f_\u0d2e\u0d3e\u0d7c\u0d1a\u0d4d\u0d1a\u0d4d_\u0d0f\u0d2a\u0d4d\u0d30\u0d3f\u0d7d_\u0d2e\u0d47\u0d2f\u0d4d_\u0d1c\u0d42\u0d7a_\u0d1c\u0d42\u0d32\u0d48_\u0d13\u0d17\u0d38\u0d4d\u0d31\u0d4d\u0d31\u0d4d_\u0d38\u0d46\u0d2a\u0d4d\u0d31\u0d4d\u0d31\u0d02\u0d2c\u0d7c_\u0d12\u0d15\u0d4d\u0d1f\u0d4b\u0d2c\u0d7c_\u0d28\u0d35\u0d02\u0d2c\u0d7c_\u0d21\u0d3f\u0d38\u0d02\u0d2c\u0d7c".split("_"),monthsShort:"\u0d1c\u0d28\u0d41._\u0d2b\u0d46\u0d2c\u0d4d\u0d30\u0d41._\u0d2e\u0d3e\u0d7c._\u0d0f\u0d2a\u0d4d\u0d30\u0d3f._\u0d2e\u0d47\u0d2f\u0d4d_\u0d1c\u0d42\u0d7a_\u0d1c\u0d42\u0d32\u0d48._\u0d13\u0d17._\u0d38\u0d46\u0d2a\u0d4d\u0d31\u0d4d\u0d31._\u0d12\u0d15\u0d4d\u0d1f\u0d4b._\u0d28\u0d35\u0d02._\u0d21\u0d3f\u0d38\u0d02.".split("_"),monthsParseExact:!0,weekdays:"\u0d1e\u0d3e\u0d2f\u0d31\u0d3e\u0d34\u0d4d\u0d1a_\u0d24\u0d3f\u0d19\u0d4d\u0d15\u0d33\u0d3e\u0d34\u0d4d\u0d1a_\u0d1a\u0d4a\u0d35\u0d4d\u0d35\u0d3e\u0d34\u0d4d\u0d1a_\u0d2c\u0d41\u0d27\u0d28\u0d3e\u0d34\u0d4d\u0d1a_\u0d35\u0d4d\u0d2f\u0d3e\u0d34\u0d3e\u0d34\u0d4d\u0d1a_\u0d35\u0d46\u0d33\u0d4d\u0d33\u0d3f\u0d2f\u0d3e\u0d34\u0d4d\u0d1a_\u0d36\u0d28\u0d3f\u0d2f\u0d3e\u0d34\u0d4d\u0d1a".split("_"),weekdaysShort:"\u0d1e\u0d3e\u0d2f\u0d7c_\u0d24\u0d3f\u0d19\u0d4d\u0d15\u0d7e_\u0d1a\u0d4a\u0d35\u0d4d\u0d35_\u0d2c\u0d41\u0d27\u0d7b_\u0d35\u0d4d\u0d2f\u0d3e\u0d34\u0d02_\u0d35\u0d46\u0d33\u0d4d\u0d33\u0d3f_\u0d36\u0d28\u0d3f".split("_"),weekdaysMin:"\u0d1e\u0d3e_\u0d24\u0d3f_\u0d1a\u0d4a_\u0d2c\u0d41_\u0d35\u0d4d\u0d2f\u0d3e_\u0d35\u0d46_\u0d36".split("_"),longDateFormat:{LT:"A h:mm -\u0d28\u0d41",LTS:"A h:mm:ss -\u0d28\u0d41",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm -\u0d28\u0d41",LLLL:"dddd, D MMMM YYYY, A h:mm -\u0d28\u0d41"},calendar:{sameDay:"[\u0d07\u0d28\u0d4d\u0d28\u0d4d] LT",nextDay:"[\u0d28\u0d3e\u0d33\u0d46] LT",nextWeek:"dddd, LT",lastDay:"[\u0d07\u0d28\u0d4d\u0d28\u0d32\u0d46] LT",lastWeek:"[\u0d15\u0d34\u0d3f\u0d1e\u0d4d\u0d1e] dddd, LT",sameElse:"L"},relativeTime:{future:"%s \u0d15\u0d34\u0d3f\u0d1e\u0d4d\u0d1e\u0d4d",past:"%s \u0d2e\u0d41\u0d7b\u0d2a\u0d4d",s:"\u0d05\u0d7d\u0d2a \u0d28\u0d3f\u0d2e\u0d3f\u0d37\u0d19\u0d4d\u0d19\u0d7e",ss:"%d \u0d38\u0d46\u0d15\u0d4d\u0d15\u0d7b\u0d21\u0d4d",m:"\u0d12\u0d30\u0d41 \u0d2e\u0d3f\u0d28\u0d3f\u0d31\u0d4d\u0d31\u0d4d",mm:"%d \u0d2e\u0d3f\u0d28\u0d3f\u0d31\u0d4d\u0d31\u0d4d",h:"\u0d12\u0d30\u0d41 \u0d2e\u0d23\u0d3f\u0d15\u0d4d\u0d15\u0d42\u0d7c",hh:"%d \u0d2e\u0d23\u0d3f\u0d15\u0d4d\u0d15\u0d42\u0d7c",d:"\u0d12\u0d30\u0d41 \u0d26\u0d3f\u0d35\u0d38\u0d02",dd:"%d \u0d26\u0d3f\u0d35\u0d38\u0d02",M:"\u0d12\u0d30\u0d41 \u0d2e\u0d3e\u0d38\u0d02",MM:"%d \u0d2e\u0d3e\u0d38\u0d02",y:"\u0d12\u0d30\u0d41 \u0d35\u0d7c\u0d37\u0d02",yy:"%d \u0d35\u0d7c\u0d37\u0d02"},meridiemParse:/\u0d30\u0d3e\u0d24\u0d4d\u0d30\u0d3f|\u0d30\u0d3e\u0d35\u0d3f\u0d32\u0d46|\u0d09\u0d1a\u0d4d\u0d1a \u0d15\u0d34\u0d3f\u0d1e\u0d4d\u0d1e\u0d4d|\u0d35\u0d48\u0d15\u0d41\u0d28\u0d4d\u0d28\u0d47\u0d30\u0d02|\u0d30\u0d3e\u0d24\u0d4d\u0d30\u0d3f/i,meridiemHour:function(e,a){return 12===e&&(e=0),"\u0d30\u0d3e\u0d24\u0d4d\u0d30\u0d3f"===a&&e>=4||"\u0d09\u0d1a\u0d4d\u0d1a \u0d15\u0d34\u0d3f\u0d1e\u0d4d\u0d1e\u0d4d"===a||"\u0d35\u0d48\u0d15\u0d41\u0d28\u0d4d\u0d28\u0d47\u0d30\u0d02"===a?e+12:e},meridiem:function(e,a,t){return e<4?"\u0d30\u0d3e\u0d24\u0d4d\u0d30\u0d3f":e<12?"\u0d30\u0d3e\u0d35\u0d3f\u0d32\u0d46":e<17?"\u0d09\u0d1a\u0d4d\u0d1a \u0d15\u0d34\u0d3f\u0d1e\u0d4d\u0d1e\u0d4d":e<20?"\u0d35\u0d48\u0d15\u0d41\u0d28\u0d4d\u0d28\u0d47\u0d30\u0d02":"\u0d30\u0d3e\u0d24\u0d4d\u0d30\u0d3f"}});var wn={1:"\u0967",2:"\u0968",3:"\u0969",4:"\u096a",5:"\u096b",6:"\u096c",7:"\u096d",8:"\u096e",9:"\u096f",0:"\u0966"},vn={"\u0967":"1","\u0968":"2","\u0969":"3","\u096a":"4","\u096b":"5","\u096c":"6","\u096d":"7","\u096e":"8","\u096f":"9","\u0966":"0"};e.defineLocale("mr",{months:"\u091c\u093e\u0928\u0947\u0935\u093e\u0930\u0940_\u092b\u0947\u092c\u094d\u0930\u0941\u0935\u093e\u0930\u0940_\u092e\u093e\u0930\u094d\u091a_\u090f\u092a\u094d\u0930\u093f\u0932_\u092e\u0947_\u091c\u0942\u0928_\u091c\u0941\u0932\u0948_\u0911\u0917\u0938\u094d\u091f_\u0938\u092a\u094d\u091f\u0947\u0902\u092c\u0930_\u0911\u0915\u094d\u091f\u094b\u092c\u0930_\u0928\u094b\u0935\u094d\u0939\u0947\u0902\u092c\u0930_\u0921\u093f\u0938\u0947\u0902\u092c\u0930".split("_"),monthsShort:"\u091c\u093e\u0928\u0947._\u092b\u0947\u092c\u094d\u0930\u0941._\u092e\u093e\u0930\u094d\u091a._\u090f\u092a\u094d\u0930\u093f._\u092e\u0947._\u091c\u0942\u0928._\u091c\u0941\u0932\u0948._\u0911\u0917._\u0938\u092a\u094d\u091f\u0947\u0902._\u0911\u0915\u094d\u091f\u094b._\u0928\u094b\u0935\u094d\u0939\u0947\u0902._\u0921\u093f\u0938\u0947\u0902.".split("_"),monthsParseExact:!0,weekdays:"\u0930\u0935\u093f\u0935\u093e\u0930_\u0938\u094b\u092e\u0935\u093e\u0930_\u092e\u0902\u0917\u0933\u0935\u093e\u0930_\u092c\u0941\u0927\u0935\u093e\u0930_\u0917\u0941\u0930\u0942\u0935\u093e\u0930_\u0936\u0941\u0915\u094d\u0930\u0935\u093e\u0930_\u0936\u0928\u093f\u0935\u093e\u0930".split("_"),weekdaysShort:"\u0930\u0935\u093f_\u0938\u094b\u092e_\u092e\u0902\u0917\u0933_\u092c\u0941\u0927_\u0917\u0941\u0930\u0942_\u0936\u0941\u0915\u094d\u0930_\u0936\u0928\u093f".split("_"),weekdaysMin:"\u0930_\u0938\u094b_\u092e\u0902_\u092c\u0941_\u0917\u0941_\u0936\u0941_\u0936".split("_"),longDateFormat:{LT:"A h:mm \u0935\u093e\u091c\u0924\u093e",LTS:"A h:mm:ss \u0935\u093e\u091c\u0924\u093e",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm \u0935\u093e\u091c\u0924\u093e",LLLL:"dddd, D MMMM YYYY, A h:mm \u0935\u093e\u091c\u0924\u093e"},calendar:{sameDay:"[\u0906\u091c] LT",nextDay:"[\u0909\u0926\u094d\u092f\u093e] LT",nextWeek:"dddd, LT",lastDay:"[\u0915\u093e\u0932] LT",lastWeek:"[\u092e\u093e\u0917\u0940\u0932] dddd, LT",sameElse:"L"},relativeTime:{future:"%s\u092e\u0927\u094d\u092f\u0947",past:"%s\u092a\u0942\u0930\u094d\u0935\u0940",s:Ta,ss:Ta,m:Ta,mm:Ta,h:Ta,hh:Ta,d:Ta,dd:Ta,M:Ta,MM:Ta,y:Ta,yy:Ta},preparse:function(e){return e.replace(/[\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f\u0966]/g,function(e){return vn[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return wn[e]})},meridiemParse:/\u0930\u093e\u0924\u094d\u0930\u0940|\u0938\u0915\u093e\u0933\u0940|\u0926\u0941\u092a\u093e\u0930\u0940|\u0938\u093e\u092f\u0902\u0915\u093e\u0933\u0940/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u0930\u093e\u0924\u094d\u0930\u0940"===a?e<4?e:e+12:"\u0938\u0915\u093e\u0933\u0940"===a?e:"\u0926\u0941\u092a\u093e\u0930\u0940"===a?e>=10?e:e+12:"\u0938\u093e\u092f\u0902\u0915\u093e\u0933\u0940"===a?e+12:void 0},meridiem:function(e,a,t){return e<4?"\u0930\u093e\u0924\u094d\u0930\u0940":e<10?"\u0938\u0915\u093e\u0933\u0940":e<17?"\u0926\u0941\u092a\u093e\u0930\u0940":e<20?"\u0938\u093e\u092f\u0902\u0915\u093e\u0933\u0940":"\u0930\u093e\u0924\u094d\u0930\u0940"},week:{dow:0,doy:6}}),e.defineLocale("ms-my",{months:"Januari_Februari_Mac_April_Mei_Jun_Julai_Ogos_September_Oktober_November_Disember".split("_"),monthsShort:"Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ogs_Sep_Okt_Nov_Dis".split("_"),weekdays:"Ahad_Isnin_Selasa_Rabu_Khamis_Jumaat_Sabtu".split("_"),weekdaysShort:"Ahd_Isn_Sel_Rab_Kha_Jum_Sab".split("_"),weekdaysMin:"Ah_Is_Sl_Rb_Km_Jm_Sb".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] HH.mm",LLLL:"dddd, D MMMM YYYY [pukul] HH.mm"},meridiemParse:/pagi|tengahari|petang|malam/,meridiemHour:function(e,a){return 12===e&&(e=0),"pagi"===a?e:"tengahari"===a?e>=11?e:e+12:"petang"===a||"malam"===a?e+12:void 0},meridiem:function(e,a,t){return e<11?"pagi":e<15?"tengahari":e<19?"petang":"malam"},calendar:{sameDay:"[Hari ini pukul] LT",nextDay:"[Esok pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kelmarin pukul] LT",lastWeek:"dddd [lepas pukul] LT",sameElse:"L"},relativeTime:{future:"dalam %s",past:"%s yang lepas",s:"beberapa saat",ss:"%d saat",m:"seminit",mm:"%d minit",h:"sejam",hh:"%d jam",d:"sehari",dd:"%d hari",M:"sebulan",MM:"%d bulan",y:"setahun",yy:"%d tahun"},week:{dow:1,doy:7}}),e.defineLocale("ms",{months:"Januari_Februari_Mac_April_Mei_Jun_Julai_Ogos_September_Oktober_November_Disember".split("_"),monthsShort:"Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ogs_Sep_Okt_Nov_Dis".split("_"),weekdays:"Ahad_Isnin_Selasa_Rabu_Khamis_Jumaat_Sabtu".split("_"),weekdaysShort:"Ahd_Isn_Sel_Rab_Kha_Jum_Sab".split("_"),weekdaysMin:"Ah_Is_Sl_Rb_Km_Jm_Sb".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] HH.mm",LLLL:"dddd, D MMMM YYYY [pukul] HH.mm"},meridiemParse:/pagi|tengahari|petang|malam/,meridiemHour:function(e,a){return 12===e&&(e=0),"pagi"===a?e:"tengahari"===a?e>=11?e:e+12:"petang"===a||"malam"===a?e+12:void 0},meridiem:function(e,a,t){return e<11?"pagi":e<15?"tengahari":e<19?"petang":"malam"},calendar:{sameDay:"[Hari ini pukul] LT",nextDay:"[Esok pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kelmarin pukul] LT",lastWeek:"dddd [lepas pukul] LT",sameElse:"L"},relativeTime:{future:"dalam %s",past:"%s yang lepas",s:"beberapa saat",ss:"%d saat",m:"seminit",mm:"%d minit",h:"sejam",hh:"%d jam",d:"sehari",dd:"%d hari",M:"sebulan",MM:"%d bulan",y:"setahun",yy:"%d tahun"},week:{dow:1,doy:7}}),e.defineLocale("mt",{months:"Jannar_Frar_Marzu_April_Mejju_\u0120unju_Lulju_Awwissu_Settembru_Ottubru_Novembru_Di\u010bembru".split("_"),monthsShort:"Jan_Fra_Mar_Apr_Mej_\u0120un_Lul_Aww_Set_Ott_Nov_Di\u010b".split("_"),weekdays:"Il-\u0126add_It-Tnejn_It-Tlieta_L-Erbg\u0127a_Il-\u0126amis_Il-\u0120img\u0127a_Is-Sibt".split("_"),weekdaysShort:"\u0126ad_Tne_Tli_Erb_\u0126am_\u0120im_Sib".split("_"),weekdaysMin:"\u0126a_Tn_Tl_Er_\u0126a_\u0120i_Si".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Illum fil-]LT",nextDay:"[G\u0127ada fil-]LT",nextWeek:"dddd [fil-]LT",lastDay:"[Il-biera\u0127 fil-]LT",lastWeek:"dddd [li g\u0127adda] [fil-]LT",sameElse:"L"},relativeTime:{future:"f\u2019 %s",past:"%s ilu",s:"ftit sekondi",ss:"%d sekondi",m:"minuta",mm:"%d minuti",h:"sieg\u0127a",hh:"%d sieg\u0127at",d:"\u0121urnata",dd:"%d \u0121ranet",M:"xahar",MM:"%d xhur",y:"sena",yy:"%d sni"},dayOfMonthOrdinalParse:/\d{1,2}\xba/,ordinal:"%d\xba",week:{dow:1,doy:4}});var Sn={1:"\u1041",2:"\u1042",3:"\u1043",4:"\u1044",5:"\u1045",6:"\u1046",7:"\u1047",8:"\u1048",9:"\u1049",0:"\u1040"},Hn={"\u1041":"1","\u1042":"2","\u1043":"3","\u1044":"4","\u1045":"5","\u1046":"6","\u1047":"7","\u1048":"8","\u1049":"9","\u1040":"0"};e.defineLocale("my",{months:"\u1007\u1014\u103a\u1014\u101d\u102b\u101b\u102e_\u1016\u1031\u1016\u1031\u102c\u103a\u101d\u102b\u101b\u102e_\u1019\u1010\u103a_\u1027\u1015\u103c\u102e_\u1019\u1031_\u1007\u103d\u1014\u103a_\u1007\u1030\u101c\u102d\u102f\u1004\u103a_\u101e\u103c\u1002\u102f\u1010\u103a_\u1005\u1000\u103a\u1010\u1004\u103a\u1018\u102c_\u1021\u1031\u102c\u1000\u103a\u1010\u102d\u102f\u1018\u102c_\u1014\u102d\u102f\u101d\u1004\u103a\u1018\u102c_\u1012\u102e\u1007\u1004\u103a\u1018\u102c".split("_"),monthsShort:"\u1007\u1014\u103a_\u1016\u1031_\u1019\u1010\u103a_\u1015\u103c\u102e_\u1019\u1031_\u1007\u103d\u1014\u103a_\u101c\u102d\u102f\u1004\u103a_\u101e\u103c_\u1005\u1000\u103a_\u1021\u1031\u102c\u1000\u103a_\u1014\u102d\u102f_\u1012\u102e".split("_"),weekdays:"\u1010\u1014\u1004\u103a\u1039\u1002\u1014\u103d\u1031_\u1010\u1014\u1004\u103a\u1039\u101c\u102c_\u1021\u1004\u103a\u1039\u1002\u102b_\u1017\u102f\u1012\u1039\u1013\u101f\u1030\u1038_\u1000\u103c\u102c\u101e\u1015\u1010\u1031\u1038_\u101e\u1031\u102c\u1000\u103c\u102c_\u1005\u1014\u1031".split("_"),weekdaysShort:"\u1014\u103d\u1031_\u101c\u102c_\u1002\u102b_\u101f\u1030\u1038_\u1000\u103c\u102c_\u101e\u1031\u102c_\u1014\u1031".split("_"),weekdaysMin:"\u1014\u103d\u1031_\u101c\u102c_\u1002\u102b_\u101f\u1030\u1038_\u1000\u103c\u102c_\u101e\u1031\u102c_\u1014\u1031".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[\u101a\u1014\u1031.] LT [\u1019\u103e\u102c]",nextDay:"[\u1019\u1014\u1000\u103a\u1016\u103c\u1014\u103a] LT [\u1019\u103e\u102c]",nextWeek:"dddd LT [\u1019\u103e\u102c]",lastDay:"[\u1019\u1014\u1031.\u1000] LT [\u1019\u103e\u102c]",lastWeek:"[\u1015\u103c\u102e\u1038\u1001\u1032\u1037\u101e\u1031\u102c] dddd LT [\u1019\u103e\u102c]",sameElse:"L"},relativeTime:{future:"\u101c\u102c\u1019\u100a\u103a\u1037 %s \u1019\u103e\u102c",past:"\u101c\u103d\u1014\u103a\u1001\u1032\u1037\u101e\u1031\u102c %s \u1000",s:"\u1005\u1000\u1039\u1000\u1014\u103a.\u1021\u1014\u100a\u103a\u1038\u1004\u101a\u103a",ss:"%d \u1005\u1000\u1039\u1000\u1014\u1037\u103a",m:"\u1010\u1005\u103a\u1019\u102d\u1014\u1005\u103a",mm:"%d \u1019\u102d\u1014\u1005\u103a",h:"\u1010\u1005\u103a\u1014\u102c\u101b\u102e",hh:"%d \u1014\u102c\u101b\u102e",d:"\u1010\u1005\u103a\u101b\u1000\u103a",dd:"%d \u101b\u1000\u103a",M:"\u1010\u1005\u103a\u101c",MM:"%d \u101c",y:"\u1010\u1005\u103a\u1014\u103e\u1005\u103a",yy:"%d \u1014\u103e\u1005\u103a"},preparse:function(e){return e.replace(/[\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u1040]/g,function(e){return Hn[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return Sn[e]})},week:{dow:1,doy:4}}),e.defineLocale("nb",{months:"januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan._feb._mars_april_mai_juni_juli_aug._sep._okt._nov._des.".split("_"),monthsParseExact:!0,weekdays:"s\xf8ndag_mandag_tirsdag_onsdag_torsdag_fredag_l\xf8rdag".split("_"),weekdaysShort:"s\xf8._ma._ti._on._to._fr._l\xf8.".split("_"),weekdaysMin:"s\xf8_ma_ti_on_to_fr_l\xf8".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] HH:mm",LLLL:"dddd D. MMMM YYYY [kl.] HH:mm"},calendar:{sameDay:"[i dag kl.] LT",nextDay:"[i morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[i g\xe5r kl.] LT",lastWeek:"[forrige] dddd [kl.] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s siden",s:"noen sekunder",ss:"%d sekunder",m:"ett minutt",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dager",M:"en m\xe5ned",MM:"%d m\xe5neder",y:"ett \xe5r",yy:"%d \xe5r"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}});var bn={1:"\u0967",2:"\u0968",3:"\u0969",4:"\u096a",5:"\u096b",6:"\u096c",7:"\u096d",8:"\u096e",9:"\u096f",0:"\u0966"},jn={"\u0967":"1","\u0968":"2","\u0969":"3","\u096a":"4","\u096b":"5","\u096c":"6","\u096d":"7","\u096e":"8","\u096f":"9","\u0966":"0"};e.defineLocale("ne",{months:"\u091c\u0928\u0935\u0930\u0940_\u092b\u0947\u092c\u094d\u0930\u0941\u0935\u0930\u0940_\u092e\u093e\u0930\u094d\u091a_\u0905\u092a\u094d\u0930\u093f\u0932_\u092e\u0908_\u091c\u0941\u0928_\u091c\u0941\u0932\u093e\u0908_\u0905\u0917\u0937\u094d\u091f_\u0938\u0947\u092a\u094d\u091f\u0947\u092e\u094d\u092c\u0930_\u0905\u0915\u094d\u091f\u094b\u092c\u0930_\u0928\u094b\u092d\u0947\u092e\u094d\u092c\u0930_\u0921\u093f\u0938\u0947\u092e\u094d\u092c\u0930".split("_"),monthsShort:"\u091c\u0928._\u092b\u0947\u092c\u094d\u0930\u0941._\u092e\u093e\u0930\u094d\u091a_\u0905\u092a\u094d\u0930\u093f._\u092e\u0908_\u091c\u0941\u0928_\u091c\u0941\u0932\u093e\u0908._\u0905\u0917._\u0938\u0947\u092a\u094d\u091f._\u0905\u0915\u094d\u091f\u094b._\u0928\u094b\u092d\u0947._\u0921\u093f\u0938\u0947.".split("_"),monthsParseExact:!0,weekdays:"\u0906\u0907\u0924\u092c\u093e\u0930_\u0938\u094b\u092e\u092c\u093e\u0930_\u092e\u0919\u094d\u0917\u0932\u092c\u093e\u0930_\u092c\u0941\u0927\u092c\u093e\u0930_\u092c\u093f\u0939\u093f\u092c\u093e\u0930_\u0936\u0941\u0915\u094d\u0930\u092c\u093e\u0930_\u0936\u0928\u093f\u092c\u093e\u0930".split("_"),weekdaysShort:"\u0906\u0907\u0924._\u0938\u094b\u092e._\u092e\u0919\u094d\u0917\u0932._\u092c\u0941\u0927._\u092c\u093f\u0939\u093f._\u0936\u0941\u0915\u094d\u0930._\u0936\u0928\u093f.".split("_"),weekdaysMin:"\u0906._\u0938\u094b._\u092e\u0902._\u092c\u0941._\u092c\u093f._\u0936\u0941._\u0936.".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"A\u0915\u094b h:mm \u092c\u091c\u0947",LTS:"A\u0915\u094b h:mm:ss \u092c\u091c\u0947",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A\u0915\u094b h:mm \u092c\u091c\u0947",LLLL:"dddd, D MMMM YYYY, A\u0915\u094b h:mm \u092c\u091c\u0947"},preparse:function(e){return e.replace(/[\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f\u0966]/g,function(e){return jn[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return bn[e]})},meridiemParse:/\u0930\u093e\u0924\u093f|\u092c\u093f\u0939\u093e\u0928|\u0926\u093f\u0909\u0901\u0938\u094b|\u0938\u093e\u0901\u091d/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u0930\u093e\u0924\u093f"===a?e<4?e:e+12:"\u092c\u093f\u0939\u093e\u0928"===a?e:"\u0926\u093f\u0909\u0901\u0938\u094b"===a?e>=10?e:e+12:"\u0938\u093e\u0901\u091d"===a?e+12:void 0},meridiem:function(e,a,t){return e<3?"\u0930\u093e\u0924\u093f":e<12?"\u092c\u093f\u0939\u093e\u0928":e<16?"\u0926\u093f\u0909\u0901\u0938\u094b":e<20?"\u0938\u093e\u0901\u091d":"\u0930\u093e\u0924\u093f"},calendar:{sameDay:"[\u0906\u091c] LT",nextDay:"[\u092d\u094b\u0932\u093f] LT",nextWeek:"[\u0906\u0909\u0901\u0926\u094b] dddd[,] LT",lastDay:"[\u0939\u093f\u091c\u094b] LT",lastWeek:"[\u0917\u090f\u0915\u094b] dddd[,] LT",sameElse:"L"},relativeTime:{future:"%s\u092e\u093e",past:"%s \u0905\u0917\u093e\u0921\u093f",s:"\u0915\u0947\u0939\u0940 \u0915\u094d\u0937\u0923",ss:"%d \u0938\u0947\u0915\u0947\u0923\u094d\u0921",m:"\u090f\u0915 \u092e\u093f\u0928\u0947\u091f",mm:"%d \u092e\u093f\u0928\u0947\u091f",h:"\u090f\u0915 \u0918\u0923\u094d\u091f\u093e",hh:"%d \u0918\u0923\u094d\u091f\u093e",d:"\u090f\u0915 \u0926\u093f\u0928",dd:"%d \u0926\u093f\u0928",M:"\u090f\u0915 \u092e\u0939\u093f\u0928\u093e",MM:"%d \u092e\u0939\u093f\u0928\u093e",y:"\u090f\u0915 \u092c\u0930\u094d\u0937",yy:"%d \u092c\u0930\u094d\u0937"},week:{dow:0,doy:6}});var xn="jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.".split("_"),Pn="jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec".split("_"),On=[/^jan/i,/^feb/i,/^maart|mrt.?$/i,/^apr/i,/^mei$/i,/^jun[i.]?$/i,/^jul[i.]?$/i,/^aug/i,/^sep/i,/^okt/i,/^nov/i,/^dec/i],Wn=/^(januari|februari|maart|april|mei|april|ju[nl]i|augustus|september|oktober|november|december|jan\.?|feb\.?|mrt\.?|apr\.?|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i;e.defineLocale("nl-be",{months:"januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december".split("_"),monthsShort:function(e,a){return e?/-MMM-/.test(a)?Pn[e.month()]:xn[e.month()]:xn},monthsRegex:Wn,monthsShortRegex:Wn,monthsStrictRegex:/^(januari|februari|maart|mei|ju[nl]i|april|augustus|september|oktober|november|december)/i,monthsShortStrictRegex:/^(jan\.?|feb\.?|mrt\.?|apr\.?|mei|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i,monthsParse:On,longMonthsParse:On,shortMonthsParse:On,weekdays:"zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag".split("_"),weekdaysShort:"zo._ma._di._wo._do._vr._za.".split("_"),weekdaysMin:"zo_ma_di_wo_do_vr_za".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[vandaag om] LT",nextDay:"[morgen om] LT",nextWeek:"dddd [om] LT",lastDay:"[gisteren om] LT",lastWeek:"[afgelopen] dddd [om] LT",sameElse:"L"},relativeTime:{future:"over %s",past:"%s geleden",s:"een paar seconden",ss:"%d seconden",m:"\xe9\xe9n minuut",mm:"%d minuten",h:"\xe9\xe9n uur",hh:"%d uur",d:"\xe9\xe9n dag",dd:"%d dagen",M:"\xe9\xe9n maand",MM:"%d maanden",y:"\xe9\xe9n jaar",yy:"%d jaar"},dayOfMonthOrdinalParse:/\d{1,2}(ste|de)/,ordinal:function(e){return e+(1===e||8===e||e>=20?"ste":"de")},week:{dow:1,doy:4}});var En="jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.".split("_"),An="jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec".split("_"),Fn=[/^jan/i,/^feb/i,/^maart|mrt.?$/i,/^apr/i,/^mei$/i,/^jun[i.]?$/i,/^jul[i.]?$/i,/^aug/i,/^sep/i,/^okt/i,/^nov/i,/^dec/i],zn=/^(januari|februari|maart|april|mei|april|ju[nl]i|augustus|september|oktober|november|december|jan\.?|feb\.?|mrt\.?|apr\.?|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i;e.defineLocale("nl",{months:"januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december".split("_"),monthsShort:function(e,a){return e?/-MMM-/.test(a)?An[e.month()]:En[e.month()]:En},monthsRegex:zn,monthsShortRegex:zn,monthsStrictRegex:/^(januari|februari|maart|mei|ju[nl]i|april|augustus|september|oktober|november|december)/i,monthsShortStrictRegex:/^(jan\.?|feb\.?|mrt\.?|apr\.?|mei|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i,monthsParse:Fn,longMonthsParse:Fn,shortMonthsParse:Fn,weekdays:"zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag".split("_"),weekdaysShort:"zo._ma._di._wo._do._vr._za.".split("_"),weekdaysMin:"zo_ma_di_wo_do_vr_za".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[vandaag om] LT",nextDay:"[morgen om] LT",nextWeek:"dddd [om] LT",lastDay:"[gisteren om] LT",lastWeek:"[afgelopen] dddd [om] LT",sameElse:"L"},relativeTime:{future:"over %s",past:"%s geleden",s:"een paar seconden",ss:"%d seconden",m:"\xe9\xe9n minuut",mm:"%d minuten",h:"\xe9\xe9n uur",hh:"%d uur",d:"\xe9\xe9n dag",dd:"%d dagen",M:"\xe9\xe9n maand",MM:"%d maanden",y:"\xe9\xe9n jaar",yy:"%d jaar"},dayOfMonthOrdinalParse:/\d{1,2}(ste|de)/,ordinal:function(e){return e+(1===e||8===e||e>=20?"ste":"de")},week:{dow:1,doy:4}}),e.defineLocale("nn",{months:"januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_"),weekdays:"sundag_m\xe5ndag_tysdag_onsdag_torsdag_fredag_laurdag".split("_"),weekdaysShort:"sun_m\xe5n_tys_ons_tor_fre_lau".split("_"),weekdaysMin:"su_m\xe5_ty_on_to_fr_l\xf8".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] H:mm",LLLL:"dddd D. MMMM YYYY [kl.] HH:mm"},calendar:{sameDay:"[I dag klokka] LT",nextDay:"[I morgon klokka] LT",nextWeek:"dddd [klokka] LT",lastDay:"[I g\xe5r klokka] LT",lastWeek:"[F\xf8reg\xe5ande] dddd [klokka] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s sidan",s:"nokre sekund",ss:"%d sekund",m:"eit minutt",mm:"%d minutt",h:"ein time",hh:"%d timar",d:"ein dag",dd:"%d dagar",M:"ein m\xe5nad",MM:"%d m\xe5nader",y:"eit \xe5r",yy:"%d \xe5r"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}});var Jn={1:"\u0a67",2:"\u0a68",3:"\u0a69",4:"\u0a6a",5:"\u0a6b",6:"\u0a6c",7:"\u0a6d",8:"\u0a6e",9:"\u0a6f",0:"\u0a66"},Nn={"\u0a67":"1","\u0a68":"2","\u0a69":"3","\u0a6a":"4","\u0a6b":"5","\u0a6c":"6","\u0a6d":"7","\u0a6e":"8","\u0a6f":"9","\u0a66":"0"};e.defineLocale("pa-in",{months:"\u0a1c\u0a28\u0a35\u0a30\u0a40_\u0a2b\u0a3c\u0a30\u0a35\u0a30\u0a40_\u0a2e\u0a3e\u0a30\u0a1a_\u0a05\u0a2a\u0a4d\u0a30\u0a48\u0a32_\u0a2e\u0a08_\u0a1c\u0a42\u0a28_\u0a1c\u0a41\u0a32\u0a3e\u0a08_\u0a05\u0a17\u0a38\u0a24_\u0a38\u0a24\u0a70\u0a2c\u0a30_\u0a05\u0a15\u0a24\u0a42\u0a2c\u0a30_\u0a28\u0a35\u0a70\u0a2c\u0a30_\u0a26\u0a38\u0a70\u0a2c\u0a30".split("_"),monthsShort:"\u0a1c\u0a28\u0a35\u0a30\u0a40_\u0a2b\u0a3c\u0a30\u0a35\u0a30\u0a40_\u0a2e\u0a3e\u0a30\u0a1a_\u0a05\u0a2a\u0a4d\u0a30\u0a48\u0a32_\u0a2e\u0a08_\u0a1c\u0a42\u0a28_\u0a1c\u0a41\u0a32\u0a3e\u0a08_\u0a05\u0a17\u0a38\u0a24_\u0a38\u0a24\u0a70\u0a2c\u0a30_\u0a05\u0a15\u0a24\u0a42\u0a2c\u0a30_\u0a28\u0a35\u0a70\u0a2c\u0a30_\u0a26\u0a38\u0a70\u0a2c\u0a30".split("_"),weekdays:"\u0a10\u0a24\u0a35\u0a3e\u0a30_\u0a38\u0a4b\u0a2e\u0a35\u0a3e\u0a30_\u0a2e\u0a70\u0a17\u0a32\u0a35\u0a3e\u0a30_\u0a2c\u0a41\u0a27\u0a35\u0a3e\u0a30_\u0a35\u0a40\u0a30\u0a35\u0a3e\u0a30_\u0a38\u0a3c\u0a41\u0a71\u0a15\u0a30\u0a35\u0a3e\u0a30_\u0a38\u0a3c\u0a28\u0a40\u0a1a\u0a30\u0a35\u0a3e\u0a30".split("_"),weekdaysShort:"\u0a10\u0a24_\u0a38\u0a4b\u0a2e_\u0a2e\u0a70\u0a17\u0a32_\u0a2c\u0a41\u0a27_\u0a35\u0a40\u0a30_\u0a38\u0a3c\u0a41\u0a15\u0a30_\u0a38\u0a3c\u0a28\u0a40".split("_"),weekdaysMin:"\u0a10\u0a24_\u0a38\u0a4b\u0a2e_\u0a2e\u0a70\u0a17\u0a32_\u0a2c\u0a41\u0a27_\u0a35\u0a40\u0a30_\u0a38\u0a3c\u0a41\u0a15\u0a30_\u0a38\u0a3c\u0a28\u0a40".split("_"),longDateFormat:{LT:"A h:mm \u0a35\u0a1c\u0a47",LTS:"A h:mm:ss \u0a35\u0a1c\u0a47",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm \u0a35\u0a1c\u0a47",LLLL:"dddd, D MMMM YYYY, A h:mm \u0a35\u0a1c\u0a47"},calendar:{sameDay:"[\u0a05\u0a1c] LT",nextDay:"[\u0a15\u0a32] LT",nextWeek:"dddd, LT",lastDay:"[\u0a15\u0a32] LT",lastWeek:"[\u0a2a\u0a3f\u0a1b\u0a32\u0a47] dddd, LT",sameElse:"L"},relativeTime:{future:"%s \u0a35\u0a3f\u0a71\u0a1a",past:"%s \u0a2a\u0a3f\u0a1b\u0a32\u0a47",s:"\u0a15\u0a41\u0a1d \u0a38\u0a15\u0a3f\u0a70\u0a1f",ss:"%d \u0a38\u0a15\u0a3f\u0a70\u0a1f",m:"\u0a07\u0a15 \u0a2e\u0a3f\u0a70\u0a1f",mm:"%d \u0a2e\u0a3f\u0a70\u0a1f",h:"\u0a07\u0a71\u0a15 \u0a18\u0a70\u0a1f\u0a3e",hh:"%d \u0a18\u0a70\u0a1f\u0a47",d:"\u0a07\u0a71\u0a15 \u0a26\u0a3f\u0a28",dd:"%d \u0a26\u0a3f\u0a28",M:"\u0a07\u0a71\u0a15 \u0a2e\u0a39\u0a40\u0a28\u0a3e",MM:"%d \u0a2e\u0a39\u0a40\u0a28\u0a47",y:"\u0a07\u0a71\u0a15 \u0a38\u0a3e\u0a32",yy:"%d \u0a38\u0a3e\u0a32"},preparse:function(e){return e.replace(/[\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e\u0a6f\u0a66]/g,function(e){return Nn[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return Jn[e]})},meridiemParse:/\u0a30\u0a3e\u0a24|\u0a38\u0a35\u0a47\u0a30|\u0a26\u0a41\u0a2a\u0a39\u0a3f\u0a30|\u0a38\u0a3c\u0a3e\u0a2e/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u0a30\u0a3e\u0a24"===a?e<4?e:e+12:"\u0a38\u0a35\u0a47\u0a30"===a?e:"\u0a26\u0a41\u0a2a\u0a39\u0a3f\u0a30"===a?e>=10?e:e+12:"\u0a38\u0a3c\u0a3e\u0a2e"===a?e+12:void 0},meridiem:function(e,a,t){return e<4?"\u0a30\u0a3e\u0a24":e<10?"\u0a38\u0a35\u0a47\u0a30":e<17?"\u0a26\u0a41\u0a2a\u0a39\u0a3f\u0a30":e<20?"\u0a38\u0a3c\u0a3e\u0a2e":"\u0a30\u0a3e\u0a24"},week:{dow:0,doy:6}});var Rn="stycze\u0144_luty_marzec_kwiecie\u0144_maj_czerwiec_lipiec_sierpie\u0144_wrzesie\u0144_pa\u017adziernik_listopad_grudzie\u0144".split("_"),In="stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_wrze\u015bnia_pa\u017adziernika_listopada_grudnia".split("_");e.defineLocale("pl",{months:function(e,a){return e?""===a?"("+In[e.month()]+"|"+Rn[e.month()]+")":/D MMMM/.test(a)?In[e.month()]:Rn[e.month()]:Rn},monthsShort:"sty_lut_mar_kwi_maj_cze_lip_sie_wrz_pa\u017a_lis_gru".split("_"),weekdays:"niedziela_poniedzia\u0142ek_wtorek_\u015broda_czwartek_pi\u0105tek_sobota".split("_"),weekdaysShort:"ndz_pon_wt_\u015br_czw_pt_sob".split("_"),weekdaysMin:"Nd_Pn_Wt_\u015ar_Cz_Pt_So".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Dzi\u015b o] LT",nextDay:"[Jutro o] LT",nextWeek:function(){switch(this.day()){case 0:return"[W niedziel\u0119 o] LT";case 2:return"[We wtorek o] LT";case 3:return"[W \u015brod\u0119 o] LT";case 6:return"[W sobot\u0119 o] LT";default:return"[W] dddd [o] LT"}},lastDay:"[Wczoraj o] LT",lastWeek:function(){switch(this.day()){case 0:return"[W zesz\u0142\u0105 niedziel\u0119 o] LT";case 3:return"[W zesz\u0142\u0105 \u015brod\u0119 o] LT";case 6:return"[W zesz\u0142\u0105 sobot\u0119 o] LT";default:return"[W zesz\u0142y] dddd [o] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"%s temu",s:"kilka sekund",ss:wa,m:wa,mm:wa,h:wa,hh:wa,d:"1 dzie\u0144",dd:"%d dni",M:"miesi\u0105c",MM:wa,y:"rok",yy:wa},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),e.defineLocale("pt-br",{months:"janeiro_fevereiro_mar\xe7o_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"Domingo_Segunda-feira_Ter\xe7a-feira_Quarta-feira_Quinta-feira_Sexta-feira_S\xe1bado".split("_"),weekdaysShort:"Dom_Seg_Ter_Qua_Qui_Sex_S\xe1b".split("_"),weekdaysMin:"Do_2\xaa_3\xaa_4\xaa_5\xaa_6\xaa_S\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY [\xe0s] HH:mm",LLLL:"dddd, D [de] MMMM [de] YYYY [\xe0s] HH:mm"},calendar:{sameDay:"[Hoje \xe0s] LT",nextDay:"[Amanh\xe3 \xe0s] LT",nextWeek:"dddd [\xe0s] LT",lastDay:"[Ontem \xe0s] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[\xdaltimo] dddd [\xe0s] LT":"[\xdaltima] dddd [\xe0s] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"%s atr\xe1s",s:"poucos segundos",ss:"%d segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um m\xeas",MM:"%d meses",y:"um ano",yy:"%d anos"},dayOfMonthOrdinalParse:/\d{1,2}\xba/,ordinal:"%d\xba"}),e.defineLocale("pt",{months:"janeiro_fevereiro_mar\xe7o_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"Domingo_Segunda-feira_Ter\xe7a-feira_Quarta-feira_Quinta-feira_Sexta-feira_S\xe1bado".split("_"),weekdaysShort:"Dom_Seg_Ter_Qua_Qui_Sex_S\xe1b".split("_"),weekdaysMin:"Do_2\xaa_3\xaa_4\xaa_5\xaa_6\xaa_S\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY HH:mm",LLLL:"dddd, D [de] MMMM [de] YYYY HH:mm"},calendar:{sameDay:"[Hoje \xe0s] LT",nextDay:"[Amanh\xe3 \xe0s] LT",nextWeek:"dddd [\xe0s] LT",lastDay:"[Ontem \xe0s] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[\xdaltimo] dddd [\xe0s] LT":"[\xdaltima] dddd [\xe0s] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"h\xe1 %s",s:"segundos",ss:"%d segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um m\xeas",MM:"%d meses",y:"um ano",yy:"%d anos"},dayOfMonthOrdinalParse:/\d{1,2}\xba/,ordinal:"%d\xba",week:{dow:1,doy:4}}),e.defineLocale("ro",{months:"ianuarie_februarie_martie_aprilie_mai_iunie_iulie_august_septembrie_octombrie_noiembrie_decembrie".split("_"),monthsShort:"ian._febr._mart._apr._mai_iun._iul._aug._sept._oct._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"duminic\u0103_luni_mar\u021bi_miercuri_joi_vineri_s\xe2mb\u0103t\u0103".split("_"),weekdaysShort:"Dum_Lun_Mar_Mie_Joi_Vin_S\xe2m".split("_"),weekdaysMin:"Du_Lu_Ma_Mi_Jo_Vi_S\xe2".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY H:mm",LLLL:"dddd, D MMMM YYYY H:mm"},calendar:{sameDay:"[azi la] LT",nextDay:"[m\xe2ine la] LT",nextWeek:"dddd [la] LT",lastDay:"[ieri la] LT",lastWeek:"[fosta] dddd [la] LT",sameElse:"L"},relativeTime:{future:"peste %s",past:"%s \xeen urm\u0103",s:"c\xe2teva secunde",ss:va,m:"un minut",mm:va,h:"o or\u0103",hh:va,d:"o zi",dd:va,M:"o lun\u0103",MM:va,y:"un an",yy:va},week:{dow:1,doy:7}});var Cn=[/^\u044f\u043d\u0432/i,/^\u0444\u0435\u0432/i,/^\u043c\u0430\u0440/i,/^\u0430\u043f\u0440/i,/^\u043c\u0430[\u0439\u044f]/i,/^\u0438\u044e\u043d/i,/^\u0438\u044e\u043b/i,/^\u0430\u0432\u0433/i,/^\u0441\u0435\u043d/i,/^\u043e\u043a\u0442/i,/^\u043d\u043e\u044f/i,/^\u0434\u0435\u043a/i];e.defineLocale("ru",{months:{format:"\u044f\u043d\u0432\u0430\u0440\u044f_\u0444\u0435\u0432\u0440\u0430\u043b\u044f_\u043c\u0430\u0440\u0442\u0430_\u0430\u043f\u0440\u0435\u043b\u044f_\u043c\u0430\u044f_\u0438\u044e\u043d\u044f_\u0438\u044e\u043b\u044f_\u0430\u0432\u0433\u0443\u0441\u0442\u0430_\u0441\u0435\u043d\u0442\u044f\u0431\u0440\u044f_\u043e\u043a\u0442\u044f\u0431\u0440\u044f_\u043d\u043e\u044f\u0431\u0440\u044f_\u0434\u0435\u043a\u0430\u0431\u0440\u044f".split("_"),standalone:"\u044f\u043d\u0432\u0430\u0440\u044c_\u0444\u0435\u0432\u0440\u0430\u043b\u044c_\u043c\u0430\u0440\u0442_\u0430\u043f\u0440\u0435\u043b\u044c_\u043c\u0430\u0439_\u0438\u044e\u043d\u044c_\u0438\u044e\u043b\u044c_\u0430\u0432\u0433\u0443\u0441\u0442_\u0441\u0435\u043d\u0442\u044f\u0431\u0440\u044c_\u043e\u043a\u0442\u044f\u0431\u0440\u044c_\u043d\u043e\u044f\u0431\u0440\u044c_\u0434\u0435\u043a\u0430\u0431\u0440\u044c".split("_")},monthsShort:{format:"\u044f\u043d\u0432._\u0444\u0435\u0432\u0440._\u043c\u0430\u0440._\u0430\u043f\u0440._\u043c\u0430\u044f_\u0438\u044e\u043d\u044f_\u0438\u044e\u043b\u044f_\u0430\u0432\u0433._\u0441\u0435\u043d\u0442._\u043e\u043a\u0442._\u043d\u043e\u044f\u0431._\u0434\u0435\u043a.".split("_"),standalone:"\u044f\u043d\u0432._\u0444\u0435\u0432\u0440._\u043c\u0430\u0440\u0442_\u0430\u043f\u0440._\u043c\u0430\u0439_\u0438\u044e\u043d\u044c_\u0438\u044e\u043b\u044c_\u0430\u0432\u0433._\u0441\u0435\u043d\u0442._\u043e\u043a\u0442._\u043d\u043e\u044f\u0431._\u0434\u0435\u043a.".split("_")},weekdays:{standalone:"\u0432\u043e\u0441\u043a\u0440\u0435\u0441\u0435\u043d\u044c\u0435_\u043f\u043e\u043d\u0435\u0434\u0435\u043b\u044c\u043d\u0438\u043a_\u0432\u0442\u043e\u0440\u043d\u0438\u043a_\u0441\u0440\u0435\u0434\u0430_\u0447\u0435\u0442\u0432\u0435\u0440\u0433_\u043f\u044f\u0442\u043d\u0438\u0446\u0430_\u0441\u0443\u0431\u0431\u043e\u0442\u0430".split("_"),format:"\u0432\u043e\u0441\u043a\u0440\u0435\u0441\u0435\u043d\u044c\u0435_\u043f\u043e\u043d\u0435\u0434\u0435\u043b\u044c\u043d\u0438\u043a_\u0432\u0442\u043e\u0440\u043d\u0438\u043a_\u0441\u0440\u0435\u0434\u0443_\u0447\u0435\u0442\u0432\u0435\u0440\u0433_\u043f\u044f\u0442\u043d\u0438\u0446\u0443_\u0441\u0443\u0431\u0431\u043e\u0442\u0443".split("_"),isFormat:/\[ ?[\u0412\u0432] ?(?:\u043f\u0440\u043e\u0448\u043b\u0443\u044e|\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e|\u044d\u0442\u0443)? ?\] ?dddd/},weekdaysShort:"\u0432\u0441_\u043f\u043d_\u0432\u0442_\u0441\u0440_\u0447\u0442_\u043f\u0442_\u0441\u0431".split("_"),weekdaysMin:"\u0432\u0441_\u043f\u043d_\u0432\u0442_\u0441\u0440_\u0447\u0442_\u043f\u0442_\u0441\u0431".split("_"),monthsParse:Cn,longMonthsParse:Cn,shortMonthsParse:Cn,monthsRegex:/^(\u044f\u043d\u0432\u0430\u0440[\u044c\u044f]|\u044f\u043d\u0432\.?|\u0444\u0435\u0432\u0440\u0430\u043b[\u044c\u044f]|\u0444\u0435\u0432\u0440?\.?|\u043c\u0430\u0440\u0442\u0430?|\u043c\u0430\u0440\.?|\u0430\u043f\u0440\u0435\u043b[\u044c\u044f]|\u0430\u043f\u0440\.?|\u043c\u0430[\u0439\u044f]|\u0438\u044e\u043d[\u044c\u044f]|\u0438\u044e\u043d\.?|\u0438\u044e\u043b[\u044c\u044f]|\u0438\u044e\u043b\.?|\u0430\u0432\u0433\u0443\u0441\u0442\u0430?|\u0430\u0432\u0433\.?|\u0441\u0435\u043d\u0442\u044f\u0431\u0440[\u044c\u044f]|\u0441\u0435\u043d\u0442?\.?|\u043e\u043a\u0442\u044f\u0431\u0440[\u044c\u044f]|\u043e\u043a\u0442\.?|\u043d\u043e\u044f\u0431\u0440[\u044c\u044f]|\u043d\u043e\u044f\u0431?\.?|\u0434\u0435\u043a\u0430\u0431\u0440[\u044c\u044f]|\u0434\u0435\u043a\.?)/i,monthsShortRegex:/^(\u044f\u043d\u0432\u0430\u0440[\u044c\u044f]|\u044f\u043d\u0432\.?|\u0444\u0435\u0432\u0440\u0430\u043b[\u044c\u044f]|\u0444\u0435\u0432\u0440?\.?|\u043c\u0430\u0440\u0442\u0430?|\u043c\u0430\u0440\.?|\u0430\u043f\u0440\u0435\u043b[\u044c\u044f]|\u0430\u043f\u0440\.?|\u043c\u0430[\u0439\u044f]|\u0438\u044e\u043d[\u044c\u044f]|\u0438\u044e\u043d\.?|\u0438\u044e\u043b[\u044c\u044f]|\u0438\u044e\u043b\.?|\u0430\u0432\u0433\u0443\u0441\u0442\u0430?|\u0430\u0432\u0433\.?|\u0441\u0435\u043d\u0442\u044f\u0431\u0440[\u044c\u044f]|\u0441\u0435\u043d\u0442?\.?|\u043e\u043a\u0442\u044f\u0431\u0440[\u044c\u044f]|\u043e\u043a\u0442\.?|\u043d\u043e\u044f\u0431\u0440[\u044c\u044f]|\u043d\u043e\u044f\u0431?\.?|\u0434\u0435\u043a\u0430\u0431\u0440[\u044c\u044f]|\u0434\u0435\u043a\.?)/i,monthsStrictRegex:/^(\u044f\u043d\u0432\u0430\u0440[\u044f\u044c]|\u0444\u0435\u0432\u0440\u0430\u043b[\u044f\u044c]|\u043c\u0430\u0440\u0442\u0430?|\u0430\u043f\u0440\u0435\u043b[\u044f\u044c]|\u043c\u0430[\u044f\u0439]|\u0438\u044e\u043d[\u044f\u044c]|\u0438\u044e\u043b[\u044f\u044c]|\u0430\u0432\u0433\u0443\u0441\u0442\u0430?|\u0441\u0435\u043d\u0442\u044f\u0431\u0440[\u044f\u044c]|\u043e\u043a\u0442\u044f\u0431\u0440[\u044f\u044c]|\u043d\u043e\u044f\u0431\u0440[\u044f\u044c]|\u0434\u0435\u043a\u0430\u0431\u0440[\u044f\u044c])/i,monthsShortStrictRegex:/^(\u044f\u043d\u0432\.|\u0444\u0435\u0432\u0440?\.|\u043c\u0430\u0440[\u0442.]|\u0430\u043f\u0440\.|\u043c\u0430[\u044f\u0439]|\u0438\u044e\u043d[\u044c\u044f.]|\u0438\u044e\u043b[\u044c\u044f.]|\u0430\u0432\u0433\.|\u0441\u0435\u043d\u0442?\.|\u043e\u043a\u0442\.|\u043d\u043e\u044f\u0431?\.|\u0434\u0435\u043a\.)/i,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY \u0433.",LLL:"D MMMM YYYY \u0433., H:mm",LLLL:"dddd, D MMMM YYYY \u0433., H:mm"},calendar:{sameDay:"[\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0432] LT",nextDay:"[\u0417\u0430\u0432\u0442\u0440\u0430 \u0432] LT",lastDay:"[\u0412\u0447\u0435\u0440\u0430 \u0432] LT",nextWeek:function(e){if(e.week()===this.week())return 2===this.day()?"[\u0412\u043e] dddd [\u0432] LT":"[\u0412] dddd [\u0432] LT";switch(this.day()){case 0:return"[\u0412 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435] dddd [\u0432] LT";case 1:case 2:case 4:return"[\u0412 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439] dddd [\u0432] LT";case 3:case 5:case 6:return"[\u0412 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e] dddd [\u0432] LT"}},lastWeek:function(e){if(e.week()===this.week())return 2===this.day()?"[\u0412\u043e] dddd [\u0432] LT":"[\u0412] dddd [\u0432] LT";switch(this.day()){case 0:return"[\u0412 \u043f\u0440\u043e\u0448\u043b\u043e\u0435] dddd [\u0432] LT";case 1:case 2:case 4:return"[\u0412 \u043f\u0440\u043e\u0448\u043b\u044b\u0439] dddd [\u0432] LT";case 3:case 5:case 6:return"[\u0412 \u043f\u0440\u043e\u0448\u043b\u0443\u044e] dddd [\u0432] LT"}},sameElse:"L"},relativeTime:{future:"\u0447\u0435\u0440\u0435\u0437 %s",past:"%s \u043d\u0430\u0437\u0430\u0434",s:"\u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0435\u043a\u0443\u043d\u0434",ss:Sa,m:Sa,mm:Sa,h:"\u0447\u0430\u0441",hh:Sa,d:"\u0434\u0435\u043d\u044c",dd:Sa,M:"\u043c\u0435\u0441\u044f\u0446",MM:Sa,y:"\u0433\u043e\u0434",yy:Sa},meridiemParse:/\u043d\u043e\u0447\u0438|\u0443\u0442\u0440\u0430|\u0434\u043d\u044f|\u0432\u0435\u0447\u0435\u0440\u0430/i,isPM:function(e){return/^(\u0434\u043d\u044f|\u0432\u0435\u0447\u0435\u0440\u0430)$/.test(e)},meridiem:function(e,a,t){return e<4?"\u043d\u043e\u0447\u0438":e<12?"\u0443\u0442\u0440\u0430":e<17?"\u0434\u043d\u044f":"\u0432\u0435\u0447\u0435\u0440\u0430"},dayOfMonthOrdinalParse:/\d{1,2}-(\u0439|\u0433\u043e|\u044f)/,ordinal:function(e,a){switch(a){case"M":case"d":case"DDD":return e+"-\u0439";case"D":return e+"-\u0433\u043e";case"w":case"W":return e+"-\u044f";default:return e}},week:{dow:1,doy:4}});var Gn=["\u062c\u0646\u0648\u0631\u064a","\u0641\u064a\u0628\u0631\u0648\u0631\u064a","\u0645\u0627\u0631\u0686","\u0627\u067e\u0631\u064a\u0644","\u0645\u0626\u064a","\u062c\u0648\u0646","\u062c\u0648\u0644\u0627\u0621\u0650","\u0622\u06af\u0633\u067d","\u0633\u064a\u067e\u067d\u0645\u0628\u0631","\u0622\u06aa\u067d\u0648\u0628\u0631","\u0646\u0648\u0645\u0628\u0631","\u068a\u0633\u0645\u0628\u0631"],Un=["\u0622\u0686\u0631","\u0633\u0648\u0645\u0631","\u0627\u06b1\u0627\u0631\u0648","\u0627\u0631\u0628\u0639","\u062e\u0645\u064a\u0633","\u062c\u0645\u0639","\u0687\u0646\u0687\u0631"];e.defineLocale("sd",{months:Gn,monthsShort:Gn,weekdays:Un,weekdaysShort:Un,weekdaysMin:Un,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd\u060c D MMMM YYYY HH:mm"},meridiemParse:/\u0635\u0628\u062d|\u0634\u0627\u0645/,isPM:function(e){return"\u0634\u0627\u0645"===e},meridiem:function(e,a,t){return e<12?"\u0635\u0628\u062d":"\u0634\u0627\u0645"},calendar:{sameDay:"[\u0627\u0684] LT",nextDay:"[\u0633\u0680\u0627\u06bb\u064a] LT",nextWeek:"dddd [\u0627\u06b3\u064a\u0646 \u0647\u0641\u062a\u064a \u062a\u064a] LT",lastDay:"[\u06aa\u0627\u0644\u0647\u0647] LT",lastWeek:"[\u06af\u0632\u0631\u064a\u0644 \u0647\u0641\u062a\u064a] dddd [\u062a\u064a] LT",sameElse:"L"},relativeTime:{future:"%s \u067e\u0648\u0621",past:"%s \u0627\u06b3",s:"\u0686\u0646\u062f \u0633\u064a\u06aa\u0646\u068a",ss:"%d \u0633\u064a\u06aa\u0646\u068a",m:"\u0647\u06aa \u0645\u0646\u067d",mm:"%d \u0645\u0646\u067d",h:"\u0647\u06aa \u06aa\u0644\u0627\u06aa",hh:"%d \u06aa\u0644\u0627\u06aa",d:"\u0647\u06aa \u068f\u064a\u0646\u0647\u0646",dd:"%d \u068f\u064a\u0646\u0647\u0646",M:"\u0647\u06aa \u0645\u0647\u064a\u0646\u0648",MM:"%d \u0645\u0647\u064a\u0646\u0627",y:"\u0647\u06aa \u0633\u0627\u0644",yy:"%d \u0633\u0627\u0644"},preparse:function(e){return e.replace(/\u060c/g,",")},postformat:function(e){return e.replace(/,/g,"\u060c")},week:{dow:1,doy:4}}),e.defineLocale("se",{months:"o\u0111\u0111ajagem\xe1nnu_guovvam\xe1nnu_njuk\u010dam\xe1nnu_cuo\u014bom\xe1nnu_miessem\xe1nnu_geassem\xe1nnu_suoidnem\xe1nnu_borgem\xe1nnu_\u010dak\u010dam\xe1nnu_golggotm\xe1nnu_sk\xe1bmam\xe1nnu_juovlam\xe1nnu".split("_"),monthsShort:"o\u0111\u0111j_guov_njuk_cuo_mies_geas_suoi_borg_\u010dak\u010d_golg_sk\xe1b_juov".split("_"),weekdays:"sotnabeaivi_vuoss\xe1rga_ma\u014b\u014beb\xe1rga_gaskavahkku_duorastat_bearjadat_l\xe1vvardat".split("_"),weekdaysShort:"sotn_vuos_ma\u014b_gask_duor_bear_l\xe1v".split("_"),weekdaysMin:"s_v_m_g_d_b_L".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"MMMM D. [b.] YYYY",LLL:"MMMM D. [b.] YYYY [ti.] HH:mm",LLLL:"dddd, MMMM D. [b.] YYYY [ti.] HH:mm"},calendar:{sameDay:"[otne ti] LT",nextDay:"[ihttin ti] LT",nextWeek:"dddd [ti] LT",lastDay:"[ikte ti] LT",lastWeek:"[ovddit] dddd [ti] LT",sameElse:"L"},relativeTime:{future:"%s gea\u017ees",past:"ma\u014bit %s",s:"moadde sekunddat",ss:"%d sekunddat",m:"okta minuhta",mm:"%d minuhtat",h:"okta diimmu",hh:"%d diimmut",d:"okta beaivi",dd:"%d beaivvit",M:"okta m\xe1nnu",MM:"%d m\xe1nut",y:"okta jahki",yy:"%d jagit"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),e.defineLocale("si",{months:"\u0da2\u0db1\u0dc0\u0dcf\u0dbb\u0dd2_\u0db4\u0dd9\u0db6\u0dbb\u0dc0\u0dcf\u0dbb\u0dd2_\u0db8\u0dcf\u0dbb\u0dca\u0dad\u0dd4_\u0d85\u0db4\u0dca\u200d\u0dbb\u0dda\u0dbd\u0dca_\u0db8\u0dd0\u0dba\u0dd2_\u0da2\u0dd6\u0db1\u0dd2_\u0da2\u0dd6\u0dbd\u0dd2_\u0d85\u0d9c\u0ddd\u0dc3\u0dca\u0dad\u0dd4_\u0dc3\u0dd0\u0db4\u0dca\u0dad\u0dd0\u0db8\u0dca\u0db6\u0dbb\u0dca_\u0d94\u0d9a\u0dca\u0dad\u0ddd\u0db6\u0dbb\u0dca_\u0db1\u0ddc\u0dc0\u0dd0\u0db8\u0dca\u0db6\u0dbb\u0dca_\u0daf\u0dd9\u0dc3\u0dd0\u0db8\u0dca\u0db6\u0dbb\u0dca".split("_"),monthsShort:"\u0da2\u0db1_\u0db4\u0dd9\u0db6_\u0db8\u0dcf\u0dbb\u0dca_\u0d85\u0db4\u0dca_\u0db8\u0dd0\u0dba\u0dd2_\u0da2\u0dd6\u0db1\u0dd2_\u0da2\u0dd6\u0dbd\u0dd2_\u0d85\u0d9c\u0ddd_\u0dc3\u0dd0\u0db4\u0dca_\u0d94\u0d9a\u0dca_\u0db1\u0ddc\u0dc0\u0dd0_\u0daf\u0dd9\u0dc3\u0dd0".split("_"),weekdays:"\u0d89\u0dbb\u0dd2\u0daf\u0dcf_\u0dc3\u0db3\u0dd4\u0daf\u0dcf_\u0d85\u0d9f\u0dc4\u0dbb\u0dd4\u0dc0\u0dcf\u0daf\u0dcf_\u0db6\u0daf\u0dcf\u0daf\u0dcf_\u0db6\u0dca\u200d\u0dbb\u0dc4\u0dc3\u0dca\u0db4\u0dad\u0dd2\u0db1\u0dca\u0daf\u0dcf_\u0dc3\u0dd2\u0d9a\u0dd4\u0dbb\u0dcf\u0daf\u0dcf_\u0dc3\u0dd9\u0db1\u0dc3\u0dd4\u0dbb\u0dcf\u0daf\u0dcf".split("_"),weekdaysShort:"\u0d89\u0dbb\u0dd2_\u0dc3\u0db3\u0dd4_\u0d85\u0d9f_\u0db6\u0daf\u0dcf_\u0db6\u0dca\u200d\u0dbb\u0dc4_\u0dc3\u0dd2\u0d9a\u0dd4_\u0dc3\u0dd9\u0db1".split("_"),weekdaysMin:"\u0d89_\u0dc3_\u0d85_\u0db6_\u0db6\u0dca\u200d\u0dbb_\u0dc3\u0dd2_\u0dc3\u0dd9".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"a h:mm",LTS:"a h:mm:ss",L:"YYYY/MM/DD",LL:"YYYY MMMM D",LLL:"YYYY MMMM D, a h:mm",LLLL:"YYYY MMMM D [\u0dc0\u0dd0\u0db1\u0dd2] dddd, a h:mm:ss"},calendar:{sameDay:"[\u0d85\u0daf] LT[\u0da7]",nextDay:"[\u0dc4\u0dd9\u0da7] LT[\u0da7]",nextWeek:"dddd LT[\u0da7]",lastDay:"[\u0d8a\u0dba\u0dda] LT[\u0da7]",lastWeek:"[\u0db4\u0dc3\u0dd4\u0d9c\u0dd2\u0dba] dddd LT[\u0da7]",sameElse:"L"},relativeTime:{future:"%s\u0d9a\u0dd2\u0db1\u0dca",past:"%s\u0d9a\u0da7 \u0db4\u0dd9\u0dbb",s:"\u0dad\u0dad\u0dca\u0db4\u0dbb \u0d9a\u0dd2\u0dc4\u0dd2\u0db4\u0dba",ss:"\u0dad\u0dad\u0dca\u0db4\u0dbb %d",m:"\u0db8\u0dd2\u0db1\u0dd2\u0dad\u0dca\u0dad\u0dd4\u0dc0",mm:"\u0db8\u0dd2\u0db1\u0dd2\u0dad\u0dca\u0dad\u0dd4 %d",h:"\u0db4\u0dd0\u0dba",hh:"\u0db4\u0dd0\u0dba %d",d:"\u0daf\u0dd2\u0db1\u0dba",dd:"\u0daf\u0dd2\u0db1 %d",M:"\u0db8\u0dcf\u0dc3\u0dba",MM:"\u0db8\u0dcf\u0dc3 %d",y:"\u0dc0\u0dc3\u0dbb",yy:"\u0dc0\u0dc3\u0dbb %d"},dayOfMonthOrdinalParse:/\d{1,2} \u0dc0\u0dd0\u0db1\u0dd2/,ordinal:function(e){return e+" \u0dc0\u0dd0\u0db1\u0dd2"},meridiemParse:/\u0db4\u0dd9\u0dbb \u0dc0\u0dbb\u0dd4|\u0db4\u0dc3\u0dca \u0dc0\u0dbb\u0dd4|\u0db4\u0dd9.\u0dc0|\u0db4.\u0dc0./,isPM:function(e){return"\u0db4.\u0dc0."===e||"\u0db4\u0dc3\u0dca \u0dc0\u0dbb\u0dd4"===e},meridiem:function(e,a,t){return e>11?t?"\u0db4.\u0dc0.":"\u0db4\u0dc3\u0dca \u0dc0\u0dbb\u0dd4":t?"\u0db4\u0dd9.\u0dc0.":"\u0db4\u0dd9\u0dbb \u0dc0\u0dbb\u0dd4"}});var Vn="janu\xe1r_febru\xe1r_marec_apr\xedl_m\xe1j_j\xfan_j\xfal_august_september_okt\xf3ber_november_december".split("_"),Kn="jan_feb_mar_apr_m\xe1j_j\xfan_j\xfal_aug_sep_okt_nov_dec".split("_");e.defineLocale("sk",{months:Vn,monthsShort:Kn,weekdays:"nede\u013ea_pondelok_utorok_streda_\u0161tvrtok_piatok_sobota".split("_"),weekdaysShort:"ne_po_ut_st_\u0161t_pi_so".split("_"),weekdaysMin:"ne_po_ut_st_\u0161t_pi_so".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd D. MMMM YYYY H:mm"},calendar:{sameDay:"[dnes o] LT",nextDay:"[zajtra o] LT",nextWeek:function(){switch(this.day()){case 0:return"[v nede\u013eu o] LT";case 1:case 2:return"[v] dddd [o] LT";case 3:return"[v stredu o] LT";case 4:return"[vo \u0161tvrtok o] LT";case 5:return"[v piatok o] LT";case 6:return"[v sobotu o] LT"}},lastDay:"[v\u010dera o] LT",lastWeek:function(){switch(this.day()){case 0:return"[minul\xfa nede\u013eu o] LT";case 1:case 2:return"[minul\xfd] dddd [o] LT";case 3:return"[minul\xfa stredu o] LT";case 4:case 5:return"[minul\xfd] dddd [o] LT";case 6:return"[minul\xfa sobotu o] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"pred %s",s:ba,ss:ba,m:ba,mm:ba,h:ba,hh:ba,d:ba,dd:ba,M:ba,MM:ba,y:ba,yy:ba},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),e.defineLocale("sl",{months:"januar_februar_marec_april_maj_junij_julij_avgust_september_oktober_november_december".split("_"),monthsShort:"jan._feb._mar._apr._maj._jun._jul._avg._sep._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"nedelja_ponedeljek_torek_sreda_\u010detrtek_petek_sobota".split("_"),weekdaysShort:"ned._pon._tor._sre._\u010det._pet._sob.".split("_"),weekdaysMin:"ne_po_to_sr_\u010de_pe_so".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[danes ob] LT",nextDay:"[jutri ob] LT",nextWeek:function(){switch(this.day()){case 0:return"[v] [nedeljo] [ob] LT";case 3:return"[v] [sredo] [ob] LT";case 6:return"[v] [soboto] [ob] LT";case 1:case 2:case 4:case 5:return"[v] dddd [ob] LT"}},lastDay:"[v\u010deraj ob] LT",lastWeek:function(){switch(this.day()){case 0:return"[prej\u0161njo] [nedeljo] [ob] LT";case 3:return"[prej\u0161njo] [sredo] [ob] LT";case 6:return"[prej\u0161njo] [soboto] [ob] LT";case 1:case 2:case 4:case 5:return"[prej\u0161nji] dddd [ob] LT"}},sameElse:"L"},relativeTime:{future:"\u010dez %s",past:"pred %s",s:ja,ss:ja,m:ja,mm:ja,h:ja,hh:ja,d:ja,dd:ja,M:ja,MM:ja,y:ja,yy:ja},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),e.defineLocale("sq",{months:"Janar_Shkurt_Mars_Prill_Maj_Qershor_Korrik_Gusht_Shtator_Tetor_N\xebntor_Dhjetor".split("_"),monthsShort:"Jan_Shk_Mar_Pri_Maj_Qer_Kor_Gus_Sht_Tet_N\xebn_Dhj".split("_"),weekdays:"E Diel_E H\xebn\xeb_E Mart\xeb_E M\xebrkur\xeb_E Enjte_E Premte_E Shtun\xeb".split("_"),weekdaysShort:"Die_H\xebn_Mar_M\xebr_Enj_Pre_Sht".split("_"),weekdaysMin:"D_H_Ma_M\xeb_E_P_Sh".split("_"),weekdaysParseExact:!0,meridiemParse:/PD|MD/,isPM:function(e){return"M"===e.charAt(0)},meridiem:function(e,a,t){return e<12?"PD":"MD"},longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Sot n\xeb] LT",nextDay:"[Nes\xebr n\xeb] LT",nextWeek:"dddd [n\xeb] LT",lastDay:"[Dje n\xeb] LT",lastWeek:"dddd [e kaluar n\xeb] LT",sameElse:"L"},relativeTime:{future:"n\xeb %s",past:"%s m\xeb par\xeb",s:"disa sekonda",ss:"%d sekonda",m:"nj\xeb minut\xeb",mm:"%d minuta",h:"nj\xeb or\xeb",hh:"%d or\xeb",d:"nj\xeb dit\xeb",dd:"%d dit\xeb",M:"nj\xeb muaj",MM:"%d muaj",y:"nj\xeb vit",yy:"%d vite"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}});var Zn={words:{ss:["\u0441\u0435\u043a\u0443\u043d\u0434\u0430","\u0441\u0435\u043a\u0443\u043d\u0434\u0435","\u0441\u0435\u043a\u0443\u043d\u0434\u0438"],m:["\u0458\u0435\u0434\u0430\u043d \u043c\u0438\u043d\u0443\u0442","\u0458\u0435\u0434\u043d\u0435 \u043c\u0438\u043d\u0443\u0442\u0435"],mm:["\u043c\u0438\u043d\u0443\u0442","\u043c\u0438\u043d\u0443\u0442\u0435","\u043c\u0438\u043d\u0443\u0442\u0430"],h:["\u0458\u0435\u0434\u0430\u043d \u0441\u0430\u0442","\u0458\u0435\u0434\u043d\u043e\u0433 \u0441\u0430\u0442\u0430"],hh:["\u0441\u0430\u0442","\u0441\u0430\u0442\u0430","\u0441\u0430\u0442\u0438"],dd:["\u0434\u0430\u043d","\u0434\u0430\u043d\u0430","\u0434\u0430\u043d\u0430"],MM:["\u043c\u0435\u0441\u0435\u0446","\u043c\u0435\u0441\u0435\u0446\u0430","\u043c\u0435\u0441\u0435\u0446\u0438"],yy:["\u0433\u043e\u0434\u0438\u043d\u0430","\u0433\u043e\u0434\u0438\u043d\u0435","\u0433\u043e\u0434\u0438\u043d\u0430"]},correctGrammaticalCase:function(e,a){return 1===e?a[0]:e>=2&&e<=4?a[1]:a[2]},translate:function(e,a,t){var s=Zn.words[t];return 1===t.length?a?s[0]:s[1]:e+" "+Zn.correctGrammaticalCase(e,s)}};e.defineLocale("sr-cyrl",{months:"\u0458\u0430\u043d\u0443\u0430\u0440_\u0444\u0435\u0431\u0440\u0443\u0430\u0440_\u043c\u0430\u0440\u0442_\u0430\u043f\u0440\u0438\u043b_\u043c\u0430\u0458_\u0458\u0443\u043d_\u0458\u0443\u043b_\u0430\u0432\u0433\u0443\u0441\u0442_\u0441\u0435\u043f\u0442\u0435\u043c\u0431\u0430\u0440_\u043e\u043a\u0442\u043e\u0431\u0430\u0440_\u043d\u043e\u0432\u0435\u043c\u0431\u0430\u0440_\u0434\u0435\u0446\u0435\u043c\u0431\u0430\u0440".split("_"),monthsShort:"\u0458\u0430\u043d._\u0444\u0435\u0431._\u043c\u0430\u0440._\u0430\u043f\u0440._\u043c\u0430\u0458_\u0458\u0443\u043d_\u0458\u0443\u043b_\u0430\u0432\u0433._\u0441\u0435\u043f._\u043e\u043a\u0442._\u043d\u043e\u0432._\u0434\u0435\u0446.".split("_"),monthsParseExact:!0,weekdays:"\u043d\u0435\u0434\u0435\u0459\u0430_\u043f\u043e\u043d\u0435\u0434\u0435\u0459\u0430\u043a_\u0443\u0442\u043e\u0440\u0430\u043a_\u0441\u0440\u0435\u0434\u0430_\u0447\u0435\u0442\u0432\u0440\u0442\u0430\u043a_\u043f\u0435\u0442\u0430\u043a_\u0441\u0443\u0431\u043e\u0442\u0430".split("_"),weekdaysShort:"\u043d\u0435\u0434._\u043f\u043e\u043d._\u0443\u0442\u043e._\u0441\u0440\u0435._\u0447\u0435\u0442._\u043f\u0435\u0442._\u0441\u0443\u0431.".split("_"),weekdaysMin:"\u043d\u0435_\u043f\u043e_\u0443\u0442_\u0441\u0440_\u0447\u0435_\u043f\u0435_\u0441\u0443".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[\u0434\u0430\u043d\u0430\u0441 \u0443] LT",nextDay:"[\u0441\u0443\u0442\u0440\u0430 \u0443] LT",nextWeek:function(){switch(this.day()){case 0:return"[\u0443] [\u043d\u0435\u0434\u0435\u0459\u0443] [\u0443] LT";case 3:return"[\u0443] [\u0441\u0440\u0435\u0434\u0443] [\u0443] LT";case 6:return"[\u0443] [\u0441\u0443\u0431\u043e\u0442\u0443] [\u0443] LT";case 1:case 2:case 4:case 5:return"[\u0443] dddd [\u0443] LT"}},lastDay:"[\u0458\u0443\u0447\u0435 \u0443] LT",lastWeek:function(){return["[\u043f\u0440\u043e\u0448\u043b\u0435] [\u043d\u0435\u0434\u0435\u0459\u0435] [\u0443] LT","[\u043f\u0440\u043e\u0448\u043b\u043e\u0433] [\u043f\u043e\u043d\u0435\u0434\u0435\u0459\u043a\u0430] [\u0443] LT","[\u043f\u0440\u043e\u0448\u043b\u043e\u0433] [\u0443\u0442\u043e\u0440\u043a\u0430] [\u0443] LT","[\u043f\u0440\u043e\u0448\u043b\u0435] [\u0441\u0440\u0435\u0434\u0435] [\u0443] LT","[\u043f\u0440\u043e\u0448\u043b\u043e\u0433] [\u0447\u0435\u0442\u0432\u0440\u0442\u043a\u0430] [\u0443] LT","[\u043f\u0440\u043e\u0448\u043b\u043e\u0433] [\u043f\u0435\u0442\u043a\u0430] [\u0443] LT","[\u043f\u0440\u043e\u0448\u043b\u0435] [\u0441\u0443\u0431\u043e\u0442\u0435] [\u0443] LT"][this.day()]},sameElse:"L"},relativeTime:{future:"\u0437\u0430 %s",past:"\u043f\u0440\u0435 %s",s:"\u043d\u0435\u043a\u043e\u043b\u0438\u043a\u043e \u0441\u0435\u043a\u0443\u043d\u0434\u0438",ss:Zn.translate,m:Zn.translate,mm:Zn.translate,h:Zn.translate,hh:Zn.translate,d:"\u0434\u0430\u043d",dd:Zn.translate,M:"\u043c\u0435\u0441\u0435\u0446",MM:Zn.translate,y:"\u0433\u043e\u0434\u0438\u043d\u0443",yy:Zn.translate},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}});var $n={words:{ss:["sekunda","sekunde","sekundi"],m:["jedan minut","jedne minute"],mm:["minut","minute","minuta"],h:["jedan sat","jednog sata"],hh:["sat","sata","sati"],dd:["dan","dana","dana"],MM:["mesec","meseca","meseci"],yy:["godina","godine","godina"]},correctGrammaticalCase:function(e,a){return 1===e?a[0]:e>=2&&e<=4?a[1]:a[2]},translate:function(e,a,t){var s=$n.words[t];return 1===t.length?a?s[0]:s[1]:e+" "+$n.correctGrammaticalCase(e,s)}};e.defineLocale("sr",{months:"januar_februar_mart_april_maj_jun_jul_avgust_septembar_oktobar_novembar_decembar".split("_"),monthsShort:"jan._feb._mar._apr._maj_jun_jul_avg._sep._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"nedelja_ponedeljak_utorak_sreda_\u010detvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sre._\u010det._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_\u010de_pe_su".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedelju] [u] LT";case 3:return"[u] [sredu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[ju\u010de u] LT",lastWeek:function(){return["[pro\u0161le] [nedelje] [u] LT","[pro\u0161log] [ponedeljka] [u] LT","[pro\u0161log] [utorka] [u] LT","[pro\u0161le] [srede] [u] LT","[pro\u0161log] [\u010detvrtka] [u] LT","[pro\u0161log] [petka] [u] LT","[pro\u0161le] [subote] [u] LT"][this.day()]},sameElse:"L"},relativeTime:{future:"za %s",past:"pre %s",s:"nekoliko sekundi",ss:$n.translate,m:$n.translate,mm:$n.translate,h:$n.translate,hh:$n.translate,d:"dan",dd:$n.translate,M:"mesec",MM:$n.translate,y:"godinu",yy:$n.translate},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),e.defineLocale("ss",{months:"Bhimbidvwane_Indlovana_Indlov'lenkhulu_Mabasa_Inkhwekhweti_Inhlaba_Kholwane_Ingci_Inyoni_Imphala_Lweti_Ingongoni".split("_"),monthsShort:"Bhi_Ina_Inu_Mab_Ink_Inh_Kho_Igc_Iny_Imp_Lwe_Igo".split("_"),weekdays:"Lisontfo_Umsombuluko_Lesibili_Lesitsatfu_Lesine_Lesihlanu_Umgcibelo".split("_"),weekdaysShort:"Lis_Umb_Lsb_Les_Lsi_Lsh_Umg".split("_"),weekdaysMin:"Li_Us_Lb_Lt_Ls_Lh_Ug".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[Namuhla nga] LT",nextDay:"[Kusasa nga] LT",nextWeek:"dddd [nga] LT",lastDay:"[Itolo nga] LT",lastWeek:"dddd [leliphelile] [nga] LT",sameElse:"L"},relativeTime:{future:"nga %s",past:"wenteka nga %s",s:"emizuzwana lomcane",ss:"%d mzuzwana",m:"umzuzu",mm:"%d emizuzu",h:"lihora",hh:"%d emahora",d:"lilanga",dd:"%d emalanga",M:"inyanga",MM:"%d tinyanga",y:"umnyaka",yy:"%d iminyaka"},meridiemParse:/ekuseni|emini|entsambama|ebusuku/,meridiem:function(e,a,t){return e<11?"ekuseni":e<15?"emini":e<19?"entsambama":"ebusuku"},meridiemHour:function(e,a){return 12===e&&(e=0),"ekuseni"===a?e:"emini"===a?e>=11?e:e+12:"entsambama"===a||"ebusuku"===a?0===e?0:e+12:void 0},dayOfMonthOrdinalParse:/\d{1,2}/,ordinal:"%d",week:{dow:1,doy:4}}),e.defineLocale("sv",{months:"januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"s\xf6ndag_m\xe5ndag_tisdag_onsdag_torsdag_fredag_l\xf6rdag".split("_"),weekdaysShort:"s\xf6n_m\xe5n_tis_ons_tor_fre_l\xf6r".split("_"),weekdaysMin:"s\xf6_m\xe5_ti_on_to_fr_l\xf6".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [kl.] HH:mm",LLLL:"dddd D MMMM YYYY [kl.] HH:mm",lll:"D MMM YYYY HH:mm",llll:"ddd D MMM YYYY HH:mm"},calendar:{sameDay:"[Idag] LT",nextDay:"[Imorgon] LT",lastDay:"[Ig\xe5r] LT",nextWeek:"[P\xe5] dddd LT",lastWeek:"[I] dddd[s] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"f\xf6r %s sedan",s:"n\xe5gra sekunder",ss:"%d sekunder",m:"en minut",mm:"%d minuter",h:"en timme",hh:"%d timmar",d:"en dag",dd:"%d dagar",M:"en m\xe5nad",MM:"%d m\xe5nader",y:"ett \xe5r",yy:"%d \xe5r"},dayOfMonthOrdinalParse:/\d{1,2}(e|a)/,ordinal:function(e){var a=e%10;return e+(1==~~(e%100/10)?"e":1===a?"a":2===a?"a":"e")},week:{dow:1,doy:4}}),e.defineLocale("sw",{months:"Januari_Februari_Machi_Aprili_Mei_Juni_Julai_Agosti_Septemba_Oktoba_Novemba_Desemba".split("_"),monthsShort:"Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ago_Sep_Okt_Nov_Des".split("_"),weekdays:"Jumapili_Jumatatu_Jumanne_Jumatano_Alhamisi_Ijumaa_Jumamosi".split("_"),weekdaysShort:"Jpl_Jtat_Jnne_Jtan_Alh_Ijm_Jmos".split("_"),weekdaysMin:"J2_J3_J4_J5_Al_Ij_J1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[leo saa] LT",nextDay:"[kesho saa] LT",nextWeek:"[wiki ijayo] dddd [saat] LT",lastDay:"[jana] LT",lastWeek:"[wiki iliyopita] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s baadaye",past:"tokea %s",s:"hivi punde",ss:"sekunde %d",m:"dakika moja",mm:"dakika %d",h:"saa limoja",hh:"masaa %d",d:"siku moja",dd:"masiku %d",M:"mwezi mmoja",MM:"miezi %d",y:"mwaka mmoja",yy:"miaka %d"},week:{dow:1,doy:7}});var Bn={1:"\u0be7",2:"\u0be8",3:"\u0be9",4:"\u0bea",5:"\u0beb",6:"\u0bec",7:"\u0bed",8:"\u0bee",9:"\u0bef",0:"\u0be6"},qn={"\u0be7":"1","\u0be8":"2","\u0be9":"3","\u0bea":"4","\u0beb":"5","\u0bec":"6","\u0bed":"7","\u0bee":"8","\u0bef":"9","\u0be6":"0"};e.defineLocale("ta",{months:"\u0b9c\u0ba9\u0bb5\u0bb0\u0bbf_\u0baa\u0bbf\u0baa\u0bcd\u0bb0\u0bb5\u0bb0\u0bbf_\u0bae\u0bbe\u0bb0\u0bcd\u0b9a\u0bcd_\u0b8f\u0baa\u0bcd\u0bb0\u0bb2\u0bcd_\u0bae\u0bc7_\u0b9c\u0bc2\u0ba9\u0bcd_\u0b9c\u0bc2\u0bb2\u0bc8_\u0b86\u0b95\u0bb8\u0bcd\u0b9f\u0bcd_\u0b9a\u0bc6\u0baa\u0bcd\u0b9f\u0bc6\u0bae\u0bcd\u0baa\u0bb0\u0bcd_\u0b85\u0b95\u0bcd\u0b9f\u0bc7\u0bbe\u0baa\u0bb0\u0bcd_\u0ba8\u0bb5\u0bae\u0bcd\u0baa\u0bb0\u0bcd_\u0b9f\u0bbf\u0b9a\u0bae\u0bcd\u0baa\u0bb0\u0bcd".split("_"),monthsShort:"\u0b9c\u0ba9\u0bb5\u0bb0\u0bbf_\u0baa\u0bbf\u0baa\u0bcd\u0bb0\u0bb5\u0bb0\u0bbf_\u0bae\u0bbe\u0bb0\u0bcd\u0b9a\u0bcd_\u0b8f\u0baa\u0bcd\u0bb0\u0bb2\u0bcd_\u0bae\u0bc7_\u0b9c\u0bc2\u0ba9\u0bcd_\u0b9c\u0bc2\u0bb2\u0bc8_\u0b86\u0b95\u0bb8\u0bcd\u0b9f\u0bcd_\u0b9a\u0bc6\u0baa\u0bcd\u0b9f\u0bc6\u0bae\u0bcd\u0baa\u0bb0\u0bcd_\u0b85\u0b95\u0bcd\u0b9f\u0bc7\u0bbe\u0baa\u0bb0\u0bcd_\u0ba8\u0bb5\u0bae\u0bcd\u0baa\u0bb0\u0bcd_\u0b9f\u0bbf\u0b9a\u0bae\u0bcd\u0baa\u0bb0\u0bcd".split("_"),weekdays:"\u0b9e\u0bbe\u0baf\u0bbf\u0bb1\u0bcd\u0bb1\u0bc1\u0b95\u0bcd\u0b95\u0bbf\u0bb4\u0bae\u0bc8_\u0ba4\u0bbf\u0b99\u0bcd\u0b95\u0b9f\u0bcd\u0b95\u0bbf\u0bb4\u0bae\u0bc8_\u0b9a\u0bc6\u0bb5\u0bcd\u0bb5\u0bbe\u0baf\u0bcd\u0b95\u0bbf\u0bb4\u0bae\u0bc8_\u0baa\u0bc1\u0ba4\u0ba9\u0bcd\u0b95\u0bbf\u0bb4\u0bae\u0bc8_\u0bb5\u0bbf\u0baf\u0bbe\u0bb4\u0b95\u0bcd\u0b95\u0bbf\u0bb4\u0bae\u0bc8_\u0bb5\u0bc6\u0bb3\u0bcd\u0bb3\u0bbf\u0b95\u0bcd\u0b95\u0bbf\u0bb4\u0bae\u0bc8_\u0b9a\u0ba9\u0bbf\u0b95\u0bcd\u0b95\u0bbf\u0bb4\u0bae\u0bc8".split("_"),weekdaysShort:"\u0b9e\u0bbe\u0baf\u0bbf\u0bb1\u0bc1_\u0ba4\u0bbf\u0b99\u0bcd\u0b95\u0bb3\u0bcd_\u0b9a\u0bc6\u0bb5\u0bcd\u0bb5\u0bbe\u0baf\u0bcd_\u0baa\u0bc1\u0ba4\u0ba9\u0bcd_\u0bb5\u0bbf\u0baf\u0bbe\u0bb4\u0ba9\u0bcd_\u0bb5\u0bc6\u0bb3\u0bcd\u0bb3\u0bbf_\u0b9a\u0ba9\u0bbf".split("_"),weekdaysMin:"\u0b9e\u0bbe_\u0ba4\u0bbf_\u0b9a\u0bc6_\u0baa\u0bc1_\u0bb5\u0bbf_\u0bb5\u0bc6_\u0b9a".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, HH:mm",LLLL:"dddd, D MMMM YYYY, HH:mm"},calendar:{sameDay:"[\u0b87\u0ba9\u0bcd\u0bb1\u0bc1] LT",nextDay:"[\u0ba8\u0bbe\u0bb3\u0bc8] LT",nextWeek:"dddd, LT",lastDay:"[\u0ba8\u0bc7\u0bb1\u0bcd\u0bb1\u0bc1] LT",lastWeek:"[\u0b95\u0b9f\u0ba8\u0bcd\u0ba4 \u0bb5\u0bbe\u0bb0\u0bae\u0bcd] dddd, LT",sameElse:"L"},relativeTime:{future:"%s \u0b87\u0bb2\u0bcd",past:"%s \u0bae\u0bc1\u0ba9\u0bcd",s:"\u0b92\u0bb0\u0bc1 \u0b9a\u0bbf\u0bb2 \u0bb5\u0bbf\u0ba8\u0bbe\u0b9f\u0bbf\u0b95\u0bb3\u0bcd",ss:"%d \u0bb5\u0bbf\u0ba8\u0bbe\u0b9f\u0bbf\u0b95\u0bb3\u0bcd",m:"\u0b92\u0bb0\u0bc1 \u0ba8\u0bbf\u0bae\u0bbf\u0b9f\u0bae\u0bcd",mm:"%d \u0ba8\u0bbf\u0bae\u0bbf\u0b9f\u0b99\u0bcd\u0b95\u0bb3\u0bcd",h:"\u0b92\u0bb0\u0bc1 \u0bae\u0ba3\u0bbf \u0ba8\u0bc7\u0bb0\u0bae\u0bcd",hh:"%d \u0bae\u0ba3\u0bbf \u0ba8\u0bc7\u0bb0\u0bae\u0bcd",d:"\u0b92\u0bb0\u0bc1 \u0ba8\u0bbe\u0bb3\u0bcd",dd:"%d \u0ba8\u0bbe\u0b9f\u0bcd\u0b95\u0bb3\u0bcd",M:"\u0b92\u0bb0\u0bc1 \u0bae\u0bbe\u0ba4\u0bae\u0bcd",MM:"%d \u0bae\u0bbe\u0ba4\u0b99\u0bcd\u0b95\u0bb3\u0bcd",y:"\u0b92\u0bb0\u0bc1 \u0bb5\u0bb0\u0bc1\u0b9f\u0bae\u0bcd",yy:"%d \u0b86\u0ba3\u0bcd\u0b9f\u0bc1\u0b95\u0bb3\u0bcd"},dayOfMonthOrdinalParse:/\d{1,2}\u0bb5\u0ba4\u0bc1/,ordinal:function(e){return e+"\u0bb5\u0ba4\u0bc1"},preparse:function(e){return e.replace(/[\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef\u0be6]/g,function(e){return qn[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return Bn[e]})},meridiemParse:/\u0baf\u0bbe\u0bae\u0bae\u0bcd|\u0bb5\u0bc8\u0b95\u0bb1\u0bc8|\u0b95\u0bbe\u0bb2\u0bc8|\u0ba8\u0ba3\u0bcd\u0baa\u0b95\u0bb2\u0bcd|\u0b8e\u0bb1\u0bcd\u0baa\u0bbe\u0b9f\u0bc1|\u0bae\u0bbe\u0bb2\u0bc8/,meridiem:function(e,a,t){return e<2?" \u0baf\u0bbe\u0bae\u0bae\u0bcd":e<6?" \u0bb5\u0bc8\u0b95\u0bb1\u0bc8":e<10?" \u0b95\u0bbe\u0bb2\u0bc8":e<14?" \u0ba8\u0ba3\u0bcd\u0baa\u0b95\u0bb2\u0bcd":e<18?" \u0b8e\u0bb1\u0bcd\u0baa\u0bbe\u0b9f\u0bc1":e<22?" \u0bae\u0bbe\u0bb2\u0bc8":" \u0baf\u0bbe\u0bae\u0bae\u0bcd"},meridiemHour:function(e,a){return 12===e&&(e=0),"\u0baf\u0bbe\u0bae\u0bae\u0bcd"===a?e<2?e:e+12:"\u0bb5\u0bc8\u0b95\u0bb1\u0bc8"===a||"\u0b95\u0bbe\u0bb2\u0bc8"===a?e:"\u0ba8\u0ba3\u0bcd\u0baa\u0b95\u0bb2\u0bcd"===a&&e>=10?e:e+12},week:{dow:0,doy:6}}),e.defineLocale("te",{months:"\u0c1c\u0c28\u0c35\u0c30\u0c3f_\u0c2b\u0c3f\u0c2c\u0c4d\u0c30\u0c35\u0c30\u0c3f_\u0c2e\u0c3e\u0c30\u0c4d\u0c1a\u0c3f_\u0c0f\u0c2a\u0c4d\u0c30\u0c3f\u0c32\u0c4d_\u0c2e\u0c47_\u0c1c\u0c42\u0c28\u0c4d_\u0c1c\u0c42\u0c32\u0c46\u0c56_\u0c06\u0c17\u0c38\u0c4d\u0c1f\u0c41_\u0c38\u0c46\u0c2a\u0c4d\u0c1f\u0c46\u0c02\u0c2c\u0c30\u0c4d_\u0c05\u0c15\u0c4d\u0c1f\u0c4b\u0c2c\u0c30\u0c4d_\u0c28\u0c35\u0c02\u0c2c\u0c30\u0c4d_\u0c21\u0c3f\u0c38\u0c46\u0c02\u0c2c\u0c30\u0c4d".split("_"),monthsShort:"\u0c1c\u0c28._\u0c2b\u0c3f\u0c2c\u0c4d\u0c30._\u0c2e\u0c3e\u0c30\u0c4d\u0c1a\u0c3f_\u0c0f\u0c2a\u0c4d\u0c30\u0c3f._\u0c2e\u0c47_\u0c1c\u0c42\u0c28\u0c4d_\u0c1c\u0c42\u0c32\u0c46\u0c56_\u0c06\u0c17._\u0c38\u0c46\u0c2a\u0c4d._\u0c05\u0c15\u0c4d\u0c1f\u0c4b._\u0c28\u0c35._\u0c21\u0c3f\u0c38\u0c46.".split("_"),monthsParseExact:!0,weekdays:"\u0c06\u0c26\u0c3f\u0c35\u0c3e\u0c30\u0c02_\u0c38\u0c4b\u0c2e\u0c35\u0c3e\u0c30\u0c02_\u0c2e\u0c02\u0c17\u0c33\u0c35\u0c3e\u0c30\u0c02_\u0c2c\u0c41\u0c27\u0c35\u0c3e\u0c30\u0c02_\u0c17\u0c41\u0c30\u0c41\u0c35\u0c3e\u0c30\u0c02_\u0c36\u0c41\u0c15\u0c4d\u0c30\u0c35\u0c3e\u0c30\u0c02_\u0c36\u0c28\u0c3f\u0c35\u0c3e\u0c30\u0c02".split("_"),weekdaysShort:"\u0c06\u0c26\u0c3f_\u0c38\u0c4b\u0c2e_\u0c2e\u0c02\u0c17\u0c33_\u0c2c\u0c41\u0c27_\u0c17\u0c41\u0c30\u0c41_\u0c36\u0c41\u0c15\u0c4d\u0c30_\u0c36\u0c28\u0c3f".split("_"),weekdaysMin:"\u0c06_\u0c38\u0c4b_\u0c2e\u0c02_\u0c2c\u0c41_\u0c17\u0c41_\u0c36\u0c41_\u0c36".split("_"),longDateFormat:{LT:"A h:mm",LTS:"A h:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm",LLLL:"dddd, D MMMM YYYY, A h:mm"},calendar:{sameDay:"[\u0c28\u0c47\u0c21\u0c41] LT",nextDay:"[\u0c30\u0c47\u0c2a\u0c41] LT",nextWeek:"dddd, LT",lastDay:"[\u0c28\u0c3f\u0c28\u0c4d\u0c28] LT",lastWeek:"[\u0c17\u0c24] dddd, LT",sameElse:"L"},relativeTime:{future:"%s \u0c32\u0c4b",past:"%s \u0c15\u0c4d\u0c30\u0c3f\u0c24\u0c02",s:"\u0c15\u0c4a\u0c28\u0c4d\u0c28\u0c3f \u0c15\u0c4d\u0c37\u0c23\u0c3e\u0c32\u0c41",ss:"%d \u0c38\u0c46\u0c15\u0c28\u0c4d\u0c32\u0c41",m:"\u0c12\u0c15 \u0c28\u0c3f\u0c2e\u0c3f\u0c37\u0c02",mm:"%d \u0c28\u0c3f\u0c2e\u0c3f\u0c37\u0c3e\u0c32\u0c41",h:"\u0c12\u0c15 \u0c17\u0c02\u0c1f",hh:"%d \u0c17\u0c02\u0c1f\u0c32\u0c41",d:"\u0c12\u0c15 \u0c30\u0c4b\u0c1c\u0c41",dd:"%d \u0c30\u0c4b\u0c1c\u0c41\u0c32\u0c41",M:"\u0c12\u0c15 \u0c28\u0c46\u0c32",MM:"%d \u0c28\u0c46\u0c32\u0c32\u0c41",y:"\u0c12\u0c15 \u0c38\u0c02\u0c35\u0c24\u0c4d\u0c38\u0c30\u0c02",yy:"%d \u0c38\u0c02\u0c35\u0c24\u0c4d\u0c38\u0c30\u0c3e\u0c32\u0c41"},dayOfMonthOrdinalParse:/\d{1,2}\u0c35/,ordinal:"%d\u0c35",meridiemParse:/\u0c30\u0c3e\u0c24\u0c4d\u0c30\u0c3f|\u0c09\u0c26\u0c2f\u0c02|\u0c2e\u0c27\u0c4d\u0c2f\u0c3e\u0c39\u0c4d\u0c28\u0c02|\u0c38\u0c3e\u0c2f\u0c02\u0c24\u0c4d\u0c30\u0c02/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u0c30\u0c3e\u0c24\u0c4d\u0c30\u0c3f"===a?e<4?e:e+12:"\u0c09\u0c26\u0c2f\u0c02"===a?e:"\u0c2e\u0c27\u0c4d\u0c2f\u0c3e\u0c39\u0c4d\u0c28\u0c02"===a?e>=10?e:e+12:"\u0c38\u0c3e\u0c2f\u0c02\u0c24\u0c4d\u0c30\u0c02"===a?e+12:void 0},meridiem:function(e,a,t){return e<4?"\u0c30\u0c3e\u0c24\u0c4d\u0c30\u0c3f":e<10?"\u0c09\u0c26\u0c2f\u0c02":e<17?"\u0c2e\u0c27\u0c4d\u0c2f\u0c3e\u0c39\u0c4d\u0c28\u0c02":e<20?"\u0c38\u0c3e\u0c2f\u0c02\u0c24\u0c4d\u0c30\u0c02":"\u0c30\u0c3e\u0c24\u0c4d\u0c30\u0c3f"},week:{dow:0,doy:6}}),e.defineLocale("tet",{months:"Janeiru_Fevereiru_Marsu_Abril_Maiu_Juniu_Juliu_Augustu_Setembru_Outubru_Novembru_Dezembru".split("_"),monthsShort:"Jan_Fev_Mar_Abr_Mai_Jun_Jul_Aug_Set_Out_Nov_Dez".split("_"),weekdays:"Domingu_Segunda_Tersa_Kuarta_Kinta_Sexta_Sabadu".split("_"),weekdaysShort:"Dom_Seg_Ters_Kua_Kint_Sext_Sab".split("_"),weekdaysMin:"Do_Seg_Te_Ku_Ki_Sex_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Ohin iha] LT",nextDay:"[Aban iha] LT",nextWeek:"dddd [iha] LT",lastDay:"[Horiseik iha] LT",lastWeek:"dddd [semana kotuk] [iha] LT",sameElse:"L"},relativeTime:{future:"iha %s",past:"%s liuba",s:"minutu balun",ss:"minutu %d",m:"minutu ida",mm:"minutus %d",h:"horas ida",hh:"horas %d",d:"loron ida",dd:"loron %d",M:"fulan ida",MM:"fulan %d",y:"tinan ida",yy:"tinan %d"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var a=e%10;return e+(1==~~(e%100/10)?"th":1===a?"st":2===a?"nd":3===a?"rd":"th")},week:{dow:1,doy:4}}),e.defineLocale("th",{months:"\u0e21\u0e01\u0e23\u0e32\u0e04\u0e21_\u0e01\u0e38\u0e21\u0e20\u0e32\u0e1e\u0e31\u0e19\u0e18\u0e4c_\u0e21\u0e35\u0e19\u0e32\u0e04\u0e21_\u0e40\u0e21\u0e29\u0e32\u0e22\u0e19_\u0e1e\u0e24\u0e29\u0e20\u0e32\u0e04\u0e21_\u0e21\u0e34\u0e16\u0e38\u0e19\u0e32\u0e22\u0e19_\u0e01\u0e23\u0e01\u0e0e\u0e32\u0e04\u0e21_\u0e2a\u0e34\u0e07\u0e2b\u0e32\u0e04\u0e21_\u0e01\u0e31\u0e19\u0e22\u0e32\u0e22\u0e19_\u0e15\u0e38\u0e25\u0e32\u0e04\u0e21_\u0e1e\u0e24\u0e28\u0e08\u0e34\u0e01\u0e32\u0e22\u0e19_\u0e18\u0e31\u0e19\u0e27\u0e32\u0e04\u0e21".split("_"),monthsShort:"\u0e21.\u0e04._\u0e01.\u0e1e._\u0e21\u0e35.\u0e04._\u0e40\u0e21.\u0e22._\u0e1e.\u0e04._\u0e21\u0e34.\u0e22._\u0e01.\u0e04._\u0e2a.\u0e04._\u0e01.\u0e22._\u0e15.\u0e04._\u0e1e.\u0e22._\u0e18.\u0e04.".split("_"),monthsParseExact:!0,weekdays:"\u0e2d\u0e32\u0e17\u0e34\u0e15\u0e22\u0e4c_\u0e08\u0e31\u0e19\u0e17\u0e23\u0e4c_\u0e2d\u0e31\u0e07\u0e04\u0e32\u0e23_\u0e1e\u0e38\u0e18_\u0e1e\u0e24\u0e2b\u0e31\u0e2a\u0e1a\u0e14\u0e35_\u0e28\u0e38\u0e01\u0e23\u0e4c_\u0e40\u0e2a\u0e32\u0e23\u0e4c".split("_"),weekdaysShort:"\u0e2d\u0e32\u0e17\u0e34\u0e15\u0e22\u0e4c_\u0e08\u0e31\u0e19\u0e17\u0e23\u0e4c_\u0e2d\u0e31\u0e07\u0e04\u0e32\u0e23_\u0e1e\u0e38\u0e18_\u0e1e\u0e24\u0e2b\u0e31\u0e2a_\u0e28\u0e38\u0e01\u0e23\u0e4c_\u0e40\u0e2a\u0e32\u0e23\u0e4c".split("_"),weekdaysMin:"\u0e2d\u0e32._\u0e08._\u0e2d._\u0e1e._\u0e1e\u0e24._\u0e28._\u0e2a.".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY \u0e40\u0e27\u0e25\u0e32 H:mm",LLLL:"\u0e27\u0e31\u0e19dddd\u0e17\u0e35\u0e48 D MMMM YYYY \u0e40\u0e27\u0e25\u0e32 H:mm"},meridiemParse:/\u0e01\u0e48\u0e2d\u0e19\u0e40\u0e17\u0e35\u0e48\u0e22\u0e07|\u0e2b\u0e25\u0e31\u0e07\u0e40\u0e17\u0e35\u0e48\u0e22\u0e07/,isPM:function(e){return"\u0e2b\u0e25\u0e31\u0e07\u0e40\u0e17\u0e35\u0e48\u0e22\u0e07"===e},meridiem:function(e,a,t){return e<12?"\u0e01\u0e48\u0e2d\u0e19\u0e40\u0e17\u0e35\u0e48\u0e22\u0e07":"\u0e2b\u0e25\u0e31\u0e07\u0e40\u0e17\u0e35\u0e48\u0e22\u0e07"},calendar:{sameDay:"[\u0e27\u0e31\u0e19\u0e19\u0e35\u0e49 \u0e40\u0e27\u0e25\u0e32] LT",nextDay:"[\u0e1e\u0e23\u0e38\u0e48\u0e07\u0e19\u0e35\u0e49 \u0e40\u0e27\u0e25\u0e32] LT",nextWeek:"dddd[\u0e2b\u0e19\u0e49\u0e32 \u0e40\u0e27\u0e25\u0e32] LT",lastDay:"[\u0e40\u0e21\u0e37\u0e48\u0e2d\u0e27\u0e32\u0e19\u0e19\u0e35\u0e49 \u0e40\u0e27\u0e25\u0e32] LT",lastWeek:"[\u0e27\u0e31\u0e19]dddd[\u0e17\u0e35\u0e48\u0e41\u0e25\u0e49\u0e27 \u0e40\u0e27\u0e25\u0e32] LT",sameElse:"L"},relativeTime:{future:"\u0e2d\u0e35\u0e01 %s",past:"%s\u0e17\u0e35\u0e48\u0e41\u0e25\u0e49\u0e27",s:"\u0e44\u0e21\u0e48\u0e01\u0e35\u0e48\u0e27\u0e34\u0e19\u0e32\u0e17\u0e35",ss:"%d \u0e27\u0e34\u0e19\u0e32\u0e17\u0e35",m:"1 \u0e19\u0e32\u0e17\u0e35",mm:"%d \u0e19\u0e32\u0e17\u0e35",h:"1 \u0e0a\u0e31\u0e48\u0e27\u0e42\u0e21\u0e07",hh:"%d \u0e0a\u0e31\u0e48\u0e27\u0e42\u0e21\u0e07",d:"1 \u0e27\u0e31\u0e19",dd:"%d \u0e27\u0e31\u0e19",M:"1 \u0e40\u0e14\u0e37\u0e2d\u0e19",MM:"%d \u0e40\u0e14\u0e37\u0e2d\u0e19",y:"1 \u0e1b\u0e35",yy:"%d \u0e1b\u0e35"}}),e.defineLocale("tl-ph",{months:"Enero_Pebrero_Marso_Abril_Mayo_Hunyo_Hulyo_Agosto_Setyembre_Oktubre_Nobyembre_Disyembre".split("_"),monthsShort:"Ene_Peb_Mar_Abr_May_Hun_Hul_Ago_Set_Okt_Nob_Dis".split("_"),weekdays:"Linggo_Lunes_Martes_Miyerkules_Huwebes_Biyernes_Sabado".split("_"),weekdaysShort:"Lin_Lun_Mar_Miy_Huw_Biy_Sab".split("_"),weekdaysMin:"Li_Lu_Ma_Mi_Hu_Bi_Sab".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"MM/D/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY HH:mm",LLLL:"dddd, MMMM DD, YYYY HH:mm"},calendar:{sameDay:"LT [ngayong araw]",nextDay:"[Bukas ng] LT",nextWeek:"LT [sa susunod na] dddd",lastDay:"LT [kahapon]",lastWeek:"LT [noong nakaraang] dddd",sameElse:"L"},relativeTime:{future:"sa loob ng %s",past:"%s ang nakalipas",s:"ilang segundo",ss:"%d segundo",m:"isang minuto",mm:"%d minuto",h:"isang oras",hh:"%d oras",d:"isang araw",dd:"%d araw",M:"isang buwan",MM:"%d buwan",y:"isang taon",yy:"%d taon"},dayOfMonthOrdinalParse:/\d{1,2}/,ordinal:function(e){return e},week:{dow:1,doy:4}});var Qn="pagh_wa\u2019_cha\u2019_wej_loS_vagh_jav_Soch_chorgh_Hut".split("_");e.defineLocale("tlh",{months:"tera\u2019 jar wa\u2019_tera\u2019 jar cha\u2019_tera\u2019 jar wej_tera\u2019 jar loS_tera\u2019 jar vagh_tera\u2019 jar jav_tera\u2019 jar Soch_tera\u2019 jar chorgh_tera\u2019 jar Hut_tera\u2019 jar wa\u2019maH_tera\u2019 jar wa\u2019maH wa\u2019_tera\u2019 jar wa\u2019maH cha\u2019".split("_"),monthsShort:"jar wa\u2019_jar cha\u2019_jar wej_jar loS_jar vagh_jar jav_jar Soch_jar chorgh_jar Hut_jar wa\u2019maH_jar wa\u2019maH wa\u2019_jar wa\u2019maH cha\u2019".split("_"),monthsParseExact:!0,weekdays:"lojmItjaj_DaSjaj_povjaj_ghItlhjaj_loghjaj_buqjaj_ghInjaj".split("_"),weekdaysShort:"lojmItjaj_DaSjaj_povjaj_ghItlhjaj_loghjaj_buqjaj_ghInjaj".split("_"),weekdaysMin:"lojmItjaj_DaSjaj_povjaj_ghItlhjaj_loghjaj_buqjaj_ghInjaj".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[DaHjaj] LT",nextDay:"[wa\u2019leS] LT",nextWeek:"LLL",lastDay:"[wa\u2019Hu\u2019] LT",lastWeek:"LLL",sameElse:"L"},relativeTime:{future:function(e){var a=e;return a=-1!==e.indexOf("jaj")?a.slice(0,-3)+"leS":-1!==e.indexOf("jar")?a.slice(0,-3)+"waQ":-1!==e.indexOf("DIS")?a.slice(0,-3)+"nem":a+" pIq"},past:function(e){var a=e;return a=-1!==e.indexOf("jaj")?a.slice(0,-3)+"Hu\u2019":-1!==e.indexOf("jar")?a.slice(0,-3)+"wen":-1!==e.indexOf("DIS")?a.slice(0,-3)+"ben":a+" ret"},s:"puS lup",ss:xa,m:"wa\u2019 tup",mm:xa,h:"wa\u2019 rep",hh:xa,d:"wa\u2019 jaj",dd:xa,M:"wa\u2019 jar",MM:xa,y:"wa\u2019 DIS",yy:xa},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}});var Xn={1:"'inci",5:"'inci",8:"'inci",70:"'inci",80:"'inci",2:"'nci",7:"'nci",20:"'nci",50:"'nci",3:"'\xfcnc\xfc",4:"'\xfcnc\xfc",100:"'\xfcnc\xfc",6:"'nc\u0131",9:"'uncu",10:"'uncu",30:"'uncu",60:"'\u0131nc\u0131",90:"'\u0131nc\u0131"};e.defineLocale("tr",{months:"Ocak_\u015eubat_Mart_Nisan_May\u0131s_Haziran_Temmuz_A\u011fustos_Eyl\xfcl_Ekim_Kas\u0131m_Aral\u0131k".split("_"),monthsShort:"Oca_\u015eub_Mar_Nis_May_Haz_Tem_A\u011fu_Eyl_Eki_Kas_Ara".split("_"),weekdays:"Pazar_Pazartesi_Sal\u0131_\xc7ar\u015famba_Per\u015fembe_Cuma_Cumartesi".split("_"),weekdaysShort:"Paz_Pts_Sal_\xc7ar_Per_Cum_Cts".split("_"),weekdaysMin:"Pz_Pt_Sa_\xc7a_Pe_Cu_Ct".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[bug\xfcn saat] LT",nextDay:"[yar\u0131n saat] LT",nextWeek:"[gelecek] dddd [saat] LT",lastDay:"[d\xfcn] LT",lastWeek:"[ge\xe7en] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s sonra",past:"%s \xf6nce",s:"birka\xe7 saniye",ss:"%d saniye",m:"bir dakika",mm:"%d dakika",h:"bir saat",hh:"%d saat",d:"bir g\xfcn",dd:"%d g\xfcn",M:"bir ay",MM:"%d ay",y:"bir y\u0131l",yy:"%d y\u0131l"},dayOfMonthOrdinalParse:/\d{1,2}'(inci|nci|\xfcnc\xfc|nc\u0131|uncu|\u0131nc\u0131)/,ordinal:function(e){if(0===e)return e+"'\u0131nc\u0131";var a=e%10;return e+(Xn[a]||Xn[e%100-a]||Xn[e>=100?100:null])},week:{dow:1,doy:7}}),e.defineLocale("tzl",{months:"Januar_Fevraglh_Mar\xe7_Avr\xefu_Mai_G\xfcn_Julia_Guscht_Setemvar_Listop\xe4ts_Noemvar_Zecemvar".split("_"),monthsShort:"Jan_Fev_Mar_Avr_Mai_G\xfcn_Jul_Gus_Set_Lis_Noe_Zec".split("_"),weekdays:"S\xfaladi_L\xfane\xe7i_Maitzi_M\xe1rcuri_Xh\xfaadi_Vi\xe9ner\xe7i_S\xe1turi".split("_"),weekdaysShort:"S\xfal_L\xfan_Mai_M\xe1r_Xh\xfa_Vi\xe9_S\xe1t".split("_"),weekdaysMin:"S\xfa_L\xfa_Ma_M\xe1_Xh_Vi_S\xe1".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD.MM.YYYY",LL:"D. MMMM [dallas] YYYY",LLL:"D. MMMM [dallas] YYYY HH.mm",LLLL:"dddd, [li] D. MMMM [dallas] YYYY HH.mm"},meridiemParse:/d\'o|d\'a/i,isPM:function(e){return"d'o"===e.toLowerCase()},meridiem:function(e,a,t){return e>11?t?"d'o":"D'O":t?"d'a":"D'A"},calendar:{sameDay:"[oxhi \xe0] LT",nextDay:"[dem\xe0 \xe0] LT",nextWeek:"dddd [\xe0] LT",lastDay:"[ieiri \xe0] LT",lastWeek:"[s\xfcr el] dddd [lasteu \xe0] LT",sameElse:"L"},relativeTime:{future:"osprei %s",past:"ja%s",s:Pa,ss:Pa,m:Pa,mm:Pa,h:Pa,hh:Pa,d:Pa,dd:Pa,M:Pa,MM:Pa,y:Pa,yy:Pa},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),e.defineLocale("tzm-latn",{months:"innayr_br\u02e4ayr\u02e4_mar\u02e4s\u02e4_ibrir_mayyw_ywnyw_ywlywz_\u0263w\u0161t_\u0161wtanbir_kt\u02e4wbr\u02e4_nwwanbir_dwjnbir".split("_"),monthsShort:"innayr_br\u02e4ayr\u02e4_mar\u02e4s\u02e4_ibrir_mayyw_ywnyw_ywlywz_\u0263w\u0161t_\u0161wtanbir_kt\u02e4wbr\u02e4_nwwanbir_dwjnbir".split("_"),weekdays:"asamas_aynas_asinas_akras_akwas_asimwas_asi\u1e0dyas".split("_"),weekdaysShort:"asamas_aynas_asinas_akras_akwas_asimwas_asi\u1e0dyas".split("_"),weekdaysMin:"asamas_aynas_asinas_akras_akwas_asimwas_asi\u1e0dyas".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[asdkh g] LT",nextDay:"[aska g] LT",nextWeek:"dddd [g] LT",lastDay:"[assant g] LT",lastWeek:"dddd [g] LT",sameElse:"L"},relativeTime:{future:"dadkh s yan %s",past:"yan %s",s:"imik",ss:"%d imik",m:"minu\u1e0d",mm:"%d minu\u1e0d",h:"sa\u025ba",hh:"%d tassa\u025bin",d:"ass",dd:"%d ossan",M:"ayowr",MM:"%d iyyirn",y:"asgas",yy:"%d isgasn"},week:{dow:6,doy:12}}),e.defineLocale("tzm",{months:"\u2d49\u2d4f\u2d4f\u2d30\u2d62\u2d54_\u2d31\u2d55\u2d30\u2d62\u2d55_\u2d4e\u2d30\u2d55\u2d5a_\u2d49\u2d31\u2d54\u2d49\u2d54_\u2d4e\u2d30\u2d62\u2d62\u2d53_\u2d62\u2d53\u2d4f\u2d62\u2d53_\u2d62\u2d53\u2d4d\u2d62\u2d53\u2d63_\u2d56\u2d53\u2d5b\u2d5c_\u2d5b\u2d53\u2d5c\u2d30\u2d4f\u2d31\u2d49\u2d54_\u2d3d\u2d5f\u2d53\u2d31\u2d55_\u2d4f\u2d53\u2d61\u2d30\u2d4f\u2d31\u2d49\u2d54_\u2d37\u2d53\u2d4a\u2d4f\u2d31\u2d49\u2d54".split("_"),monthsShort:"\u2d49\u2d4f\u2d4f\u2d30\u2d62\u2d54_\u2d31\u2d55\u2d30\u2d62\u2d55_\u2d4e\u2d30\u2d55\u2d5a_\u2d49\u2d31\u2d54\u2d49\u2d54_\u2d4e\u2d30\u2d62\u2d62\u2d53_\u2d62\u2d53\u2d4f\u2d62\u2d53_\u2d62\u2d53\u2d4d\u2d62\u2d53\u2d63_\u2d56\u2d53\u2d5b\u2d5c_\u2d5b\u2d53\u2d5c\u2d30\u2d4f\u2d31\u2d49\u2d54_\u2d3d\u2d5f\u2d53\u2d31\u2d55_\u2d4f\u2d53\u2d61\u2d30\u2d4f\u2d31\u2d49\u2d54_\u2d37\u2d53\u2d4a\u2d4f\u2d31\u2d49\u2d54".split("_"),weekdays:"\u2d30\u2d59\u2d30\u2d4e\u2d30\u2d59_\u2d30\u2d62\u2d4f\u2d30\u2d59_\u2d30\u2d59\u2d49\u2d4f\u2d30\u2d59_\u2d30\u2d3d\u2d54\u2d30\u2d59_\u2d30\u2d3d\u2d61\u2d30\u2d59_\u2d30\u2d59\u2d49\u2d4e\u2d61\u2d30\u2d59_\u2d30\u2d59\u2d49\u2d39\u2d62\u2d30\u2d59".split("_"),weekdaysShort:"\u2d30\u2d59\u2d30\u2d4e\u2d30\u2d59_\u2d30\u2d62\u2d4f\u2d30\u2d59_\u2d30\u2d59\u2d49\u2d4f\u2d30\u2d59_\u2d30\u2d3d\u2d54\u2d30\u2d59_\u2d30\u2d3d\u2d61\u2d30\u2d59_\u2d30\u2d59\u2d49\u2d4e\u2d61\u2d30\u2d59_\u2d30\u2d59\u2d49\u2d39\u2d62\u2d30\u2d59".split("_"),weekdaysMin:"\u2d30\u2d59\u2d30\u2d4e\u2d30\u2d59_\u2d30\u2d62\u2d4f\u2d30\u2d59_\u2d30\u2d59\u2d49\u2d4f\u2d30\u2d59_\u2d30\u2d3d\u2d54\u2d30\u2d59_\u2d30\u2d3d\u2d61\u2d30\u2d59_\u2d30\u2d59\u2d49\u2d4e\u2d61\u2d30\u2d59_\u2d30\u2d59\u2d49\u2d39\u2d62\u2d30\u2d59".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[\u2d30\u2d59\u2d37\u2d45 \u2d34] LT",nextDay:"[\u2d30\u2d59\u2d3d\u2d30 \u2d34] LT",nextWeek:"dddd [\u2d34] LT",lastDay:"[\u2d30\u2d5a\u2d30\u2d4f\u2d5c \u2d34] LT",lastWeek:"dddd [\u2d34] LT",sameElse:"L"},relativeTime:{future:"\u2d37\u2d30\u2d37\u2d45 \u2d59 \u2d62\u2d30\u2d4f %s",past:"\u2d62\u2d30\u2d4f %s",s:"\u2d49\u2d4e\u2d49\u2d3d",ss:"%d \u2d49\u2d4e\u2d49\u2d3d",m:"\u2d4e\u2d49\u2d4f\u2d53\u2d3a",mm:"%d \u2d4e\u2d49\u2d4f\u2d53\u2d3a",h:"\u2d59\u2d30\u2d44\u2d30",hh:"%d \u2d5c\u2d30\u2d59\u2d59\u2d30\u2d44\u2d49\u2d4f",d:"\u2d30\u2d59\u2d59",dd:"%d o\u2d59\u2d59\u2d30\u2d4f",M:"\u2d30\u2d62o\u2d53\u2d54",MM:"%d \u2d49\u2d62\u2d62\u2d49\u2d54\u2d4f",y:"\u2d30\u2d59\u2d33\u2d30\u2d59",yy:"%d \u2d49\u2d59\u2d33\u2d30\u2d59\u2d4f"},week:{dow:6,doy:12}}),e.defineLocale("uk",{months:{format:"\u0441\u0456\u0447\u043d\u044f_\u043b\u044e\u0442\u043e\u0433\u043e_\u0431\u0435\u0440\u0435\u0437\u043d\u044f_\u043a\u0432\u0456\u0442\u043d\u044f_\u0442\u0440\u0430\u0432\u043d\u044f_\u0447\u0435\u0440\u0432\u043d\u044f_\u043b\u0438\u043f\u043d\u044f_\u0441\u0435\u0440\u043f\u043d\u044f_\u0432\u0435\u0440\u0435\u0441\u043d\u044f_\u0436\u043e\u0432\u0442\u043d\u044f_\u043b\u0438\u0441\u0442\u043e\u043f\u0430\u0434\u0430_\u0433\u0440\u0443\u0434\u043d\u044f".split("_"),standalone:"\u0441\u0456\u0447\u0435\u043d\u044c_\u043b\u044e\u0442\u0438\u0439_\u0431\u0435\u0440\u0435\u0437\u0435\u043d\u044c_\u043a\u0432\u0456\u0442\u0435\u043d\u044c_\u0442\u0440\u0430\u0432\u0435\u043d\u044c_\u0447\u0435\u0440\u0432\u0435\u043d\u044c_\u043b\u0438\u043f\u0435\u043d\u044c_\u0441\u0435\u0440\u043f\u0435\u043d\u044c_\u0432\u0435\u0440\u0435\u0441\u0435\u043d\u044c_\u0436\u043e\u0432\u0442\u0435\u043d\u044c_\u043b\u0438\u0441\u0442\u043e\u043f\u0430\u0434_\u0433\u0440\u0443\u0434\u0435\u043d\u044c".split("_")},monthsShort:"\u0441\u0456\u0447_\u043b\u044e\u0442_\u0431\u0435\u0440_\u043a\u0432\u0456\u0442_\u0442\u0440\u0430\u0432_\u0447\u0435\u0440\u0432_\u043b\u0438\u043f_\u0441\u0435\u0440\u043f_\u0432\u0435\u0440_\u0436\u043e\u0432\u0442_\u043b\u0438\u0441\u0442_\u0433\u0440\u0443\u0434".split("_"),weekdays:function(e,a){var t={nominative:"\u043d\u0435\u0434\u0456\u043b\u044f_\u043f\u043e\u043d\u0435\u0434\u0456\u043b\u043e\u043a_\u0432\u0456\u0432\u0442\u043e\u0440\u043e\u043a_\u0441\u0435\u0440\u0435\u0434\u0430_\u0447\u0435\u0442\u0432\u0435\u0440_\u043f\u2019\u044f\u0442\u043d\u0438\u0446\u044f_\u0441\u0443\u0431\u043e\u0442\u0430".split("_"),accusative:"\u043d\u0435\u0434\u0456\u043b\u044e_\u043f\u043e\u043d\u0435\u0434\u0456\u043b\u043e\u043a_\u0432\u0456\u0432\u0442\u043e\u0440\u043e\u043a_\u0441\u0435\u0440\u0435\u0434\u0443_\u0447\u0435\u0442\u0432\u0435\u0440_\u043f\u2019\u044f\u0442\u043d\u0438\u0446\u044e_\u0441\u0443\u0431\u043e\u0442\u0443".split("_"),genitive:"\u043d\u0435\u0434\u0456\u043b\u0456_\u043f\u043e\u043d\u0435\u0434\u0456\u043b\u043a\u0430_\u0432\u0456\u0432\u0442\u043e\u0440\u043a\u0430_\u0441\u0435\u0440\u0435\u0434\u0438_\u0447\u0435\u0442\u0432\u0435\u0440\u0433\u0430_\u043f\u2019\u044f\u0442\u043d\u0438\u0446\u0456_\u0441\u0443\u0431\u043e\u0442\u0438".split("_")};return e?t[/(\[[\u0412\u0432\u0423\u0443]\]) ?dddd/.test(a)?"accusative":/\[?(?:\u043c\u0438\u043d\u0443\u043b\u043e\u0457|\u043d\u0430\u0441\u0442\u0443\u043f\u043d\u043e\u0457)? ?\] ?dddd/.test(a)?"genitive":"nominative"][e.day()]:t.nominative},weekdaysShort:"\u043d\u0434_\u043f\u043d_\u0432\u0442_\u0441\u0440_\u0447\u0442_\u043f\u0442_\u0441\u0431".split("_"),weekdaysMin:"\u043d\u0434_\u043f\u043d_\u0432\u0442_\u0441\u0440_\u0447\u0442_\u043f\u0442_\u0441\u0431".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY \u0440.",LLL:"D MMMM YYYY \u0440., HH:mm",LLLL:"dddd, D MMMM YYYY \u0440., HH:mm"},calendar:{sameDay:Wa("[\u0421\u044c\u043e\u0433\u043e\u0434\u043d\u0456 "),nextDay:Wa("[\u0417\u0430\u0432\u0442\u0440\u0430 "),lastDay:Wa("[\u0412\u0447\u043e\u0440\u0430 "),nextWeek:Wa("[\u0423] dddd ["),lastWeek:function(){switch(this.day()){case 0:case 3:case 5:case 6:return Wa("[\u041c\u0438\u043d\u0443\u043b\u043e\u0457] dddd [").call(this);case 1:case 2:case 4:return Wa("[\u041c\u0438\u043d\u0443\u043b\u043e\u0433\u043e] dddd [").call(this)}},sameElse:"L"},relativeTime:{future:"\u0437\u0430 %s",past:"%s \u0442\u043e\u043c\u0443",s:"\u0434\u0435\u043a\u0456\u043b\u044c\u043a\u0430 \u0441\u0435\u043a\u0443\u043d\u0434",ss:Oa,m:Oa,mm:Oa,h:"\u0433\u043e\u0434\u0438\u043d\u0443",hh:Oa,d:"\u0434\u0435\u043d\u044c",dd:Oa,M:"\u043c\u0456\u0441\u044f\u0446\u044c",MM:Oa,y:"\u0440\u0456\u043a",yy:Oa},meridiemParse:/\u043d\u043e\u0447\u0456|\u0440\u0430\u043d\u043a\u0443|\u0434\u043d\u044f|\u0432\u0435\u0447\u043e\u0440\u0430/,isPM:function(e){return/^(\u0434\u043d\u044f|\u0432\u0435\u0447\u043e\u0440\u0430)$/.test(e)},meridiem:function(e,a,t){return e<4?"\u043d\u043e\u0447\u0456":e<12?"\u0440\u0430\u043d\u043a\u0443":e<17?"\u0434\u043d\u044f":"\u0432\u0435\u0447\u043e\u0440\u0430"},dayOfMonthOrdinalParse:/\d{1,2}-(\u0439|\u0433\u043e)/,ordinal:function(e,a){switch(a){case"M":case"d":case"DDD":case"w":case"W":return e+"-\u0439";case"D":return e+"-\u0433\u043e";default:return e}},week:{dow:1,doy:7}});var ed=["\u062c\u0646\u0648\u0631\u06cc","\u0641\u0631\u0648\u0631\u06cc","\u0645\u0627\u0631\u0686","\u0627\u067e\u0631\u06cc\u0644","\u0645\u0626\u06cc","\u062c\u0648\u0646","\u062c\u0648\u0644\u0627\u0626\u06cc","\u0627\u06af\u0633\u062a","\u0633\u062a\u0645\u0628\u0631","\u0627\u06a9\u062a\u0648\u0628\u0631","\u0646\u0648\u0645\u0628\u0631","\u062f\u0633\u0645\u0628\u0631"],ad=["\u0627\u062a\u0648\u0627\u0631","\u067e\u06cc\u0631","\u0645\u0646\u06af\u0644","\u0628\u062f\u06be","\u062c\u0645\u0639\u0631\u0627\u062a","\u062c\u0645\u0639\u06c1","\u06c1\u0641\u062a\u06c1"];return e.defineLocale("ur",{months:ed,monthsShort:ed,weekdays:ad,weekdaysShort:ad,weekdaysMin:ad,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd\u060c D MMMM YYYY HH:mm"},meridiemParse:/\u0635\u0628\u062d|\u0634\u0627\u0645/,isPM:function(e){return"\u0634\u0627\u0645"===e},meridiem:function(e,a,t){return e<12?"\u0635\u0628\u062d":"\u0634\u0627\u0645"},calendar:{sameDay:"[\u0622\u062c \u0628\u0648\u0642\u062a] LT",nextDay:"[\u06a9\u0644 \u0628\u0648\u0642\u062a] LT",nextWeek:"dddd [\u0628\u0648\u0642\u062a] LT",lastDay:"[\u06af\u0630\u0634\u062a\u06c1 \u0631\u0648\u0632 \u0628\u0648\u0642\u062a] LT",lastWeek:"[\u06af\u0630\u0634\u062a\u06c1] dddd [\u0628\u0648\u0642\u062a] LT",sameElse:"L"},relativeTime:{future:"%s \u0628\u0639\u062f",past:"%s \u0642\u0628\u0644",s:"\u0686\u0646\u062f \u0633\u06cc\u06a9\u0646\u0688",ss:"%d \u0633\u06cc\u06a9\u0646\u0688",m:"\u0627\u06cc\u06a9 \u0645\u0646\u0679",mm:"%d \u0645\u0646\u0679",h:"\u0627\u06cc\u06a9 \u06af\u06be\u0646\u0679\u06c1",hh:"%d \u06af\u06be\u0646\u0679\u06d2",d:"\u0627\u06cc\u06a9 \u062f\u0646",dd:"%d \u062f\u0646",M:"\u0627\u06cc\u06a9 \u0645\u0627\u06c1",MM:"%d \u0645\u0627\u06c1",y:"\u0627\u06cc\u06a9 \u0633\u0627\u0644",yy:"%d \u0633\u0627\u0644"},preparse:function(e){return e.replace(/\u060c/g,",")},postformat:function(e){return e.replace(/,/g,"\u060c")},week:{dow:1,doy:4}}),e.defineLocale("uz-latn",{months:"Yanvar_Fevral_Mart_Aprel_May_Iyun_Iyul_Avgust_Sentabr_Oktabr_Noyabr_Dekabr".split("_"),monthsShort:"Yan_Fev_Mar_Apr_May_Iyun_Iyul_Avg_Sen_Okt_Noy_Dek".split("_"),weekdays:"Yakshanba_Dushanba_Seshanba_Chorshanba_Payshanba_Juma_Shanba".split("_"),weekdaysShort:"Yak_Dush_Sesh_Chor_Pay_Jum_Shan".split("_"),weekdaysMin:"Ya_Du_Se_Cho_Pa_Ju_Sha".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"D MMMM YYYY, dddd HH:mm"},calendar:{sameDay:"[Bugun soat] LT [da]",nextDay:"[Ertaga] LT [da]",nextWeek:"dddd [kuni soat] LT [da]",lastDay:"[Kecha soat] LT [da]",lastWeek:"[O'tgan] dddd [kuni soat] LT [da]",sameElse:"L"},relativeTime:{future:"Yaqin %s ichida",past:"Bir necha %s oldin",s:"soniya",ss:"%d soniya",m:"bir daqiqa",mm:"%d daqiqa",h:"bir soat",hh:"%d soat",d:"bir kun",dd:"%d kun",M:"bir oy",MM:"%d oy",y:"bir yil",yy:"%d yil"},week:{dow:1,doy:7}}),e.defineLocale("uz",{months:"\u044f\u043d\u0432\u0430\u0440_\u0444\u0435\u0432\u0440\u0430\u043b_\u043c\u0430\u0440\u0442_\u0430\u043f\u0440\u0435\u043b_\u043c\u0430\u0439_\u0438\u044e\u043d_\u0438\u044e\u043b_\u0430\u0432\u0433\u0443\u0441\u0442_\u0441\u0435\u043d\u0442\u044f\u0431\u0440_\u043e\u043a\u0442\u044f\u0431\u0440_\u043d\u043e\u044f\u0431\u0440_\u0434\u0435\u043a\u0430\u0431\u0440".split("_"),monthsShort:"\u044f\u043d\u0432_\u0444\u0435\u0432_\u043c\u0430\u0440_\u0430\u043f\u0440_\u043c\u0430\u0439_\u0438\u044e\u043d_\u0438\u044e\u043b_\u0430\u0432\u0433_\u0441\u0435\u043d_\u043e\u043a\u0442_\u043d\u043e\u044f_\u0434\u0435\u043a".split("_"),weekdays:"\u042f\u043a\u0448\u0430\u043d\u0431\u0430_\u0414\u0443\u0448\u0430\u043d\u0431\u0430_\u0421\u0435\u0448\u0430\u043d\u0431\u0430_\u0427\u043e\u0440\u0448\u0430\u043d\u0431\u0430_\u041f\u0430\u0439\u0448\u0430\u043d\u0431\u0430_\u0416\u0443\u043c\u0430_\u0428\u0430\u043d\u0431\u0430".split("_"),weekdaysShort:"\u042f\u043a\u0448_\u0414\u0443\u0448_\u0421\u0435\u0448_\u0427\u043e\u0440_\u041f\u0430\u0439_\u0416\u0443\u043c_\u0428\u0430\u043d".split("_"),weekdaysMin:"\u042f\u043a_\u0414\u0443_\u0421\u0435_\u0427\u043e_\u041f\u0430_\u0416\u0443_\u0428\u0430".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"D MMMM YYYY, dddd HH:mm"},calendar:{sameDay:"[\u0411\u0443\u0433\u0443\u043d \u0441\u043e\u0430\u0442] LT [\u0434\u0430]",nextDay:"[\u042d\u0440\u0442\u0430\u0433\u0430] LT [\u0434\u0430]",nextWeek:"dddd [\u043a\u0443\u043d\u0438 \u0441\u043e\u0430\u0442] LT [\u0434\u0430]",lastDay:"[\u041a\u0435\u0447\u0430 \u0441\u043e\u0430\u0442] LT [\u0434\u0430]",lastWeek:"[\u0423\u0442\u0433\u0430\u043d] dddd [\u043a\u0443\u043d\u0438 \u0441\u043e\u0430\u0442] LT [\u0434\u0430]",sameElse:"L"},relativeTime:{future:"\u042f\u043a\u0438\u043d %s \u0438\u0447\u0438\u0434\u0430",past:"\u0411\u0438\u0440 \u043d\u0435\u0447\u0430 %s \u043e\u043b\u0434\u0438\u043d",s:"\u0444\u0443\u0440\u0441\u0430\u0442",ss:"%d \u0444\u0443\u0440\u0441\u0430\u0442",m:"\u0431\u0438\u0440 \u0434\u0430\u043a\u0438\u043a\u0430",mm:"%d \u0434\u0430\u043a\u0438\u043a\u0430",h:"\u0431\u0438\u0440 \u0441\u043e\u0430\u0442",hh:"%d \u0441\u043e\u0430\u0442",d:"\u0431\u0438\u0440 \u043a\u0443\u043d",dd:"%d \u043a\u0443\u043d",M:"\u0431\u0438\u0440 \u043e\u0439",MM:"%d \u043e\u0439",y:"\u0431\u0438\u0440 \u0439\u0438\u043b",yy:"%d \u0439\u0438\u043b"},week:{dow:1,doy:7}}),e.defineLocale("vi",{months:"th\xe1ng 1_th\xe1ng 2_th\xe1ng 3_th\xe1ng 4_th\xe1ng 5_th\xe1ng 6_th\xe1ng 7_th\xe1ng 8_th\xe1ng 9_th\xe1ng 10_th\xe1ng 11_th\xe1ng 12".split("_"),monthsShort:"Th01_Th02_Th03_Th04_Th05_Th06_Th07_Th08_Th09_Th10_Th11_Th12".split("_"),monthsParseExact:!0,weekdays:"ch\u1ee7 nh\u1eadt_th\u1ee9 hai_th\u1ee9 ba_th\u1ee9 t\u01b0_th\u1ee9 n\u0103m_th\u1ee9 s\xe1u_th\u1ee9 b\u1ea3y".split("_"),weekdaysShort:"CN_T2_T3_T4_T5_T6_T7".split("_"),weekdaysMin:"CN_T2_T3_T4_T5_T6_T7".split("_"),weekdaysParseExact:!0,meridiemParse:/sa|ch/i,isPM:function(e){return/^ch$/i.test(e)},meridiem:function(e,a,t){return e<12?t?"sa":"SA":t?"ch":"CH"},longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM [n\u0103m] YYYY",LLL:"D MMMM [n\u0103m] YYYY HH:mm",LLLL:"dddd, D MMMM [n\u0103m] YYYY HH:mm",l:"DD/M/YYYY",ll:"D MMM YYYY",lll:"D MMM YYYY HH:mm",llll:"ddd, D MMM YYYY HH:mm"},calendar:{sameDay:"[H\xf4m nay l\xfac] LT",nextDay:"[Ng\xe0y mai l\xfac] LT",nextWeek:"dddd [tu\u1ea7n t\u1edbi l\xfac] LT",lastDay:"[H\xf4m qua l\xfac] LT",lastWeek:"dddd [tu\u1ea7n r\u1ed3i l\xfac] LT",sameElse:"L"},relativeTime:{future:"%s t\u1edbi",past:"%s tr\u01b0\u1edbc",s:"v\xe0i gi\xe2y",ss:"%d gi\xe2y",m:"m\u1ed9t ph\xfat",mm:"%d ph\xfat",h:"m\u1ed9t gi\u1edd",hh:"%d gi\u1edd",d:"m\u1ed9t ng\xe0y",dd:"%d ng\xe0y",M:"m\u1ed9t th\xe1ng",MM:"%d th\xe1ng",y:"m\u1ed9t n\u0103m",yy:"%d n\u0103m"},dayOfMonthOrdinalParse:/\d{1,2}/,ordinal:function(e){return e},week:{dow:1,doy:4}}),e.defineLocale("x-pseudo",{months:"J~\xe1\xf1\xfa\xe1~r\xfd_F~\xe9br\xfa~\xe1r\xfd_~M\xe1rc~h_\xc1p~r\xedl_~M\xe1\xfd_~J\xfa\xf1\xe9~_J\xfal~\xfd_\xc1\xfa~g\xfast~_S\xe9p~t\xe9mb~\xe9r_\xd3~ct\xf3b~\xe9r_\xd1~\xf3v\xe9m~b\xe9r_~D\xe9c\xe9~mb\xe9r".split("_"),monthsShort:"J~\xe1\xf1_~F\xe9b_~M\xe1r_~\xc1pr_~M\xe1\xfd_~J\xfa\xf1_~J\xfal_~\xc1\xfag_~S\xe9p_~\xd3ct_~\xd1\xf3v_~D\xe9c".split("_"),monthsParseExact:!0,weekdays:"S~\xfa\xf1d\xe1~\xfd_M\xf3~\xf1d\xe1\xfd~_T\xfa\xe9~sd\xe1\xfd~_W\xe9d~\xf1\xe9sd~\xe1\xfd_T~h\xfars~d\xe1\xfd_~Fr\xedd~\xe1\xfd_S~\xe1t\xfar~d\xe1\xfd".split("_"),weekdaysShort:"S~\xfa\xf1_~M\xf3\xf1_~T\xfa\xe9_~W\xe9d_~Th\xfa_~Fr\xed_~S\xe1t".split("_"),weekdaysMin:"S~\xfa_M\xf3~_T\xfa_~W\xe9_T~h_Fr~_S\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[T~\xf3d\xe1~\xfd \xe1t] LT",nextDay:"[T~\xf3m\xf3~rr\xf3~w \xe1t] LT",nextWeek:"dddd [\xe1t] LT",lastDay:"[\xdd~\xe9st~\xe9rd\xe1~\xfd \xe1t] LT",lastWeek:"[L~\xe1st] dddd [\xe1t] LT",sameElse:"L"},relativeTime:{future:"\xed~\xf1 %s",past:"%s \xe1~g\xf3",s:"\xe1 ~f\xe9w ~s\xe9c\xf3~\xf1ds",ss:"%d s~\xe9c\xf3\xf1~ds",m:"\xe1 ~m\xed\xf1~\xfat\xe9",mm:"%d m~\xed\xf1\xfa~t\xe9s",h:"\xe1~\xf1 h\xf3~\xfar",hh:"%d h~\xf3\xfars",d:"\xe1 ~d\xe1\xfd",dd:"%d d~\xe1\xfds",M:"\xe1 ~m\xf3\xf1~th",MM:"%d m~\xf3\xf1t~hs",y:"\xe1 ~\xfd\xe9\xe1r",yy:"%d \xfd~\xe9\xe1rs"},dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var a=e%10;return e+(1==~~(e%100/10)?"th":1===a?"st":2===a?"nd":3===a?"rd":"th")},week:{dow:1,doy:4}}),e.defineLocale("yo",{months:"S\u1eb9\u0301r\u1eb9\u0301_E\u0300re\u0300le\u0300_\u1eb8r\u1eb9\u0300na\u0300_I\u0300gbe\u0301_E\u0300bibi_O\u0300ku\u0300du_Ag\u1eb9mo_O\u0300gu\u0301n_Owewe_\u1ecc\u0300wa\u0300ra\u0300_Be\u0301lu\u0301_\u1ecc\u0300p\u1eb9\u0300\u0300".split("_"),monthsShort:"S\u1eb9\u0301r_E\u0300rl_\u1eb8rn_I\u0300gb_E\u0300bi_O\u0300ku\u0300_Ag\u1eb9_O\u0300gu\u0301_Owe_\u1ecc\u0300wa\u0300_Be\u0301l_\u1ecc\u0300p\u1eb9\u0300\u0300".split("_"),weekdays:"A\u0300i\u0300ku\u0301_Aje\u0301_I\u0300s\u1eb9\u0301gun_\u1eccj\u1ecd\u0301ru\u0301_\u1eccj\u1ecd\u0301b\u1ecd_\u1eb8ti\u0300_A\u0300ba\u0301m\u1eb9\u0301ta".split("_"),weekdaysShort:"A\u0300i\u0300k_Aje\u0301_I\u0300s\u1eb9\u0301_\u1eccjr_\u1eccjb_\u1eb8ti\u0300_A\u0300ba\u0301".split("_"),weekdaysMin:"A\u0300i\u0300_Aj_I\u0300s_\u1eccr_\u1eccb_\u1eb8t_A\u0300b".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[O\u0300ni\u0300 ni] LT",nextDay:"[\u1ecc\u0300la ni] LT",nextWeek:"dddd [\u1eccs\u1eb9\u0300 to\u0301n'b\u1ecd] [ni] LT",lastDay:"[A\u0300na ni] LT",lastWeek:"dddd [\u1eccs\u1eb9\u0300 to\u0301l\u1ecd\u0301] [ni] LT",sameElse:"L"},relativeTime:{future:"ni\u0301 %s",past:"%s k\u1ecdja\u0301",s:"i\u0300s\u1eb9ju\u0301 aaya\u0301 die",ss:"aaya\u0301 %d",m:"i\u0300s\u1eb9ju\u0301 kan",mm:"i\u0300s\u1eb9ju\u0301 %d",h:"wa\u0301kati kan",hh:"wa\u0301kati %d",d:"\u1ecdj\u1ecd\u0301 kan",dd:"\u1ecdj\u1ecd\u0301 %d",M:"osu\u0300 kan",MM:"osu\u0300 %d",y:"\u1ecddu\u0301n kan",yy:"\u1ecddu\u0301n %d"},dayOfMonthOrdinalParse:/\u1ecdj\u1ecd\u0301\s\d{1,2}/,ordinal:"\u1ecdj\u1ecd\u0301 %d",week:{dow:1,doy:4}}),e.defineLocale("zh-cn",{months:"\u4e00\u6708_\u4e8c\u6708_\u4e09\u6708_\u56db\u6708_\u4e94\u6708_\u516d\u6708_\u4e03\u6708_\u516b\u6708_\u4e5d\u6708_\u5341\u6708_\u5341\u4e00\u6708_\u5341\u4e8c\u6708".split("_"),monthsShort:"1\u6708_2\u6708_3\u6708_4\u6708_5\u6708_6\u6708_7\u6708_8\u6708_9\u6708_10\u6708_11\u6708_12\u6708".split("_"),weekdays:"\u661f\u671f\u65e5_\u661f\u671f\u4e00_\u661f\u671f\u4e8c_\u661f\u671f\u4e09_\u661f\u671f\u56db_\u661f\u671f\u4e94_\u661f\u671f\u516d".split("_"),weekdaysShort:"\u5468\u65e5_\u5468\u4e00_\u5468\u4e8c_\u5468\u4e09_\u5468\u56db_\u5468\u4e94_\u5468\u516d".split("_"),weekdaysMin:"\u65e5_\u4e00_\u4e8c_\u4e09_\u56db_\u4e94_\u516d".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY/MM/DD",LL:"YYYY\u5e74M\u6708D\u65e5",LLL:"YYYY\u5e74M\u6708D\u65e5Ah\u70b9mm\u5206",LLLL:"YYYY\u5e74M\u6708D\u65e5ddddAh\u70b9mm\u5206",l:"YYYY/M/D",ll:"YYYY\u5e74M\u6708D\u65e5",lll:"YYYY\u5e74M\u6708D\u65e5 HH:mm",llll:"YYYY\u5e74M\u6708D\u65e5dddd HH:mm"},meridiemParse:/\u51cc\u6668|\u65e9\u4e0a|\u4e0a\u5348|\u4e2d\u5348|\u4e0b\u5348|\u665a\u4e0a/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u51cc\u6668"===a||"\u65e9\u4e0a"===a||"\u4e0a\u5348"===a?e:"\u4e0b\u5348"===a||"\u665a\u4e0a"===a?e+12:e>=11?e:e+12},meridiem:function(e,a,t){var s=100*e+a;return s<600?"\u51cc\u6668":s<900?"\u65e9\u4e0a":s<1130?"\u4e0a\u5348":s<1230?"\u4e2d\u5348":s<1800?"\u4e0b\u5348":"\u665a\u4e0a"},calendar:{sameDay:"[\u4eca\u5929]LT",nextDay:"[\u660e\u5929]LT",nextWeek:"[\u4e0b]ddddLT",lastDay:"[\u6628\u5929]LT",lastWeek:"[\u4e0a]ddddLT",sameElse:"L"},dayOfMonthOrdinalParse:/\d{1,2}(\u65e5|\u6708|\u5468)/,ordinal:function(e,a){switch(a){case"d":case"D":case"DDD":return e+"\u65e5";case"M":return e+"\u6708";case"w":case"W":return e+"\u5468";default:return e}},relativeTime:{future:"%s\u5185",past:"%s\u524d",s:"\u51e0\u79d2",ss:"%d \u79d2",m:"1 \u5206\u949f",mm:"%d \u5206\u949f",h:"1 \u5c0f\u65f6",hh:"%d \u5c0f\u65f6",d:"1 \u5929",dd:"%d \u5929",M:"1 \u4e2a\u6708",MM:"%d \u4e2a\u6708",y:"1 \u5e74",yy:"%d \u5e74"},week:{dow:1,doy:4}}),e.defineLocale("zh-hk",{months:"\u4e00\u6708_\u4e8c\u6708_\u4e09\u6708_\u56db\u6708_\u4e94\u6708_\u516d\u6708_\u4e03\u6708_\u516b\u6708_\u4e5d\u6708_\u5341\u6708_\u5341\u4e00\u6708_\u5341\u4e8c\u6708".split("_"),monthsShort:"1\u6708_2\u6708_3\u6708_4\u6708_5\u6708_6\u6708_7\u6708_8\u6708_9\u6708_10\u6708_11\u6708_12\u6708".split("_"),weekdays:"\u661f\u671f\u65e5_\u661f\u671f\u4e00_\u661f\u671f\u4e8c_\u661f\u671f\u4e09_\u661f\u671f\u56db_\u661f\u671f\u4e94_\u661f\u671f\u516d".split("_"),weekdaysShort:"\u9031\u65e5_\u9031\u4e00_\u9031\u4e8c_\u9031\u4e09_\u9031\u56db_\u9031\u4e94_\u9031\u516d".split("_"),weekdaysMin:"\u65e5_\u4e00_\u4e8c_\u4e09_\u56db_\u4e94_\u516d".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY/MM/DD",LL:"YYYY\u5e74M\u6708D\u65e5",LLL:"YYYY\u5e74M\u6708D\u65e5 HH:mm",LLLL:"YYYY\u5e74M\u6708D\u65e5dddd HH:mm",l:"YYYY/M/D",ll:"YYYY\u5e74M\u6708D\u65e5",lll:"YYYY\u5e74M\u6708D\u65e5 HH:mm",llll:"YYYY\u5e74M\u6708D\u65e5dddd HH:mm"},meridiemParse:/\u51cc\u6668|\u65e9\u4e0a|\u4e0a\u5348|\u4e2d\u5348|\u4e0b\u5348|\u665a\u4e0a/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u51cc\u6668"===a||"\u65e9\u4e0a"===a||"\u4e0a\u5348"===a?e:"\u4e2d\u5348"===a?e>=11?e:e+12:"\u4e0b\u5348"===a||"\u665a\u4e0a"===a?e+12:void 0},meridiem:function(e,a,t){var s=100*e+a;return s<600?"\u51cc\u6668":s<900?"\u65e9\u4e0a":s<1130?"\u4e0a\u5348":s<1230?"\u4e2d\u5348":s<1800?"\u4e0b\u5348":"\u665a\u4e0a"},calendar:{sameDay:"[\u4eca\u5929]LT",nextDay:"[\u660e\u5929]LT",nextWeek:"[\u4e0b]ddddLT",lastDay:"[\u6628\u5929]LT",lastWeek:"[\u4e0a]ddddLT",sameElse:"L"},dayOfMonthOrdinalParse:/\d{1,2}(\u65e5|\u6708|\u9031)/,ordinal:function(e,a){switch(a){case"d":case"D":case"DDD":return e+"\u65e5";case"M":return e+"\u6708";case"w":case"W":return e+"\u9031";default:return e}},relativeTime:{future:"%s\u5167",past:"%s\u524d",s:"\u5e7e\u79d2",ss:"%d \u79d2",m:"1 \u5206\u9418",mm:"%d \u5206\u9418",h:"1 \u5c0f\u6642",hh:"%d \u5c0f\u6642",d:"1 \u5929",dd:"%d \u5929",M:"1 \u500b\u6708",MM:"%d \u500b\u6708",y:"1 \u5e74",yy:"%d \u5e74"}}),e.defineLocale("zh-tw",{months:"\u4e00\u6708_\u4e8c\u6708_\u4e09\u6708_\u56db\u6708_\u4e94\u6708_\u516d\u6708_\u4e03\u6708_\u516b\u6708_\u4e5d\u6708_\u5341\u6708_\u5341\u4e00\u6708_\u5341\u4e8c\u6708".split("_"),monthsShort:"1\u6708_2\u6708_3\u6708_4\u6708_5\u6708_6\u6708_7\u6708_8\u6708_9\u6708_10\u6708_11\u6708_12\u6708".split("_"),weekdays:"\u661f\u671f\u65e5_\u661f\u671f\u4e00_\u661f\u671f\u4e8c_\u661f\u671f\u4e09_\u661f\u671f\u56db_\u661f\u671f\u4e94_\u661f\u671f\u516d".split("_"),weekdaysShort:"\u9031\u65e5_\u9031\u4e00_\u9031\u4e8c_\u9031\u4e09_\u9031\u56db_\u9031\u4e94_\u9031\u516d".split("_"),weekdaysMin:"\u65e5_\u4e00_\u4e8c_\u4e09_\u56db_\u4e94_\u516d".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY/MM/DD",LL:"YYYY\u5e74M\u6708D\u65e5",LLL:"YYYY\u5e74M\u6708D\u65e5 HH:mm",LLLL:"YYYY\u5e74M\u6708D\u65e5dddd HH:mm",l:"YYYY/M/D",ll:"YYYY\u5e74M\u6708D\u65e5",lll:"YYYY\u5e74M\u6708D\u65e5 HH:mm",llll:"YYYY\u5e74M\u6708D\u65e5dddd HH:mm"},meridiemParse:/\u51cc\u6668|\u65e9\u4e0a|\u4e0a\u5348|\u4e2d\u5348|\u4e0b\u5348|\u665a\u4e0a/,meridiemHour:function(e,a){return 12===e&&(e=0),"\u51cc\u6668"===a||"\u65e9\u4e0a"===a||"\u4e0a\u5348"===a?e:"\u4e2d\u5348"===a?e>=11?e:e+12:"\u4e0b\u5348"===a||"\u665a\u4e0a"===a?e+12:void 0},meridiem:function(e,a,t){var s=100*e+a;return s<600?"\u51cc\u6668":s<900?"\u65e9\u4e0a":s<1130?"\u4e0a\u5348":s<1230?"\u4e2d\u5348":s<1800?"\u4e0b\u5348":"\u665a\u4e0a"},calendar:{sameDay:"[\u4eca\u5929]LT",nextDay:"[\u660e\u5929]LT",nextWeek:"[\u4e0b]ddddLT",lastDay:"[\u6628\u5929]LT",lastWeek:"[\u4e0a]ddddLT",sameElse:"L"},dayOfMonthOrdinalParse:/\d{1,2}(\u65e5|\u6708|\u9031)/,ordinal:function(e,a){switch(a){case"d":case"D":case"DDD":return e+"\u65e5";case"M":return e+"\u6708";case"w":case"W":return e+"\u9031";default:return e}},relativeTime:{future:"%s\u5167",past:"%s\u524d",s:"\u5e7e\u79d2",ss:"%d \u79d2",m:"1 \u5206\u9418",mm:"%d \u5206\u9418",h:"1 \u5c0f\u6642",hh:"%d \u5c0f\u6642",d:"1 \u5929",dd:"%d \u5929",M:"1 \u500b\u6708",MM:"%d \u500b\u6708",y:"1 \u5e74",yy:"%d \u5e74"}}),e.locale("en"),e}); \ No newline at end of file diff --git a/domain-server/resources/web/css/bootstrap-sortable.css b/domain-server/resources/web/css/bootstrap-sortable.css new file mode 100755 index 0000000000..aed89cd62e --- /dev/null +++ b/domain-server/resources/web/css/bootstrap-sortable.css @@ -0,0 +1,110 @@ +/** + * adding sorting ability to HTML tables with Bootstrap styling + * @summary HTML tables sorting ability + * @version 2.0.0 + * @requires tinysort, moment.js, jQuery + * @license MIT + * @author Matus Brlit (drvic10k) + * @copyright Matus Brlit (drvic10k), bootstrap-sortable contributors + */ + +table.sortable span.sign { + display: block; + position: absolute; + top: 50%; + right: 5px; + font-size: 12px; + margin-top: -10px; + color: #bfbfc1; +} + +table.sortable th:after { + display: block; + position: absolute; + top: 50%; + right: 5px; + font-size: 12px; + margin-top: -10px; + color: #bfbfc1; +} + +table.sortable th.arrow:after { + content: ''; +} + +table.sortable span.arrow, span.reversed, th.arrow.down:after, th.reversedarrow.down:after, th.arrow.up:after, th.reversedarrow.up:after { + border-style: solid; + border-width: 5px; + font-size: 0; + border-color: #ccc transparent transparent transparent; + line-height: 0; + height: 0; + width: 0; + margin-top: -2px; +} + + table.sortable span.arrow.up, th.arrow.up:after { + border-color: transparent transparent #ccc transparent; + margin-top: -7px; + } + +table.sortable span.reversed, th.reversedarrow.down:after { + border-color: transparent transparent #ccc transparent; + margin-top: -7px; +} + + table.sortable span.reversed.up, th.reversedarrow.up:after { + border-color: #ccc transparent transparent transparent; + margin-top: -2px; + } + +table.sortable span.az:before, th.az.down:after { + content: "a .. z"; +} + +table.sortable span.az.up:before, th.az.up:after { + content: "z .. a"; +} + +table.sortable th.az.nosort:after, th.AZ.nosort:after, th._19.nosort:after, th.month.nosort:after { + content: ".."; +} + +table.sortable span.AZ:before, th.AZ.down:after { + content: "A .. Z"; +} + +table.sortable span.AZ.up:before, th.AZ.up:after { + content: "Z .. A"; +} + +table.sortable span._19:before, th._19.down:after { + content: "1 .. 9"; +} + +table.sortable span._19.up:before, th._19.up:after { + content: "9 .. 1"; +} + +table.sortable span.month:before, th.month.down:after { + content: "jan .. dec"; +} + +table.sortable span.month.up:before, th.month.up:after { + content: "dec .. jan"; +} + +table.sortable>thead th:not([data-defaultsort=disabled]) { + cursor: pointer; + position: relative; + top: 0; + left: 0; +} + +table.sortable>thead th:hover:not([data-defaultsort=disabled]) { + background: #efefef; +} + +table.sortable>thead th div.mozilla { + position: relative; +} diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 5121b85a42..2bcc870ecf 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -355,21 +355,31 @@ table .headers + .headers td { } } -ul.nav li.dropdown ul.dropdown-menu { +ul.dropdown-menu { padding: 0px 0px; } -ul.nav li.dropdown li a { +ul.dropdown-menu li a { padding-top: 7px; padding-bottom: 7px; } -ul.nav li.dropdown li a:hover { +ul.dropdown-menu li a:hover { color: white; background-color: #337ab7; } -ul.nav li.dropdown ul.dropdown-menu .divider { +table ul.dropdown-menu li:first-child a:hover { + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} + +ul.dropdown-menu li:last-child a:hover { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; +} + +ul.dropdown-menu .divider { margin: 0px 0; } @@ -434,3 +444,37 @@ ul.nav li.dropdown ul.dropdown-menu .divider { .save-button-text { pointer-events: none; } + +#content_archives .panel-body { + padding: 0; +} + +#content_archives .panel-body .form-group { + padding: 15px; +} + +#content_archives .panel-body th, #content_archives .panel-body td { + padding: 8px 15px; +} + +#content_archives table { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; +} + +tr.gray-tr { + background-color: #f5f5f5; +} + +.dropdown-toggle span.glyphicon-option-vertical { + font-size: 110%; + cursor: pointer; + border-radius: 50%; + background-color: #F5F5F5; + padding: 4px 4px 4px 6px; +} + +.dropdown.open span.glyphicon-option-vertical { + background-color: #337AB7; + color: white; +} diff --git a/domain-server/resources/web/header.html b/domain-server/resources/web/header.html index 1b7b306fff..bf1d1d1df1 100644 --- a/domain-server/resources/web/header.html +++ b/domain-server/resources/web/header.html @@ -9,6 +9,7 @@ + diff --git a/domain-server/resources/web/js/base-settings.js b/domain-server/resources/web/js/base-settings.js index 17f06f3ad1..961a7df3b2 100644 --- a/domain-server/resources/web/js/base-settings.js +++ b/domain-server/resources/web/js/base-settings.js @@ -106,8 +106,12 @@ function reloadSettings(callback) { $.getJSON(Settings.endpoint, function(data){ _.extend(data, viewHelpers); - for (var spliceIndex in Settings.extraGroups) { - data.descriptions.splice(spliceIndex, 0, Settings.extraGroups[spliceIndex]); + for (var spliceIndex in Settings.extraGroupsAtIndex) { + data.descriptions.splice(spliceIndex, 0, Settings.extraGroupsAtIndex[spliceIndex]); + } + + for (var endGroupIndex in Settings.extraGroupsAtEnd) { + data.descriptions.push(Settings.extraGroupsAtEnd[endGroupIndex]); } $('#panels').html(Settings.panelsTemplate(data)); diff --git a/domain-server/resources/web/js/domain-server.js b/domain-server/resources/web/js/domain-server.js index 2f75794786..184b2b954f 100644 --- a/domain-server/resources/web/js/domain-server.js +++ b/domain-server/resources/web/js/domain-server.js @@ -55,6 +55,34 @@ $(document).ready(function(){ var $contentDropdown = $('#content-settings-nav-dropdown'); var $settingsDropdown = $('#domain-settings-nav-dropdown'); + // define extra groups to add to setting panels, with their splice index + Settings.extraContentGroupsAtIndex = { + 0: { + html_id: Settings.CONTENT_ARCHIVES_PANEL_ID, + label: 'Content Archives' + }, + 1: { + html_id: Settings.UPLOAD_CONTENT_BACKUP_PANEL_ID, + label: 'Upload Content' + } + }; + + Settings.extraContentGroupsAtEnd = []; + + Settings.extraDomainGroupsAtIndex = { + 1: { + html_id: 'places', + label: 'Places' + } + } + + Settings.extraDomainGroupsAtEnd = [ + { + html_id: 'settings_backup', + label: 'Settings Backup' + } + ] + // for pages that have the settings dropdowns if ($contentDropdown.length && $settingsDropdown.length) { // make a JSON request to get the dropdown menus for content and settings @@ -65,6 +93,15 @@ $(document).ready(function(){ return "
  • " + group.label + "
  • "; } + // add the dummy settings groups that get populated via JS + for (var spliceIndex in Settings.extraContentGroupsAtIndex) { + data.content_settings.splice(spliceIndex, 0, Settings.extraContentGroupsAtIndex[spliceIndex]); + } + + for (var endIndex in Settings.extraContentGroupsAtEnd) { + data.content_settings.push(Settings.extraContentGroupsAtIndex[spliceIndex]); + } + $.each(data.content_settings, function(index, group){ if (index > 0) { $contentDropdown.append(""); @@ -73,25 +110,22 @@ $(document).ready(function(){ $contentDropdown.append(makeGroupDropdownElement(group, "/content/")); }); + // add the dummy settings groups that get populated via JS + for (var spliceIndex in Settings.extraDomainGroupsAtIndex) { + data.domain_settings.splice(spliceIndex, 0, Settings.extraDomainGroupsAtIndex[spliceIndex]); + } + + for (var endIndex in Settings.extraDomainGroupsAtEnd) { + data.domain_settings.push(Settings.extraDomainGroupsAtEnd[endIndex]); + } + $.each(data.domain_settings, function(index, group){ if (index > 0) { $settingsDropdown.append(""); } $settingsDropdown.append(makeGroupDropdownElement(group, "/settings/")); - - // for domain settings, we add a dummy "Places" group that we fill - // via the API - add it to the dropdown menu in the right spot - // which is after "Metaverse / Networking" - if (group.name == "metaverse") { - $settingsDropdown.append(""); - $settingsDropdown.append(makeGroupDropdownElement({ html_id: 'places', label: 'Places' }, "/settings/")); - } }); - - // append a link for the "Settings Backup" panel - $settingsDropdown.append(""); - $settingsDropdown.append(makeGroupDropdownElement({ html_id: 'settings_backup', label: 'Settings Backup'}, "/settings")); }); } }); diff --git a/domain-server/resources/web/js/shared.js b/domain-server/resources/web/js/shared.js index 69721ee924..040d8959e7 100644 --- a/domain-server/resources/web/js/shared.js +++ b/domain-server/resources/web/js/shared.js @@ -42,7 +42,9 @@ Object.assign(Settings, { ADD_PLACE_BTN_ID: 'add-place-btn', FORM_ID: 'settings-form', INVALID_ROW_CLASS: 'invalid-input', - DATA_ROW_INDEX: 'data-row-index' + DATA_ROW_INDEX: 'data-row-index', + CONTENT_ARCHIVES_PANEL_ID: 'content_archives', + UPLOAD_CONTENT_BACKUP_PANEL_ID: 'upload_content' }); var URLs = { @@ -164,7 +166,7 @@ function getDomainFromAPI(callback) { if (callback === undefined) { callback = function() {}; } - + if (!domainIDIsSet()) { callback({ status: 'fail' }); return null; diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 68684c9106..d4de0d5f4c 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -14,17 +14,9 @@ $(document).ready(function(){ return b; })(window.location.search.substr(1).split('&')); - // define extra groups to add to description, with their splice index - Settings.extraGroups = { - 1: { - html_id: 'places', - label: 'Places' - }, - "-1": { - html_id: 'settings_backup', - label: 'Settings Backup' - } - } + Settings.extraGroupsAtEnd = Settings.extraDomainGroupsAtEnd; + Settings.extraGroupsAtIndex = Settings.extraDomainGroupsAtIndex; + Settings.afterReloadActions = function() { // append the domain selection modal @@ -643,7 +635,6 @@ $(document).ready(function(){ autoNetworkingEl.after(form); } - function setupPlacesTable() { // create a dummy table using our view helper var placesTableSetting = { @@ -1097,8 +1088,5 @@ $(document).ready(function(){ html += ""; $('#settings_backup .panel-body').html(html); - - // add an upload button to the footer to kick off the upload form - } }); From dd5a705836d22a9d34d8d56b672d77115e817557 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 14 Feb 2018 13:29:42 -0800 Subject: [PATCH 338/569] move rolling interval backup rules to automatic content archives --- .../resources/describe-settings.json | 130 +++++++++--------- .../resources/web/content/js/content.js | 30 +++- .../resources/web/js/base-settings.js | 4 + .../src/DomainContentBackupManager.cpp | 6 +- domain-server/src/DomainServer.cpp | 9 +- .../src/DomainServerSettingsManager.cpp | 17 +++ .../src/DomainServerSettingsManager.h | 1 + 7 files changed, 123 insertions(+), 74 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 93d703c8b3..427dc62520 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1,5 +1,5 @@ { - "version": 2.1, + "version": 2.2, "settings": [ { "name": "metaverse", @@ -1321,73 +1321,6 @@ "default": "30000", "advanced": true }, - { - "name": "backups", - "type": "table", - "label": "Backup Rules", - "help": "In this table you can define a set of rules for how frequently to backup copies of your entites content file.", - "numbered": false, - "can_add_new_rows": true, - "default": [ - { - "Name": "Half Hourly Rolling", - "backupInterval": 1800, - "format": ".backup.halfhourly.%N", - "maxBackupVersions": 5 - }, - { - "Name": "Daily Rolling", - "backupInterval": 86400, - "format": ".backup.daily.%N", - "maxBackupVersions": 7 - }, - { - "Name": "Weekly Rolling", - "backupInterval": 604800, - "format": ".backup.weekly.%N", - "maxBackupVersions": 4 - }, - { - "Name": "Thirty Day Rolling", - "backupInterval": 2592000, - "format": ".backup.thirtyday.%N", - "maxBackupVersions": 12 - } - ], - "columns": [ - { - "name": "Name", - "label": "Name", - "can_set": true, - "placeholder": "Example", - "default": "Example" - }, - { - "name": "format", - "label": "Rule Format", - "can_set": true, - "help": "Format used to create the extension for the backup of your persisted entities. Use a format with %N to get rolling. Or use date formatting like %Y-%m-%d.%H:%M:%S.%z", - "placeholder": ".backup.example.%N", - "default": ".backup.example.%N" - }, - { - "name": "backupInterval", - "label": "Backup Interval in Seconds", - "help": "Interval between backup checks in seconds.", - "placeholder": 1800, - "default": 1800, - "can_set": true - }, - { - "name": "maxBackupVersions", - "label": "Max Rolled Backup Versions", - "help": "If your backup extension format uses 'rolling', how many versions do you want us to keep?", - "placeholder": 5, - "default": 5, - "can_set": true - } - ] - }, { "name": "NoPersist", "type": "checkbox", @@ -1649,6 +1582,67 @@ } ] }, + { + "name": "automatic_content_archives", + "label": "Automatic Content Archives", + "settings": [ + { + "name": "backup_rules", + "type": "table", + "label": "Rolling Backup Rules", + "help": "Define how frequently to create automatic content archives", + "numbered": false, + "can_add_new_rows": true, + "default": [ + { + "Name": "Half Hourly Rolling", + "backupInterval": 1800, + "maxBackupVersions": 5 + }, + { + "Name": "Daily Rolling", + "backupInterval": 86400, + "maxBackupVersions": 7 + }, + { + "Name": "Weekly Rolling", + "backupInterval": 604800, + "maxBackupVersions": 4 + }, + { + "Name": "Thirty Day Rolling", + "backupInterval": 2592000, + "maxBackupVersions": 12 + } + ], + "columns": [ + { + "name": "Name", + "label": "Name", + "can_set": true, + "placeholder": "Example", + "default": "Example" + }, + { + "name": "backupInterval", + "label": "Backup Interval in Seconds", + "help": "Interval between backup checks in seconds.", + "placeholder": 1800, + "default": 1800, + "can_set": true + }, + { + "name": "maxBackupVersions", + "label": "Max Rolled Backup Versions", + "help": "If your backup extension format uses 'rolling', how many versions do you want us to keep?", + "placeholder": 5, + "default": 5, + "can_set": true + } + ] + } + ] + }, { "name": "wizard", "label": "Setup Wizard", diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js index e2b653995f..0fd5f37a94 100644 --- a/domain-server/resources/web/content/js/content.js +++ b/domain-server/resources/web/content/js/content.js @@ -23,15 +23,17 @@ $(document).ready(function(){ var AUTOMATIC_ARCHIVES_TBODY_ID = 'automatic-archives-tbody'; var MANUAL_ARCHIVES_TABLE_ID = 'manual-archives-table'; var MANUAL_ARCHIVES_TBODY_ID = 'manual-archives-tbody'; + var AUTO_ARCHIVES_SETTINGS_LINK_ID = 'auto-archives-settings-link'; + var automaticBackups = []; var manualBackups = []; function setupContentArchives() { - // construct the HTML needed for the content archives panel var html = "
    "; html += ""; - html += "Your domain server makes regular archives of the content in your domain. In the list below, you can see and download all of your domain content and settings backups." + html += "Your domain server makes regular archives of the content in your domain. In the list below, you can see and download all of your domain content and settings backups. " + html += "Click here to manage automatic content archive intervals."; html += "
    "; html += ""; @@ -120,6 +122,30 @@ $(document).ready(function(){ }); } + // handle click on automatic content archive settings link + $('body').on('click', '#' + AUTO_ARCHIVES_SETTINGS_LINK_ID, function(e) { + if (Settings.pendingChanges > 0) { + // don't follow the link right away, make sure the user knows they are about to leave + // the page and lose changes + e.preventDefault(); + + var settingsLink = $(this).attr('href'); + + swal({ + title: "Are you sure?", + text: "You have pending changes to content settings that have not been saved. They will be lost if you leave the page to manage automatic content archive intervals.", + type: "warning", + showCancelButton: true, + confirmButtonText: "Leave and Lose Pending Changes", + closeOnConfirm: true + }, + function () { + // user wants to drop their changes, switch pages + window.location = settingsLink; + }); + } + }); + // handle click on manual archive creation button $('body').on('click', '#' + GENERATE_ARCHIVE_BUTTON_ID, function(e) { e.preventDefault(); diff --git a/domain-server/resources/web/js/base-settings.js b/domain-server/resources/web/js/base-settings.js index 961a7df3b2..3476792222 100644 --- a/domain-server/resources/web/js/base-settings.js +++ b/domain-server/resources/web/js/base-settings.js @@ -126,6 +126,8 @@ function reloadSettings(callback) { $('[data-toggle="tooltip"]').tooltip(); + Settings.pendingChanges = 0; + // call the callback now that settings are loaded callback(true); }).fail(function() { @@ -805,6 +807,8 @@ function badgeForDifferences(changedElement) { } }); + Settings.pendingChanges = totalChanges; + if (totalChanges == 0) { totalChanges = "" } diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index a711d2112d..345faffec4 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -63,9 +63,9 @@ DomainContentBackupManager::DomainContentBackupManager(const QString& backupDire } void DomainContentBackupManager::parseSettings(const QJsonObject& settings) { - qDebug() << settings << settings["backups"] << settings["backups"].isArray(); - if (settings["backups"].isArray()) { - const QJsonArray& backupRules = settings["backups"].toArray(); + static const QString BACKUP_RULES_KEY = "backup_rules"; + if (settings[BACKUP_RULES_KEY].isArray()) { + const QJsonArray& backupRules = settings[BACKUP_RULES_KEY].toArray(); qCDebug(domain_server) << "BACKUP RULES:"; for (const QJsonValue& value : backupRules) { diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 157eaa483f..8247e12de5 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -296,8 +296,15 @@ DomainServer::DomainServer(int argc, char* argv[]) : qCDebug(domain_server) << "Created entities data directory"; } maybeHandleReplacementEntityFile(); + + auto contentArchivesGroup = _settingsManager.valueOrDefaultValueForKeyPath(AUTOMATIC_CONTENT_ARCHIVES_GROUP); + auto archivesIntervalObject = QJsonObject(); - _contentManager.reset(new DomainContentBackupManager(getContentBackupDir(), _settingsManager.settingsResponseObjectForType("6")["entity_server_settings"].toObject())); + if (contentArchivesGroup.canConvert()) { + archivesIntervalObject = QJsonObject::fromVariantMap(contentArchivesGroup.toMap()); + } + + _contentManager.reset(new DomainContentBackupManager(getContentBackupDir(), archivesIntervalObject)); connect(_contentManager.get(), &DomainContentBackupManager::started, _contentManager.get(), [this](){ _contentManager->addBackupHandler(BackupHandlerPointer(new EntitiesBackupHandler(getEntitiesFilePath(), getEntitiesReplacementFilePath()))); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 85d6a046b5..a50cde0807 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -393,6 +393,7 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList _standardAgentPermissions[NodePermissions::standardNameLocalhost]->set(NodePermissions::Permission::canRezTemporaryCertifiedEntities); packPermissions(); } + if (oldVersion < 2.0) { const QString WIZARD_COMPLETED_ONCE = "wizard.completed_once"; @@ -400,6 +401,7 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList *wizardCompletedOnce = QVariant(true); } + if (oldVersion < 2.1) { // convert old avatar scale settings into avatar height. @@ -421,6 +423,21 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList } } + if (oldVersion < 2.2) { + // migrate entity server rolling backup intervals to new location for automatic content archive intervals + + const QString ENTITY_SERVER_BACKUPS_KEYPATH = "entity_server_settings.backups"; + const QString AUTO_CONTENT_ARCHIVES_RULES_KEYPATH = "automatic_content_archives.backup_rules"; + + QVariant* previousBackupsVariant = _configMap.valueForKeyPath(ENTITY_SERVER_BACKUPS_KEYPATH); + + if (previousBackupsVariant) { + auto migratedBackupsVariant = _configMap.valueForKeyPath(AUTO_CONTENT_ARCHIVES_RULES_KEYPATH, true); + *migratedBackupsVariant = *previousBackupsVariant; + } + } + + // write the current description version to our settings *versionVariant = _descriptionVersion; diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index abc70751a8..897a15485f 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -37,6 +37,7 @@ const QString MAC_PERMISSIONS_KEYPATH = "security.mac_permissions"; const QString MACHINE_FINGERPRINT_PERMISSIONS_KEYPATH = "security.machine_fingerprint_permissions"; const QString GROUP_PERMISSIONS_KEYPATH = "security.group_permissions"; const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens"; +const QString AUTOMATIC_CONTENT_ARCHIVES_GROUP = "automatic_content_archives"; using GroupByUUIDKey = QPair; // groupID, rankID From 2d9f2ebf81c0de164948c372e26e8bba286c7bb9 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 14 Feb 2018 13:50:46 -0800 Subject: [PATCH 339/569] fix active with anchor and settings dropdowns on non-settings pages --- domain-server/resources/web/base-settings-scripts.html | 1 - domain-server/resources/web/footer.html | 1 + domain-server/resources/web/js/domain-server.js | 6 +++--- domain-server/resources/web/settings/js/settings.js | 1 - domain-server/resources/web/wizard/index.shtml | 1 - 5 files changed, 4 insertions(+), 6 deletions(-) diff --git a/domain-server/resources/web/base-settings-scripts.html b/domain-server/resources/web/base-settings-scripts.html index fe370c4675..877b0a6125 100644 --- a/domain-server/resources/web/base-settings-scripts.html +++ b/domain-server/resources/web/base-settings-scripts.html @@ -3,5 +3,4 @@ - diff --git a/domain-server/resources/web/footer.html b/domain-server/resources/web/footer.html index e8ea392b49..49e883509e 100644 --- a/domain-server/resources/web/footer.html +++ b/domain-server/resources/web/footer.html @@ -1,4 +1,5 @@ + diff --git a/domain-server/resources/web/js/domain-server.js b/domain-server/resources/web/js/domain-server.js index 184b2b954f..6b2d4e1316 100644 --- a/domain-server/resources/web/js/domain-server.js +++ b/domain-server/resources/web/js/domain-server.js @@ -28,7 +28,7 @@ function settingsGroupAnchor(base, html_id) { } $(document).ready(function(){ - var url = window.location; + var url = location.protocol + '//' + location.host+location.pathname; // Will only work if string in href matches with location $('ul.nav a[href="'+ url +'"]').parent().addClass('active'); @@ -55,7 +55,7 @@ $(document).ready(function(){ var $contentDropdown = $('#content-settings-nav-dropdown'); var $settingsDropdown = $('#domain-settings-nav-dropdown'); - // define extra groups to add to setting panels, with their splice index + // define extra groups to add to setting panels, with their splice index Settings.extraContentGroupsAtIndex = { 0: { html_id: Settings.CONTENT_ARCHIVES_PANEL_ID, @@ -99,7 +99,7 @@ $(document).ready(function(){ } for (var endIndex in Settings.extraContentGroupsAtEnd) { - data.content_settings.push(Settings.extraContentGroupsAtIndex[spliceIndex]); + data.content_settings.push(Settings.extraContentGroupsAtEnd[endIndex]); } $.each(data.content_settings, function(index, group){ diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index d4de0d5f4c..1c6510298f 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -17,7 +17,6 @@ $(document).ready(function(){ Settings.extraGroupsAtEnd = Settings.extraDomainGroupsAtEnd; Settings.extraGroupsAtIndex = Settings.extraDomainGroupsAtIndex; - Settings.afterReloadActions = function() { // append the domain selection modal appendDomainIDButtons(); diff --git a/domain-server/resources/web/wizard/index.shtml b/domain-server/resources/web/wizard/index.shtml index b526a5719b..5a3286296d 100644 --- a/domain-server/resources/web/wizard/index.shtml +++ b/domain-server/resources/web/wizard/index.shtml @@ -261,6 +261,5 @@ - From b019895fce5051e18188d75a89c1a4ce791d3765 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 14 Feb 2018 15:49:42 -0800 Subject: [PATCH 340/569] handle entity file upload from new content upload --- .../resources/web/content/js/content.js | 58 +++++++++++++++++-- .../resources/web/js/domain-server.js | 2 +- .../resources/web/settings/js/settings.js | 53 ++++++++++------- domain-server/src/DomainServer.cpp | 30 ++++++++-- 4 files changed, 111 insertions(+), 32 deletions(-) diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js index 0fd5f37a94..4e2c27bf54 100644 --- a/domain-server/resources/web/content/js/content.js +++ b/domain-server/resources/web/content/js/content.js @@ -7,17 +7,67 @@ $(document).ready(function(){ // construct the HTML needed for the settings backup panel var html = "
    "; - html += "Upload a Content Backup to replace the content of this domain"; - html += "
    Note: Your domain's content will be replaced by the content you upload, but the existing backup files of your domain's content will not immediately be changed.
    "; + html += "Upload a Content Archive (.zip) or entity file (.json, .json.gz) to replace the content of this domain."; + html += "
    Note: Your domain content will be replaced by the content you upload, but the existing backup files of your domain's content will not immediately be changed.
    "; html += ""; - html += ""; + html += ""; html += "
    "; $('#' + Settings.UPLOAD_CONTENT_BACKUP_PANEL_ID + ' .panel-body').html(html); } + // handle content archive or entity file upload + + // when the selected file is changed, enable the button if there's a selected file + $('body').on('change', '#' + RESTORE_SETTINGS_FILE_ID, function() { + if ($(this).val()) { + $('#' + RESTORE_SETTINGS_UPLOAD_ID).attr('disabled', false); + } + }); + + // when the upload button is clicked, send the file to the DS + // and reload the page if restore was successful or + // show an error if not + $('body').on('click', '#' + RESTORE_SETTINGS_UPLOAD_ID, function(e){ + e.preventDefault(); + + swal({ + title: "Are you sure?", + text: "Your domain content will be replaced by the uploaded Content Archive or entity file", + type: "warning", + showCancelButton: true, + closeOnConfirm: false + }, + function () { + var files = $('#' + RESTORE_SETTINGS_FILE_ID).prop('files'); + + var fileFormData = new FormData(); + fileFormData.append('restore-file', files[0]); + + showSpinnerAlert("Restoring Content"); + + $.ajax({ + url: '/content/upload', + type: 'POST', + cache: false, + processData: false, + contentType: false, + data: fileFormData + }).done(function(data, textStatus, jqXHR) { + swal.close(); + showRestartModal(); + }).fail(function(jqXHR, textStatus, errorThrown) { + showErrorMessage( + "Error", + "There was a problem restoring domain content.\n" + + "Please ensure that the content archive or entity file is valid and try again." + ); + }); + }); + }); + var GENERATE_ARCHIVE_BUTTON_ID = 'generate-archive-button'; var AUTOMATIC_ARCHIVES_TABLE_ID = 'automatic-archives-table'; var AUTOMATIC_ARCHIVES_TBODY_ID = 'automatic-archives-tbody'; @@ -136,7 +186,7 @@ $(document).ready(function(){ text: "You have pending changes to content settings that have not been saved. They will be lost if you leave the page to manage automatic content archive intervals.", type: "warning", showCancelButton: true, - confirmButtonText: "Leave and Lose Pending Changes", + confirmButtonText: "Proceed without Saving", closeOnConfirm: true }, function () { diff --git a/domain-server/resources/web/js/domain-server.js b/domain-server/resources/web/js/domain-server.js index 6b2d4e1316..d3b20d40bb 100644 --- a/domain-server/resources/web/js/domain-server.js +++ b/domain-server/resources/web/js/domain-server.js @@ -79,7 +79,7 @@ $(document).ready(function(){ Settings.extraDomainGroupsAtEnd = [ { html_id: 'settings_backup', - label: 'Settings Backup' + label: 'Settings Backup / Restore' } ] diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 1c6510298f..b73337ef2d 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -1033,31 +1033,40 @@ $(document).ready(function(){ $('body').on('click', '#' + RESTORE_SETTINGS_UPLOAD_ID, function(e){ e.preventDefault(); - var files = $('#' + RESTORE_SETTINGS_FILE_ID).prop('files'); + swal({ + title: "Are you sure?", + text: "Your domain settings will be replaced by the uploaded settings", + type: "warning", + showCancelButton: true, + closeOnConfirm: false + }, + function() { + var files = $('#' + RESTORE_SETTINGS_FILE_ID).prop('files'); - var fileFormData = new FormData(); - fileFormData.append('restore-file', files[0]); + var fileFormData = new FormData(); + fileFormData.append('restore-file', files[0]); - showSpinnerAlert("Restoring Settings"); + showSpinnerAlert("Restoring Settings"); - $.ajax({ - url: '/settings/restore', - type: 'POST', - processData: false, - contentType: false, - dataType: 'json', - data: fileFormData - }).done(function(data, textStatus, jqXHR) { - swal.close(); - showRestartModal(); - }).fail(function(jqXHR, textStatus, errorThrown) { - showErrorMessage( - "Error", - "There was a problem restoring domain settings.\n" - + "Please ensure that your current domain settings are valid and try again." - ); + $.ajax({ + url: '/settings/restore', + type: 'POST', + processData: false, + contentType: false, + dataType: 'json', + data: fileFormData + }).done(function(data, textStatus, jqXHR) { + swal.close(); + showRestartModal(); + }).fail(function(jqXHR, textStatus, errorThrown) { + showErrorMessage( + "Error", + "There was a problem restoring domain settings.\n" + + "Please ensure that your current domain settings are valid and try again." + ); - reloadSettings(); + reloadSettings(); + }); }); }); @@ -1079,7 +1088,7 @@ $(document).ready(function(){ html += "
    "; html += ""; html += "Upload a settings configuration to quickly configure this domain"; - html += "
    Note: Your domain's settings will be replaced by the settings you upload
    "; + html += "
    Note: Your domain settings will be replaced by the settings you upload"; html += ""; html += ""; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 8247e12de5..f3765f6868 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2256,12 +2256,32 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QList formData = connection->parseFormData(); if (formData.size() > 0 && formData[0].second.size() > 0) { - // invoke our method to hand the new octree file off to the octree server - QMetaObject::invokeMethod(this, "handleOctreeFileReplacement", - Qt::QueuedConnection, Q_ARG(QByteArray, formData[0].second)); + auto& firstFormData = formData[0]; + + // check the file extension to see what kind of file this is + // to match sure we handle this filetype for a content restore + auto dispositionValue = QString(firstFormData.first.value("Content-Disposition")); + auto formDataFilenameRegex = QRegExp("filename=\"(\\S+)\""); + auto matchIndex = formDataFilenameRegex.indexIn(dispositionValue); + + QString uploadedFilename = ""; + if (matchIndex != -1) { + uploadedFilename = formDataFilenameRegex.cap(1); + } + + if (uploadedFilename.endsWith(".json", Qt::CaseInsensitive) + || uploadedFilename.endsWith(".json.gz", Qt::CaseInsensitive)) { + // invoke our method to hand the new octree file off to the octree server + QMetaObject::invokeMethod(this, "handleOctreeFileReplacement", + Qt::QueuedConnection, Q_ARG(QByteArray, formData[0].second)); + + // respond with a 200 for success + connection->respond(HTTPConnection::StatusCode200); + } else { + // we don't have handling for this filetype, send back a 400 for failure + connection->respond(HTTPConnection::StatusCode400); + } - // respond with a 200 for success - connection->respond(HTTPConnection::StatusCode200); } else { // respond with a 400 for failure connection->respond(HTTPConnection::StatusCode400); From 2b39419795166233273eb6688621f66266ddd10d Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 14 Feb 2018 17:42:49 -0800 Subject: [PATCH 341/569] keeping AYS DRY and hooking up restore/delete for content archives --- .../resources/web/content/js/content.js | 165 +++++++++++++----- .../resources/web/js/domain-server.js | 19 +- domain-server/resources/web/js/shared.js | 11 ++ .../resources/web/settings/js/settings.js | 78 ++++----- 4 files changed, 175 insertions(+), 98 deletions(-) diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js index 4e2c27bf54..34af9262a2 100644 --- a/domain-server/resources/web/content/js/content.js +++ b/domain-server/resources/web/content/js/content.js @@ -7,7 +7,7 @@ $(document).ready(function(){ // construct the HTML needed for the settings backup panel var html = "
    "; - html += "Upload a Content Archive (.zip) or entity file (.json, .json.gz) to replace the content of this domain."; + html += "Upload a content archive (.zip) or entity file (.json, .json.gz) to replace the content of this domain."; html += "
    Note: Your domain content will be replaced by the content you upload, but the existing backup files of your domain's content will not immediately be changed.
    "; html += ""; @@ -33,39 +33,36 @@ $(document).ready(function(){ $('body').on('click', '#' + RESTORE_SETTINGS_UPLOAD_ID, function(e){ e.preventDefault(); - swal({ - title: "Are you sure?", - text: "Your domain content will be replaced by the uploaded Content Archive or entity file", - type: "warning", - showCancelButton: true, - closeOnConfirm: false - }, - function () { - var files = $('#' + RESTORE_SETTINGS_FILE_ID).prop('files'); + swalAreYouSure( + "Your domain content will be replaced by the uploaded Content Archive or entity file", + "Restore content", + function() { + var files = $('#' + RESTORE_SETTINGS_FILE_ID).prop('files'); - var fileFormData = new FormData(); - fileFormData.append('restore-file', files[0]); + var fileFormData = new FormData(); + fileFormData.append('restore-file', files[0]); - showSpinnerAlert("Restoring Content"); + showSpinnerAlert("Restoring Content"); - $.ajax({ - url: '/content/upload', - type: 'POST', - cache: false, - processData: false, - contentType: false, - data: fileFormData - }).done(function(data, textStatus, jqXHR) { - swal.close(); - showRestartModal(); - }).fail(function(jqXHR, textStatus, errorThrown) { - showErrorMessage( - "Error", - "There was a problem restoring domain content.\n" - + "Please ensure that the content archive or entity file is valid and try again." - ); - }); - }); + $.ajax({ + url: '/content/upload', + type: 'POST', + cache: false, + processData: false, + contentType: false, + data: fileFormData + }).done(function(data, textStatus, jqXHR) { + swal.close(); + showRestartModal(); + }).fail(function(jqXHR, textStatus, errorThrown) { + showErrorMessage( + "Error", + "There was a problem restoring domain content.\n" + + "Please ensure that the content archive or entity file is valid and try again." + ); + }); + } + ); }); var GENERATE_ARCHIVE_BUTTON_ID = 'generate-archive-button'; @@ -104,6 +101,10 @@ $(document).ready(function(){ $('#' + Settings.CONTENT_ARCHIVES_PANEL_ID + ' .panel-body').html(html); } + var BACKUP_RESTORE_LINK_CLASS = 'restore-backup'; + var BACKUP_DOWNLOAD_LINK_CLASS = 'download-backup'; + var BACKUP_DELETE_LINK_CLASS = 'delete-backup'; + function reloadLatestBackups() { // make a GET request to get backup information to populate the table $.get('/api/backups', function(data) { @@ -117,12 +118,15 @@ $(document).ready(function(){ // populate the backups tables with the backups function createBackupTableRow(backup) { - return "
    " + + ""; + + ""; } var automaticRows = ""; @@ -172,6 +176,79 @@ $(document).ready(function(){ }); } + // handle click in table to restore a given content backup + $('body').on('click', '.' + BACKUP_RESTORE_LINK_CLASS, function(e){ + // stop the default behaviour + e.preventDefault(); + + // grab the name of this backup so we can show it in alerts + var backupName = $(this).closest('tr').attr('data-backup-name'); + + // grab the ID of this backup in case we need to send a POST + var backupID = $(this).closest('tr').attr('data-backup-id'); + + // make sure the user knows what is about to happen + swalAreYouSure( + "Your domain content will be replaced by the content archive " + backupName, + "Restore content", + function() { + // show a spinner while we send off our request + showSpinnerAlert("Restoring Content Archive " + backupName); + + // setup an AJAX POST to request content restore + $.post('/api/backups/recover/' + backupID).done(function(data, textStatus, jqXHR) { + swal.close(); + showRestartModal(); + }).fail(function(jqXHR, textStatus, errorThrown) { + showErrorMessage( + "Error", + "There was a problem restoring domain content.\n" + + "If the problem persists, the content archive may be corrupted." + ); + }); + } + ) + }); + + // handle click in table to delete a given content backup + $('body').on('click', '.' + BACKUP_DELETE_LINK_CLASS, function(e){ + // stop the default behaviour + e.preventDefault(); + + // grab the name of this backup so we can show it in alerts + var backupName = $(this).closest('tr').attr('data-backup-name'); + + // grab the ID of this backup in case we need to send the DELETE request + var backupID = $(this).closest('tr').attr('data-backup-id'); + + // make sure the user knows what is about to happen + swalAreYouSure( + "The content archive " + backupName + " will be deleted and will no longer be available for restore or download from this page.", + "Delete content archive", + function() { + // show a spinner while we send off our request + showSpinnerAlert("Deleting content archive " + backupName); + + // setup an AJAX DELETE to request content archive delete + $.ajax({ + url: '/api/backups/' + backupID, + type: 'DELETE' + }).done(function(data, textStatus, jqXHR) { + swal.close(); + }).fail(function(jqXHR, textStatus, errorThrown) { + showErrorMessage( + "Error", + "There was an unexpected error deleting the content archive" + ); + }).always(function(){ + // reload the list of content archives in case we deleted a backup + // or it's no longer an available backup for some other reason + reloadContentArchives(); + }); + } + ) + }); + // handle click on automatic content archive settings link $('body').on('click', '#' + AUTO_ARCHIVES_SETTINGS_LINK_ID, function(e) { if (Settings.pendingChanges > 0) { @@ -181,18 +258,14 @@ $(document).ready(function(){ var settingsLink = $(this).attr('href'); - swal({ - title: "Are you sure?", - text: "You have pending changes to content settings that have not been saved. They will be lost if you leave the page to manage automatic content archive intervals.", - type: "warning", - showCancelButton: true, - confirmButtonText: "Proceed without Saving", - closeOnConfirm: true - }, - function () { - // user wants to drop their changes, switch pages - window.location = settingsLink; - }); + swalAreYouSure( + "You have pending changes to content settings that have not been saved. They will be lost if you leave the page to manage automatic content archive intervals.", + "Proceed without Saving", + function() { + // user wants to drop their changes, switch pages + window.location = settingsLink; + } + ); } }); diff --git a/domain-server/resources/web/js/domain-server.js b/domain-server/resources/web/js/domain-server.js index d3b20d40bb..2c12e2683a 100644 --- a/domain-server/resources/web/js/domain-server.js +++ b/domain-server/resources/web/js/domain-server.js @@ -39,16 +39,15 @@ $(document).ready(function(){ }).parent().addClass('active'); $('body').on('click', '#restart-server', function(e) { - swal( { - title: "Are you sure?", - text: "This will restart your domain server, causing your domain to be briefly offline.", - type: "warning", - html: true, - showCancelButton: true - }, function() { - $.get("/restart"); - showRestartModal(); - }); + swalAreYouSure( + "This will restart your domain server, causing your domain to be briefly offline.", + "Restart", + function() { + swal.close(); + $.get("/restart"); + showRestartModal(); + } + ) return false; }); diff --git a/domain-server/resources/web/js/shared.js b/domain-server/resources/web/js/shared.js index 040d8959e7..84bba4de56 100644 --- a/domain-server/resources/web/js/shared.js +++ b/domain-server/resources/web/js/shared.js @@ -98,6 +98,17 @@ var DOMAIN_ID_TYPE_TEMP = 1; var DOMAIN_ID_TYPE_FULL = 2; var DOMAIN_ID_TYPE_UNKNOWN = 3; +function swalAreYouSure(text, confirmButtonText, callback) { + swal({ + title: "Are you sure?", + text: text, + type: "warning", + showCancelButton: true, + confirmButtonText: confirmButtonText, + closeOnConfirm: false + }, callback); +} + function domainIDIsSet() { if (typeof Settings.data.values.metaverse !== 'undefined' && typeof Settings.data.values.metaverse.id !== 'undefined') { diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index b73337ef2d..e67ea43158 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -94,20 +94,17 @@ $(document).ready(function(){ var password = formJSON["security"]["http_password"]; if ((password == sha256_digest("")) && (username == undefined || (username && username.length != 0))) { - swal({ - title: "Are you sure?", - text: "You have entered a blank password with a non-blank username. Are you sure you want to require a blank password?", - type: "warning", - showCancelButton: true, - confirmButtonColor: "#5cb85c", - confirmButtonText: "Yes!", - closeOnConfirm: true - }, - function () { + swalAreYouSure( + "You have entered a blank password with a non-blank username. Are you sure you want to require a blank password?", + "Use blank password", + function() { + swal.close(); + formJSON["security"]["http_password"] = ""; postSettings(formJSON); - }); + } + ); return; } @@ -1033,41 +1030,38 @@ $(document).ready(function(){ $('body').on('click', '#' + RESTORE_SETTINGS_UPLOAD_ID, function(e){ e.preventDefault(); - swal({ - title: "Are you sure?", - text: "Your domain settings will be replaced by the uploaded settings", - type: "warning", - showCancelButton: true, - closeOnConfirm: false - }, - function() { - var files = $('#' + RESTORE_SETTINGS_FILE_ID).prop('files'); + swalAreYouSure( + "Your domain settings will be replaced by the uploaded settings", + "Restore settings", + function() { + var files = $('#' + RESTORE_SETTINGS_FILE_ID).prop('files'); - var fileFormData = new FormData(); - fileFormData.append('restore-file', files[0]); + var fileFormData = new FormData(); + fileFormData.append('restore-file', files[0]); - showSpinnerAlert("Restoring Settings"); + showSpinnerAlert("Restoring Settings"); - $.ajax({ - url: '/settings/restore', - type: 'POST', - processData: false, - contentType: false, - dataType: 'json', - data: fileFormData - }).done(function(data, textStatus, jqXHR) { - swal.close(); - showRestartModal(); - }).fail(function(jqXHR, textStatus, errorThrown) { - showErrorMessage( - "Error", - "There was a problem restoring domain settings.\n" - + "Please ensure that your current domain settings are valid and try again." - ); + $.ajax({ + url: '/settings/restore', + type: 'POST', + processData: false, + contentType: false, + dataType: 'json', + data: fileFormData + }).done(function(data, textStatus, jqXHR) { + swal.close(); + showRestartModal(); + }).fail(function(jqXHR, textStatus, errorThrown) { + showErrorMessage( + "Error", + "There was a problem restoring domain settings.\n" + + "Please ensure that your current domain settings are valid and try again." + ); - reloadSettings(); - }); - }); + reloadSettings(); + }); + } + ); }); $('body').on('change', '#' + RESTORE_SETTINGS_FILE_ID, function() { From 41b0bb8c581ae249b2e3dbb4a915f8912e270fed Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 15 Feb 2018 10:06:36 -0800 Subject: [PATCH 342/569] connect download link from content archive tables --- domain-server/resources/web/content/js/content.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js index 34af9262a2..12a6d4b734 100644 --- a/domain-server/resources/web/content/js/content.js +++ b/domain-server/resources/web/content/js/content.js @@ -126,7 +126,7 @@ $(document).ready(function(){ + ""; + + "
  • Delete
  • "; } var automaticRows = ""; From 910f3425f8bf3fa080b68dae1e0b5786e6564517 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 15 Feb 2018 10:57:07 -0800 Subject: [PATCH 343/569] fix latest backup refreshing with no caching --- domain-server/resources/web/content/js/content.js | 14 +++++++++----- domain-server/src/DomainServer.cpp | 4 ++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js index 12a6d4b734..1e5b6ac131 100644 --- a/domain-server/resources/web/content/js/content.js +++ b/domain-server/resources/web/content/js/content.js @@ -107,7 +107,11 @@ $(document).ready(function(){ function reloadLatestBackups() { // make a GET request to get backup information to populate the table - $.get('/api/backups', function(data) { + $.ajax({ + url: '/api/backups', + cache: false + }).done(function(data) { + // split the returned data into manual and automatic manual backups var splitBackups = _.partition(data.backups, function(value, index) { return value.isManualBackup; @@ -126,7 +130,7 @@ $(document).ready(function(){ + ""; + + "
  • Delete
  • "; } var automaticRows = ""; @@ -243,7 +247,7 @@ $(document).ready(function(){ }).always(function(){ // reload the list of content archives in case we deleted a backup // or it's no longer an available backup for some other reason - reloadContentArchives(); + reloadLatestBackups(); }); } ) @@ -295,14 +299,14 @@ $(document).ready(function(){ // post the provided archive name to ask the server to kick off a manual backup $.ajax({ type: 'POST', - url: '/api/backup', + url: '/api/backups', data: { 'name': inputValue } }).done(function(data) { // since we successfully setup a new content archive, reload the table of archives // which should show that this archive is pending creation - reloadContentArchives(); + reloadLatestBackups(); }).fail(function(jqXHR, textStatus, errorThrown) { }); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index f3765f6868..c23d17ed95 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1941,7 +1941,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url const QString URI_ASSIGNMENT = "/assignment"; const QString URI_NODES = "/nodes"; const QString URI_SETTINGS = "/settings"; - const QString URI_ENTITY_FILE_UPLOAD = "/content/upload"; + const QString URI_CONTENT_UPLOAD = "/content/upload"; const QString URI_RESTART = "/restart"; const QString URI_API_PLACES = "/api/places"; const QString URI_API_DOMAINS = "/api/domains"; @@ -2251,7 +2251,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url connection->respond(HTTPConnection::StatusCode200); return true; - } else if (url.path() == URI_ENTITY_FILE_UPLOAD) { + } else if (url.path() == URI_CONTENT_UPLOAD) { // this is an entity file upload, ask the HTTPConnection to parse the data QList formData = connection->parseFormData(); From 6f8381d3787038175b3dff25d345675efeed9657 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 15 Feb 2018 10:59:48 -0800 Subject: [PATCH 344/569] use automatic content archives group const --- domain-server/src/DomainServerSettingsManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index a50cde0807..a3f99facea 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -427,7 +427,7 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList // migrate entity server rolling backup intervals to new location for automatic content archive intervals const QString ENTITY_SERVER_BACKUPS_KEYPATH = "entity_server_settings.backups"; - const QString AUTO_CONTENT_ARCHIVES_RULES_KEYPATH = "automatic_content_archives.backup_rules"; + const QString AUTO_CONTENT_ARCHIVES_RULES_KEYPATH = AUTOMATIC_CONTENT_ARCHIVES_GROUP + ".backup_rules"; QVariant* previousBackupsVariant = _configMap.valueForKeyPath(ENTITY_SERVER_BACKUPS_KEYPATH); From 2020ce5907e05338ec8f7e9fbda3a457dd198df0 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 15 Feb 2018 11:20:02 -0800 Subject: [PATCH 345/569] add API to recover from content archive --- .../src/DomainContentBackupManager.cpp | 54 ++++++++++++++----- .../src/DomainContentBackupManager.h | 3 ++ domain-server/src/DomainServer.cpp | 17 +++++- 3 files changed, 60 insertions(+), 14 deletions(-) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 345faffec4..379aa640f8 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -256,6 +257,22 @@ void DomainContentBackupManager::deleteBackup(MiniPromise::Promise promise, cons }); } +bool DomainContentBackupManager::recoverFromBackupZip(QuaZip& zip, const QString& backupName) { + if (!zip.open(QuaZip::Mode::mdUnzip)) { + qWarning() << "Failed to unzip file: " << zip.getZipName(); + return false; + } else { + _isRecovering = true; + + for (auto& handler : _backupHandlers) { + handler->recoverBackup(zip); + } + + qDebug() << "Successfully started recovering from " << zip.getZipName(); + return true; + } +} + void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise, const QString& backupName) { if (_isRecovering) { promise->resolve({ @@ -277,19 +294,9 @@ void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise, QFile backupFile { backupDir.filePath(backupName) }; if (backupFile.open(QIODevice::ReadOnly)) { QuaZip zip { &backupFile }; - if (!zip.open(QuaZip::Mode::mdUnzip)) { - qWarning() << "Failed to unzip file: " << backupName; - success = false; - } else { - _isRecovering = true; - _recoveryFilename = backupName; - for (auto& handler : _backupHandlers) { - handler->recoverBackup(zip); - } - - qDebug() << "Successfully started recovering from " << backupName; - success = true; - } + + success = recoverFromBackupZip(zip, backupName); + backupFile.close(); } else { success = false; @@ -301,7 +308,28 @@ void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise, }); } +void DomainContentBackupManager::recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "recoverFromUploadedBackup", Q_ARG(MiniPromise::Promise, promise), + Q_ARG(QByteArray, uploadedBackup)); + return; + } + + qDebug() << "Recovering from uploaded content archive"; + + // create a buffer and then a QuaZip from that buffer + QBuffer uploadedBackupBuffer { &uploadedBackup }; + QuaZip uploadedZip { &uploadedBackupBuffer }; + + bool success = recoverFromBackupZip(uploadedZip, MANUAL_BACKUP_PREFIX + "uploaded.zip"); + + promise->resolve({ + { "success", success } + }); +} + std::vector DomainContentBackupManager::getAllBackups() { + QDir backupDir { _backupDirectory }; auto matchingFiles = backupDir.entryInfoList({ AUTOMATIC_BACKUP_PREFIX + "*.zip", MANUAL_BACKUP_PREFIX + "*.zip" }, diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h index f1aa4acab2..d4b1f60b87 100644 --- a/domain-server/src/DomainContentBackupManager.h +++ b/domain-server/src/DomainContentBackupManager.h @@ -63,6 +63,7 @@ public slots: void getAllBackupsAndStatus(MiniPromise::Promise promise); void createManualBackup(MiniPromise::Promise promise, const QString& name); void recoverFromBackup(MiniPromise::Promise promise, const QString& backupName); + void recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup); void deleteBackup(MiniPromise::Promise promise, const QString& backupName); void consolidateBackup(MiniPromise::Promise promise, QString fileName); @@ -85,6 +86,8 @@ protected: std::pair createBackup(const QString& prefix, const QString& name); + bool recoverFromBackupZip(QuaZip& backupZip, const QString& backupName); + private: const QString _backupDirectory; std::vector _backupHandlers; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index c23d17ed95..7fe1d1a0ab 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2273,10 +2273,25 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url || uploadedFilename.endsWith(".json.gz", Qt::CaseInsensitive)) { // invoke our method to hand the new octree file off to the octree server QMetaObject::invokeMethod(this, "handleOctreeFileReplacement", - Qt::QueuedConnection, Q_ARG(QByteArray, formData[0].second)); + Qt::QueuedConnection, Q_ARG(QByteArray, firstFormData.second)); // respond with a 200 for success connection->respond(HTTPConnection::StatusCode200); + } else if (uploadedFilename.endsWith(".zip", Qt::CaseInsensitive)) { + auto deferred = makePromise("recoverFromUploadedBackup"); + + deferred->then([connection, JSON_MIME_TYPE](QString error, QVariantMap result) { + QJsonObject rootJSON; + auto success = result["success"].toBool(); + rootJSON["success"] = success; + QJsonDocument docJSON(rootJSON); + connection->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(), + JSON_MIME_TYPE.toUtf8()); + }); + + _contentManager->recoverFromUploadedBackup(deferred, firstFormData.second); + + return true; } else { // we don't have handling for this filetype, send back a 400 for failure connection->respond(HTTPConnection::StatusCode400); From de75fe8e9f04618b0e3e9febbc6c2c6481d37e18 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 16 Feb 2018 15:34:03 -0800 Subject: [PATCH 346/569] CR fix for typo in comment --- domain-server/src/DomainServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 7fe1d1a0ab..7cd6cd34fe 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2259,7 +2259,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url auto& firstFormData = formData[0]; // check the file extension to see what kind of file this is - // to match sure we handle this filetype for a content restore + // to make sure we handle this filetype for a content restore auto dispositionValue = QString(firstFormData.first.value("Content-Disposition")); auto formDataFilenameRegex = QRegExp("filename=\"(\\S+)\""); auto matchIndex = formDataFilenameRegex.indexIn(dispositionValue); From 40078450dd855cb4dc8ea371ee9bcf5a0d0cab90 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 15 Feb 2018 11:20:02 -0800 Subject: [PATCH 347/569] add API to recover from content archive --- domain-server/src/DomainContentBackupManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 379aa640f8..40a2a55486 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -309,6 +309,7 @@ void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise, } void DomainContentBackupManager::recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup) { + if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "recoverFromUploadedBackup", Q_ARG(MiniPromise::Promise, promise), Q_ARG(QByteArray, uploadedBackup)); From cb747c9cdfc394d53b12ba91a6041cd96f807d10 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 15 Feb 2018 17:32:53 -0800 Subject: [PATCH 348/569] refresh backups for availability and restore status --- .../resources/web/content/js/content.js | 89 +++++++++++++++---- domain-server/resources/web/css/style.css | 5 ++ .../resources/web/js/domain-server.js | 2 +- 3 files changed, 78 insertions(+), 18 deletions(-) diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js index 1e5b6ac131..69a8c93f82 100644 --- a/domain-server/resources/web/content/js/content.js +++ b/domain-server/resources/web/content/js/content.js @@ -2,10 +2,19 @@ $(document).ready(function(){ var RESTORE_SETTINGS_UPLOAD_ID = 'restore-settings-button'; var RESTORE_SETTINGS_FILE_ID = 'restore-settings-file'; + var UPLOAD_CONTENT_ALLOWED_DIV_ID = 'upload-content-allowed'; + var UPLOAD_CONTENT_RECOVERING_DIV_ID = 'upload-content-recovering'; + + function progressBarHTML(extraClass, label) { + var html = "
    "; + html += "
    "; + html += label + "
    "; + return html; + } function setupBackupUpload() { // construct the HTML needed for the settings backup panel - var html = "
    "; + var html = "
    "; html += "Upload a content archive (.zip) or entity file (.json, .json.gz) to replace the content of this domain."; html += "
    Note: Your domain content will be replaced by the content you upload, but the existing backup files of your domain's content will not immediately be changed.
    "; @@ -13,7 +22,10 @@ $(document).ready(function(){ html += ""; html += ""; - html += "
    "; + html += "
    "; + html += "Restore in progress"; + html += progressBarHTML('recovery', 'Restoring'); + html += "
    "; $('#' + Settings.UPLOAD_CONTENT_BACKUP_PANEL_ID + ' .panel-body').html(html); } @@ -71,6 +83,7 @@ $(document).ready(function(){ var MANUAL_ARCHIVES_TABLE_ID = 'manual-archives-table'; var MANUAL_ARCHIVES_TBODY_ID = 'manual-archives-tbody'; var AUTO_ARCHIVES_SETTINGS_LINK_ID = 'auto-archives-settings-link'; + var ACTION_MENU_CLASS = 'action-menu'; var automaticBackups = []; var manualBackups = []; @@ -84,7 +97,9 @@ $(document).ready(function(){ html += ""; html += "
    " + backup.name + "" + return "
    " + backup.name + "" + moment(backup.createdAtMillis).format('lll') + "" + "" - + "
    "; - var backups_table_head = ""; + var backups_table_head = "" + + "" + + ""; html += backups_table_head; html += "
    Archive NameArchive DateActions
    Archive NameArchive DateActions
    "; @@ -105,7 +120,7 @@ $(document).ready(function(){ var BACKUP_DOWNLOAD_LINK_CLASS = 'download-backup'; var BACKUP_DELETE_LINK_CLASS = 'delete-backup'; - function reloadLatestBackups() { + function reloadBackupInformation() { // make a GET request to get backup information to populate the table $.ajax({ url: '/api/backups', @@ -125,7 +140,7 @@ $(document).ready(function(){ return "" + "" + backup.name + "" + moment(backup.createdAtMillis).format('lll') - + "" + + "" + ""; } + function updateProgressBars($progressBar, value) { + $progressBar.attr('aria-valuenow', value).attr('style', 'width: ' + value + '%'); + $progressBar.find('.sr-only').html(data.status.recoveryProgress + "% Complete"); + } + + function updateOrAddTableRow(backup, tableBodyID) { + // check for a backup with this ID + var $backupRow = $("tr[data-backup-id='" + backup.id + "']"); + + if ($backupRow.length == 0) { + // create a new row and then add it to the table + $backupRow = $(createBackupTableRow(backup)); + $('#' + tableBodyID).append($backupRow); + } + + // update the row status column depending on if it is available or recovering + if (!backup.isAvailable) { + // add a progress bar to the status row for availability + $backupRow.find('td.backup-status').html(progressBarHTML('availability', 'Archiving')); + + // set the value of the progress bar based on availability progress + updateProgressBars($backupRow.find('.progress-bar'), backup.availabilityProgress); + } else if (backup.id == data.status.recoveringBackupId) { + // add a progress bar to the status row for recovery + $backupRow.find('td.backup-status').html(progressBarHTML('recovery', 'Restoring')); + } else { + // no special status for this row, use an empty status column + $backupRow.find('td.backup-status').html(''); + } + + $backupRow.find('td.' + ACTION_MENU_CLASS + ' .dropdown').toggle(backup.isAvailable); + } + var automaticRows = ""; if (automaticBackups.length > 0) { for (var backupIndex in automaticBackups) { - // create a table row for this backup and add it to the rows we'll put in the table body - automaticRows += createBackupTableRow(automaticBackups[backupIndex]); + updateOrAddTableRow(automaticBackups[backupIndex], AUTOMATIC_ARCHIVES_TBODY_ID) } } - $('#' + AUTOMATIC_ARCHIVES_TBODY_ID).html(automaticRows); - - var manualRows = ""; - if (manualBackups.length > 0) { for (var backupIndex in manualBackups) { - // create a table row for this backup and add it to the rows we'll put in the table body - manualRows += createBackupTableRow(manualBackups[backupIndex]); + updateOrAddTableRow(manualBackups[backupIndex], MANUAL_ARCHIVES_TBODY_ID) } } - $('#' + MANUAL_ARCHIVES_TBODY_ID).html(manualRows); + // check if the restore action on all rows should be enabled or disabled + $('.' + BACKUP_RESTORE_LINK_CLASS).parent().toggleClass('disabled', data.status.isRecovering); + + // hide or show the manual content upload file and button depending on our recovering status + $('#' + UPLOAD_CONTENT_ALLOWED_DIV_ID).toggle(!data.status.isRecovering); + $('#' + UPLOAD_CONTENT_RECOVERING_DIV_ID).toggle(data.status.isRecovering); + + // update the progress bars for current restore status + if (data.status.isRecovering) { + updateProgressBars($('.recovery.progress-bar'), data.status.recoveryProgress); + } // tell bootstrap sortable to update for the new rows $.bootstrapSortable({ applyLast: true }); @@ -247,7 +299,7 @@ $(document).ready(function(){ }).always(function(){ // reload the list of content archives in case we deleted a backup // or it's no longer an available backup for some other reason - reloadLatestBackups(); + reloadBackupInformation(); }); } ) @@ -306,7 +358,7 @@ $(document).ready(function(){ }).done(function(data) { // since we successfully setup a new content archive, reload the table of archives // which should show that this archive is pending creation - reloadLatestBackups(); + reloadBackupInformation(); }).fail(function(jqXHR, textStatus, errorThrown) { }); @@ -322,6 +374,9 @@ $(document).ready(function(){ setupContentArchives(); // load the latest backups immediately - reloadLatestBackups(); + reloadBackupInformation(); + + // setup a timer to reload them every 5 seconds + setTimeout(reloadBackupInformation(), 5000); }; }); diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 2bcc870ecf..62f442584e 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -466,6 +466,11 @@ tr.gray-tr { background-color: #f5f5f5; } +table .action-menu { + text-align: right; + width: 90px; +} + .dropdown-toggle span.glyphicon-option-vertical { font-size: 110%; cursor: pointer; diff --git a/domain-server/resources/web/js/domain-server.js b/domain-server/resources/web/js/domain-server.js index 2c12e2683a..ed9559b6e9 100644 --- a/domain-server/resources/web/js/domain-server.js +++ b/domain-server/resources/web/js/domain-server.js @@ -62,7 +62,7 @@ $(document).ready(function(){ }, 1: { html_id: Settings.UPLOAD_CONTENT_BACKUP_PANEL_ID, - label: 'Upload Content' + label: 'Upload Content Archive' } }; From faacd986b3924ccc092801a58e79b63f8fe9563f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 16 Feb 2018 10:37:15 -0800 Subject: [PATCH 349/569] remove deleted backups from content archives tables --- .../resources/web/content/js/content.js | 46 +++++++++++++------ .../src/DomainServerSettingsManager.cpp | 3 +- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js index 69a8c93f82..5c2e134102 100644 --- a/domain-server/resources/web/content/js/content.js +++ b/domain-server/resources/web/content/js/content.js @@ -78,6 +78,8 @@ $(document).ready(function(){ }); var GENERATE_ARCHIVE_BUTTON_ID = 'generate-archive-button'; + var CONTENT_ARCHIVES_NORMAL_ID = 'content-archives-success'; + var CONTENT_ARCHIVES_ERROR_ID = 'content-archives-error'; var AUTOMATIC_ARCHIVES_TABLE_ID = 'automatic-archives-table'; var AUTOMATIC_ARCHIVES_TBODY_ID = 'automatic-archives-tbody'; var MANUAL_ARCHIVES_TABLE_ID = 'manual-archives-table'; @@ -90,10 +92,10 @@ $(document).ready(function(){ function setupContentArchives() { // construct the HTML needed for the content archives panel - var html = "
    "; + var html = "
    "; html += ""; html += "Your domain server makes regular archives of the content in your domain. In the list below, you can see and download all of your domain content and settings backups. " - html += "Click here to manage automatic content archive intervals."; + html += "Click here to manage automatic content archive intervals."; html += "
    "; html += ""; @@ -110,7 +112,11 @@ $(document).ready(function(){ html += ""; html += "
    "; html += backups_table_head; - html += "
    "; + html += "
    "; + + html += ""; // put the base HTML in the content archives panel $('#' + Settings.CONTENT_ARCHIVES_PANEL_ID + ' .panel-body').html(html); @@ -119,7 +125,8 @@ $(document).ready(function(){ var BACKUP_RESTORE_LINK_CLASS = 'restore-backup'; var BACKUP_DOWNLOAD_LINK_CLASS = 'download-backup'; var BACKUP_DELETE_LINK_CLASS = 'delete-backup'; - + var ACTIVE_BACKUP_ROW_CLASS = 'active-backup'; + function reloadBackupInformation() { // make a GET request to get backup information to populate the table $.ajax({ @@ -153,6 +160,10 @@ $(document).ready(function(){ $progressBar.find('.sr-only').html(data.status.recoveryProgress + "% Complete"); } + // before we add any new rows and update existing ones + // remove our flag for active rows + $('.' + ACTIVE_BACKUP_ROW_CLASS).removeClass(ACTIVE_BACKUP_ROW_CLASS); + function updateOrAddTableRow(backup, tableBodyID) { // check for a backup with this ID var $backupRow = $("tr[data-backup-id='" + backup.id + "']"); @@ -169,7 +180,7 @@ $(document).ready(function(){ $backupRow.find('td.backup-status').html(progressBarHTML('availability', 'Archiving')); // set the value of the progress bar based on availability progress - updateProgressBars($backupRow.find('.progress-bar'), backup.availabilityProgress); + updateProgressBars($backupRow.find('.progress-bar'), backup.availabilityProgress * 100); } else if (backup.id == data.status.recoveringBackupId) { // add a progress bar to the status row for recovery $backupRow.find('td.backup-status').html(progressBarHTML('recovery', 'Restoring')); @@ -179,22 +190,29 @@ $(document).ready(function(){ } $backupRow.find('td.' + ACTION_MENU_CLASS + ' .dropdown').toggle(backup.isAvailable); + + $backupRow.addClass(ACTIVE_BACKUP_ROW_CLASS); } var automaticRows = ""; if (automaticBackups.length > 0) { for (var backupIndex in automaticBackups) { - updateOrAddTableRow(automaticBackups[backupIndex], AUTOMATIC_ARCHIVES_TBODY_ID) + updateOrAddTableRow(automaticBackups[backupIndex], AUTOMATIC_ARCHIVES_TBODY_ID); + } } if (manualBackups.length > 0) { for (var backupIndex in manualBackups) { - updateOrAddTableRow(manualBackups[backupIndex], MANUAL_ARCHIVES_TBODY_ID) + updateOrAddTableRow(manualBackups[backupIndex], MANUAL_ARCHIVES_TBODY_ID); } } + // at this point, any rows that no longer have the ACTIVE_BACKUP_ROW_CLASS + // are deleted backups, so we remove them from the table + $('tbody tr:not(.' + ACTIVE_BACKUP_ROW_CLASS + ')').remove(); + // check if the restore action on all rows should be enabled or disabled $('.' + BACKUP_RESTORE_LINK_CLASS).parent().toggleClass('disabled', data.status.isRecovering); @@ -204,12 +222,15 @@ $(document).ready(function(){ // update the progress bars for current restore status if (data.status.isRecovering) { - updateProgressBars($('.recovery.progress-bar'), data.status.recoveryProgress); + updateProgressBars($('.recovery.progress-bar'), data.status.recoveryProgress * 100); } // tell bootstrap sortable to update for the new rows $.bootstrapSortable({ applyLast: true }); + $('#' + CONTENT_ARCHIVES_NORMAL_ID).toggle(true); + $('#' + CONTENT_ARCHIVES_ERROR_ID).toggle(false); + }).fail(function(){ // we've hit the very rare case where we couldn't load the list of backups from the domain server @@ -219,11 +240,8 @@ $(document).ready(function(){ // replace the content archives panel with a simple error message // stating that the user should reload the page - $('#' + Settings.CONTENT_ARCHIVES_PANEL_ID + ' .panel-body').html( - "
    " + - "There was a problem loading your list of automatic and manual content archives. Please reload the page to try again." + - "
    " - ); + $('#' + CONTENT_ARCHIVES_NORMAL_ID).toggle(false); + $('#' + CONTENT_ARCHIVES_ERROR_ID).toggle(true); }).always(function(){ // toggle showing or hiding the tables depending on if they have entries @@ -377,6 +395,6 @@ $(document).ready(function(){ reloadBackupInformation(); // setup a timer to reload them every 5 seconds - setTimeout(reloadBackupInformation(), 5000); + setInterval(reloadBackupInformation, 5000); }; }); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index a3f99facea..cd7155d9da 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -1356,7 +1356,7 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt BLOCKING_INVOKE_METHOD(this, "settingsResponseObjectForType", Q_RETURN_ARG(QJsonObject, responseObject), - Q_ARG(const QString&, typeValue), + Q_ARG(QString, typeValue), Q_ARG(bool, isAuthenticated), Q_ARG(bool, includeDomainSettings), Q_ARG(bool, includeContentSettings), @@ -1374,6 +1374,7 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt // only enumerate the requested settings type (domain setting or content setting) QJsonArray* filteredDescriptionArray = &_descriptionArray; + if (includeDomainSettings && !includeContentSettings) { filteredDescriptionArray = &_domainSettingsDescription; } else if (includeContentSettings && !includeDomainSettings) { From 5dec3aba505a2ab8c3c210f19bb2441b457b1f73 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 16 Feb 2018 10:49:57 -0800 Subject: [PATCH 350/569] fix download link and restore behaviour with pending --- domain-server/resources/web/content/js/content.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js index 5c2e134102..717f149760 100644 --- a/domain-server/resources/web/content/js/content.js +++ b/domain-server/resources/web/content/js/content.js @@ -126,7 +126,7 @@ $(document).ready(function(){ var BACKUP_DOWNLOAD_LINK_CLASS = 'download-backup'; var BACKUP_DELETE_LINK_CLASS = 'delete-backup'; var ACTIVE_BACKUP_ROW_CLASS = 'active-backup'; - + function reloadBackupInformation() { // make a GET request to get backup information to populate the table $.ajax({ @@ -151,8 +151,8 @@ $(document).ready(function(){ + ""; + + "
  • Download
  • " + + "
  • Delete
  • "; } function updateProgressBars($progressBar, value) { @@ -267,12 +267,11 @@ $(document).ready(function(){ "Restore content", function() { // show a spinner while we send off our request - showSpinnerAlert("Restoring Content Archive " + backupName); + showSpinnerAlert("Starting restore of " + backupName); // setup an AJAX POST to request content restore $.post('/api/backups/recover/' + backupID).done(function(data, textStatus, jqXHR) { swal.close(); - showRestartModal(); }).fail(function(jqXHR, textStatus, errorThrown) { showErrorMessage( "Error", From 494f93304b9eb84c5815e5e9a50b1c0dd57d421e Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 16 Feb 2018 10:58:32 -0800 Subject: [PATCH 351/569] take down AssetClient after content manager --- domain-server/src/DomainServer.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 7cd6cd34fe..584cbe3513 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -380,11 +380,6 @@ void DomainServer::parseCommandLine() { DomainServer::~DomainServer() { qInfo() << "Domain Server is shutting down."; - // cleanup the AssetClient thread - DependencyManager::destroy(); - _assetClientThread.quit(); - _assetClientThread.wait(); - // destroy the LimitedNodeList before the DomainServer QCoreApplication is down DependencyManager::destroy(); @@ -392,6 +387,11 @@ DomainServer::~DomainServer() { _contentManager->aboutToFinish(); _contentManager->terminate(); } + + // cleanup the AssetClient thread + DependencyManager::destroy(); + _assetClientThread.quit(); + _assetClientThread.wait(); } void DomainServer::queuedQuit(QString quitMessage, int exitCode) { From 441b55301f8637fa86f6c297266e4cd250f66252 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 16 Feb 2018 11:01:22 -0800 Subject: [PATCH 352/569] cleanup LNL last during DS shutdown --- domain-server/src/DomainServer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 584cbe3513..5b8d253110 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -380,9 +380,6 @@ void DomainServer::parseCommandLine() { DomainServer::~DomainServer() { qInfo() << "Domain Server is shutting down."; - // destroy the LimitedNodeList before the DomainServer QCoreApplication is down - DependencyManager::destroy(); - if (_contentManager) { _contentManager->aboutToFinish(); _contentManager->terminate(); @@ -392,6 +389,9 @@ DomainServer::~DomainServer() { DependencyManager::destroy(); _assetClientThread.quit(); _assetClientThread.wait(); + + // destroy the LimitedNodeList before the DomainServer QCoreApplication is down + DependencyManager::destroy(); } void DomainServer::queuedQuit(QString quitMessage, int exitCode) { From 8e621a95a3c9abb5a0c4d948d60389f3b80d2533 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 16 Feb 2018 11:04:58 -0800 Subject: [PATCH 353/569] fix typo in debug for writing new entities --- domain-server/src/DomainServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 5b8d253110..81dcf65be5 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1761,7 +1761,7 @@ void DomainServer::processOctreeDataPersistMessage(QSharedPointer Date: Fri, 16 Feb 2018 11:11:54 -0800 Subject: [PATCH 354/569] set DomainContentBackupManager object name so it appears on thread --- domain-server/src/DomainContentBackupManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 40a2a55486..f6c6e7a7ba 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -57,6 +57,8 @@ DomainContentBackupManager::DomainContentBackupManager(const QString& backupDire _persistInterval(persistInterval), _lastCheck(usecTimestampNow()) { + setObjectName("DomainContentBackupManager"); + // Make sure the backup directory exists. QDir(_backupDirectory).mkpath("."); From 1c053730eb997c5d0f1b037c882c9ab9bbae669d Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 16 Feb 2018 14:09:00 -0800 Subject: [PATCH 355/569] make DomainServerSettingsManager thread-safe for use in content backup --- .../src/DomainContentBackupManager.cpp | 2 +- domain-server/src/DomainGatekeeper.cpp | 15 +- domain-server/src/DomainMetadata.cpp | 17 +- domain-server/src/DomainServer.cpp | 150 ++++++++---------- .../src/DomainServerSettingsManager.cpp | 95 +++++------ .../src/DomainServerSettingsManager.h | 26 +-- 6 files changed, 153 insertions(+), 152 deletions(-) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index f6c6e7a7ba..0bef6bb891 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -58,7 +58,7 @@ DomainContentBackupManager::DomainContentBackupManager(const QString& backupDire _lastCheck(usecTimestampNow()) { setObjectName("DomainContentBackupManager"); - + // Make sure the backup directory exists. QDir(_backupDirectory).mkpath("."); diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 3aab7b4563..e697bbdda1 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -435,10 +435,11 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect if (!userPerms.can(NodePermissions::Permission::canConnectPastMaxCapacity) && !isWithinMaxCapacity()) { // we can't allow this user to connect because we are at max capacity QString redirectOnMaxCapacity; - const QVariant* redirectOnMaxCapacityVariant = - valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION); - if (redirectOnMaxCapacityVariant && redirectOnMaxCapacityVariant->canConvert()) { - redirectOnMaxCapacity = redirectOnMaxCapacityVariant->toString(); + + QVariant redirectOnMaxCapacityVariant = + _server->_settingsManager.valueOrDefaultValueForKeyPath(MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION); + if (redirectOnMaxCapacityVariant.canConvert()) { + redirectOnMaxCapacity = redirectOnMaxCapacityVariant.toString(); qDebug() << "Redirection domain:" << redirectOnMaxCapacity; } @@ -610,9 +611,9 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, bool DomainGatekeeper::isWithinMaxCapacity() { // find out what our maximum capacity is - const QVariant* maximumUserCapacityVariant = - valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY); - unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0; + QVariant maximumUserCapacityVariant = + _server->_settingsManager.valueOrDefaultValueForKeyPath(MAXIMUM_USER_CAPACITY); + unsigned int maximumUserCapacity = !maximumUserCapacityVariant.isValid() ? maximumUserCapacityVariant.toUInt() : 0; if (maximumUserCapacity > 0) { unsigned int connectedUsers = _server->countConnectedUsers(); diff --git a/domain-server/src/DomainMetadata.cpp b/domain-server/src/DomainMetadata.cpp index eee5673af3..24d55d74b6 100644 --- a/domain-server/src/DomainMetadata.cpp +++ b/domain-server/src/DomainMetadata.cpp @@ -84,21 +84,22 @@ void DomainMetadata::descriptorsChanged() { // get descriptors assert(_metadata[DESCRIPTORS].canConvert()); auto& state = *static_cast(_metadata[DESCRIPTORS].data()); - auto& settings = static_cast(parent())->_settingsManager.getSettingsMap(); - auto& descriptors = static_cast(parent())->_settingsManager.getDescriptorsMap(); + + static const QString DESCRIPTORS_GROUP_KEYPATH = "descriptors"; + auto descriptorsMap = static_cast(parent())->_settingsManager.valueForKeyPath(DESCRIPTORS).toMap(); // copy simple descriptors (description/maturity) - state[Descriptors::DESCRIPTION] = descriptors[Descriptors::DESCRIPTION]; - state[Descriptors::MATURITY] = descriptors[Descriptors::MATURITY]; + state[Descriptors::DESCRIPTION] = descriptorsMap[Descriptors::DESCRIPTION]; + state[Descriptors::MATURITY] = descriptorsMap[Descriptors::MATURITY]; // copy array descriptors (hosts/tags) - state[Descriptors::HOSTS] = descriptors[Descriptors::HOSTS].toList(); - state[Descriptors::TAGS] = descriptors[Descriptors::TAGS].toList(); + state[Descriptors::HOSTS] = descriptorsMap[Descriptors::HOSTS].toList(); + state[Descriptors::TAGS] = descriptorsMap[Descriptors::TAGS].toList(); // parse capacity static const QString CAPACITY = "security.maximum_user_capacity"; - const QVariant* capacityVariant = valueForKeyPath(settings, CAPACITY); - unsigned int capacity = capacityVariant ? capacityVariant->toUInt() : 0; + QVariant capacityVariant = static_cast(parent())->_settingsManager.valueForKeyPath(CAPACITY); + unsigned int capacity = capacityVariant.isValid() ? capacityVariant.toUInt() : 0; state[Descriptors::CAPACITY] = capacity; #if DEV_BUILD || PR_BUILD diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 81dcf65be5..9cecea5f70 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -75,8 +75,8 @@ bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection, std::initializer_list optionalData, bool requireAccessToken) { - auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH); - if (accessTokenVariant == nullptr && requireAccessToken) { + auto accessTokenVariant = _settingsManager.valueForKeyPath(ACCESS_TOKEN_KEY_PATH); + if (!accessTokenVariant.isValid() && requireAccessToken) { connection->respond(HTTPConnection::StatusCode400, "User access token has not been set"); return true; } @@ -112,8 +112,8 @@ bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection, req.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - if (accessTokenVariant != nullptr) { - auto accessTokenHeader = QString("Bearer ") + accessTokenVariant->toString(); + if (accessTokenVariant.isValid()) { + auto accessTokenHeader = QString("Bearer ") + accessTokenVariant.toString(); req.setRawHeader("Authorization", accessTokenHeader.toLatin1()); } @@ -417,8 +417,8 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() { const QString X509_PRIVATE_KEY_OPTION = "key"; const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE"; - QString certPath = _settingsManager.getSettingsMap().value(X509_CERTIFICATE_OPTION).toString(); - QString keyPath = _settingsManager.getSettingsMap().value(X509_PRIVATE_KEY_OPTION).toString(); + QString certPath = _settingsManager.valueForKeyPath(X509_CERTIFICATE_OPTION).toString(); + QString keyPath = _settingsManager.valueForKeyPath(X509_PRIVATE_KEY_OPTION).toString(); if (!certPath.isEmpty() && !keyPath.isEmpty()) { // the user wants to use the following cert and key for HTTPS @@ -461,8 +461,7 @@ bool DomainServer::optionallySetupOAuth() { const QString OAUTH_CLIENT_SECRET_ENV = "DOMAIN_SERVER_CLIENT_SECRET"; const QString REDIRECT_HOSTNAME_OPTION = "hostname"; - const QVariantMap& settingsMap = _settingsManager.getSettingsMap(); - _oauthProviderURL = QUrl(settingsMap.value(OAUTH_PROVIDER_URL_OPTION).toString()); + _oauthProviderURL = QUrl(_settingsManager.valueForKeyPath(OAUTH_PROVIDER_URL_OPTION).toString()); // if we don't have an oauth provider URL then we default to the default node auth url if (_oauthProviderURL.isEmpty()) { @@ -472,9 +471,9 @@ bool DomainServer::optionallySetupOAuth() { auto accountManager = DependencyManager::get(); accountManager->setAuthURL(_oauthProviderURL); - _oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString(); + _oauthClientID = _settingsManager.valueForKeyPath(OAUTH_CLIENT_ID_OPTION).toString(); _oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV); - _hostname = settingsMap.value(REDIRECT_HOSTNAME_OPTION).toString(); + _hostname = _settingsManager.valueForKeyPath(REDIRECT_HOSTNAME_OPTION).toString(); if (!_oauthClientID.isEmpty()) { if (_oauthProviderURL.isEmpty() @@ -499,11 +498,11 @@ static const QString METAVERSE_DOMAIN_ID_KEY_PATH = "metaverse.id"; void DomainServer::getTemporaryName(bool force) { // check if we already have a domain ID - const QVariant* idValueVariant = valueForKeyPath(_settingsManager.getSettingsMap(), METAVERSE_DOMAIN_ID_KEY_PATH); + QVariant idValueVariant = _settingsManager.valueForKeyPath(METAVERSE_DOMAIN_ID_KEY_PATH); qInfo() << "Requesting temporary domain name"; - if (idValueVariant) { - qDebug() << "A domain ID is already present in domain-server settings:" << idValueVariant->toString(); + if (idValueVariant.isValid()) { + qDebug() << "A domain ID is already present in domain-server settings:" << idValueVariant.toString(); if (force) { qDebug() << "Requesting temporary domain name to replace current ID:" << getID(); } else { @@ -543,9 +542,6 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) { auto settingsDocument = QJsonDocument::fromJson(newSettingsJSON.toUtf8()); _settingsManager.recurseJSONObjectAndOverwriteSettings(settingsDocument.object(), DomainSettings); - // store the new ID and auto networking setting on disk - _settingsManager.persistToFile(); - // store the new token to the account info auto accountManager = DependencyManager::get(); accountManager->setTemporaryDomain(id, key); @@ -647,8 +643,6 @@ void DomainServer::setupNodeListAndAssignments() { QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION); int domainServerPort = localPortValue.toInt(); - QVariantMap& settingsMap = _settingsManager.getSettingsMap(); - int domainServerDTLSPort = INVALID_PORT; if (_isUsingDTLS) { @@ -656,8 +650,9 @@ void DomainServer::setupNodeListAndAssignments() { const QString CUSTOM_DTLS_PORT_OPTION = "dtls-port"; - if (settingsMap.contains(CUSTOM_DTLS_PORT_OPTION)) { - domainServerDTLSPort = (unsigned short) settingsMap.value(CUSTOM_DTLS_PORT_OPTION).toUInt(); + auto dtlsPortVariant = _settingsManager.valueForKeyPath(CUSTOM_DTLS_PORT_OPTION); + if (dtlsPortVariant.isValid()) { + domainServerDTLSPort = (unsigned short) dtlsPortVariant.toUInt(); } } @@ -687,9 +682,9 @@ void DomainServer::setupNodeListAndAssignments() { nodeList->setSessionUUID(_overridingDomainID); isMetaverseDomain = true; // assume metaverse domain } else { - const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH); - if (idValueVariant) { - nodeList->setSessionUUID(idValueVariant->toString()); + QVariant idValueVariant = _settingsManager.valueForKeyPath(METAVERSE_DOMAIN_ID_KEY_PATH); + if (idValueVariant.isValid()) { + nodeList->setSessionUUID(idValueVariant.toString()); isMetaverseDomain = true; // if we have an ID, we'll assume we're a metaverse domain } else { nodeList->setSessionUUID(QUuid::createUuid()); // Use random UUID @@ -758,10 +753,10 @@ bool DomainServer::resetAccountManagerAccessToken() { QString accessToken = QProcessEnvironment::systemEnvironment().value(ENV_ACCESS_TOKEN_KEY); if (accessToken.isEmpty()) { - const QVariant* accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH); + QVariant accessTokenVariant = _settingsManager.valueForKeyPath(ACCESS_TOKEN_KEY_PATH); - if (accessTokenVariant && accessTokenVariant->canConvert(QMetaType::QString)) { - accessToken = accessTokenVariant->toString(); + if (accessTokenVariant.isValid() && accessTokenVariant.canConvert(QMetaType::QString)) { + accessToken = accessTokenVariant.toString(); } else { qWarning() << "No access token is present. Some operations that use the metaverse API will fail."; qDebug() << "Set an access token via the web interface, in your user config" @@ -892,31 +887,26 @@ void DomainServer::updateICEServerAddresses() { } void DomainServer::parseAssignmentConfigs(QSet& excludedTypes) { - const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)"; - QRegExp assignmentConfigRegex(ASSIGNMENT_CONFIG_REGEX_STRING); - - const QVariantMap& settingsMap = _settingsManager.getSettingsMap(); + const QString ASSIGNMENT_CONFIG_PREFIX = "config-"; // scan for assignment config keys - QStringList variantMapKeys = settingsMap.keys(); - int configIndex = variantMapKeys.indexOf(assignmentConfigRegex); + for (int i = 0; i < Assignment::AllTypes; ++i) { + QVariant assignmentConfigVariant = _settingsManager.valueOrDefaultValueForKeyPath(ASSIGNMENT_CONFIG_PREFIX + QString::number(i)); - while (configIndex != -1) { - // figure out which assignment type this matches - Assignment::Type assignmentType = (Assignment::Type) assignmentConfigRegex.cap(1).toInt(); + if (assignmentConfigVariant.isValid()) { + // figure out which assignment type this matches + Assignment::Type assignmentType = static_cast(i); - if (assignmentType < Assignment::AllTypes && !excludedTypes.contains(assignmentType)) { - QVariant mapValue = settingsMap[variantMapKeys[configIndex]]; - QVariantList assignmentList = mapValue.toList(); + if (!excludedTypes.contains(assignmentType)) { + QVariantList assignmentList = assignmentConfigVariant.toList(); - if (assignmentType != Assignment::AgentType) { - createStaticAssignmentsForType(assignmentType, assignmentList); + if (assignmentType != Assignment::AgentType) { + createStaticAssignmentsForType(assignmentType, assignmentList); + } + + excludedTypes.insert(assignmentType); } - - excludedTypes.insert(assignmentType); } - - configIndex = variantMapKeys.indexOf(assignmentConfigRegex, configIndex + 1); } } @@ -928,10 +918,10 @@ void DomainServer::addStaticAssignmentToAssignmentHash(Assignment* newAssignment void DomainServer::populateStaticScriptedAssignmentsFromSettings() { const QString PERSISTENT_SCRIPTS_KEY_PATH = "scripts.persistent_scripts"; - const QVariant* persistentScriptsVariant = valueForKeyPath(_settingsManager.getSettingsMap(), PERSISTENT_SCRIPTS_KEY_PATH); + QVariant persistentScriptsVariant = _settingsManager.valueOrDefaultValueForKeyPath(PERSISTENT_SCRIPTS_KEY_PATH); - if (persistentScriptsVariant) { - QVariantList persistentScriptsList = persistentScriptsVariant->toList(); + if (persistentScriptsVariant.isValid()) { + QVariantList persistentScriptsList = persistentScriptsVariant.toList(); foreach(const QVariant& persistentScriptVariant, persistentScriptsList) { QVariantMap persistentScript = persistentScriptVariant.toMap(); @@ -1954,13 +1944,12 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url auto nodeList = DependencyManager::get(); - auto getSetting = [this](QString keyPath, QVariant& value) -> bool { - QVariantMap& settingsMap = _settingsManager.getSettingsMap(); - QVariant* var = valueForKeyPath(settingsMap, keyPath); - if (var == nullptr) { + auto getSetting = [this](QString keyPath, QVariant value) -> bool { + + value = _settingsManager.valueForKeyPath(keyPath); + if (!value.isValid()) { return false; } - value = *var; return true; }; @@ -2028,8 +2017,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url if (connection->requestOperation() == QNetworkAccessManager::GetOperation) { const QString URI_WIZARD = "/wizard/"; const QString WIZARD_COMPLETED_ONCE_KEY_PATH = "wizard.completed_once"; - const QVariant* wizardCompletedOnce = valueForKeyPath(_settingsManager.getSettingsMap(), WIZARD_COMPLETED_ONCE_KEY_PATH); - const bool completedOnce = wizardCompletedOnce && wizardCompletedOnce->toBool(); + QVariant wizardCompletedOnce = _settingsManager.valueForKeyPath(WIZARD_COMPLETED_ONCE_KEY_PATH); + const bool completedOnce = wizardCompletedOnce.isValid() && wizardCompletedOnce.toBool(); if (url.path() != URI_WIZARD && url.path().endsWith('/') && !completedOnce) { // First visit, redirect to the wizard @@ -2326,8 +2315,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return true; } else if (url.path() == "/domain_settings") { - auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH); - if (!accessTokenVariant) { + auto accessTokenVariant = _settingsManager.valueForKeyPath(ACCESS_TOKEN_KEY_PATH); + if (!accessTokenVariant.isValid()) { connection->respond(HTTPConnection::StatusCode400); return true; } @@ -2360,8 +2349,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return forwardMetaverseAPIRequest(connection, "/api/v1/domains/" + domainID, "domain", { }, { "network_address", "network_port", "label" }); } else if (url.path() == URI_API_PLACES) { - auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH); - if (!accessTokenVariant->isValid()) { + auto accessTokenVariant = _settingsManager.valueForKeyPath(ACCESS_TOKEN_KEY_PATH); + if (!accessTokenVariant.isValid()) { connection->respond(HTTPConnection::StatusCode400, "User access token has not been set"); return true; } @@ -2409,7 +2398,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QUrl url { NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/api/v1/places/" + place_id }; - url.setQuery("access_token=" + accessTokenVariant->toString()); + url.setQuery("access_token=" + accessTokenVariant.toString()); QNetworkRequest req(url); req.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); @@ -2604,10 +2593,11 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl const QByteArray UNAUTHENTICATED_BODY = "You do not have permission to access this domain-server."; - QVariantMap& settingsMap = _settingsManager.getSettingsMap(); + QVariant adminUsersVariant = _settingsManager.valueForKeyPath(ADMIN_USERS_CONFIG_KEY); + QVariant adminRolesVariant = _settingsManager.valueForKeyPath(ADMIN_ROLES_CONFIG_KEY); if (!_oauthProviderURL.isEmpty() - && (settingsMap.contains(ADMIN_USERS_CONFIG_KEY) || settingsMap.contains(ADMIN_ROLES_CONFIG_KEY))) { + && (adminUsersVariant.isValid() || adminRolesVariant.isValid())) { QString cookieString = connection->requestHeaders().value(HTTP_COOKIE_HEADER_KEY); const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)"; @@ -2618,7 +2608,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl cookieUUID = cookieUUIDRegex.cap(1); } - if (valueForKeyPath(settingsMap, BASIC_AUTH_USERNAME_KEY_PATH)) { + if (_settingsManager.valueForKeyPath(BASIC_AUTH_USERNAME_KEY_PATH).isValid()) { qDebug() << "Config file contains web admin settings for OAuth and basic HTTP authentication." << "These cannot be combined - using OAuth for authentication."; } @@ -2628,13 +2618,13 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl DomainServerWebSessionData sessionData = _cookieSessionHash.value(cookieUUID); QString profileUsername = sessionData.getUsername(); - if (settingsMap.value(ADMIN_USERS_CONFIG_KEY).toStringList().contains(profileUsername)) { + if (_settingsManager.valueForKeyPath(ADMIN_USERS_CONFIG_KEY).toStringList().contains(profileUsername)) { // this is an authenticated user return true; } // loop the roles of this user and see if they are in the admin-roles array - QStringList adminRolesArray = settingsMap.value(ADMIN_ROLES_CONFIG_KEY).toStringList(); + QStringList adminRolesArray = _settingsManager.valueForKeyPath(ADMIN_ROLES_CONFIG_KEY).toStringList(); if (!adminRolesArray.isEmpty()) { foreach(const QString& userRole, sessionData.getRoles()) { @@ -2679,7 +2669,7 @@ 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 (valueForKeyPath(settingsMap, BASIC_AUTH_USERNAME_KEY_PATH)) { + } else if (_settingsManager.valueForKeyPath(BASIC_AUTH_USERNAME_KEY_PATH).isValid()) { // config file contains username and password combinations for basic auth const QByteArray BASIC_AUTH_HEADER_KEY = "Authorization"; @@ -2698,10 +2688,10 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl QString headerPassword = credentialList[1]; // we've pulled a username and password - now check if there is a match in our basic auth hash - QString settingsUsername = valueForKeyPath(settingsMap, BASIC_AUTH_USERNAME_KEY_PATH)->toString(); - const QVariant* settingsPasswordVariant = valueForKeyPath(settingsMap, BASIC_AUTH_PASSWORD_KEY_PATH); + QString settingsUsername = _settingsManager.valueForKeyPath(BASIC_AUTH_USERNAME_KEY_PATH).toString(); + QVariant settingsPasswordVariant = _settingsManager.valueForKeyPath(BASIC_AUTH_PASSWORD_KEY_PATH); - QString settingsPassword = settingsPasswordVariant ? settingsPasswordVariant->toString() : ""; + QString settingsPassword = settingsPasswordVariant.isValid() ? settingsPasswordVariant.toString() : ""; QString hexHeaderPassword = headerPassword.isEmpty() ? "" : QCryptographicHash::hash(headerPassword.toUtf8(), QCryptographicHash::Sha256).toHex(); @@ -2838,13 +2828,14 @@ ReplicationServerInfo serverInformationFromSettings(QVariantMap serverMap, Repli } void DomainServer::updateReplicationNodes(ReplicationServerDirection direction) { - auto settings = _settingsManager.getSettingsMap(); - if (settings.contains(BROADCASTING_SETTINGS_KEY)) { + auto broadcastSettingsVariant = _settingsManager.valueForKeyPath(BROADCASTING_SETTINGS_KEY); + + if (broadcastSettingsVariant.isValid()) { auto nodeList = DependencyManager::get(); std::vector replicationNodesInSettings; - auto replicationSettings = settings.value(BROADCASTING_SETTINGS_KEY).toMap(); + auto replicationSettings = broadcastSettingsVariant.toMap(); QString serversKey = direction == Upstream ? "upstream_servers" : "downstream_servers"; QString replicationDirection = direction == Upstream ? "upstream" : "downstream"; @@ -2920,13 +2911,12 @@ void DomainServer::updateUpstreamNodes() { void DomainServer::updateReplicatedNodes() { // Make sure we have downstream nodes in our list - auto settings = _settingsManager.getSettingsMap(); - static const QString REPLICATED_USERS_KEY = "users"; _replicatedUsernames.clear(); - - if (settings.contains(BROADCASTING_SETTINGS_KEY)) { - auto replicationSettings = settings.value(BROADCASTING_SETTINGS_KEY).toMap(); + + auto replicationVariant = _settingsManager.valueForKeyPath(BROADCASTING_SETTINGS_KEY); + if (replicationVariant.isValid()) { + auto replicationSettings = replicationVariant.toMap(); if (replicationSettings.contains(REPLICATED_USERS_KEY)) { auto usersSettings = replicationSettings.value(REPLICATED_USERS_KEY).toList(); for (auto& username : usersSettings) { @@ -3114,17 +3104,17 @@ void DomainServer::processPathQueryPacket(QSharedPointer messag // check out paths in the _configMap to see if we have a match auto keypath = QString(PATHS_SETTINGS_KEYPATH_FORMAT).arg(SETTINGS_PATHS_KEY).arg(pathQuery); - const QVariant* pathMatch = valueForKeyPath(_settingsManager.getSettingsMap(), keypath); + QVariant pathMatch = _settingsManager.valueForKeyPath(keypath); - if (pathMatch || pathQuery == INDEX_PATH) { + if (pathMatch.isValid() || pathQuery == INDEX_PATH) { // we got a match, respond with the resulting viewpoint auto nodeList = DependencyManager::get(); QString responseViewpoint; // if we didn't match the path BUT this is for the index path then send back our default - if (pathMatch) { - responseViewpoint = pathMatch->toMap()[PATH_VIEWPOINT_KEY].toString(); + if (pathMatch.isValid()) { + responseViewpoint = pathMatch.toMap()[PATH_VIEWPOINT_KEY].toString(); } else { const QString DEFAULT_INDEX_PATH = "/0,0,0/0,0,0,1"; responseViewpoint = DEFAULT_INDEX_PATH; diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index cd7155d9da..ad0381a697 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -38,6 +38,9 @@ #include "DomainServerNodeData.h" const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json"; +const QString SETTINGS_PATH = "/settings"; +const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json"; +const QString CONTENT_SETTINGS_PATH_JSON = "/content-settings.json"; const QString DESCRIPTION_SETTINGS_KEY = "settings"; const QString SETTING_DEFAULT_KEY = "default"; @@ -190,6 +193,9 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer(getSettingsMap()[DESCRIPTORS].data()); -} - void DomainServerSettingsManager::initializeGroupPermissions(NodePermissionsMap& permissionsRows, QString groupName, NodePermissionsPointer perms) { // this is called when someone has used the domain-settings webpage to add a group. They type the group's name @@ -487,6 +482,9 @@ void DomainServerSettingsManager::initializeGroupPermissions(NodePermissionsMap& void DomainServerSettingsManager::packPermissionsForMap(QString mapName, NodePermissionsMap& permissionsRows, QString keyPath) { + // grab a write lock on the settings mutex since we're about to change the config map + QWriteLocker locker(&_settingsLock); + // find (or create) the "security" section of the settings map QVariant* security = _configMap.valueForKeyPath("security", true); if (!security->canConvert(QMetaType::QVariantMap)) { @@ -576,15 +574,15 @@ bool DomainServerSettingsManager::unpackPermissionsForKeypath(const QString& key mapPointer->clear(); - QVariant* permissions = _configMap.valueForKeyPath(keyPath, true); - if (!permissions->canConvert(QMetaType::QVariantList)) { + QVariant permissions = valueForKeyPath(keyPath); + + if (!permissions.canConvert(QMetaType::QVariantList)) { qDebug() << "Failed to extract permissions for key path" << keyPath << "from settings."; - (*permissions) = QVariantList(); } bool needPack = false; - QList permissionsList = permissions->toList(); + QList permissionsList = permissions.toList(); foreach (QVariant permsHash, permissionsList) { NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) }; QString id = perms->getID(); @@ -1068,12 +1066,22 @@ NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QUuid& return getForbiddensForGroup(groupKey.first, groupKey.second); } +QVariant DomainServerSettingsManager::valueForKeyPath(const QString& keyPath) { + QReadLocker locker(&_settingsLock); + auto foundValue = _configMap.valueForKeyPath(keyPath); + return foundValue ? *foundValue : QVariant(); +} + QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString& keyPath) { + QReadLocker locker(&_settingsLock); const QVariant* foundValue = _configMap.valueForKeyPath(keyPath); if (foundValue) { return *foundValue; } else { + // we don't need the settings lock anymore since we're done reading from the config map + _settingsLock.unlock(); + int dotIndex = keyPath.indexOf('.'); QString groupKey = keyPath.mid(0, dotIndex); @@ -1112,9 +1120,6 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection // we recurse one level deep below each group for the appropriate setting bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject, endpointType); - // store whatever the current _settingsMap is to file - persistToFile(); - // return success to the caller QString jsonSuccess = "{\"status\": \"success\"}"; connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json"); @@ -1216,16 +1221,9 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection } bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType) { - - if (thread() != QThread::currentThread()) { - bool success; - BLOCKING_INVOKE_METHOD(this, "restoreSettingsFromObject", - Q_RETURN_ARG(bool, success), - Q_ARG(QJsonObject, settingsToRestore), - Q_ARG(SettingsType, settingsType)); - return success; - } + // grab a write lock since we're about to change the settings map + QWriteLocker locker(&_settingsLock); QJsonArray* filteredDescriptionArray = settingsType == DomainSettings ? &_domainSettingsDescription : &_contentSettingsDescription; @@ -1341,6 +1339,10 @@ bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settings } else { // restore completed, persist the new settings qDebug() << "Restore completed, persisting restored settings to file"; + + // let go of the write lock since we're done making changes to the config map + locker.unlock(); + persistToFile(); return true; } @@ -1352,20 +1354,6 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt bool includeDefaults, bool isForBackup) { QJsonObject responseObject; - if (thread() != QThread::currentThread()) { - - BLOCKING_INVOKE_METHOD(this, "settingsResponseObjectForType", - Q_RETURN_ARG(QJsonObject, responseObject), - Q_ARG(QString, typeValue), - Q_ARG(bool, isAuthenticated), - Q_ARG(bool, includeDomainSettings), - Q_ARG(bool, includeContentSettings), - Q_ARG(bool, includeDefaults), - Q_ARG(bool, isForBackup)); - - return responseObject; - } - if (!typeValue.isEmpty() || isAuthenticated) { // convert the string type value to a QJsonValue QJsonValue queryType = typeValue.isEmpty() ? QJsonValue() : QJsonValue(typeValue.toInt()); @@ -1414,21 +1402,21 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt QVariant variantValue; if (!groupKey.isEmpty()) { - QVariant settingsMapGroupValue = _configMap.value(groupKey); + QVariant settingsMapGroupValue = valueForKeyPath(groupKey); if (!settingsMapGroupValue.isNull()) { variantValue = settingsMapGroupValue.toMap().value(settingName); } } else { - variantValue = _configMap.value(settingName); + variantValue = valueForKeyPath(settingName); } // final check for inclusion // either we include default values or we don't but this isn't a default value - if (includeDefaults || !variantValue.isNull()) { + if (includeDefaults || variantValue.isValid()) { QJsonValue result; - if (variantValue.isNull()) { + if (!variantValue.isValid()) { // no value for this setting, pass the default if (settingObject.contains(SETTING_DEFAULT_KEY)) { result = settingObject[SETTING_DEFAULT_KEY]; @@ -1567,6 +1555,10 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, SettingsType settingsType) { + + // take a write lock since we're about to overwrite settings in the config map + QWriteLocker locker(&_settingsLock); + static const QString SECURITY_ROOT_KEY = "security"; static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist"; static const QString BROADCASTING_KEY = "broadcasting"; @@ -1664,6 +1656,12 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ } } + // we're done making changes to the config map, let go of our read lock + locker.unlock(); + + // store whatever the current config map is to file + persistToFile(); + return needRestart; } @@ -1690,6 +1688,9 @@ bool permissionVariantLessThan(const QVariant &v1, const QVariant &v2) { } void DomainServerSettingsManager::sortPermissions() { + // take a write lock since we're about to change the config map data + QWriteLocker locker(&_settingsLock); + // sort the permission-names QVariant* standardPermissions = _configMap.valueForKeyPath(AGENT_STANDARD_PERMISSIONS_KEYPATH); if (standardPermissions && standardPermissions->canConvert(QMetaType::QVariantList)) { @@ -1726,11 +1727,15 @@ void DomainServerSettingsManager::persistToFile() { QFile settingsFile(_configMap.getUserConfigFilename()); if (settingsFile.open(QIODevice::WriteOnly)) { + // take a read lock so we can grab the config and write it to file + QReadLocker locker(&_settingsLock); settingsFile.write(QJsonDocument::fromVariant(_configMap.getConfig()).toJson()); } else { qCritical("Could not write to JSON settings file. Unable to persist settings."); // failed to write, reload whatever the current config state is + // with a write lock since we're about to overwrite the config map + QWriteLocker locker(&_settingsLock); _configMap.loadConfig(_argumentList); } } diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 897a15485f..d81547410b 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -27,9 +27,6 @@ const QString SETTINGS_PATHS_KEY = "paths"; -const QString SETTINGS_PATH = "/settings"; -const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json"; -const QString CONTENT_SETTINGS_PATH_JSON = "/content-settings.json"; const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions"; const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions"; const QString IP_PERMISSIONS_KEYPATH = "security.ip_permissions"; @@ -53,11 +50,12 @@ public: bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url); void setupConfigMap(const QStringList& argumentList); + + // each of the three methods in this group takes a read lock of _settingsLock + // and cannot be called when the a write lock is held by the same thread QVariant valueOrDefaultValueForKeyPath(const QString& keyPath); - - QVariantMap& getSettingsMap() { return _configMap.getConfig(); } - - QVariantMap& getDescriptorsMap(); + QVariant valueForKeyPath(const QString& keyPath); + bool containsKeyPath(const QString& keyPath) { return valueForKeyPath(keyPath).isValid(); } // these give access to anonymous/localhost/logged-in settings from the domain-server settings page bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name, 0); } @@ -119,6 +117,8 @@ public: /// thread safe method to restore settings from a JSON object Q_INVOKABLE bool restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType); + bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, SettingsType settingsType); + signals: void updateNodePermissions(); void settingsUpdated(); @@ -138,12 +138,13 @@ private: QStringList _argumentList; QJsonArray filteredDescriptionArray(bool isContentSettings); - bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, SettingsType settingsType); - void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap, const QJsonObject& settingDescription); QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName); void sortPermissions(); + + // you cannot be holding the _settingsLock when persisting to file from the same thread + // since it may take either a read lock or write lock and recursive locking doesn't allow a change in type void persistToFile(); void splitSettingsDescription(); @@ -155,10 +156,10 @@ private: QJsonArray _contentSettingsDescription; QJsonObject _settingsMenuGroups; + // any method that calls _valueForKeyPath on this _configMap must get a write lock it keeps until it + // is done with the returned QVariant* HifiConfigVariantMap _configMap; - friend class DomainServer; - // these cause calls to metaverse's group api void apiGetGroupID(const QString& groupName); void apiGetGroupRanks(const QUuid& groupID); @@ -192,6 +193,9 @@ private: // keep track of answers to api queries about which users are in which groups QHash> _groupMembership; // QHash> + + /// guard read/write access from multiple threads to settings + QReadWriteLock _settingsLock { QReadWriteLock::Recursive }; }; #endif // hifi_DomainServerSettingsManager_h From 679513599cce5f1b7a31b9312e3198c46bede1d2 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 16 Feb 2018 14:09:14 -0800 Subject: [PATCH 356/569] fix row hiding and paste events for badging --- domain-server/resources/web/content/js/content.js | 4 ++-- domain-server/resources/web/js/base-settings.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js index 717f149760..525b989259 100644 --- a/domain-server/resources/web/content/js/content.js +++ b/domain-server/resources/web/content/js/content.js @@ -145,7 +145,7 @@ $(document).ready(function(){ // populate the backups tables with the backups function createBackupTableRow(backup) { return "" - + "" + backup.name + "" + + "" + backup.name + "" + moment(backup.createdAtMillis).format('lll') + "" + "