Merge branch 'master' of https://github.com/highfidelity/hifi into project-freeloco

This commit is contained in:
r3tk0n 2019-04-03 12:42:33 -07:00
commit 874fb6b6d7
83 changed files with 1882 additions and 819 deletions

View file

@ -22,7 +22,6 @@
#include <ScriptCache.h>
#include <EntityEditFilters.h>
#include <NetworkingConstants.h>
#include <AddressManager.h>
#include <hfm/ModelFormatRegistry.h>
#include "../AssignmentDynamicFactory.h"
@ -471,77 +470,7 @@ void EntityServer::startDynamicDomainVerification() {
qCDebug(entities) << "Starting Dynamic Domain Verification...";
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
QHash<QString, EntityItemID> localMap(tree->getEntityCertificateIDMap());
QHashIterator<QString, EntityItemID> i(localMap);
qCDebug(entities) << localMap.size() << "entities in _entityCertificateIDMap";
while (i.hasNext()) {
i.next();
const auto& certificateID = i.key();
const auto& entityID = i.value();
EntityItemPointer entity = tree->findEntityByEntityItemID(entityID);
if (entity) {
if (!entity->getProperties().verifyStaticCertificateProperties()) {
qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << entityID << "failed"
<< "static certificate verification.";
// Delete the entity if it doesn't pass static certificate verification
tree->withWriteLock([&] {
tree->deleteEntity(entityID, true);
});
} else {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest;
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL();
requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/location");
QJsonObject request;
request["certificate_id"] = certificateID;
networkRequest.setUrl(requestURL);
QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
connect(networkReply, &QNetworkReply::finished, this, [this, entityID, networkReply] {
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object();
jsonObject = jsonObject["data"].toObject();
if (networkReply->error() == QNetworkReply::NoError) {
QString thisDomainID = DependencyManager::get<AddressManager>()->getDomainID().remove(QRegExp("\\{|\\}"));
if (jsonObject["domain_id"].toString() != thisDomainID) {
EntityItemPointer entity = tree->findEntityByEntityItemID(entityID);
if (!entity) {
qCDebug(entities) << "Entity undergoing dynamic domain verification is no longer available:" << entityID;
networkReply->deleteLater();
return;
}
if (entity->getAge() > (_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS/MSECS_PER_SECOND)) {
qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString()
<< "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << entityID;
tree->withWriteLock([&] {
tree->deleteEntity(entityID, true);
});
} else {
qCDebug(entities) << "Entity failed dynamic domain verification, but was created too recently to necessitate deletion:" << entityID;
}
} else {
qCDebug(entities) << "Entity passed dynamic domain verification:" << entityID;
}
} else {
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; NOT deleting entity" << entityID
<< "More info:" << jsonObject;
}
networkReply->deleteLater();
});
}
} else {
qCWarning(entities) << "During DDV, an entity with ID" << entityID << "was NOT found in the Entity Tree!";
}
}
tree->startDynamicDomainVerificationOnServer((float) _MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS / MSECS_PER_SECOND);
int nextInterval = qrand() % ((_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS + 1) - _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS) + _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS;
qCDebug(entities) << "Restarting Dynamic Domain Verification timer for" << nextInterval / 1000 << "seconds";

View file

@ -1734,7 +1734,7 @@ void DomainServer::processOctreeDataPersistMessage(QSharedPointer<ReceivedMessag
f.write(data);
OctreeUtils::RawEntityData entityData;
if (entityData.readOctreeDataInfoFromData(data)) {
qCDebug(domain_server) << "Wrote new entities file" << entityData.id << entityData.version;
qCDebug(domain_server) << "Wrote new entities file" << entityData.id << entityData.dataVersion;
} else {
qCDebug(domain_server) << "Failed to read new octree data info";
}

View file

@ -0,0 +1,10 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.3383 4.13446L23.8713 5.60152C21.9374 3.46761 19.2033 2.06723 16.0691 2.06723C13.0683 2.06723 10.4009 3.40093 8.46707 5.40147L7 3.9344C9.26728 1.53375 12.5348 0 16.0691 0C19.7368 0 23.071 1.60044 25.3383 4.13446ZM21.9376 7.53584L20.4705 9.0029C19.4703 7.66921 17.8698 6.86899 16.0693 6.86899C14.4022 6.86899 12.9351 7.66921 11.8682 8.80285L10.4011 7.33578C11.8015 5.80203 13.802 4.80176 16.0693 4.80176C18.4033 4.80176 20.5372 5.86871 21.9376 7.53584ZM17.9575 30.1572C17.9575 31.1771 17.1307 32.0039 16.1108 32.0039C15.0909 32.0039 14.2642 31.1771 14.2642 30.1572C14.2642 29.1373 15.0909 28.3105 16.1108 28.3105C17.1307 28.3105 17.9575 29.1373 17.9575 30.1572ZM18.3632 11.0801H14.1597L15.0116 25.8539H17.4867L18.3632 11.0801Z" fill="#EA4C5F"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.1999 4.72587C17.5049 4.72587 18.5628 3.66795 18.5628 2.36294C18.5628 1.05792 17.5049 0 16.1999 0C14.8948 0 13.8369 1.05792 13.8369 2.36294C13.8369 3.66795 14.8948 4.72587 16.1999 4.72587ZM18.6667 11.8004V14.7337H13.7334V11.8004C13.7334 10.467 14.8667 9.40039 16.2 9.40039C17.6 9.40039 18.6667 10.467 18.6667 11.8004ZM13.7334 20.1332V17.2666H18.6667V20.1332C18.6667 21.4665 17.5333 22.5332 16.2 22.5332C14.8667 22.5332 13.7334 21.4665 13.7334 20.1332ZM23.6665 20.6V17.0667C23.6665 16.4 23.0665 15.9334 22.4665 15.9334C21.7998 15.9334 21.3332 16.4667 21.3332 17.1333V20.6C21.3332 23.0666 19.0665 25.0666 16.3332 25.0666C13.5999 25.0666 11.3333 23.0666 11.3333 20.6V17.0667C11.3333 16.4 10.8666 15.8667 10.2666 15.8667C9.59999 15.8 9 16.2667 9 16.9333V20.6C9 23.9999 11.6666 26.7999 15.1333 27.3332V29.5998H12.2666C11.6 29.5998 11.0666 30.1332 11.0666 30.7998C11.0666 31.4665 11.6 31.9998 12.2666 31.9998H20.4665C21.1332 31.9998 21.6665 31.4665 21.6665 30.7998C21.6665 30.1332 21.1332 29.5998 20.4665 29.5998H17.5332V27.3332C20.9998 26.7332 23.6665 23.9999 23.6665 20.6Z" fill="#00B4EF" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -1,25 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1"
id="svg2" inkscape:version="0.91 r13725" sodipodi:docname="mic-mute-a.svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 50 50"
style="enable-background:new 0 0 50 50;" xml:space="preserve">
<style type="text/css">
.st0{fill:#EA4C5F;}
</style>
<sodipodi:namedview bordercolor="#666666" borderopacity="1" gridtolerance="10" guidetolerance="10" id="namedview18" inkscape:current-layer="svg2" inkscape:cx="25" inkscape:cy="25" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-height="480" inkscape:window-maximized="0" inkscape:window-width="852" inkscape:window-x="0" inkscape:window-y="0" inkscape:zoom="4.72" objecttolerance="10" pagecolor="#ff0000" showgrid="false">
</sodipodi:namedview>
<g id="Layer_2">
</g>
<g id="Layer_1">
<path id="path8" class="st0" d="M28.9,17.1v-0.5c0-2-1.7-3.6-3.7-3.6l0,0c-2,0-3.7,1.6-3.7,3.6v6.9L28.9,17.1z"/>
<path id="path10" class="st0" d="M21.5,29.2v0.2c0,2,1.6,3.6,3.7,3.6l0,0c2,0,3.7-1.6,3.7-3.6v-6.6L21.5,29.2z"/>
<path id="path12" class="st0" d="M39.1,16.8L13.6,39.1c-0.7,0.6-1.8,0.5-2.4-0.2L11,38.7c-0.6-0.7-0.5-1.8,0.2-2.4l25.4-22.4
c0.7-0.6,1.8-0.5,2.4,0.2l0.2,0.2C39.8,15.1,39.7,16.1,39.1,16.8z"/>
<path id="path14" class="st0" d="M23.4,40.2v3.4h-4.3c-1,0-1.8,0.8-1.8,1.8s0.8,1.8,1.8,1.8h12.3c1,0,1.8-0.8,1.8-1.8
s-0.8-1.8-1.8-1.8H27v-3.4c5.2-0.8,9.2-5,9.2-10.1c0-0.1,0-5.1,0-5.3c0-1-0.9-1.7-1.8-1.7c-1,0-1.7,0.8-1.7,1.8c0,0.3,0,4.8,0,5.2
c0,3.7-3.4,6.7-7.5,6.7c-3.6,0-6.7-2.3-7.3-5.4L15,34C16.4,37.2,19.6,39.7,23.4,40.2z"/>
<path id="path16" class="st0" d="M17.7,24.9c0-1-0.7-1.8-1.6-1.8c-1-0.1-1.8,0.7-1.9,1.6c0,0.2,0,4.2,0,5.3l3.5-3.1
C17.7,25.9,17.7,25,17.7,24.9z"/>
</g>
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.6441 4.13328L24.1775 5.59992C22.2442 3.46662 19.5109 2.06664 16.3776 2.06664C13.3776 2.06664 10.711 3.39995 8.77768 5.39993L7.31104 3.93328C9.57767 1.53331 12.8443 0 16.3776 0C20.0442 0 23.3775 1.59998 25.6441 4.13328ZM20.7777 9.00072L22.2444 7.53408C20.8444 5.86743 18.7111 4.80078 16.3778 4.80078C14.1111 4.80078 12.1112 5.80077 10.7112 7.33408L12.1778 8.80073C13.2445 7.66741 14.7111 6.86742 16.3778 6.86742C18.1777 6.86742 19.7777 7.66741 20.7777 9.00072ZM18.8803 12.0758V11.7496C18.8803 10.4445 17.7763 9.40039 16.4775 9.40039C15.1787 9.40039 14.0747 10.4445 14.0747 11.7496V16.2521L18.8803 12.0758ZM14.543 21.5129L12.6113 23.2103C13.4959 24.2599 14.9141 24.9311 16.4774 24.9311C19.14 24.9311 21.348 22.9735 21.348 20.559V17.1658C21.348 16.5132 21.8026 15.9912 22.452 15.9912C23.0364 15.9912 23.6209 16.448 23.6209 17.1005V20.559C23.6209 23.887 21.0233 26.6277 17.6464 27.1498V29.3684H20.5038C21.1532 29.3684 21.6727 29.8905 21.6727 30.543C21.6727 31.1956 21.1532 31.7176 20.5038 31.7176H12.5161C11.8667 31.7176 11.3471 31.1956 11.3471 30.543C11.3471 29.8905 11.8667 29.3684 12.5161 29.3684H15.3085V27.1498C13.5328 26.915 11.9589 26.0045 10.8771 24.7343L8.9443 26.4327C8.48972 26.8242 7.77537 26.759 7.38573 26.3022L7.25585 26.1717C6.8662 25.7149 6.93114 24.9971 7.38573 24.6056L23.8806 9.98853C24.3352 9.597 25.0495 9.66226 25.4392 10.119L25.5691 10.2495C25.9587 10.7716 25.8938 11.4241 25.5041 11.8809L18.8803 17.7015V20.1017C18.8803 21.4068 17.7763 22.4509 16.4775 22.4509C15.6689 22.4509 14.9744 22.0838 14.543 21.5129ZM10.5679 15.9919C11.1523 15.9919 11.6069 16.5139 11.6069 17.1664V18.4715L9.33398 20.4944V17.0359C9.39892 16.4486 9.91845 15.9266 10.5679 15.9919Z" fill="#EA4C5F"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -1,25 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1"
id="svg2" inkscape:version="0.91 r13725" sodipodi:docname="mic-mute-a.svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 50 50"
style="enable-background:new 0 0 50 50;" xml:space="preserve">
<style type="text/css">
.st0{fill:#EA4C5F;}
</style>
<sodipodi:namedview bordercolor="#666666" borderopacity="1" gridtolerance="10" guidetolerance="10" id="namedview18" inkscape:current-layer="svg2" inkscape:cx="25" inkscape:cy="25" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-height="480" inkscape:window-maximized="0" inkscape:window-width="852" inkscape:window-x="0" inkscape:window-y="0" inkscape:zoom="4.72" objecttolerance="10" pagecolor="#ff0000" showgrid="false">
</sodipodi:namedview>
<g id="Layer_2">
</g>
<g id="Layer_1">
<path id="path8" class="st0" d="M28.9,17.1v-0.5c0-2-1.7-3.6-3.7-3.6l0,0c-2,0-3.7,1.6-3.7,3.6v6.9L28.9,17.1z"/>
<path id="path10" class="st0" d="M21.5,29.2v0.2c0,2,1.6,3.6,3.7,3.6l0,0c2,0,3.7-1.6,3.7-3.6v-6.6L21.5,29.2z"/>
<path id="path12" class="st0" d="M39.1,16.8L13.6,39.1c-0.7,0.6-1.8,0.5-2.4-0.2L11,38.7c-0.6-0.7-0.5-1.8,0.2-2.4l25.4-22.4
c0.7-0.6,1.8-0.5,2.4,0.2l0.2,0.2C39.8,15.1,39.7,16.1,39.1,16.8z"/>
<path id="path14" class="st0" d="M23.4,40.2v3.4h-4.3c-1,0-1.8,0.8-1.8,1.8s0.8,1.8,1.8,1.8h12.3c1,0,1.8-0.8,1.8-1.8
s-0.8-1.8-1.8-1.8H27v-3.4c5.2-0.8,9.2-5,9.2-10.1c0-0.1,0-5.1,0-5.3c0-1-0.9-1.7-1.8-1.7c-1,0-1.7,0.8-1.7,1.8c0,0.3,0,4.8,0,5.2
c0,3.7-3.4,6.7-7.5,6.7c-3.6,0-6.7-2.3-7.3-5.4L15,34C16.4,37.2,19.6,39.7,23.4,40.2z"/>
<path id="path16" class="st0" d="M17.7,24.9c0-1-0.7-1.8-1.6-1.8c-1-0.1-1.8,0.7-1.9,1.6c0,0.2,0,4.2,0,5.3l3.5-3.1
C17.7,25.9,17.7,25,17.7,24.9z"/>
</g>
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.6441 4.13328L24.1775 5.59992C22.2442 3.46662 19.5109 2.06664 16.3776 2.06664C13.3776 2.06664 10.711 3.39995 8.77768 5.39993L7.31104 3.93328C9.57767 1.53331 12.8443 0 16.3776 0C20.0442 0 23.3775 1.59998 25.6441 4.13328ZM20.7777 9.00072L22.2444 7.53408C20.8444 5.86743 18.7111 4.80078 16.3778 4.80078C14.1111 4.80078 12.1112 5.80077 10.7112 7.33408L12.1778 8.80073C13.2445 7.66741 14.7111 6.86742 16.3778 6.86742C18.1777 6.86742 19.7777 7.66741 20.7777 9.00072ZM18.8803 12.0758V11.7496C18.8803 10.4445 17.7763 9.40039 16.4775 9.40039C15.1787 9.40039 14.0747 10.4445 14.0747 11.7496V16.2521L18.8803 12.0758ZM14.543 21.5129L12.6113 23.2103C13.4959 24.2599 14.9141 24.9311 16.4774 24.9311C19.14 24.9311 21.348 22.9735 21.348 20.559V17.1658C21.348 16.5132 21.8026 15.9912 22.452 15.9912C23.0364 15.9912 23.6209 16.448 23.6209 17.1005V20.559C23.6209 23.887 21.0233 26.6277 17.6464 27.1498V29.3684H20.5038C21.1532 29.3684 21.6727 29.8905 21.6727 30.543C21.6727 31.1956 21.1532 31.7176 20.5038 31.7176H12.5161C11.8667 31.7176 11.3471 31.1956 11.3471 30.543C11.3471 29.8905 11.8667 29.3684 12.5161 29.3684H15.3085V27.1498C13.5328 26.915 11.9589 26.0045 10.8771 24.7343L8.9443 26.4327C8.48972 26.8242 7.77537 26.759 7.38573 26.3022L7.25585 26.1717C6.8662 25.7149 6.93114 24.9971 7.38573 24.6056L23.8806 9.98853C24.3352 9.597 25.0495 9.66226 25.4392 10.119L25.5691 10.2495C25.9587 10.7716 25.8938 11.4241 25.5041 11.8809L18.8803 17.7015V20.1017C18.8803 21.4068 17.7763 22.4509 16.4775 22.4509C15.6689 22.4509 14.9744 22.0838 14.543 21.5129ZM10.5679 15.9919C11.1523 15.9919 11.6069 16.5139 11.6069 17.1664V18.4715L9.33398 20.4944V17.0359C9.39893 16.4486 9.91845 15.9266 10.5679 15.9919Z" fill="#EA4C5F"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -1,60 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 40 40"
style="enable-background:new 0 0 40 40;"
xml:space="preserve"
inkscape:version="0.91 r13725"
sodipodi:docname="mic-mute.svg"><metadata
id="metadata6958"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs6956" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1824"
inkscape:window-height="1057"
id="namedview6954"
showgrid="false"
inkscape:zoom="5.9"
inkscape:cx="-40.338983"
inkscape:cy="20"
inkscape:window-x="88"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" /><style
type="text/css"
id="style6942">
.st0{fill:#FFFFFF;}
</style><ellipse
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path8048"
cx="20.1"
cy="20.5"
rx="15.967586"
ry="15.967585" /><rect
style="fill:#ffffff;fill-opacity:1;stroke:#feffff;stroke-width:0;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="rect8065"
width="30.1991"
height="2.9999897"
x="13.432917"
y="-1.2235159"
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)" /></svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -1,70 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
x="0px"
y="0px"
viewBox="0 0 50 50"
style="enable-background:new 0 0 50 50;"
xml:space="preserve"
id="svg2"
inkscape:version="0.91 r13725"
sodipodi:docname="mic-unmute-a.svg"><metadata
id="metadata22"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs20" /><sodipodi:namedview
pagecolor="#ff0000"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="852"
inkscape:window-height="480"
id="namedview18"
showgrid="false"
inkscape:zoom="4.72"
inkscape:cx="25"
inkscape:cy="25"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" /><style
type="text/css"
id="style4">
.st0{fill:#FFFFFF;}
</style><g
id="Layer_2" /><g
id="Layer_1"
style="fill:#000000;fill-opacity:1"><path
class="st0"
d="M31.4,14.1l2.2-2.2c-2.1-2.5-5.3-4.1-8.8-4.1c-3.4,0-6.4,1.5-8.5,3.8c0.7,0.7,1.5,1.5,2.2,2.2 c1.6-1.7,3.8-2.9,6.3-2.9C27.5,10.9,29.9,12.1,31.4,14.1z"
id="path8"
style="fill:#000000;fill-opacity:1" /><path
class="st0"
d="M36.5,9l2.2-2.2C35.3,3,30.3,0.6,24.8,0.6c-5.3,0-10.2,2.3-13.6,5.9c0.7,0.7,1.5,1.5,2.2,2.2 c2.9-3,6.9-5,11.4-5C29.5,3.7,33.6,5.8,36.5,9z"
id="path10"
style="fill:#000000;fill-opacity:1" /><path
class="st0"
d="M28.5,22.7v-4.4c0-2-1.6-3.6-3.7-3.6h0c-2,0-3.7,1.6-3.7,3.6v4.4H28.5z"
id="path12"
style="fill:#000000;fill-opacity:1" /><path
class="st0"
d="M21.1,26.5v4.3c0,2,1.7,3.6,3.7,3.6h0c2,0,3.7-1.6,3.7-3.6v-4.3H21.1z"
id="path14"
style="fill:#000000;fill-opacity:1" /><path
class="st0"
d="M36,31.5c0-0.1,0-5.1,0-5.3c0-1-0.9-1.7-1.8-1.7c-1,0-1.7,0.8-1.7,1.8c0,0.3,0,4.8,0,5.2 c0,3.7-3.4,6.7-7.5,6.7c-4.1,0-7.5-3-7.5-6.7c0-0.4,0-4.9,0-5.3c0-1-0.7-1.8-1.6-1.8C14.9,24.3,14,25,14,26c0,0.3,0,5.4,0,5.5 c0,5.1,4,9.3,9.2,10.1l0,3.4h-4.3c-1,0-1.8,0.8-1.8,1.8c0,1,0.8,1.8,1.8,1.8h12.3c1,0,1.8-0.8,1.8-1.8c0-1-0.8-1.8-1.8-1.8h-4.4 l0-3.4C32,40.7,36,36.6,36,31.5z"
id="path16"
style="fill:#000000;fill-opacity:1" /></g></svg>
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.8664 5.59992L25.3331 4.13328C23.0664 1.59998 19.7332 0 16.0665 0C12.5333 0 9.26664 1.53331 7 3.93328L8.46665 5.39993C10.4 3.39995 13.0666 2.06664 16.0665 2.06664C19.1998 2.06664 21.9331 3.46662 23.8664 5.59992ZM20.4664 8.99975L21.9331 7.5331C20.5331 5.86646 18.3998 4.7998 16.0665 4.7998C13.7999 4.7998 11.7999 5.79979 10.3999 7.3331L11.8665 8.79975C12.9332 7.66643 14.3998 6.86644 16.0665 6.86644C17.8665 6.86644 19.4664 7.66643 20.4664 8.99975ZM18.5334 11.8004V14.7337H13.6001V11.8004C13.6001 10.467 14.7334 9.40039 16.0667 9.40039C17.4667 9.40039 18.5334 10.467 18.5334 11.8004ZM13.6001 17.2666V20.1332C13.6001 21.4665 14.7334 22.5332 16.0667 22.5332C17.4 22.5332 18.5334 21.4665 18.5334 20.1332V17.2666H13.6001ZM23.5332 17.0667V20.6C23.5332 23.9999 20.8665 26.7332 17.3999 27.3332V29.5998H20.3332C20.9999 29.5998 21.5332 30.1332 21.5332 30.7998C21.5332 31.4665 20.9999 31.9998 20.3332 31.9998H12.1333C11.4667 31.9998 10.9333 31.4665 10.9333 30.7998C10.9333 30.1332 11.4667 29.5998 12.1333 29.5998H14.9999V27.3332C11.5333 26.7999 8.8667 23.9999 8.8667 20.6V16.9333C8.8667 16.2667 9.46669 15.8 10.1333 15.8667C10.7333 15.8667 11.2 16.4 11.2 17.0667V20.6C11.2 23.0666 13.4666 25.0666 16.1999 25.0666C18.9332 25.0666 21.1999 23.0666 21.1999 20.6V17.1333C21.1999 16.4667 21.6665 15.9334 22.3332 15.9334C22.9332 15.9334 23.5332 16.4 23.5332 17.0667Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1,22 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g id="Layer_2">
</g>
<g id="Layer_1">
<path class="st0" d="M31.4,14.1l2.2-2.2c-2.1-2.5-5.3-4.1-8.8-4.1c-3.4,0-6.4,1.5-8.5,3.8c0.7,0.7,1.5,1.5,2.2,2.2
c1.6-1.7,3.8-2.9,6.3-2.9C27.5,10.9,29.9,12.1,31.4,14.1z"/>
<path class="st0" d="M36.5,9l2.2-2.2C35.3,3,30.3,0.6,24.8,0.6c-5.3,0-10.2,2.3-13.6,5.9c0.7,0.7,1.5,1.5,2.2,2.2
c2.9-3,6.9-5,11.4-5C29.5,3.7,33.6,5.8,36.5,9z"/>
<path class="st0" d="M28.5,22.7v-4.4c0-2-1.6-3.6-3.7-3.6h0c-2,0-3.7,1.6-3.7,3.6v4.4H28.5z"/>
<path class="st0" d="M21.1,26.5v4.3c0,2,1.7,3.6,3.7,3.6h0c2,0,3.7-1.6,3.7-3.6v-4.3H21.1z"/>
<path class="st0" d="M36,31.5c0-0.1,0-5.1,0-5.3c0-1-0.9-1.7-1.8-1.7c-1,0-1.7,0.8-1.7,1.8c0,0.3,0,4.8,0,5.2
c0,3.7-3.4,6.7-7.5,6.7c-4.1,0-7.5-3-7.5-6.7c0-0.4,0-4.9,0-5.3c0-1-0.7-1.8-1.6-1.8C14.9,24.3,14,25,14,26c0,0.3,0,5.4,0,5.5
c0,5.1,4,9.3,9.2,10.1l0,3.4h-4.3c-1,0-1.8,0.8-1.8,1.8c0,1,0.8,1.8,1.8,1.8h12.3c1,0,1.8-0.8,1.8-1.8c0-1-0.8-1.8-1.8-1.8h-4.4
l0-3.4C32,40.7,36,36.6,36,31.5z"/>
</g>
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.8664 5.59992L25.3331 4.13328C23.0664 1.59998 19.7332 0 16.0665 0C12.5333 0 9.26664 1.53331 7 3.93328L8.46665 5.39993C10.4 3.39995 13.0666 2.06664 16.0665 2.06664C19.1998 2.06664 21.9331 3.46662 23.8664 5.59992ZM20.4664 8.99975L21.9331 7.5331C20.5331 5.86646 18.3998 4.7998 16.0665 4.7998C13.7999 4.7998 11.7999 5.79979 10.3999 7.3331L11.8665 8.79975C12.9332 7.66643 14.3998 6.86644 16.0665 6.86644C17.8665 6.86644 19.4664 7.66643 20.4664 8.99975ZM18.5334 11.8004V14.7337H13.6001V11.8004C13.6001 10.467 14.7334 9.40039 16.0667 9.40039C17.4667 9.40039 18.5334 10.467 18.5334 11.8004ZM13.6001 17.2666V20.1332C13.6001 21.4665 14.7334 22.5332 16.0667 22.5332C17.4 22.5332 18.5334 21.4665 18.5334 20.1332V17.2666H13.6001ZM23.5332 17.0667V20.6C23.5332 23.9999 20.8665 26.7332 17.3999 27.3332V29.5998H20.3332C20.9999 29.5998 21.5332 30.1332 21.5332 30.7998C21.5332 31.4665 20.9999 31.9998 20.3332 31.9998H12.1333C11.4667 31.9998 10.9333 31.4665 10.9333 30.7998C10.9333 30.1332 11.4667 29.5998 12.1333 29.5998H14.9999V27.3332C11.5333 26.7999 8.8667 23.9999 8.8667 20.6V16.9333C8.8667 16.2667 9.46669 15.8 10.1333 15.8667C10.7333 15.8667 11.2 16.4 11.2 17.0667V20.6C11.2 23.0666 13.4666 25.0666 16.1999 25.0666C18.9332 25.0666 21.1999 23.0666 21.1999 20.6V17.1333C21.1999 16.4667 21.6665 15.9334 22.3332 15.9334C22.9332 15.9334 23.5332 16.4 23.5332 17.0667Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -59,4 +59,4 @@
d="m 27.9,20.9 c 0,0 0,-3.6 0,-3.8 0,-0.7 -0.6,-1.2 -1.3,-1.2 -0.7,0 -1.2,0.6 -1.2,1.3 0,0.2 0,3.4 0,3.7 0,2.6 -2.4,4.8 -5.3,4.8 -2.9,0 -5.3,-2.1 -5.3,-4.8 0,-0.3 0,-3.5 0,-3.8 0,-0.7 -0.5,-1.3 -1.2,-1.3 -0.7,0 -1.3,0.5 -1.3,1.2 0,0.2 0,3.9 0,3.9 0,3.6 2.9,6.6 6.6,7.2 l 0,2.4 -3.1,0 c -0.7,0 -1.3,0.6 -1.3,1.3 0,0.7 0.6,1.3 1.3,1.3 l 8.8,0 c 0.7,0 1.3,-0.6 1.3,-1.3 0,-0.7 -0.6,-1.3 -1.3,-1.3 l -3.2,0 0,-2.4 c 3.6,-0.5 6.5,-3.5 6.5,-7.2 z"
id="path6952"
inkscape:connector-curvature="0"
style="fill:#ffffff" /></svg>
style="fill:#ffffff" /></svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -7,24 +7,57 @@
//
import Hifi 1.0 as Hifi
import QtQuick 2.4
import QtQuick 2.5
import QtGraphicalEffects 1.0
import "./hifi/audio" as HifiAudio
import TabletScriptingInterface 1.0
Item {
id: root;
objectName: "AvatarInputsBar"
property int modality: Qt.NonModal
width: audio.width;
height: audio.height;
x: 10; y: 5;
readonly property bool ignoreRadiusEnabled: AvatarInputs.ignoreRadiusEnabled;
x: 10;
y: 5;
readonly property bool shouldReposition: true;
property bool hmdActive: HMD.active;
width: hmdActive ? audio.width : audioApplication.width;
height: hmdActive ? audio.height : audioApplication.height;
Timer {
id: hmdActiveCheckTimer;
interval: 500;
repeat: true;
onTriggered: {
root.hmdActive = HMD.active;
}
}
HifiAudio.MicBar {
id: audio;
visible: AvatarInputs.showAudioTools;
visible: AvatarInputs.showAudioTools && root.hmdActive;
standalone: true;
dragTarget: parent;
dragTarget: parent;
}
HifiAudio.MicBarApplication {
id: audioApplication;
visible: AvatarInputs.showAudioTools && !root.hmdActive;
standalone: true;
dragTarget: parent;
}
Component.onCompleted: {
HMD.displayModeChanged.connect(function(isHmdMode) {
root.hmdActive = isHmdMode;
});
}
BubbleIcon {
dragTarget: parent
visible: !root.hmdActive;
}
}

View file

@ -0,0 +1,100 @@
//
// Created by Bradley Austin Davis on 2015/06/19
// Copyright 2015 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
//
import Hifi 1.0 as Hifi
import QtQuick 2.5
import QtGraphicalEffects 1.0
import "./hifi/audio" as HifiAudio
import TabletScriptingInterface 1.0
Rectangle {
id: bubbleRect
width: bubbleIcon.width + 10
height: bubbleIcon.height + 10
radius: 5;
property var dragTarget: null;
property bool ignoreRadiusEnabled: AvatarInputs.ignoreRadiusEnabled;
function updateOpacity() {
if (ignoreRadiusEnabled) {
bubbleRect.opacity = 1.0;
} else {
bubbleRect.opacity = 0.7;
}
}
Component.onCompleted: {
updateOpacity();
}
onIgnoreRadiusEnabledChanged: {
updateOpacity();
}
color: "#00000000";
border {
width: mouseArea.containsMouse || mouseArea.containsPress ? 2 : 0;
color: "#80FFFFFF";
}
anchors {
left: dragTarget ? dragTarget.right : undefined
top: dragTarget ? dragTarget.top : undefined
}
// borders are painted over fill, so reduce the fill to fit inside the border
Rectangle {
color: "#55000000";
width: 40;
height: 40;
radius: 5;
anchors {
verticalCenter: parent.verticalCenter;
horizontalCenter: parent.horizontalCenter;
}
}
MouseArea {
id: mouseArea;
anchors.fill: parent
hoverEnabled: true;
scrollGestureEnabled: false;
onClicked: {
Tablet.playSound(TabletEnums.ButtonClick);
Users.toggleIgnoreRadius();
}
drag.target: dragTarget;
onContainsMouseChanged: {
var rectOpacity = (ignoreRadiusEnabled && containsMouse) ? 1.0 : (containsMouse ? 1.0 : 0.7);
if (containsMouse) {
Tablet.playSound(TabletEnums.ButtonHover);
}
bubbleRect.opacity = rectOpacity;
}
}
Image {
id: bubbleIcon
source: "../icons/tablet-icons/bubble-i.svg";
sourceSize: Qt.size(32, 32);
smooth: true;
anchors.top: parent.top
anchors.topMargin: (parent.height - bubbleIcon.height) / 2
anchors.left: parent.left
anchors.leftMargin: (parent.width - bubbleIcon.width) / 2
}
ColorOverlay {
id: bubbleIconOverlay
anchors.fill: bubbleIcon
source: bubbleIcon
color: "#FFFFFF";
}
}

View file

@ -24,6 +24,7 @@ CheckBox {
leftPadding: 0
property int colorScheme: hifi.colorSchemes.light
property string color: hifi.colors.lightGrayText
property int fontSize: hifi.fontSizes.inputLabel
readonly property bool isLightColorScheme: colorScheme === hifi.colorSchemes.light
property bool isRedCheck: false
property bool isRound: false
@ -109,7 +110,7 @@ CheckBox {
contentItem: Text {
id: root
font.pixelSize: hifi.fontSizes.inputLabel
font.pixelSize: fontSize;
font.family: "Raleway"
font.weight: Font.DemiBold
text: checkBox.text

View file

@ -21,6 +21,7 @@ Item {
property int switchWidth: 70;
readonly property int switchRadius: height/2;
property string labelTextOff: "";
property int labelTextSize: hifi.fontSizes.inputLabel;
property string labelGlyphOffText: "";
property int labelGlyphOffSize: 32;
property string labelTextOn: "";
@ -89,7 +90,7 @@ Item {
RalewaySemiBold {
id: labelOff;
text: labelTextOff;
size: hifi.fontSizes.inputLabel;
size: labelTextSize;
color: originalSwitch.checked ? hifi.colors.lightGrayText : "#FFFFFF";
anchors.top: parent.top;
anchors.right: parent.right;
@ -130,7 +131,7 @@ Item {
RalewaySemiBold {
id: labelOn;
text: labelTextOn;
size: hifi.fontSizes.inputLabel;
size: labelTextSize;
color: originalSwitch.checked ? "#FFFFFF" : hifi.colors.lightGrayText;
anchors.top: parent.top;
anchors.left: parent.left;

View file

@ -0,0 +1,152 @@
//
// EditAvatarInputsBar.qml
// qml/hifi
//
// Audio setup
//
// Created by Wayne Chen on 3/20/2019
// Copyright 2019 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
//
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import stylesUit 1.0
import controlsUit 1.0 as HifiControlsUit
import "../windows"
Rectangle {
id: editRect
HifiConstants { id: hifi; }
color: hifi.colors.baseGray;
signal sendToScript(var message);
function emitSendToScript(message) {
sendToScript(message);
}
function fromScript(message) {
}
RalewayRegular {
id: title;
color: hifi.colors.white;
text: qsTr("Avatar Inputs Persistent UI Settings")
size: 20
font.bold: true
anchors {
top: parent.top
left: parent.left
leftMargin: (parent.width - width) / 2
}
}
HifiControlsUit.Slider {
id: xSlider
anchors {
top: title.bottom
topMargin: 50
left: parent.left
leftMargin: 20
}
label: "X OFFSET: " + value.toFixed(2);
maximumValue: 1.0
minimumValue: -1.0
stepSize: 0.05
value: -0.2
width: 300
onValueChanged: {
emitSendToScript({
"method": "reposition",
"x": value
});
}
}
HifiControlsUit.Slider {
id: ySlider
anchors {
top: xSlider.bottom
topMargin: 50
left: parent.left
leftMargin: 20
}
label: "Y OFFSET: " + value.toFixed(2);
maximumValue: 1.0
minimumValue: -1.0
stepSize: 0.05
value: -0.125
width: 300
onValueChanged: {
emitSendToScript({
"method": "reposition",
"y": value
});
}
}
HifiControlsUit.Slider {
id: zSlider
anchors {
top: ySlider.bottom
topMargin: 50
left: parent.left
leftMargin: 20
}
label: "Z OFFSET: " + value.toFixed(2);
maximumValue: 0.0
minimumValue: -1.0
stepSize: 0.05
value: -0.5
width: 300
onValueChanged: {
emitSendToScript({
"method": "reposition",
"z": value
});
}
}
HifiControlsUit.Button {
id: setVisibleButton;
text: setVisible ? "SET INVISIBLE" : "SET VISIBLE";
width: 300;
property bool setVisible: true;
anchors {
top: zSlider.bottom
topMargin: 50
left: parent.left
leftMargin: 20
}
onClicked: {
setVisible = !setVisible;
emitSendToScript({
"method": "setVisible",
"visible": setVisible
});
}
}
HifiControlsUit.Button {
id: printButton;
text: "PRINT POSITIONS";
width: 300;
anchors {
top: setVisibleButton.bottom
topMargin: 50
left: parent.left
leftMargin: 20
}
onClicked: {
emitSendToScript({
"method": "print",
});
}
}
}

View file

@ -31,6 +31,8 @@ Rectangle {
property string title: "Audio Settings"
property int switchHeight: 16
property int switchWidth: 40
property bool pushToTalk: (bar.currentIndex === 0) ? AudioScriptingInterface.pushToTalkDesktop : AudioScriptingInterface.pushToTalkHMD;
property bool muted: (bar.currentIndex === 0) ? AudioScriptingInterface.mutedDesktop : AudioScriptingInterface.mutedHMD;
readonly property real verticalScrollWidth: 10
readonly property real verticalScrollShaft: 8
signal sendToScript(var message);
@ -44,7 +46,7 @@ Rectangle {
property bool isVR: AudioScriptingInterface.context === "VR"
property real rightMostInputLevelPos: 440
property real rightMostInputLevelPos: root.width
//placeholder for control sizes and paddings
//recalculates dynamically in case of UI size is changed
QtObject {
@ -103,7 +105,9 @@ Rectangle {
}
}
Component.onCompleted: enablePeakValues();
Component.onCompleted: {
enablePeakValues();
}
Flickable {
id: flickView;
@ -178,15 +182,25 @@ Rectangle {
height: root.switchHeight;
switchWidth: root.switchWidth;
labelTextOn: "Mute microphone";
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
checked: AudioScriptingInterface.muted;
checked: muted;
onClicked: {
if (AudioScriptingInterface.pushToTalk && !checked) {
if (pushToTalk && !checked) {
// disable push to talk if unmuting
AudioScriptingInterface.pushToTalk = false;
if (bar.currentIndex === 0) {
AudioScriptingInterface.pushToTalkDesktop = false;
}
else {
AudioScriptingInterface.pushToTalkHMD = false;
}
}
if (bar.currentIndex === 0) {
AudioScriptingInterface.mutedDesktop = checked;
}
else {
AudioScriptingInterface.mutedHMD = checked;
}
AudioScriptingInterface.muted = checked;
checked = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding
}
}
@ -198,6 +212,7 @@ Rectangle {
anchors.topMargin: 24
anchors.left: parent.left
labelTextOn: "Noise Reduction";
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
checked: AudioScriptingInterface.noiseReduction;
onCheckedChanged: {
@ -213,7 +228,8 @@ Rectangle {
anchors.top: noiseReductionSwitch.bottom
anchors.topMargin: 24
anchors.left: parent.left
labelTextOn: qsTr("Push To Talk (T)");
labelTextOn: (bar.currentIndex === 0) ? qsTr("Push To Talk (T)") : qsTr("Push To Talk");
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
checked: (bar.currentIndex === 0) ? AudioScriptingInterface.pushToTalkDesktop : AudioScriptingInterface.pushToTalkHMD;
onCheckedChanged: {
@ -222,13 +238,6 @@ Rectangle {
} else {
AudioScriptingInterface.pushToTalkHMD = checked;
}
checked = Qt.binding(function() {
if (bar.currentIndex === 0) {
return AudioScriptingInterface.pushToTalkDesktop;
} else {
return AudioScriptingInterface.pushToTalkHMD;
}
}); // restore binding
}
}
}
@ -245,7 +254,8 @@ Rectangle {
switchWidth: root.switchWidth;
anchors.top: parent.top
anchors.left: parent.left
labelTextOn: qsTr("Warn when muted");
labelTextOn: qsTr("Warn when muted in HMD");
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
checked: AudioScriptingInterface.warnWhenMuted;
onClicked: {
@ -263,6 +273,7 @@ Rectangle {
anchors.topMargin: 24
anchors.left: parent.left
labelTextOn: qsTr("Audio Level Meter");
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
checked: AvatarInputs.showAudioTools;
onCheckedChanged: {
@ -279,6 +290,7 @@ Rectangle {
anchors.topMargin: 24
anchors.left: parent.left
labelTextOn: qsTr("Stereo input");
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
checked: AudioScriptingInterface.isStereoInput;
onCheckedChanged: {
@ -314,7 +326,7 @@ Rectangle {
Separator {
id: secondSeparator;
anchors.top: pttTextContainer.bottom;
anchors.top: pttTextContainer.visible ? pttTextContainer.bottom : switchesContainer.bottom;
anchors.topMargin: 10;
}
@ -341,7 +353,7 @@ Rectangle {
width: margins.sizeText + margins.sizeLevel;
anchors.left: parent.left;
anchors.leftMargin: margins.sizeCheckBox;
size: 16;
size: 22;
color: hifi.colors.white;
text: qsTr("Choose input device");
}
@ -349,16 +361,17 @@ Rectangle {
ListView {
id: inputView;
width: parent.width - margins.paddings*2;
width: rightMostInputLevelPos;
anchors.top: inputDeviceHeader.bottom;
anchors.topMargin: 10;
x: margins.paddings
interactive: false;
height: contentHeight;
spacing: 4;
clip: true;
model: AudioScriptingInterface.devices.input;
delegate: Item {
width: rightMostInputLevelPos
width: rightMostInputLevelPos - margins.paddings*2
height: margins.sizeCheckBox > checkBoxInput.implicitHeight ?
margins.sizeCheckBox : checkBoxInput.implicitHeight
@ -374,6 +387,7 @@ Rectangle {
boxSize: margins.sizeCheckBox / 2
isRound: true
text: devicename
fontSize: 16;
onPressed: {
if (!checked) {
stereoInput.checked = false;
@ -407,7 +421,7 @@ Rectangle {
Separator {
id: thirdSeparator;
anchors.top: loopbackAudio.bottom;
anchors.top: loopbackAudio.visible ? loopbackAudio.bottom : inputView.bottom;
anchors.topMargin: 10;
}
@ -434,7 +448,7 @@ Rectangle {
anchors.left: parent.left
anchors.leftMargin: margins.sizeCheckBox
anchors.verticalCenter: parent.verticalCenter;
size: 16;
size: 22;
color: hifi.colors.white;
text: qsTr("Choose output device");
}
@ -443,7 +457,8 @@ Rectangle {
ListView {
id: outputView
width: parent.width - margins.paddings*2
x: margins.paddings
x: margins.paddings;
interactive: false;
height: contentHeight;
anchors.top: outputDeviceHeader.bottom;
anchors.topMargin: 10;
@ -464,6 +479,7 @@ Rectangle {
checked: bar.currentIndex === 0 ? selectedDesktop : selectedHMD;
checkable: !checked
text: devicename
fontSize: 16
onPressed: {
if (!checked) {
AudioScriptingInterface.setOutputDevice(info, bar.currentIndex === 1);
@ -526,7 +542,7 @@ Rectangle {
RalewayRegular {
// The slider for my card is special, it controls the master gain
id: avatarGainSliderText;
text: "Avatar volume";
text: "People volume";
size: 16;
anchors.left: parent.left;
color: hifi.colors.white;

View file

@ -12,24 +12,26 @@
import QtQuick 2.5
import QtGraphicalEffects 1.0
Rectangle {
Item {
property var peak;
width: 70;
height: 8;
color: "transparent";
Item {
QtObject {
id: colors;
readonly property string unmuted: "#FFF";
readonly property string muted: "#E2334D";
readonly property string gutter: "#575757";
readonly property string greenStart: "#39A38F";
readonly property string greenEnd: "#1FC6A6";
readonly property string yellow: "#C0C000";
readonly property string red: colors.muted;
readonly property string fill: "#55000000";
}
Text {
id: status;
@ -79,23 +81,19 @@ Rectangle {
anchors { fill: mask }
source: mask
start: Qt.point(0, 0);
end: Qt.point(70, 0);
end: Qt.point(bar.width, 0);
gradient: Gradient {
GradientStop {
position: 0;
color: colors.greenStart;
}
GradientStop {
position: 0.8;
position: 0.5;
color: colors.greenEnd;
}
GradientStop {
position: 0.801;
color: colors.red;
}
GradientStop {
position: 1;
color: colors.red;
color: colors.yellow;
}
}
}

View file

@ -60,11 +60,11 @@ RowLayout {
}
}
// RalewayRegular {
// Layout.leftMargin: 2;
// size: 14;
// color: "white";
// font.italic: true
// text: audioLoopedBack ? qsTr("Speak in your input") : "";
// }
RalewayRegular {
Layout.leftMargin: 2;
size: 18;
color: "white";
font.italic: true
text: audioLoopedBack ? qsTr("Speak in your input") : "";
}
}

View file

@ -16,13 +16,16 @@ import stylesUit 1.0
import TabletScriptingInterface 1.0
Rectangle {
id: micBar
HifiConstants { id: hifi; }
property var muted: AudioScriptingInterface.muted;
readonly property var level: AudioScriptingInterface.inputLevel;
readonly property var clipping: AudioScriptingInterface.clipping;
property var pushToTalk: AudioScriptingInterface.pushToTalk;
property var pushingToTalk: AudioScriptingInterface.pushingToTalk;
readonly property var userSpeakingLevel: 0.4;
property bool gated: false;
Component.onCompleted: {
AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; });
@ -40,7 +43,6 @@ Rectangle {
AudioScriptingInterface.pushingToTalkChanged.connect(function() {
pushingToTalk = AudioScriptingInterface.pushingToTalk;
});
}
property bool standalone: false;
@ -87,7 +89,7 @@ Rectangle {
if (pushToTalk) {
return;
}
muted = !muted;
AudioScriptingInterface.muted = !muted;
Tablet.playSound(TabletEnums.ButtonClick);
}
drag.target: dragTarget;
@ -130,9 +132,12 @@ Rectangle {
readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg";
readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg";
readonly property string pushToTalkIcon: "../../../icons/tablet-icons/mic-ptt-i.svg";
readonly property string clippingIcon: "../../../icons/tablet-icons/mic-clip-i.svg";
readonly property string gatedIcon: "../../../icons/tablet-icons/mic-gate-i.svg";
id: image;
source: (pushToTalk && !pushingToTalk) ? pushToTalkIcon : muted ? mutedIcon : unmutedIcon;
source: (pushToTalk && !pushingToTalk) ? pushToTalkIcon : muted ? mutedIcon :
clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon;
width: 30;
height: 30;

View file

@ -0,0 +1,255 @@
//
// MicBarApplication.qml
// qml/hifi/audio
//
// Created by Zach Pomerantz on 6/14/2017
// 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
//
import QtQuick 2.5
import QtGraphicalEffects 1.0
import stylesUit 1.0
import TabletScriptingInterface 1.0
Rectangle {
id: micBar;
readonly property var level: AudioScriptingInterface.inputLevel;
readonly property var clipping: AudioScriptingInterface.clipping;
property var muted: AudioScriptingInterface.muted;
property var pushToTalk: AudioScriptingInterface.pushToTalk;
property var pushingToTalk: AudioScriptingInterface.pushingToTalk;
readonly property var userSpeakingLevel: 0.4;
property bool gated: false;
Component.onCompleted: {
AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; });
AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; });
HMD.displayModeChanged.connect(function() {
muted = AudioScriptingInterface.muted;
pushToTalk = AudioScriptingInterface.pushToTalk;
});
AudioScriptingInterface.mutedChanged.connect(function() {
muted = AudioScriptingInterface.muted;
});
AudioScriptingInterface.pushToTalkChanged.connect(function() {
pushToTalk = AudioScriptingInterface.pushToTalk;
});
}
readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg";
readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg";
readonly property string pushToTalkIcon: "../../../icons/tablet-icons/mic-ptt-i.svg";
readonly property string clippingIcon: "../../../icons/tablet-icons/mic-clip-i.svg";
readonly property string gatedIcon: "../../../icons/tablet-icons/mic-gate-i.svg";
property bool standalone: false;
property var dragTarget: null;
width: 44;
height: 44;
radius: 5;
opacity: 0.7;
onLevelChanged: {
var rectOpacity = (muted && (level >= userSpeakingLevel)) ? 1.0 : 0.7;
if (pushToTalk && !pushingToTalk) {
rectOpacity = (mouseArea.containsMouse) ? 1.0 : 0.7;
} else if (mouseArea.containsMouse && rectOpacity != 1.0) {
rectOpacity = 1.0;
}
micBar.opacity = rectOpacity;
}
color: "#00000000";
border {
width: mouseArea.containsMouse || mouseArea.containsPress ? 2 : 0;
color: colors.border;
}
// borders are painted over fill, so reduce the fill to fit inside the border
Rectangle {
color: standalone ? colors.fill : "#00000000";
width: 40;
height: 40;
radius: 5;
anchors {
verticalCenter: parent.verticalCenter;
horizontalCenter: parent.horizontalCenter;
}
}
MouseArea {
id: mouseArea;
anchors {
left: icon.left;
right: bar.right;
top: icon.top;
bottom: icon.bottom;
}
hoverEnabled: true;
scrollGestureEnabled: false;
onClicked: {
if (pushToTalk) {
return;
}
AudioScriptingInterface.muted = !muted;
Tablet.playSound(TabletEnums.ButtonClick);
muted = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding
}
drag.target: dragTarget;
onContainsMouseChanged: {
if (containsMouse) {
Tablet.playSound(TabletEnums.ButtonHover);
}
}
}
QtObject {
id: colors;
readonly property string unmutedColor: "#FFF";
readonly property string gatedColor: "#00BDFF";
readonly property string mutedColor: "#E2334D";
readonly property string gutter: "#575757";
readonly property string greenStart: "#39A38F";
readonly property string greenEnd: "#1FC6A6";
readonly property string yellow: "#C0C000";
readonly property string fill: "#55000000";
readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF";
readonly property string icon: (muted || clipping) ? mutedColor : gated ? gatedColor : unmutedColor;
}
Item {
id: icon;
anchors {
left: parent.left;
top: parent.top;
}
width: 40;
height: 40;
Item {
Image {
id: image;
source: (pushToTalk) ? pushToTalkIcon : muted ? mutedIcon :
clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon;
width: 29;
height: 32;
anchors {
left: parent.left;
top: parent.top;
topMargin: 5;
}
}
ColorOverlay {
id: imageOverlay
anchors { fill: image }
source: image;
color: pushToTalk ? (pushingToTalk ? colors.unmutedColor : colors.mutedColor) : colors.icon;
}
}
}
Item {
id: status;
visible: pushToTalk || (muted && (level >= userSpeakingLevel));
anchors {
left: parent.left;
top: icon.bottom;
topMargin: 2;
}
width: parent.width;
height: statusTextMetrics.height;
TextMetrics {
id: statusTextMetrics
text: statusText.text
font: statusText.font
}
RalewaySemiBold {
id: statusText
anchors {
horizontalCenter: parent.horizontalCenter;
verticalCenter: parent.verticalCenter;
}
color: pushToTalk ? (pushingToTalk ? colors.unmutedColor : colors.mutedColor) : (level >= userSpeakingLevel && muted) ? colors.mutedColor : colors.unmutedColor;
font.bold: true
text: pushToTalk ? (HMD.active ? "PTT" : "PTT-(T)") : (muted ? "MUTED" : "MUTE");
size: 12;
}
}
Item {
id: bar;
anchors {
right: parent.right;
rightMargin: 7;
top: parent.top
topMargin: 5
}
width: 8;
height: 32;
Rectangle { // base
id: baseBar
radius: 4;
anchors { fill: parent }
color: colors.gutter;
}
Rectangle { // mask
id: mask;
visible: (!(pushToTalk && !pushingToTalk))
height: parent.height * level;
width: parent.width;
radius: 5;
anchors {
bottom: parent.bottom;
bottomMargin: 0;
left: parent.left;
leftMargin: 0;
}
}
LinearGradient {
anchors { fill: mask }
visible: (!(pushToTalk && !pushingToTalk))
source: mask
start: Qt.point(0, 0);
end: Qt.point(0, bar.height);
rotation: 180
gradient: Gradient {
GradientStop {
position: 0.0;
color: colors.greenStart;
}
GradientStop {
position: 0.5;
color: colors.greenEnd;
}
GradientStop {
position: 1.0;
color: colors.yellow;
}
}
}
}
}

View file

@ -64,11 +64,11 @@ RowLayout {
height: 32;
}
// RalewayRegular {
// Layout.leftMargin: 2;
// size: 14;
// color: "white";
// font.italic: true
// text: isPlaying ? qsTr("Listen to your output") : "";
// }
RalewayRegular {
Layout.leftMargin: 2;
size: 18;
color: "white";
font.italic: true
text: isPlaying ? qsTr("Listen to your output") : "";
}
}

View file

@ -40,7 +40,7 @@ Item {
}
}
HifiAudio.MicBar {
HifiAudio.MicBarApplication {
anchors {
left: parent.left
leftMargin: 30

View file

@ -338,6 +338,10 @@ Setting::Handle<int> maxOctreePacketsPerSecond{"maxOctreePPS", DEFAULT_MAX_OCTRE
Setting::Handle<bool> loginDialogPoppedUp{"loginDialogPoppedUp", false};
static const QUrl AVATAR_INPUTS_BAR_QML = PathUtils::qmlUrl("AvatarInputsBar.qml");
static const QUrl MIC_BAR_APPLICATION_QML = PathUtils::qmlUrl("hifi/audio/MicBarApplication.qml");
static const QUrl BUBBLE_ICON_QML = PathUtils::qmlUrl("BubbleIcon.qml");
static const QString STANDARD_TO_ACTION_MAPPING_NAME = "Standard to Action";
static const QString NO_MOVEMENT_MAPPING_NAME = "Standard to Action (No Movement)";
static const QString NO_MOVEMENT_MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/standard_nomovement.json";
@ -2386,7 +2390,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
});
});
auto rootItemLoadedFunctor = [webSurface, url, isTablet] {
Application::setupQmlSurface(webSurface->getSurfaceContext(), isTablet || url == LOGIN_DIALOG.toString());
Application::setupQmlSurface(webSurface->getSurfaceContext(), isTablet || url == LOGIN_DIALOG.toString() || url == AVATAR_INPUTS_BAR_QML.toString() ||
url == BUBBLE_ICON_QML.toString());
};
if (webSurface->getRootItem()) {
rootItemLoadedFunctor();
@ -3313,6 +3318,7 @@ void Application::onDesktopRootItemCreated(QQuickItem* rootItem) {
auto qml = PathUtils::qmlUrl("AvatarInputsBar.qml");
offscreenUi->show(qml, "AvatarInputsBar");
#endif
_desktopRootItemCreated = true;
}
void Application::userKickConfirmation(const QUuid& nodeID) {
@ -3744,14 +3750,11 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
// If this is a first run we short-circuit the address passed in
if (_firstRun.get()) {
#if !defined(Q_OS_ANDROID)
DependencyManager::get<AddressManager>()->goToEntry();
sentTo = SENT_TO_ENTRY;
#endif
_firstRun.set(false);
} else {
#if !defined(Q_OS_ANDROID)
QString goingTo = "";
if (addressLookupString.isEmpty()) {
if (Menu::getInstance()->isOptionChecked(MenuOption::HomeLocation)) {
@ -3765,7 +3768,6 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(!goingTo.isEmpty() ? goingTo : addressLookupString);
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
sentTo = SENT_TO_PREVIOUS_LOCATION;
#endif
}
UserActivityLogger::getInstance().logAction("startup_sent_to", {
@ -8994,6 +8996,38 @@ void Application::updateLoginDialogPosition() {
}
}
void Application::createAvatarInputsBar() {
const glm::vec3 LOCAL_POSITION { 0.0, 0.0, -1.0 };
// DEFAULT_DPI / tablet scale percentage
const float DPI = 31.0f / (75.0f / 100.0f);
EntityItemProperties properties;
properties.setType(EntityTypes::Web);
properties.setName("AvatarInputsBarEntity");
properties.setSourceUrl(AVATAR_INPUTS_BAR_QML.toString());
properties.setParentID(getMyAvatar()->getSelfID());
properties.setParentJointIndex(getMyAvatar()->getJointIndex("_CAMERA_MATRIX"));
properties.setPosition(LOCAL_POSITION);
properties.setLocalRotation(Quaternions::IDENTITY);
//properties.setDimensions(LOGIN_DIMENSIONS);
properties.setPrimitiveMode(PrimitiveMode::SOLID);
properties.getGrab().setGrabbable(false);
properties.setIgnorePickIntersection(false);
properties.setAlpha(1.0f);
properties.setDPI(DPI);
properties.setVisible(true);
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
_avatarInputsBarID = entityScriptingInterface->addEntityInternal(properties, entity::HostType::LOCAL);
}
void Application::destroyAvatarInputsBar() {
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
if (!_avatarInputsBarID.isNull()) {
entityScriptingInterface->deleteEntity(_avatarInputsBarID);
}
}
bool Application::hasRiftControllers() {
return PluginUtils::isOculusTouchControllerAvailable();
}

View file

@ -330,6 +330,9 @@ public:
void createLoginDialog();
void updateLoginDialogPosition();
void createAvatarInputsBar();
void destroyAvatarInputsBar();
// Check if a headset is connected
bool hasRiftControllers();
bool hasViveControllers();
@ -704,12 +707,14 @@ private:
int _maxOctreePPS = DEFAULT_MAX_OCTREE_PPS;
bool _interstitialModeEnabled{ false };
bool _loginDialogPoppedUp = false;
bool _loginDialogPoppedUp{ false };
bool _desktopRootItemCreated{ false };
bool _developerMenuVisible{ false };
QString _previousAvatarSkeletonModel;
float _previousAvatarTargetScale;
CameraMode _previousCameraMode;
QUuid _loginDialogID;
QUuid _avatarInputsBarID;
LoginStateManager _loginStateManager;
quint64 _lastFaceTrackerUpdate;

View file

@ -38,6 +38,7 @@
#include <UsersScriptingInterface.h>
#include <UUID.h>
#include <shared/ConicalViewFrustum.h>
#include <ui/AvatarInputs.h>
#include "Application.h"
#include "InterfaceLogging.h"
@ -84,7 +85,6 @@ AvatarManager::AvatarManager(QObject* parent) :
AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) {
AvatarSharedPointer avatar = AvatarHashMap::addAvatar(sessionUUID, mixerWeakPointer);
const auto otherAvatar = std::static_pointer_cast<OtherAvatar>(avatar);
if (otherAvatar && _space) {
std::unique_lock<std::mutex> lock(_spaceLock);
@ -210,7 +210,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
{
// lock the hash for read to check the size
QReadLocker lock(&_hashLock);
if (_avatarHash.size() < 2 && _avatarsToFadeOut.isEmpty()) {
if (_avatarHash.size() < 2) {
return;
}
}
@ -375,19 +375,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
qApp->getMain3DScene()->enqueueTransaction(renderTransaction);
}
if (!_spaceProxiesToDelete.empty() && _space) {
std::unique_lock<std::mutex> lock(_spaceLock);
workloadTransaction.remove(_spaceProxiesToDelete);
_spaceProxiesToDelete.clear();
}
_space->enqueueTransaction(workloadTransaction);
_numAvatarsUpdated = numAvatarsUpdated;
_numAvatarsNotUpdated = numAvatarsNotUpdated;
_numHeroAvatarsUpdated = numHerosUpdated;
simulateAvatarFades(deltaTime);
_avatarSimulationTime = (float)(usecTimestampNow() - startTime) / (float)USECS_PER_MSEC;
}
@ -400,31 +393,6 @@ void AvatarManager::postUpdate(float deltaTime, const render::ScenePointer& scen
}
}
void AvatarManager::simulateAvatarFades(float deltaTime) {
if (_avatarsToFadeOut.empty()) {
return;
}
QReadLocker locker(&_hashLock);
QVector<AvatarSharedPointer>::iterator avatarItr = _avatarsToFadeOut.begin();
const render::ScenePointer& scene = qApp->getMain3DScene();
render::Transaction transaction;
while (avatarItr != _avatarsToFadeOut.end()) {
auto avatar = std::static_pointer_cast<Avatar>(*avatarItr);
avatar->updateFadingStatus();
if (!avatar->isFading()) {
// fading to zero is such a rare event we push a unique transaction for each
if (avatar->isInScene()) {
avatar->removeFromScene(*avatarItr, scene, transaction);
}
avatarItr = _avatarsToFadeOut.erase(avatarItr);
} else {
++avatarItr;
}
}
scene->enqueueTransaction(transaction);
}
AvatarSharedPointer AvatarManager::newSharedAvatar(const QUuid& sessionUUID) {
auto otherAvatar = new OtherAvatar(qApp->thread());
otherAvatar->setSessionUUID(sessionUUID);
@ -452,7 +420,6 @@ void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transact
transaction.objectsToRemove.push_back(mState);
}
avatar->resetDetailedMotionStates();
} else {
if (avatar->getDetailedMotionStates().size() == 0) {
avatar->createDetailedMotionStates(avatar);
@ -520,10 +487,6 @@ void AvatarManager::removeDeadAvatarEntities(const SetOfEntities& deadEntities)
void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
auto avatar = std::static_pointer_cast<OtherAvatar>(removedAvatar);
{
std::unique_lock<std::mutex> lock(_spaceLock);
_spaceProxiesToDelete.push_back(avatar->getSpaceIndex());
}
AvatarHashMap::handleRemovedAvatar(avatar, removalReason);
avatar->tearDownGrabs();
@ -534,16 +497,39 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
// it might not fire until after we create a new instance for the same remote avatar, which creates a race
// on the creation of entities for that avatar instance and the deletion of entities for this instance
avatar->removeAvatarEntitiesFromTree();
if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) {
emit AvatarInputs::getInstance()->avatarEnteredIgnoreRadius(avatar->getSessionUUID());
emit DependencyManager::get<UsersScriptingInterface>()->enteredIgnoreRadius();
workload::Transaction workloadTransaction;
workloadTransaction.remove(avatar->getSpaceIndex());
_space->enqueueTransaction(workloadTransaction);
const render::ScenePointer& scene = qApp->getMain3DScene();
render::Transaction transaction;
avatar->removeFromScene(avatar, scene, transaction);
scene->enqueueTransaction(transaction);
} else if (removalReason == KillAvatarReason::AvatarDisconnected) {
// remove from node sets, if present
DependencyManager::get<NodeList>()->removeFromIgnoreMuteSets(avatar->getSessionUUID());
DependencyManager::get<UsersScriptingInterface>()->avatarDisconnected(avatar->getSessionUUID());
avatar->fadeOut(qApp->getMain3DScene(), removalReason);
render::Transaction transaction;
auto scene = qApp->getMain3DScene();
avatar->fadeOut(transaction, removalReason);
workload::SpacePointer space = _space;
transaction.transitionFinishedOperator(avatar->getRenderItemID(), [space, avatar]() {
const render::ScenePointer& scene = qApp->getMain3DScene();
render::Transaction transaction;
avatar->removeFromScene(avatar, scene, transaction);
scene->enqueueTransaction(transaction);
workload::Transaction workloadTransaction;
workloadTransaction.remove(avatar->getSpaceIndex());
space->enqueueTransaction(workloadTransaction);
});
scene->enqueueTransaction(transaction);
}
_avatarsToFadeOut.push_back(removedAvatar);
}
void AvatarManager::clearOtherAvatars() {

View file

@ -220,8 +220,6 @@ private:
explicit AvatarManager(QObject* parent = 0);
explicit AvatarManager(const AvatarManager& other);
void simulateAvatarFades(float deltaTime);
AvatarSharedPointer newSharedAvatar(const QUuid& sessionUUID) override;
// called only from the AvatarHashMap thread - cannot be called while this thread holds the
@ -231,8 +229,6 @@ private:
KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
void handleTransitAnimations(AvatarTransit::Status status);
QVector<AvatarSharedPointer> _avatarsToFadeOut;
using SetOfOtherAvatars = std::set<OtherAvatarPointer>;
SetOfOtherAvatars _avatarsToChangeInPhysics;
@ -252,7 +248,6 @@ private:
mutable std::mutex _spaceLock;
workload::SpacePointer _space;
std::vector<int32_t> _spaceProxiesToDelete;
AvatarTransit::TransitConfig _transitConfig;
};

View file

@ -960,8 +960,6 @@ void MyAvatar::simulate(float deltaTime, bool inView) {
}
handleChangedAvatarEntityData();
updateFadingStatus();
}
// As far as I know no HMD system supports a play area of a kilometer in radius.

View file

@ -356,7 +356,6 @@ void OtherAvatar::simulate(float deltaTime, bool inView) {
PROFILE_RANGE(simulation, "grabs");
applyGrabChanges();
}
updateFadingStatus();
}
void OtherAvatar::handleChangedAvatarEntityData() {

View file

@ -17,6 +17,7 @@
#include <QJsonObject>
#include <DependencyManager.h>
#include <QtNetwork/QNetworkReply>
#include <EntityItemID.h>
#include "AccountManager.h"
@ -65,7 +66,7 @@ signals:
void availableUpdatesResult(QJsonObject result);
void updateItemResult(QJsonObject result);
void updateCertificateStatus(const QString& certID, uint certStatus);
void updateCertificateStatus(const EntityItemID& entityID, uint certStatus);
public slots:
void buySuccess(QNetworkReply* reply);

View file

@ -19,6 +19,7 @@
#include <QPixmap>
#include <EntityItemID.h>
#include <DependencyManager.h>
class QmlCommerce : public QObject, public Dependency {
@ -49,7 +50,7 @@ signals:
void availableUpdatesResult(QJsonObject result);
void updateItemResult(QJsonObject result);
void updateCertificateStatus(const QString& certID, uint certStatus);
void updateCertificateStatus(const EntityItemID& entityID, uint certStatus);
void transferAssetToNodeResult(QJsonObject result);
void transferAssetToUsernameResult(QJsonObject result);

View file

@ -816,18 +816,18 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> pack
bool challengeOriginatedFromClient = packet->getType() == PacketType::ChallengeOwnershipRequest;
int status;
int certIDByteArraySize;
int idByteArraySize;
int textByteArraySize;
int challengingNodeUUIDByteArraySize;
packet->readPrimitive(&certIDByteArraySize);
packet->readPrimitive(&idByteArraySize);
packet->readPrimitive(&textByteArraySize); // returns a cast char*, size
if (challengeOriginatedFromClient) {
packet->readPrimitive(&challengingNodeUUIDByteArraySize);
}
// "encryptedText" is now a series of random bytes, a nonce
QByteArray certID = packet->read(certIDByteArraySize);
QByteArray id = packet->read(idByteArraySize);
QByteArray text = packet->read(textByteArraySize);
QByteArray challengingNodeUUID;
if (challengeOriginatedFromClient) {
@ -853,32 +853,32 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> pack
textByteArray = sig.toUtf8();
}
textByteArraySize = textByteArray.size();
int certIDSize = certID.size();
int idSize = id.size();
// setup the packet
if (challengeOriginatedFromClient) {
auto textPacket = NLPacket::create(PacketType::ChallengeOwnershipReply,
certIDSize + textByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int),
idSize + textByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int),
true);
textPacket->writePrimitive(certIDSize);
textPacket->writePrimitive(idSize);
textPacket->writePrimitive(textByteArraySize);
textPacket->writePrimitive(challengingNodeUUIDByteArraySize);
textPacket->write(certID);
textPacket->write(id);
textPacket->write(textByteArray);
textPacket->write(challengingNodeUUID);
qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing signed text" << textByteArray << "for CertID" << certID;
qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing signed text" << textByteArray << "for id" << id;
nodeList->sendPacket(std::move(textPacket), *sendingNode);
} else {
auto textPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + textByteArraySize + 2 * sizeof(int), true);
auto textPacket = NLPacket::create(PacketType::ChallengeOwnership, idSize + textByteArraySize + 2 * sizeof(int), true);
textPacket->writePrimitive(certIDSize);
textPacket->writePrimitive(idSize);
textPacket->writePrimitive(textByteArraySize);
textPacket->write(certID);
textPacket->write(id);
textPacket->write(textByteArray);
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing signed text" << textByteArray << "for CertID" << certID;
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing signed text" << textByteArray << "for id" << id;
nodeList->sendPacket(std::move(textPacket), *sendingNode);
}

View file

@ -88,44 +88,44 @@ void Audio::setMuted(bool isMuted) {
void Audio::setMutedDesktop(bool isMuted) {
bool changed = false;
withWriteLock([&] {
if (_desktopMuted != isMuted) {
if (_mutedDesktop != isMuted) {
changed = true;
_desktopMuted = isMuted;
_mutedDesktop = isMuted;
auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false));
}
});
if (changed) {
emit mutedChanged(isMuted);
emit desktopMutedChanged(isMuted);
emit mutedDesktopChanged(isMuted);
}
}
bool Audio::getMutedDesktop() const {
return resultWithReadLock<bool>([&] {
return _desktopMuted;
return _mutedDesktop;
});
}
void Audio::setMutedHMD(bool isMuted) {
bool changed = false;
withWriteLock([&] {
if (_hmdMuted != isMuted) {
if (_mutedHMD != isMuted) {
changed = true;
_hmdMuted = isMuted;
_mutedHMD = isMuted;
auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false));
}
});
if (changed) {
emit mutedChanged(isMuted);
emit hmdMutedChanged(isMuted);
emit mutedHMDChanged(isMuted);
}
}
bool Audio::getMutedHMD() const {
return resultWithReadLock<bool>([&] {
return _hmdMuted;
return _mutedHMD;
});
}
@ -217,15 +217,15 @@ void Audio::setPTTHMD(bool enabled) {
}
void Audio::saveData() {
_desktopMutedSetting.set(getMutedDesktop());
_hmdMutedSetting.set(getMutedHMD());
_mutedDesktopSetting.set(getMutedDesktop());
_mutedHMDSetting.set(getMutedHMD());
_pttDesktopSetting.set(getPTTDesktop());
_pttHMDSetting.set(getPTTHMD());
}
void Audio::loadData() {
setMutedDesktop(_desktopMutedSetting.get());
setMutedHMD(_hmdMutedSetting.get());
setMutedDesktop(_mutedDesktopSetting.get());
setMutedHMD(_mutedHMDSetting.get());
setPTTDesktop(_pttDesktopSetting.get());
setPTTHMD(_pttHMDSetting.get());

View file

@ -41,6 +41,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
* @hifi-assignment-client
*
* @property {boolean} muted - <code>true</code> if the audio input is muted, otherwise <code>false</code>.
* @property {boolean} mutedDesktop - <code>true</code> if the audio input is muted, otherwise <code>false</code>.
* @property {boolean} noiseReduction - <code>true</code> if noise reduction is enabled, otherwise <code>false</code>. When
* enabled, the input audio signal is blocked (fully attenuated) when it falls below an adaptive threshold set just
* above the noise floor.
@ -68,8 +69,8 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged)
Q_PROPERTY(QString context READ getContext NOTIFY contextChanged)
Q_PROPERTY(AudioDevices* devices READ getDevices NOTIFY nop)
Q_PROPERTY(bool desktopMuted READ getMutedDesktop WRITE setMutedDesktop NOTIFY desktopMutedChanged)
Q_PROPERTY(bool hmdMuted READ getMutedHMD WRITE setMutedHMD NOTIFY hmdMutedChanged)
Q_PROPERTY(bool mutedDesktop READ getMutedDesktop WRITE setMutedDesktop NOTIFY mutedDesktopChanged)
Q_PROPERTY(bool mutedHMD READ getMutedHMD WRITE setMutedHMD NOTIFY mutedHMDChanged)
Q_PROPERTY(bool pushToTalk READ getPTT WRITE setPTT NOTIFY pushToTalkChanged);
Q_PROPERTY(bool pushToTalkDesktop READ getPTTDesktop WRITE setPTTDesktop NOTIFY pushToTalkDesktopChanged)
Q_PROPERTY(bool pushToTalkHMD READ getPTTHMD WRITE setPTTHMD NOTIFY pushToTalkHMDChanged)
@ -287,19 +288,19 @@ signals:
/**jsdoc
* Triggered when desktop audio input is muted or unmuted.
* @function Audio.desktopMutedChanged
* @function Audio.mutedDesktopChanged
* @param {boolean} isMuted - <code>true</code> if the audio input is muted for desktop mode, otherwise <code>false</code>.
* @returns {Signal}
*/
void desktopMutedChanged(bool isMuted);
void mutedDesktopChanged(bool isMuted);
/**jsdoc
* Triggered when HMD audio input is muted or unmuted.
* @function Audio.hmdMutedChanged
* @function Audio.mutedHMDChanged
* @param {boolean} isMuted - <code>true</code> if the audio input is muted for HMD mode, otherwise <code>false</code>.
* @returns {Signal}
*/
void hmdMutedChanged(bool isMuted);
void mutedHMDChanged(bool isMuted);
/**
* Triggered when Push-to-Talk has been enabled or disabled.
@ -418,12 +419,12 @@ private:
bool _contextIsHMD { false };
AudioDevices* getDevices() { return &_devices; }
AudioDevices _devices;
Setting::Handle<bool> _desktopMutedSetting{ QStringList { Audio::AUDIO, "desktopMuted" }, true };
Setting::Handle<bool> _hmdMutedSetting{ QStringList { Audio::AUDIO, "hmdMuted" }, true };
Setting::Handle<bool> _mutedDesktopSetting{ QStringList { Audio::AUDIO, "mutedDesktop" }, true };
Setting::Handle<bool> _mutedHMDSetting{ QStringList { Audio::AUDIO, "mutedHMD" }, true };
Setting::Handle<bool> _pttDesktopSetting{ QStringList { Audio::AUDIO, "pushToTalkDesktop" }, false };
Setting::Handle<bool> _pttHMDSetting{ QStringList { Audio::AUDIO, "pushToTalkHMD" }, false };
bool _desktopMuted{ true };
bool _hmdMuted{ false };
bool _mutedDesktop{ true };
bool _mutedHMD{ false };
bool _pttDesktop{ false };
bool _pttHMD{ false };
bool _pushingToTalk{ false };

View file

@ -12,6 +12,7 @@
#include <AudioClient.h>
#include <SettingHandle.h>
#include <trackers/FaceTracker.h>
#include <UsersScriptingInterface.h>
#include "Application.h"
#include "Menu.h"
@ -30,6 +31,10 @@ AvatarInputs* AvatarInputs::getInstance() {
AvatarInputs::AvatarInputs(QObject* parent) : QObject(parent) {
_showAudioTools = showAudioToolsSetting.get();
auto nodeList = DependencyManager::get<NodeList>();
auto usersScriptingInterface = DependencyManager::get<UsersScriptingInterface>();
connect(nodeList.data(), &NodeList::ignoreRadiusEnabledChanged, this, &AvatarInputs::ignoreRadiusEnabledChanged);
connect(usersScriptingInterface.data(), &UsersScriptingInterface::enteredIgnoreRadius, this, &AvatarInputs::enteredIgnoreRadiusChanged);
}
#define AI_UPDATE(name, src) \
@ -83,6 +88,10 @@ void AvatarInputs::setShowAudioTools(bool showAudioTools) {
emit showAudioToolsChanged(_showAudioTools);
}
bool AvatarInputs::getIgnoreRadiusEnabled() const {
return DependencyManager::get<NodeList>()->getIgnoreRadiusEnabled();
}
void AvatarInputs::toggleCameraMute() {
FaceTracker* faceTracker = qApp->getSelectedFaceTracker();
if (faceTracker) {

View file

@ -42,6 +42,8 @@ class AvatarInputs : public QObject {
AI_PROPERTY(bool, isHMD, false)
Q_PROPERTY(bool showAudioTools READ showAudioTools WRITE setShowAudioTools NOTIFY showAudioToolsChanged)
Q_PROPERTY(bool ignoreRadiusEnabled READ getIgnoreRadiusEnabled NOTIFY ignoreRadiusEnabledChanged)
//Q_PROPERTY(bool enteredIgnoreRadius READ getEnteredIgnoreRadius NOTIFY enteredIgnoreRadiusChanged)
public:
static AvatarInputs* getInstance();
@ -55,7 +57,9 @@ public:
AvatarInputs(QObject* parent = nullptr);
void update();
bool showAudioTools() const { return _showAudioTools; }
bool showAudioTools() const { return _showAudioTools; }
bool getIgnoreRadiusEnabled() const;
//bool getEnteredIgnoreRadius() const;
public slots:
@ -93,6 +97,34 @@ signals:
*/
void showAudioToolsChanged(bool show);
/**jsdoc
* @function AvatarInputs.avatarEnteredIgnoreRadius
* @param {QUuid} avatarID
* @returns {Signal}
*/
void avatarEnteredIgnoreRadius(QUuid avatarID);
/**jsdoc
* @function AvatarInputs.avatarLeftIgnoreRadius
* @param {QUuid} avatarID
* @returns {Signal}
*/
void avatarLeftIgnoreRadius(QUuid avatarID);
/**jsdoc
* @function AvatarInputs.ignoreRadiusEnabledChanged
* @param {boolean} enabled
* @returns {Signal}
*/
void ignoreRadiusEnabledChanged(bool enabled);
/**jsdoc
* @function AvatarInputs.enteredIgnoreRadiusChanged
* @param {boolean} enabled
* @returns {Signal}
*/
void enteredIgnoreRadiusChanged();
protected:
/**jsdoc
@ -106,6 +138,8 @@ protected:
Q_INVOKABLE void toggleCameraMute();
private:
void onAvatarEnteredIgnoreRadius();
void onAvatarLeftIgnoreRadius();
float _trailingAudioLoudness{ 0 };
bool _showAudioTools { false };
};

View file

@ -292,7 +292,7 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID
setLastInspectedEntity(entityID);
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags);
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityID, _entityPropertyFlags);
auto nodeList = DependencyManager::get<NodeList>();
@ -328,31 +328,31 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID
} else {
QString ownerKey = jsonObject["transfer_recipient_key"].toString();
QByteArray certID = entityProperties.getCertificateID().toUtf8();
QByteArray text = DependencyManager::get<EntityTreeRenderer>()->getTree()->computeNonce(certID, ownerKey);
QByteArray id = entityID.toByteArray();
QByteArray text = DependencyManager::get<EntityTreeRenderer>()->getTree()->computeNonce(entityID, ownerKey);
QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122();
int certIDByteArraySize = certID.length();
int idByteArraySize = id.length();
int textByteArraySize = text.length();
int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length();
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest,
certIDByteArraySize + textByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int),
idByteArraySize + textByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int),
true);
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
challengeOwnershipPacket->writePrimitive(idByteArraySize);
challengeOwnershipPacket->writePrimitive(textByteArraySize);
challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize);
challengeOwnershipPacket->write(certID);
challengeOwnershipPacket->write(id);
challengeOwnershipPacket->write(text);
challengeOwnershipPacket->write(nodeToChallengeByteArray);
nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer);
// Kickoff a 10-second timeout timer that marks the cert if we don't get an ownership response in time
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer");
QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityID));
return;
} else {
startChallengeOwnershipTimer();
startChallengeOwnershipTimer(entityID);
}
}
} else {
@ -370,14 +370,14 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID
// so they always pass Ownership Verification. It's necessary to emit this signal
// so that the Inspection Certificate can continue its information-grabbing process.
auto ledger = DependencyManager::get<Ledger>();
emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS));
emit ledger->updateCertificateStatus(entityID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS));
}
} else {
auto ledger = DependencyManager::get<Ledger>();
_challengeOwnershipTimeoutTimer.stop();
emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED));
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationFailed(_lastInspectedEntity);
qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!";
emit ledger->updateCertificateStatus(entityID, (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED));
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationFailed(entityID);
qCDebug(context_overlay) << "Entity" << entityID << "failed static certificate verification!";
}
}
@ -395,14 +395,14 @@ void ContextOverlayInterface::deletingEntity(const EntityItemID& entityID) {
}
}
void ContextOverlayInterface::startChallengeOwnershipTimer() {
void ContextOverlayInterface::startChallengeOwnershipTimer(const EntityItemID& entityItemID) {
auto ledger = DependencyManager::get<Ledger>();
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags);
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityItemID, _entityPropertyFlags);
connect(&_challengeOwnershipTimeoutTimer, &QTimer::timeout, this, [=]() {
qCDebug(entities) << "Ownership challenge timed out for" << _lastInspectedEntity;
emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_TIMEOUT));
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationFailed(_lastInspectedEntity);
qCDebug(entities) << "Ownership challenge timed out for" << entityItemID;
emit ledger->updateCertificateStatus(entityItemID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_TIMEOUT));
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationFailed(entityItemID);
});
_challengeOwnershipTimeoutTimer.start(5000);
@ -413,23 +413,22 @@ void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer
_challengeOwnershipTimeoutTimer.stop();
int certIDByteArraySize;
int idByteArraySize;
int textByteArraySize;
packet->readPrimitive(&certIDByteArraySize);
packet->readPrimitive(&idByteArraySize);
packet->readPrimitive(&textByteArraySize);
QString certID(packet->read(certIDByteArraySize));
EntityItemID id(packet->read(idByteArraySize));
QString text(packet->read(textByteArraySize));
EntityItemID id;
bool verificationSuccess = DependencyManager::get<EntityTreeRenderer>()->getTree()->verifyNonce(certID, text, id);
bool verificationSuccess = DependencyManager::get<EntityTreeRenderer>()->getTree()->verifyNonce(id, text);
if (verificationSuccess) {
emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS));
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationSuccess(_lastInspectedEntity);
emit ledger->updateCertificateStatus(id, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS));
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationSuccess(id);
} else {
emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED));
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationFailed(_lastInspectedEntity);
emit ledger->updateCertificateStatus(id, (uint)(ledger->CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED));
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationFailed(id);
}
}

View file

@ -46,7 +46,7 @@ public:
ContextOverlayInterface();
Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; }
void setCurrentEntityWithContextOverlay(const QUuid& entityID) { _currentEntityWithContextOverlay = entityID; }
void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); _lastInspectedEntity = entityID; }
void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); }
void setEnabled(bool enabled);
bool getEnabled() { return _enabled; }
bool getIsInMarketplaceInspectionMode() { return _isInMarketplaceInspectionMode; }
@ -83,7 +83,6 @@ private:
EntityItemID _mouseDownEntity;
quint64 _mouseDownEntityTimestamp;
EntityItemID _currentEntityWithContextOverlay;
EntityItemID _lastInspectedEntity;
QString _entityMarketplaceID;
bool _contextOverlayJustClicked { false };
@ -94,7 +93,7 @@ private:
void deletingEntity(const EntityItemID& entityItemID);
Q_INVOKABLE void startChallengeOwnershipTimer();
Q_INVOKABLE void startChallengeOwnershipTimer(const EntityItemID& entityItemID);
QTimer _challengeOwnershipTimeoutTimer;
};

View file

@ -652,9 +652,8 @@ void Avatar::fadeIn(render::ScenePointer scene) {
scene->enqueueTransaction(transaction);
}
void Avatar::fadeOut(render::ScenePointer scene, KillAvatarReason reason) {
void Avatar::fadeOut(render::Transaction& transaction, KillAvatarReason reason) {
render::Transition::Type transitionType = render::Transition::USER_LEAVE_DOMAIN;
render::Transaction transaction;
if (reason == KillAvatarReason::YourAvatarEnteredTheirBubble) {
transitionType = render::Transition::BUBBLE_ISECT_TRESPASSER;
@ -662,7 +661,6 @@ void Avatar::fadeOut(render::ScenePointer scene, KillAvatarReason reason) {
transitionType = render::Transition::BUBBLE_ISECT_OWNER;
}
fade(transaction, transitionType);
scene->enqueueTransaction(transaction);
}
void Avatar::fade(render::Transaction& transaction, render::Transition::Type type) {
@ -672,19 +670,6 @@ void Avatar::fade(render::Transaction& transaction, render::Transition::Type typ
transaction.addTransitionToItem(itemId, type, _renderItemID);
}
}
_isFading = true;
}
void Avatar::updateFadingStatus() {
if (_isFading) {
render::Transaction transaction;
transaction.queryTransitionOnItem(_renderItemID, [this](render::ItemID id, const render::Transition* transition) {
if (!transition || transition->isFinished) {
_isFading = false;
}
});
AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction);
}
}
void Avatar::removeFromScene(AvatarSharedPointer self, const render::ScenePointer& scene, render::Transaction& transaction) {

View file

@ -520,9 +520,7 @@ public:
bool isMoving() const { return _moving; }
void fadeIn(render::ScenePointer scene);
void fadeOut(render::ScenePointer scene, KillAvatarReason reason);
bool isFading() const { return _isFading; }
void updateFadingStatus();
void fadeOut(render::Transaction& transaction, KillAvatarReason reason);
// JSDoc is in AvatarData.h.
Q_INVOKABLE virtual float getEyeHeight() const override;
@ -727,7 +725,6 @@ protected:
bool _initialized { false };
bool _isAnimatingScale { false };
bool _mustFadeIn { false };
bool _isFading { false };
bool _reconstructSoftEntitiesJointMap { false };
float _modelScale { 1.0f };

View file

@ -1707,6 +1707,7 @@ protected:
glm::vec3 _globalBoundingBoxOffset;
AABox _defaultBubbleBox;
AABox _fitBoundingBox;
mutable ReadWriteLockable _avatarEntitiesLock;
AvatarEntityIDs _avatarEntityRemoved; // recently removed AvatarEntity ids

View file

@ -439,7 +439,6 @@ void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason remo
}
auto removedAvatar = _avatarHash.take(sessionUUID);
if (removedAvatar) {
removedAvatars.push_back(removedAvatar);
}

View file

@ -26,11 +26,10 @@ QUrl getBakeableModelURL(const QUrl& url) {
GLTF_EXTENSION
};
QUrl cleanURL = url.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment);
QString cleanURLString = cleanURL.fileName();
QString filename = url.fileName();
for (auto& extension : extensionsToBake) {
if (cleanURLString.endsWith(extension, Qt::CaseInsensitive)) {
return cleanURL;
if (filename.endsWith(extension, Qt::CaseInsensitive)) {
return url;
}
}

View file

@ -221,17 +221,15 @@ void EntityTreeRenderer::clearDomainAndNonOwnedEntities() {
// remove all entities from the scene
auto scene = _viewState->getMain3DScene();
if (scene) {
render::Transaction transaction;
for (const auto& entry : _entitiesInScene) {
const auto& renderer = entry.second;
const EntityItemPointer& entityItem = renderer->getEntity();
if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) {
renderer->removeFromScene(scene, transaction);
fadeOutRenderable(renderer);
} else {
savedEntities[entry.first] = entry.second;
}
}
scene->enqueueTransaction(transaction);
}
_renderablesToUpdate = savedEntities;
@ -258,12 +256,10 @@ void EntityTreeRenderer::clear() {
// remove all entities from the scene
auto scene = _viewState->getMain3DScene();
if (scene) {
render::Transaction transaction;
for (const auto& entry : _entitiesInScene) {
const auto& renderer = entry.second;
renderer->removeFromScene(scene, transaction);
fadeOutRenderable(renderer);
}
scene->enqueueTransaction(transaction);
} else {
qCWarning(entitiesrenderer) << "EntitityTreeRenderer::clear(), Unexpected null scene, possibly during application shutdown";
}
@ -1016,10 +1012,7 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
forceRecheckEntities(); // reset our state to force checking our inside/outsideness of entities
// here's where we remove the entity payload from the scene
render::Transaction transaction;
renderable->removeFromScene(scene, transaction);
scene->enqueueTransaction(transaction);
fadeOutRenderable(renderable);
}
void EntityTreeRenderer::addingEntity(const EntityItemID& entityID) {
@ -1057,13 +1050,26 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, bool
}
}
void EntityTreeRenderer::fadeOutRenderable(const EntityRendererPointer& renderable) {
render::Transaction transaction;
auto scene = _viewState->getMain3DScene();
transaction.transitionFinishedOperator(renderable->getRenderItemID(), [scene, renderable]() {
render::Transaction transaction;
renderable->removeFromScene(scene, transaction);
scene->enqueueTransaction(transaction);
});
scene->enqueueTransaction(transaction);
}
void EntityTreeRenderer::playEntityCollisionSound(const EntityItemPointer& entity, const Collision& collision) {
assert((bool)entity);
auto renderable = renderableForEntity(entity);
if (!renderable) {
return;
if (!renderable) {
return;
}
SharedSoundPointer collisionSound = renderable->getCollisionSound();
if (!collisionSound) {
return;

View file

@ -93,6 +93,8 @@ public:
/// reloads the entity scripts, calling unload and preload
void reloadEntityScripts();
void fadeOutRenderable(const EntityRendererPointer& renderable);
// event handles which may generate entity related events
QUuid mousePressEvent(QMouseEvent* event);
void mouseReleaseEvent(QMouseEvent* event);
@ -255,6 +257,7 @@ private:
std::unordered_map<EntityItemID, EntityRendererPointer> _renderablesToUpdate;
std::unordered_map<EntityItemID, EntityRendererPointer> _entitiesInScene;
std::unordered_map<EntityItemID, EntityItemWeakPointer> _entitiesToAdd;
// For Scene.shouldRenderEntities
QList<EntityItemID> _entityIDsLastInScene;

View file

@ -148,7 +148,7 @@ EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _created(entit
});
}
EntityRenderer::~EntityRenderer() { }
EntityRenderer::~EntityRenderer() {}
//
// Smart payload proxy members, implementing the payload interface
@ -421,6 +421,7 @@ void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transa
if (fading) {
_isFading = Interpolate::calculateFadeRatio(_fadeStartTime) < 1.0f;
}
_prevIsTransparent = transparent;
updateModelTransformAndBound();
@ -493,4 +494,4 @@ glm::vec4 EntityRenderer::calculatePulseColor(const glm::vec4& color, const Puls
}
return result;
}
}

View file

@ -307,10 +307,6 @@ void RenderableModelEntityItem::setShapeType(ShapeType type) {
}
void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) {
// because the caching system only allows one Geometry per url, and because this url might also be used
// as a visual model, we need to change this url in some way. We add a "collision-hull" query-arg so it
// will end up in a different hash-key in ResourceCache. TODO: It would be better to use the same URL and
// parse it twice.
auto currentCompoundShapeURL = getCompoundShapeURL();
ModelEntityItem::setCompoundShapeURL(url);
if (getCompoundShapeURL() != currentCompoundShapeURL || !getModel()) {

View file

@ -1,4 +1,4 @@
//
//
// RenderableParticleEffectEntityItem.cpp
// interface/src
//
@ -9,12 +9,12 @@
//
#include "RenderableParticleEffectEntityItem.h"
#include <StencilMaskPass.h>
#include <GeometryCache.h>
#include <shaders/Shaders.h>
#include <glm/gtx/transform.hpp>
using namespace render;
using namespace render::entities;
@ -79,6 +79,14 @@ bool ParticleEffectEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedE
return true;
}
if (_shapeType != entity->getShapeType()) {
return true;
}
if (_compoundShapeURL != entity->getCompoundShapeURL()) {
return true;
}
return false;
}
@ -87,10 +95,10 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
if (!newParticleProperties.valid()) {
qCWarning(entitiesrenderer) << "Bad particle properties";
}
if (resultWithReadLock<bool>([&]{ return _particleProperties != newParticleProperties; })) {
if (resultWithReadLock<bool>([&] { return _particleProperties != newParticleProperties; })) {
_timeUntilNextEmit = 0;
withWriteLock([&]{
withWriteLock([&] {
_particleProperties = newParticleProperties;
if (!_prevEmitterShouldTrailInitialized) {
_prevEmitterShouldTrailInitialized = true;
@ -101,13 +109,20 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
withWriteLock([&] {
_pulseProperties = entity->getPulseProperties();
_shapeType = entity->getShapeType();
QString compoundShapeURL = entity->getCompoundShapeURL();
if (_compoundShapeURL != compoundShapeURL) {
_compoundShapeURL = compoundShapeURL;
_hasComputedTriangles = false;
fetchGeometryResource();
}
});
_emitting = entity->getIsEmitting();
bool textureEmpty = resultWithReadLock<bool>([&]{ return _particleProperties.textures.isEmpty(); });
bool textureEmpty = resultWithReadLock<bool>([&] { return _particleProperties.textures.isEmpty(); });
if (textureEmpty) {
if (_networkTexture) {
withWriteLock([&] {
withWriteLock([&] {
_networkTexture.reset();
});
}
@ -116,11 +131,11 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
entity->setVisuallyReady(true);
});
} else {
bool textureNeedsUpdate = resultWithReadLock<bool>([&]{
bool textureNeedsUpdate = resultWithReadLock<bool>([&] {
return !_networkTexture || _networkTexture->getURL() != QUrl(_particleProperties.textures);
});
if (textureNeedsUpdate) {
withWriteLock([&] {
withWriteLock([&] {
_networkTexture = DependencyManager::get<TextureCache>()->getTexture(_particleProperties.textures);
});
}
@ -144,7 +159,7 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) {
// Fill in Uniforms structure
ParticleUniforms particleUniforms;
withReadLock([&]{
withReadLock([&] {
particleUniforms.radius.start = _particleProperties.radius.range.start;
particleUniforms.radius.middle = _particleProperties.radius.gradient.target;
particleUniforms.radius.finish = _particleProperties.radius.range.finish;
@ -181,9 +196,32 @@ Item::Bound ParticleEffectEntityRenderer::getBound() {
return _bound;
}
static const size_t VERTEX_PER_PARTICLE = 4;
// FIXME: these methods assume uniform emitDimensions, need to importance sample based on dimensions
float importanceSample2DDimension(float startDim) {
float dimension = 1.0f;
if (startDim < 1.0f) {
float innerDimensionSquared = startDim * startDim;
float outerDimensionSquared = 1.0f; // pow(particle::MAXIMUM_EMIT_RADIUS_START, 2);
float randDimensionSquared = randFloatInRange(innerDimensionSquared, outerDimensionSquared);
dimension = std::sqrt(randDimensionSquared);
}
return dimension;
}
ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties) {
float importanceSample3DDimension(float startDim) {
float dimension = 1.0f;
if (startDim < 1.0f) {
float innerDimensionCubed = startDim * startDim * startDim;
float outerDimensionCubed = 1.0f; // pow(particle::MAXIMUM_EMIT_RADIUS_START, 3);
float randDimensionCubed = randFloatInRange(innerDimensionCubed, outerDimensionCubed);
dimension = std::cbrt(randDimensionCubed);
}
return dimension;
}
ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties,
const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource,
const TriangleInfo& triangleInfo) {
CpuParticle particle;
const auto& accelerationSpread = particleProperties.emission.acceleration.spread;
@ -221,33 +259,130 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa
float azimuth;
if (azimuthFinish >= azimuthStart) {
azimuth = azimuthStart + (azimuthFinish - azimuthStart) * randFloat();
azimuth = azimuthStart + (azimuthFinish - azimuthStart) * randFloat();
} else {
azimuth = azimuthStart + (TWO_PI + azimuthFinish - azimuthStart) * randFloat();
}
// TODO: azimuth and elevation are only used for ellipsoids/circles, but could be used for other shapes too
if (emitDimensions == Vectors::ZERO) {
// Point
emitDirection = glm::quat(glm::vec3(PI_OVER_TWO - elevation, 0.0f, azimuth)) * Vectors::UNIT_Z;
} else {
// Ellipsoid
float radiusScale = 1.0f;
if (emitRadiusStart < 1.0f) {
float randRadius =
emitRadiusStart + randFloatInRange(0.0f, particle::MAXIMUM_EMIT_RADIUS_START - emitRadiusStart);
radiusScale = 1.0f - std::pow(1.0f - randRadius, 3.0f);
glm::vec3 emitPosition;
switch (shapeType) {
case SHAPE_TYPE_BOX: {
glm::vec3 dim = importanceSample3DDimension(emitRadiusStart) * 0.5f * emitDimensions;
int side = randIntInRange(0, 5);
int axis = side % 3;
float direction = side > 2 ? 1.0f : -1.0f;
emitDirection[axis] = direction;
emitPosition[axis] = direction * dim[axis];
axis = (axis + 1) % 3;
emitPosition[axis] = dim[axis] * randFloatInRange(-1.0f, 1.0f);
axis = (axis + 1) % 3;
emitPosition[axis] = dim[axis] * randFloatInRange(-1.0f, 1.0f);
break;
}
case SHAPE_TYPE_CYLINDER_X:
case SHAPE_TYPE_CYLINDER_Y:
case SHAPE_TYPE_CYLINDER_Z: {
glm::vec3 radii = importanceSample2DDimension(emitRadiusStart) * 0.5f * emitDimensions;
int axis = shapeType - SHAPE_TYPE_CYLINDER_X;
emitPosition[axis] = emitDimensions[axis] * randFloatInRange(-0.5f, 0.5f);
emitDirection[axis] = 0.0f;
axis = (axis + 1) % 3;
emitPosition[axis] = radii[axis] * glm::cos(azimuth);
emitDirection[axis] = radii[axis] > 0.0f ? emitPosition[axis] / (radii[axis] * radii[axis]) : 0.0f;
axis = (axis + 1) % 3;
emitPosition[axis] = radii[axis] * glm::sin(azimuth);
emitDirection[axis] = radii[axis] > 0.0f ? emitPosition[axis] / (radii[axis] * radii[axis]) : 0.0f;
emitDirection = glm::normalize(emitDirection);
break;
}
case SHAPE_TYPE_CIRCLE: {
glm::vec2 radii = importanceSample2DDimension(emitRadiusStart) * 0.5f * glm::vec2(emitDimensions.x, emitDimensions.z);
float x = radii.x * glm::cos(azimuth);
float z = radii.y * glm::sin(azimuth);
emitPosition = glm::vec3(x, 0.0f, z);
emitDirection = Vectors::UP;
break;
}
case SHAPE_TYPE_PLANE: {
glm::vec2 dim = importanceSample2DDimension(emitRadiusStart) * 0.5f * glm::vec2(emitDimensions.x, emitDimensions.z);
int side = randIntInRange(0, 3);
int axis = side % 2;
float direction = side > 1 ? 1.0f : -1.0f;
glm::vec2 pos;
pos[axis] = direction * dim[axis];
axis = (axis + 1) % 2;
pos[axis] = dim[axis] * randFloatInRange(-1.0f, 1.0f);
emitPosition = glm::vec3(pos.x, 0.0f, pos.y);
emitDirection = Vectors::UP;
break;
}
case SHAPE_TYPE_COMPOUND: {
// if we get here we know that geometryResource is loaded
size_t index = randFloat() * triangleInfo.totalSamples;
Triangle triangle;
for (size_t i = 0; i < triangleInfo.samplesPerTriangle.size(); i++) {
size_t numSamples = triangleInfo.samplesPerTriangle[i];
if (index < numSamples) {
triangle = triangleInfo.triangles[i];
break;
}
index -= numSamples;
}
float edgeLength1 = glm::length(triangle.v1 - triangle.v0);
float edgeLength2 = glm::length(triangle.v2 - triangle.v1);
float edgeLength3 = glm::length(triangle.v0 - triangle.v2);
float perimeter = edgeLength1 + edgeLength2 + edgeLength3;
float fraction1 = randFloatInRange(0.0f, 1.0f);
float fractionEdge1 = glm::min(fraction1 * perimeter / edgeLength1, 1.0f);
float fraction2 = fraction1 - edgeLength1 / perimeter;
float fractionEdge2 = glm::clamp(fraction2 * perimeter / edgeLength2, 0.0f, 1.0f);
float fraction3 = fraction2 - edgeLength2 / perimeter;
float fractionEdge3 = glm::clamp(fraction3 * perimeter / edgeLength3, 0.0f, 1.0f);
float dim = importanceSample2DDimension(emitRadiusStart);
triangle = triangle * (glm::scale(emitDimensions) * triangleInfo.transform);
glm::vec3 center = (triangle.v0 + triangle.v1 + triangle.v2) / 3.0f;
glm::vec3 v0 = (dim * (triangle.v0 - center)) + center;
glm::vec3 v1 = (dim * (triangle.v1 - center)) + center;
glm::vec3 v2 = (dim * (triangle.v2 - center)) + center;
emitPosition = glm::mix(v0, glm::mix(v1, glm::mix(v2, v0, fractionEdge3), fractionEdge2), fractionEdge1);
emitDirection = triangle.getNormal();
break;
}
case SHAPE_TYPE_SPHERE:
case SHAPE_TYPE_ELLIPSOID:
default: {
glm::vec3 radii = importanceSample3DDimension(emitRadiusStart) * 0.5f * emitDimensions;
float x = radii.x * glm::cos(elevation) * glm::cos(azimuth);
float y = radii.y * glm::cos(elevation) * glm::sin(azimuth);
float z = radii.z * glm::sin(elevation);
emitPosition = glm::vec3(x, y, z);
emitDirection = glm::normalize(glm::vec3(radii.x > 0.0f ? x / (radii.x * radii.x) : 0.0f,
radii.y > 0.0f ? y / (radii.y * radii.y) : 0.0f,
radii.z > 0.0f ? z / (radii.z * radii.z) : 0.0f));
break;
}
}
glm::vec3 radii = radiusScale * 0.5f * emitDimensions;
float x = radii.x * glm::cos(elevation) * glm::cos(azimuth);
float y = radii.y * glm::cos(elevation) * glm::sin(azimuth);
float z = radii.z * glm::sin(elevation);
glm::vec3 emitPosition = glm::vec3(x, y, z);
emitDirection = glm::normalize(glm::vec3(
radii.x > 0.0f ? x / (radii.x * radii.x) : 0.0f,
radii.y > 0.0f ? y / (radii.y * radii.y) : 0.0f,
radii.z > 0.0f ? z / (radii.z * radii.z) : 0.0f
));
particle.relativePosition += emitOrientation * emitPosition;
}
}
@ -267,20 +402,28 @@ void ParticleEffectEntityRenderer::stepSimulation() {
const auto now = usecTimestampNow();
const auto interval = std::min<uint64_t>(USECS_PER_SECOND / 60, now - _lastSimulated);
_lastSimulated = now;
particle::Properties particleProperties;
withReadLock([&]{
ShapeType shapeType;
GeometryResource::Pointer geometryResource;
withReadLock([&] {
particleProperties = _particleProperties;
shapeType = _shapeType;
geometryResource = _geometryResource;
});
const auto& modelTransform = getModelTransform();
if (_emitting && particleProperties.emitting()) {
if (_emitting && particleProperties.emitting() &&
(shapeType != SHAPE_TYPE_COMPOUND || (geometryResource && geometryResource->isLoaded()))) {
uint64_t emitInterval = particleProperties.emitIntervalUsecs();
if (emitInterval > 0 && interval >= _timeUntilNextEmit) {
auto timeRemaining = interval;
while (timeRemaining > _timeUntilNextEmit) {
if (_shapeType == SHAPE_TYPE_COMPOUND && !_hasComputedTriangles) {
computeTriangles(geometryResource->getHFMModel());
}
// emit particle
_cpuParticles.push_back(createParticle(now, modelTransform, particleProperties));
_cpuParticles.push_back(createParticle(now, modelTransform, particleProperties, shapeType, geometryResource, _triangleInfo));
_timeUntilNextEmit = emitInterval;
if (emitInterval < timeRemaining) {
timeRemaining -= emitInterval;
@ -297,7 +440,7 @@ void ParticleEffectEntityRenderer::stepSimulation() {
}
const float deltaTime = (float)interval / (float)USECS_PER_SECOND;
// update the particles
// update the particles
for (auto& particle : _cpuParticles) {
if (_prevEmitterShouldTrail != particleProperties.emission.shouldTrail) {
if (_prevEmitterShouldTrail) {
@ -313,7 +456,7 @@ void ParticleEffectEntityRenderer::stepSimulation() {
static GpuParticles gpuParticles;
gpuParticles.clear();
gpuParticles.reserve(_cpuParticles.size()); // Reserve space
std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [&particleProperties, &modelTransform](const CpuParticle& particle) {
std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [&particleProperties, &modelTransform] (const CpuParticle& particle) {
glm::vec3 position = particle.relativePosition + (particleProperties.emission.shouldTrail ? particle.basePosition : modelTransform.getTranslation());
return GpuParticle(position, glm::vec2(particle.lifetime, particle.seed));
});
@ -356,5 +499,131 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) {
batch.setInputBuffer(0, _particleBuffer, 0, sizeof(GpuParticle));
auto numParticles = _particleBuffer->getSize() / sizeof(GpuParticle);
static const size_t VERTEX_PER_PARTICLE = 4;
batch.drawInstanced((gpu::uint32)numParticles, gpu::TRIANGLE_STRIP, (gpu::uint32)VERTEX_PER_PARTICLE);
}
void ParticleEffectEntityRenderer::fetchGeometryResource() {
QUrl hullURL(_compoundShapeURL);
if (hullURL.isEmpty()) {
_geometryResource.reset();
} else {
_geometryResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(hullURL);
}
}
// FIXME: this is very similar to Model::calculateTriangleSets
void ParticleEffectEntityRenderer::computeTriangles(const hfm::Model& hfmModel) {
PROFILE_RANGE(render, __FUNCTION__);
int numberOfMeshes = hfmModel.meshes.size();
_hasComputedTriangles = true;
_triangleInfo.triangles.clear();
_triangleInfo.samplesPerTriangle.clear();
std::vector<float> areas;
float minArea = FLT_MAX;
AABox bounds;
for (int i = 0; i < numberOfMeshes; i++) {
const HFMMesh& mesh = hfmModel.meshes.at(i);
const int numberOfParts = mesh.parts.size();
for (int j = 0; j < numberOfParts; j++) {
const HFMMeshPart& part = mesh.parts.at(j);
const int INDICES_PER_TRIANGLE = 3;
const int INDICES_PER_QUAD = 4;
const int TRIANGLES_PER_QUAD = 2;
// tell our triangleSet how many triangles to expect.
int numberOfQuads = part.quadIndices.size() / INDICES_PER_QUAD;
int numberOfTris = part.triangleIndices.size() / INDICES_PER_TRIANGLE;
int totalTriangles = (numberOfQuads * TRIANGLES_PER_QUAD) + numberOfTris;
_triangleInfo.triangles.reserve(_triangleInfo.triangles.size() + totalTriangles);
areas.reserve(areas.size() + totalTriangles);
auto meshTransform = hfmModel.offset * mesh.modelTransform;
if (part.quadIndices.size() > 0) {
int vIndex = 0;
for (int q = 0; q < numberOfQuads; q++) {
int i0 = part.quadIndices[vIndex++];
int i1 = part.quadIndices[vIndex++];
int i2 = part.quadIndices[vIndex++];
int i3 = part.quadIndices[vIndex++];
// track the model space version... these points will be transformed by the FST's offset,
// which includes the scaling, rotation, and translation specified by the FST/FBX,
// this can't change at runtime, so we can safely store these in our TriangleSet
glm::vec3 v0 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i0], 1.0f));
glm::vec3 v1 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i1], 1.0f));
glm::vec3 v2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f));
glm::vec3 v3 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i3], 1.0f));
Triangle tri1 = { v0, v1, v3 };
Triangle tri2 = { v1, v2, v3 };
_triangleInfo.triangles.push_back(tri1);
_triangleInfo.triangles.push_back(tri2);
float area1 = tri1.getArea();
areas.push_back(area1);
if (area1 > EPSILON) {
minArea = std::min(minArea, area1);
}
float area2 = tri2.getArea();
areas.push_back(area2);
if (area2 > EPSILON) {
minArea = std::min(minArea, area2);
}
bounds += v0;
bounds += v1;
bounds += v2;
bounds += v3;
}
}
if (part.triangleIndices.size() > 0) {
int vIndex = 0;
for (int t = 0; t < numberOfTris; t++) {
int i0 = part.triangleIndices[vIndex++];
int i1 = part.triangleIndices[vIndex++];
int i2 = part.triangleIndices[vIndex++];
// track the model space version... these points will be transformed by the FST's offset,
// which includes the scaling, rotation, and translation specified by the FST/FBX,
// this can't change at runtime, so we can safely store these in our TriangleSet
glm::vec3 v0 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i0], 1.0f));
glm::vec3 v1 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i1], 1.0f));
glm::vec3 v2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f));
Triangle tri = { v0, v1, v2 };
_triangleInfo.triangles.push_back(tri);
float area = tri.getArea();
areas.push_back(area);
if (area > EPSILON) {
minArea = std::min(minArea, area);
}
bounds += v0;
bounds += v1;
bounds += v2;
}
}
}
}
_triangleInfo.totalSamples = 0;
for (auto& area : areas) {
size_t numSamples = area / minArea;
_triangleInfo.samplesPerTriangle.push_back(numSamples);
_triangleInfo.totalSamples += numSamples;
}
glm::vec3 scale = bounds.getScale();
_triangleInfo.transform = glm::scale(1.0f / scale) * glm::translate(-bounds.calcCenter());
}

View file

@ -81,7 +81,18 @@ private:
glm::vec2 spare;
};
static CpuParticle createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties);
void computeTriangles(const hfm::Model& hfmModel);
bool _hasComputedTriangles{ false };
struct TriangleInfo {
std::vector<Triangle> triangles;
std::vector<size_t> samplesPerTriangle;
size_t totalSamples;
glm::mat4 transform;
} _triangleInfo;
static CpuParticle createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties,
const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource,
const TriangleInfo& triangleInfo);
void stepSimulation();
particle::Properties _particleProperties;
@ -90,11 +101,16 @@ private:
CpuParticles _cpuParticles;
bool _emitting { false };
uint64_t _timeUntilNextEmit { 0 };
BufferPointer _particleBuffer{ std::make_shared<Buffer>() };
BufferPointer _particleBuffer { std::make_shared<Buffer>() };
BufferView _uniformBuffer;
quint64 _lastSimulated { 0 };
PulsePropertyGroup _pulseProperties;
ShapeType _shapeType;
QString _compoundShapeURL;
void fetchGeometryResource();
GeometryResource::Pointer _geometryResource;
NetworkTexturePointer _networkTexture;
ScenePointer _scene;

View file

@ -1698,10 +1698,7 @@ AACube EntityItem::getQueryAACube(bool& success) const {
}
bool EntityItem::shouldPuffQueryAACube() const {
bool hasGrabs = _grabsLock.resultWithReadLock<bool>([&] {
return _grabs.count() > 0;
});
return hasActions() || isChildOfMyAvatar() || isMovingRelativeToParent() || hasGrabs;
return hasActions() || isChildOfMyAvatar() || isMovingRelativeToParent();
}
// TODO: get rid of all users of this function...
@ -1896,7 +1893,8 @@ void EntityItem::setScaledDimensions(const glm::vec3& value) {
void EntityItem::setUnscaledDimensions(const glm::vec3& value) {
glm::vec3 newDimensions = glm::max(value, glm::vec3(ENTITY_ITEM_MIN_DIMENSION));
if (getUnscaledDimensions() != newDimensions) {
const float MIN_SCALE_CHANGE_SQUARED = 1.0e-6f;
if (glm::length2(getUnscaledDimensions() - newDimensions) > MIN_SCALE_CHANGE_SQUARED) {
withWriteLock([&] {
_unscaledDimensions = newDimensions;
});
@ -2086,7 +2084,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int32_t& group, int32_t& mask
} else {
if (getDynamic()) {
group = BULLET_COLLISION_GROUP_DYNAMIC;
} else if (isMovingRelativeToParent() || hasActions()) {
} else if (hasActions() || isMovingRelativeToParent()) {
group = BULLET_COLLISION_GROUP_KINEMATIC;
} else {
group = BULLET_COLLISION_GROUP_STATIC;
@ -3057,30 +3055,18 @@ bool EntityItem::getCollisionless() const {
}
uint16_t EntityItem::getCollisionMask() const {
uint16_t result;
withReadLock([&] {
result = _collisionMask;
});
return result;
return _collisionMask;
}
bool EntityItem::getDynamic() const {
if (SHAPE_TYPE_STATIC_MESH == getShapeType()) {
return false;
}
bool result;
withReadLock([&] {
result = _dynamic;
});
return result;
return _dynamic;
}
bool EntityItem::getLocked() const {
bool result;
withReadLock([&] {
result = _locked;
});
return result;
return _locked;
}
void EntityItem::setLocked(bool value) {
@ -3152,7 +3138,6 @@ uint32_t EntityItem::getDirtyFlags() const {
return result;
}
void EntityItem::markDirtyFlags(uint32_t mask) {
withWriteLock([&] {
mask &= Simulation::DIRTY_FLAGS;

View file

@ -448,7 +448,7 @@ public:
bool clearActions(EntitySimulationPointer simulation);
void setDynamicData(QByteArray dynamicData);
const QByteArray getDynamicData() const;
bool hasActions() const { return !_objectActions.empty(); }
bool hasActions() const { return !_objectActions.empty() || !_grabActions.empty(); }
QList<QUuid> getActionIDs() const { return _objectActions.keys(); }
QVariantMap getActionArguments(const QUuid& actionID) const;
void deserializeActions();

View file

@ -127,6 +127,7 @@ void buildStringToShapeTypeLookup() {
addShapeType(SHAPE_TYPE_SIMPLE_COMPOUND);
addShapeType(SHAPE_TYPE_STATIC_MESH);
addShapeType(SHAPE_TYPE_ELLIPSOID);
addShapeType(SHAPE_TYPE_CIRCLE);
}
QHash<QString, MaterialMappingMode> stringToMaterialMappingModeLookup;
@ -1114,23 +1115,28 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* default, particles emit along the entity's local z-axis, and <code>azimuthStart</code> and <code>azimuthFinish</code>
* are relative to the entity's local x-axis. The default value is a rotation of -90 degrees about the local x-axis, i.e.,
* the particles emit vertically.
* @property {Vec3} emitDimensions=0,0,0 - The dimensions of the ellipsoid from which particles are emitted.
* @property {number} emitRadiusStart=1 - The starting radius within the ellipsoid at which particles start being emitted;
* range <code>0.0</code> &ndash; <code>1.0</code> for the ellipsoid center to the ellipsoid surface, respectively.
* Particles are emitted from the portion of the ellipsoid that lies between <code>emitRadiusStart</code> and the
* ellipsoid's surface.
* @property {Vec3} emitDimensions=0,0,0 - The dimensions of the shape from which particles are emitted. The shape is specified with
* <code>shapeType</code>.
* @property {number} emitRadiusStart=1 - The starting radius within the shape at which particles start being emitted;
* range <code>0.0</code> &ndash; <code>1.0</code> for the center to the surface, respectively.
* Particles are emitted from the portion of the shape that lies between <code>emitRadiusStart</code> and the
* shape's surface.
* @property {number} polarStart=0 - The angle in radians from the entity's local z-axis at which particles start being emitted
* within the ellipsoid; range <code>0</code> &ndash; <code>Math.PI</code>. Particles are emitted from the portion of the
* ellipsoid that lies between <code>polarStart<code> and <code>polarFinish</code>.
* within the shape; range <code>0</code> &ndash; <code>Math.PI</code>. Particles are emitted from the portion of the
* shape that lies between <code>polarStart<code> and <code>polarFinish</code>. Only used if <code>shapeType</code> is
* <code>ellipsoid</code> or <code>sphere</code>.
* @property {number} polarFinish=0 - The angle in radians from the entity's local z-axis at which particles stop being emitted
* within the ellipsoid; range <code>0</code> &ndash; <code>Math.PI</code>. Particles are emitted from the portion of the
* ellipsoid that lies between <code>polarStart<code> and <code>polarFinish</code>.
* within the shape; range <code>0</code> &ndash; <code>Math.PI</code>. Particles are emitted from the portion of the
* shape that lies between <code>polarStart<code> and <code>polarFinish</code>. Only used if <code>shapeType</code> is
* <code>ellipsoid</code> or <code>sphere</code>.
* @property {number} azimuthStart=-Math.PI - The angle in radians from the entity's local x-axis about the entity's local
* z-axis at which particles start being emitted; range <code>-Math.PI</code> &ndash; <code>Math.PI</code>. Particles are
* emitted from the portion of the ellipsoid that lies between <code>azimuthStart<code> and <code>azimuthFinish</code>.
* emitted from the portion of the shape that lies between <code>azimuthStart<code> and <code>azimuthFinish</code>.
* Only used if <code>shapeType</code> is <code>ellipsoid</code>, <code>sphere</code>, or <code>circle</code>.
* @property {number} azimuthFinish=Math.PI - The angle in radians from the entity's local x-axis about the entity's local
* z-axis at which particles stop being emitted; range <code>-Math.PI</code> &ndash; <code>Math.PI</code>. Particles are
* emitted from the portion of the ellipsoid that lies between <code>azimuthStart<code> and <code>azimuthFinish</code>.
* emitted from the portion of the shape that lies between <code>azimuthStart<code> and <code>azimuthFinish</code>.
* Only used if <code>shapeType</code> is <code>ellipsoid</code>, <code>sphere</code>, or <code>circle</code>..
*
* @property {string} textures="" - The URL of a JPG or PNG image file to display for each particle. If you want transparency,
* use PNG format.
@ -1170,7 +1176,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* up in the world. If true, they will point towards the entity's up vector, based on its orientation.
* @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated.
*
* @property {ShapeType} shapeType="none" - <em>Currently not used.</em> <em>Read-only.</em>
* @property {ShapeType} shapeType="ellipsoid" - The shape of the collision hull used if collisions are enabled.
* @property {string} compoundShapeURL="" - The model file to use for the compound shape if <code>shapeType</code> is
* <code>"compound"</code>.
*
* @example <caption>Create a ball of green smoke.</caption>
* particles = Entities.addEntity({
@ -1658,6 +1666,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
// Particles only
if (_type == EntityTypes::ParticleEffect) {
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, getShapeTypeAsString());
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COMPOUND_SHAPE_URL, compoundShapeURL);
COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha);
_pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
@ -3104,6 +3113,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
if (properties.getType() == EntityTypes::ParticleEffect) {
APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)(properties.getShapeType()));
APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, properties.getCompoundShapeURL());
APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor());
APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha());
_staticPulse.setProperties(properties);
@ -3584,6 +3594,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
if (properties.getType() == EntityTypes::ParticleEffect) {
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE_TYPE, ShapeType, setShapeType);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha);
properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes);

View file

@ -1101,13 +1101,13 @@ void EntityScriptingInterface::handleEntityScriptCallMethodPacket(QSharedPointer
void EntityScriptingInterface::onAddingEntity(EntityItem* entity) {
if (entity->isWearable()) {
QMetaObject::invokeMethod(this, "addingWearable", Q_ARG(QUuid, entity->getEntityItemID()));
QMetaObject::invokeMethod(this, "addingWearable", Q_ARG(EntityItemID, entity->getEntityItemID()));
}
}
void EntityScriptingInterface::onDeletingEntity(EntityItem* entity) {
if (entity->isWearable()) {
QMetaObject::invokeMethod(this, "deletingWearable", Q_ARG(QUuid, entity->getEntityItemID()));
QMetaObject::invokeMethod(this, "deletingWearable", Q_ARG(EntityItemID, entity->getEntityItemID()));
}
}

View file

@ -26,6 +26,7 @@
#include <Extents.h>
#include <PerfStat.h>
#include <Profile.h>
#include <AddressManager.h>
#include "EntitySimulation.h"
#include "VariantMapToScriptValue.h"
@ -286,27 +287,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) {
assert(entity);
if (getIsServer()) {
QString certID(entity->getCertificateID());
EntityItemID entityItemID = entity->getEntityItemID();
EntityItemID existingEntityItemID;
{
QWriteLocker locker(&_entityCertificateIDMapLock);
existingEntityItemID = _entityCertificateIDMap.value(certID);
if (!certID.isEmpty()) {
_entityCertificateIDMap.insert(certID, entityItemID);
qCDebug(entities) << "Certificate ID" << certID << "belongs to" << entityItemID;
}
}
// Delete an already-existing entity from the tree if it has the same
// CertificateID as the entity we're trying to add.
if (!existingEntityItemID.isNull() && !entity->getCertificateType().contains(DOMAIN_UNLIMITED)) {
qCDebug(entities) << "Certificate ID" << certID << "already exists on entity with ID"
<< existingEntityItemID << ". Deleting existing entity.";
deleteEntity(existingEntityItemID, true);
return;
}
addCertifiedEntityOnServer(entity);
}
// check to see if we need to simulate this entity..
@ -764,13 +745,7 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator)
theEntity->die();
if (getIsServer()) {
{
QWriteLocker entityCertificateIDMapLocker(&_entityCertificateIDMapLock);
QString certID = theEntity->getCertificateID();
if (theEntity->getEntityItemID() == _entityCertificateIDMap.value(certID)) {
_entityCertificateIDMap.remove(certID);
}
}
removeCertifiedEntityOnServer(theEntity);
// set up the deleted entities ID
QWriteLocker recentlyDeletedEntitiesLocker(&_recentlyDeletedEntitiesLock);
@ -1421,11 +1396,123 @@ bool EntityTree::isScriptInWhitelist(const QString& scriptProperty) {
return false;
}
void EntityTree::addCertifiedEntityOnServer(EntityItemPointer entity) {
QString certID(entity->getCertificateID());
EntityItemID existingEntityItemID;
if (!certID.isEmpty()) {
EntityItemID entityItemID = entity->getEntityItemID();
QWriteLocker locker(&_entityCertificateIDMapLock);
QList<EntityItemID>& entityList = _entityCertificateIDMap[certID]; // inserts it if needed.
if (!entityList.isEmpty() && !entity->getCertificateType().contains(DOMAIN_UNLIMITED)) {
existingEntityItemID = entityList.first(); // we will only care about the first, if any, below.
entityList.removeOne(existingEntityItemID);
}
entityList << entityItemID; // adds to list within hash because entityList is a reference.
qCDebug(entities) << "Certificate ID" << certID << "belongs to" << entityItemID << "total" << entityList.size() << "entities.";
}
// Delete an already-existing entity from the tree if it has the same
// CertificateID as the entity we're trying to add.
if (!existingEntityItemID.isNull()) {
qCDebug(entities) << "Certificate ID" << certID << "already exists on entity with ID"
<< existingEntityItemID << ". Deleting existing entity.";
withWriteLock([&] {
deleteEntity(existingEntityItemID, true);
});
}
}
void EntityTree::removeCertifiedEntityOnServer(EntityItemPointer entity) {
QString certID = entity->getCertificateID();
if (!certID.isEmpty()) {
QWriteLocker entityCertificateIDMapLocker(&_entityCertificateIDMapLock);
QList<EntityItemID>& entityList = _entityCertificateIDMap[certID];
entityList.removeOne(entity->getEntityItemID());
if (entityList.isEmpty()) {
// hmmm, do we to make it be a hash instead of a list, so that this is faster if you stamp out 1000 of a domainUnlimited?
_entityCertificateIDMap.remove(certID);
}
}
}
void EntityTree::startDynamicDomainVerificationOnServer(float minimumAgeToRemove) {
QReadLocker locker(&_entityCertificateIDMapLock);
QHashIterator<QString, QList<EntityItemID>> i(_entityCertificateIDMap);
qCDebug(entities) << _entityCertificateIDMap.size() << "certificates present.";
while (i.hasNext()) {
i.next();
const auto& certificateID = i.key();
const auto& entityIDs = i.value();
if (entityIDs.isEmpty()) {
continue;
}
// Examine each cert:
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest;
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL();
requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/location");
QJsonObject request;
request["certificate_id"] = certificateID;
networkRequest.setUrl(requestURL);
QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
connect(networkReply, &QNetworkReply::finished, this, [this, entityIDs, networkReply, minimumAgeToRemove, &certificateID] {
QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object();
jsonObject = jsonObject["data"].toObject();
bool failure = networkReply->error() != QNetworkReply::NoError;
auto failureReason = networkReply->error();
networkReply->deleteLater();
if (failure) {
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << failureReason
<< "; NOT deleting cert" << certificateID << "More info:" << jsonObject;
return;
}
QString thisDomainID = DependencyManager::get<AddressManager>()->getDomainID().remove(QRegExp("\\{|\\}"));
if (jsonObject["domain_id"].toString() == thisDomainID) {
// Entity belongs here. Nothing to do.
return;
}
// Entity does not belong here:
QList<EntityItemID> retained;
for (int i = 0; i < entityIDs.size(); i++) {
EntityItemID entityID = entityIDs.at(i);
EntityItemPointer entity = findEntityByEntityItemID(entityID);
if (!entity) {
qCDebug(entities) << "Entity undergoing dynamic domain verification is no longer available:" << entityID;
continue;
}
if (entity->getAge() <= minimumAgeToRemove) {
qCDebug(entities) << "Entity failed dynamic domain verification, but was created too recently to necessitate deletion:" << entityID;
retained << entityID;
continue;
}
qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString()
<< "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << entityID;
withWriteLock([&] {
deleteEntity(entityID, true);
});
}
{
QWriteLocker entityCertificateIDMapLocker(&_entityCertificateIDMapLock);
if (retained.isEmpty()) {
qCDebug(entities) << "Removed" << certificateID;
_entityCertificateIDMap.remove(certificateID);
} else {
qCDebug(entities) << "Retained" << retained.size() << "young entities for" << certificateID;
_entityCertificateIDMap[certificateID] = retained;
}
}
});
}
}
void EntityTree::startChallengeOwnershipTimer(const EntityItemID& entityItemID) {
QTimer* _challengeOwnershipTimeoutTimer = new QTimer(this);
connect(this, &EntityTree::killChallengeOwnershipTimeoutTimer, this, [=](const QString& certID) {
QReadLocker locker(&_entityCertificateIDMapLock);
EntityItemID id = _entityCertificateIDMap.value(certID);
connect(this, &EntityTree::killChallengeOwnershipTimeoutTimer, this, [=](const EntityItemID& id) {
if (entityItemID == id && _challengeOwnershipTimeoutTimer) {
_challengeOwnershipTimeoutTimer->stop();
_challengeOwnershipTimeoutTimer->deleteLater();
@ -1445,26 +1532,21 @@ void EntityTree::startChallengeOwnershipTimer(const EntityItemID& entityItemID)
_challengeOwnershipTimeoutTimer->start(5000);
}
QByteArray EntityTree::computeNonce(const QString& certID, const QString ownerKey) {
QByteArray EntityTree::computeNonce(const EntityItemID& entityID, const QString ownerKey) {
QUuid nonce = QUuid::createUuid(); //random, 5-hex value, separated by "-"
QByteArray nonceBytes = nonce.toByteArray();
QWriteLocker locker(&_certNonceMapLock);
_certNonceMap.insert(certID, QPair<QUuid, QString>(nonce, ownerKey));
QWriteLocker locker(&_entityNonceMapLock);
_entityNonceMap.insert(entityID, QPair<QUuid, QString>(nonce, ownerKey));
return nonceBytes;
}
bool EntityTree::verifyNonce(const QString& certID, const QString& nonce, EntityItemID& id) {
{
QReadLocker certIdMapLocker(&_entityCertificateIDMapLock);
id = _entityCertificateIDMap.value(certID);
}
bool EntityTree::verifyNonce(const EntityItemID& entityID, const QString& nonce) {
QString actualNonce, key;
{
QWriteLocker locker(&_certNonceMapLock);
QPair<QUuid, QString> sent = _certNonceMap.take(certID);
QWriteLocker locker(&_entityNonceMapLock);
QPair<QUuid, QString> sent = _entityNonceMap.take(entityID);
actualNonce = sent.first.toString();
key = sent.second;
}
@ -1474,9 +1556,9 @@ bool EntityTree::verifyNonce(const QString& certID, const QString& nonce, Entity
bool verificationSuccess = EntityItemProperties::verifySignature(annotatedKey.toUtf8(), hashedActualNonce, QByteArray::fromBase64(nonce.toUtf8()));
if (verificationSuccess) {
qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded.";
qCDebug(entities) << "Ownership challenge for Entity ID" << entityID << "succeeded.";
} else {
qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed. Actual nonce:" << actualNonce <<
qCDebug(entities) << "Ownership challenge for Entity ID" << entityID << "failed. Actual nonce:" << actualNonce <<
"\nHashed actual nonce (digest):" << hashedActualNonce << "\nSent nonce (signature)" << nonce << "\nKey" << key;
}
@ -1484,42 +1566,42 @@ bool EntityTree::verifyNonce(const QString& certID, const QString& nonce, Entity
}
void EntityTree::processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) {
int certIDByteArraySize;
int idByteArraySize;
int textByteArraySize;
int nodeToChallengeByteArraySize;
message.readPrimitive(&certIDByteArraySize);
message.readPrimitive(&idByteArraySize);
message.readPrimitive(&textByteArraySize);
message.readPrimitive(&nodeToChallengeByteArraySize);
QByteArray certID(message.read(certIDByteArraySize));
QByteArray id(message.read(idByteArraySize));
QByteArray text(message.read(textByteArraySize));
QByteArray nodeToChallenge(message.read(nodeToChallengeByteArraySize));
sendChallengeOwnershipRequestPacket(certID, text, nodeToChallenge, sourceNode);
sendChallengeOwnershipRequestPacket(id, text, nodeToChallenge, sourceNode);
}
void EntityTree::processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) {
auto nodeList = DependencyManager::get<NodeList>();
int certIDByteArraySize;
int idByteArraySize;
int textByteArraySize;
int challengingNodeUUIDByteArraySize;
message.readPrimitive(&certIDByteArraySize);
message.readPrimitive(&idByteArraySize);
message.readPrimitive(&textByteArraySize);
message.readPrimitive(&challengingNodeUUIDByteArraySize);
QByteArray certID(message.read(certIDByteArraySize));
QByteArray id(message.read(idByteArraySize));
QByteArray text(message.read(textByteArraySize));
QUuid challengingNode = QUuid::fromRfc4122(message.read(challengingNodeUUIDByteArraySize));
auto challengeOwnershipReplyPacket = NLPacket::create(PacketType::ChallengeOwnershipReply,
certIDByteArraySize + text.length() + 2 * sizeof(int),
idByteArraySize + text.length() + 2 * sizeof(int),
true);
challengeOwnershipReplyPacket->writePrimitive(certIDByteArraySize);
challengeOwnershipReplyPacket->writePrimitive(idByteArraySize);
challengeOwnershipReplyPacket->writePrimitive(text.length());
challengeOwnershipReplyPacket->write(certID);
challengeOwnershipReplyPacket->write(id);
challengeOwnershipReplyPacket->write(text);
nodeList->sendPacket(std::move(challengeOwnershipReplyPacket), *(nodeList->nodeWithUUID(challengingNode)));
@ -1529,7 +1611,7 @@ void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QStri
// 1. Obtain a nonce
auto nodeList = DependencyManager::get<NodeList>();
QByteArray text = computeNonce(certID, ownerKey);
QByteArray text = computeNonce(entityItemID, ownerKey);
if (text == "") {
qCDebug(entities) << "CRITICAL ERROR: Couldn't compute nonce. Deleting entity...";
@ -1539,14 +1621,14 @@ void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QStri
} else {
qCDebug(entities) << "Challenging ownership of Cert ID" << certID;
// 2. Send the nonce to the rezzing avatar's node
QByteArray certIDByteArray = certID.toUtf8();
int certIDByteArraySize = certIDByteArray.size();
QByteArray idByteArray = entityItemID.toByteArray();
int idByteArraySize = idByteArray.size();
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership,
certIDByteArraySize + text.length() + 2 * sizeof(int),
idByteArraySize + text.length() + 2 * sizeof(int),
true);
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
challengeOwnershipPacket->writePrimitive(idByteArraySize);
challengeOwnershipPacket->writePrimitive(text.length());
challengeOwnershipPacket->write(certIDByteArray);
challengeOwnershipPacket->write(idByteArray);
challengeOwnershipPacket->write(text);
nodeList->sendPacket(std::move(challengeOwnershipPacket), *senderNode);
@ -1560,24 +1642,24 @@ void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QStri
}
}
void EntityTree::sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode) {
void EntityTree::sendChallengeOwnershipRequestPacket(const QByteArray& id, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode) {
auto nodeList = DependencyManager::get<NodeList>();
// In this case, Client A is challenging Client B. Client A is inspecting a certified entity that it wants
// to make sure belongs to Avatar B.
QByteArray senderNodeUUID = senderNode->getUUID().toRfc4122();
int certIDByteArraySize = certID.length();
int idByteArraySize = id.length();
int TextByteArraySize = text.length();
int senderNodeUUIDSize = senderNodeUUID.length();
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest,
certIDByteArraySize + TextByteArraySize + senderNodeUUIDSize + 3 * sizeof(int),
idByteArraySize + TextByteArraySize + senderNodeUUIDSize + 3 * sizeof(int),
true);
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
challengeOwnershipPacket->writePrimitive(idByteArraySize);
challengeOwnershipPacket->writePrimitive(TextByteArraySize);
challengeOwnershipPacket->writePrimitive(senderNodeUUIDSize);
challengeOwnershipPacket->write(certID);
challengeOwnershipPacket->write(id);
challengeOwnershipPacket->write(text);
challengeOwnershipPacket->write(senderNodeUUID);
@ -1636,22 +1718,21 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt
}
void EntityTree::processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) {
int certIDByteArraySize;
int idByteArraySize;
int textByteArraySize;
message.readPrimitive(&certIDByteArraySize);
message.readPrimitive(&idByteArraySize);
message.readPrimitive(&textByteArraySize);
QString certID(message.read(certIDByteArraySize));
EntityItemID id(message.read(idByteArraySize));
QString text(message.read(textByteArraySize));
emit killChallengeOwnershipTimeoutTimer(certID);
emit killChallengeOwnershipTimeoutTimer(id);
EntityItemID id;
if (!verifyNonce(certID, text, id)) {
if (!id.isNull()) {
if (!verifyNonce(id, text)) {
withWriteLock([&] {
deleteEntity(id, true);
}
});
}
}

View file

@ -157,11 +157,6 @@ public:
return _recentlyDeletedEntityItemIDs;
}
QHash<QString, EntityItemID> getEntityCertificateIDMap() const {
QReadLocker locker(&_entityCertificateIDMapLock);
return _entityCertificateIDMap;
}
void forgetEntitiesDeletedBefore(quint64 sinceTime);
int processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode);
@ -252,8 +247,8 @@ public:
static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME;
QByteArray computeNonce(const QString& certID, const QString ownerKey);
bool verifyNonce(const QString& certID, const QString& nonce, EntityItemID& id);
QByteArray computeNonce(const EntityItemID& entityID, const QString ownerKey);
bool verifyNonce(const EntityItemID& entityID, const QString& nonce);
QUuid getMyAvatarSessionUUID() { return _myAvatar ? _myAvatar->getSessionUUID() : QUuid(); }
void setMyAvatar(std::shared_ptr<AvatarData> myAvatar) { _myAvatar = myAvatar; }
@ -279,6 +274,7 @@ public:
void updateEntityQueryAACube(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender,
bool force, bool tellServer);
void startDynamicDomainVerificationOnServer(float minimumAgeToRemove);
signals:
void deletingEntity(const EntityItemID& entityID);
@ -290,7 +286,7 @@ signals:
void entityServerScriptChanging(const EntityItemID& entityItemID, const bool reload);
void newCollisionSoundURL(const QUrl& url, const EntityItemID& entityID);
void clearingEntities();
void killChallengeOwnershipTimeoutTimer(const QString& certID);
void killChallengeOwnershipTimeoutTimer(const EntityItemID& certID);
protected:
@ -327,10 +323,10 @@ protected:
QHash<EntityItemID, EntityItemPointer> _entityMap;
mutable QReadWriteLock _entityCertificateIDMapLock;
QHash<QString, EntityItemID> _entityCertificateIDMap;
QHash<QString, QList<EntityItemID>> _entityCertificateIDMap;
mutable QReadWriteLock _certNonceMapLock;
QHash<QString, QPair<QUuid, QString>> _certNonceMap;
mutable QReadWriteLock _entityNonceMapLock;
QHash<EntityItemID, QPair<QUuid, QString>> _entityNonceMap;
EntitySimulationPointer _simulation;
@ -377,8 +373,10 @@ protected:
Q_INVOKABLE void startChallengeOwnershipTimer(const EntityItemID& entityItemID);
private:
void addCertifiedEntityOnServer(EntityItemPointer entity);
void removeCertifiedEntityOnServer(EntityItemPointer entity);
void sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode);
void sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode);
void sendChallengeOwnershipRequestPacket(const QByteArray& id, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode);
void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode);
std::shared_ptr<AvatarData> _myAvatar{ nullptr };

View file

@ -49,8 +49,6 @@ class LineEntityItem : public EntityItem {
QVector<glm::vec3> getLinePoints() const;
virtual ShapeType getShapeType() const override { return SHAPE_TYPE_NONE; }
// never have a ray intersection pick a LineEntityItem.
virtual bool supportsDetailedIntersection() const override { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,

View file

@ -175,7 +175,7 @@ protected:
QString _textures;
ShapeType _shapeType = SHAPE_TYPE_NONE;
ShapeType _shapeType { SHAPE_TYPE_NONE };
private:
uint64_t _lastAnimated{ 0 };

View file

@ -410,6 +410,7 @@ EntityItemProperties ParticleEffectEntityItem::getProperties(const EntityPropert
EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class
COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(compoundShapeURL, getCompoundShapeURL);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha);
withReadLock([&] {
@ -464,6 +465,7 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert
bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha);
withWriteLock([&] {
@ -540,6 +542,7 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch
const unsigned char* dataAt = data;
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType);
READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL);
READ_ENTITY_PROPERTY(PROP_COLOR, u8vec3Color, setColor);
READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha);
withWriteLock([&] {
@ -598,6 +601,7 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_SHAPE_TYPE;
requestedProperties += PROP_COMPOUND_SHAPE_URL;
requestedProperties += PROP_COLOR;
requestedProperties += PROP_ALPHA;
requestedProperties += _pulseProperties.getEntityProperties(params);
@ -656,6 +660,7 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData,
bool successPropertyFits = true;
APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType());
APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL());
APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor());
APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha());
withReadLock([&] {
@ -718,11 +723,42 @@ void ParticleEffectEntityItem::debugDump() const {
}
void ParticleEffectEntityItem::setShapeType(ShapeType type) {
switch (type) {
case SHAPE_TYPE_NONE:
case SHAPE_TYPE_CAPSULE_X:
case SHAPE_TYPE_CAPSULE_Y:
case SHAPE_TYPE_CAPSULE_Z:
case SHAPE_TYPE_HULL:
case SHAPE_TYPE_SIMPLE_HULL:
case SHAPE_TYPE_SIMPLE_COMPOUND:
case SHAPE_TYPE_STATIC_MESH:
// these types are unsupported for ParticleEffectEntity
type = particle::DEFAULT_SHAPE_TYPE;
break;
default:
break;
}
withWriteLock([&] {
if (type != _shapeType) {
_shapeType = type;
_flags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
}
_shapeType = type;
});
}
ShapeType ParticleEffectEntityItem::getShapeType() const {
return resultWithReadLock<ShapeType>([&] {
return _shapeType;
});
}
void ParticleEffectEntityItem::setCompoundShapeURL(const QString& compoundShapeURL) {
withWriteLock([&] {
_compoundShapeURL = compoundShapeURL;
});
}
QString ParticleEffectEntityItem::getCompoundShapeURL() const {
return resultWithReadLock<QString>([&] {
return _compoundShapeURL;
});
}

View file

@ -79,6 +79,7 @@ namespace particle {
static const QString DEFAULT_TEXTURES = "";
static const bool DEFAULT_EMITTER_SHOULD_TRAIL = false;
static const bool DEFAULT_ROTATE_WITH_ENTITY = false;
static const ShapeType DEFAULT_SHAPE_TYPE = ShapeType::SHAPE_TYPE_ELLIPSOID;
template <typename T>
struct Range {
@ -255,7 +256,10 @@ public:
float getAlphaSpread() const { return _particleProperties.alpha.gradient.spread; }
void setShapeType(ShapeType type) override;
virtual ShapeType getShapeType() const override { return _shapeType; }
virtual ShapeType getShapeType() const override;
QString getCompoundShapeURL() const;
virtual void setCompoundShapeURL(const QString& url);
virtual void debugDump() const override;
@ -349,7 +353,8 @@ protected:
PulsePropertyGroup _pulseProperties;
bool _isEmitting { true };
ShapeType _shapeType { SHAPE_TYPE_NONE };
ShapeType _shapeType{ particle::DEFAULT_SHAPE_TYPE };
QString _compoundShapeURL { "" };
};
#endif // hifi_ParticleEffectEntityItem_h

View file

@ -112,7 +112,7 @@ protected:
//! This is SHAPE_TYPE_ELLIPSOID rather than SHAPE_TYPE_NONE to maintain
//! prior functionality where new or unsupported shapes are treated as
//! ellipsoids.
ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_ELLIPSOID };
ShapeType _collisionShapeType { ShapeType::SHAPE_TYPE_ELLIPSOID };
};
#endif // hifi_ShapeEntityItem_h

View file

@ -13,7 +13,6 @@
#include <glm/gtx/transform.hpp>
#include <QDebug>
#include <QUrlQuery>
#include <ByteCountCoding.h>
@ -353,7 +352,7 @@ bool ZoneEntityItem::contains(const glm::vec3& point) const {
Extents meshExtents = hfmModel.getMeshExtents();
glm::vec3 meshExtentsDiagonal = meshExtents.maximum - meshExtents.minimum;
glm::vec3 offset = -meshExtents.minimum- (meshExtentsDiagonal * getRegistrationPoint());
glm::vec3 offset = -meshExtents.minimum - (meshExtentsDiagonal * getRegistrationPoint());
glm::vec3 scale(getScaledDimensions() / meshExtentsDiagonal);
glm::mat4 hfmToEntityMatrix = glm::scale(scale) * glm::translate(offset);
@ -463,9 +462,6 @@ void ZoneEntityItem::fetchCollisionGeometryResource() {
if (hullURL.isEmpty()) {
_shapeResource.reset();
} else {
QUrlQuery queryArgs(hullURL);
queryArgs.addQueryItem("collision-hull", "");
hullURL.setQuery(queryArgs);
_shapeResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(hullURL);
}
}

View file

@ -133,7 +133,7 @@ protected:
KeyLightPropertyGroup _keyLightProperties;
AmbientLightPropertyGroup _ambientLightProperties;
ShapeType _shapeType = DEFAULT_SHAPE_TYPE;
ShapeType _shapeType { DEFAULT_SHAPE_TYPE };
QString _compoundShapeURL;
// The following 3 values are the defaults for zone creation

View file

@ -71,12 +71,12 @@ void UserActivityLoggerScriptingInterface::makeUserConnection(QString otherID, b
doLogAction("makeUserConnection", payload);
}
void UserActivityLoggerScriptingInterface::bubbleToggled(bool newValue) {
doLogAction(newValue ? "bubbleOn" : "bubbleOff");
void UserActivityLoggerScriptingInterface::privacyShieldToggled(bool newValue) {
doLogAction(newValue ? "privacyShieldOn" : "privacyShieldOff");
}
void UserActivityLoggerScriptingInterface::bubbleActivated() {
doLogAction("bubbleActivated");
void UserActivityLoggerScriptingInterface::privacyShieldActivated() {
doLogAction("privacyShieldActivated");
}
void UserActivityLoggerScriptingInterface::logAction(QString action, QVariantMap details) {

View file

@ -30,8 +30,8 @@ public:
Q_INVOKABLE void palAction(QString action, QString target);
Q_INVOKABLE void palOpened(float secondsOpen);
Q_INVOKABLE void makeUserConnection(QString otherUser, bool success, QString details = "");
Q_INVOKABLE void bubbleToggled(bool newValue);
Q_INVOKABLE void bubbleActivated();
Q_INVOKABLE void privacyShieldToggled(bool newValue);
Q_INVOKABLE void privacyShieldActivated();
Q_INVOKABLE void logAction(QString action, QVariantMap details = QVariantMap{});
Q_INVOKABLE void commercePurchaseSuccess(QString marketplaceID, QString contentCreator, int cost, bool firstPurchaseOfThisItem);
Q_INVOKABLE void commercePurchaseFailure(QString marketplaceID, QString contentCreator, int cost, bool firstPurchaseOfThisItem, QString errorDetails);

View file

@ -38,10 +38,10 @@ PacketVersion versionForPacketType(PacketType packetType) {
return static_cast<PacketVersion>(EntityQueryPacketVersion::ConicalFrustums);
case PacketType::AvatarIdentity:
case PacketType::AvatarData:
return static_cast<PacketVersion>(AvatarMixerPacketVersion::SendMaxTranslationDimension);
return static_cast<PacketVersion>(AvatarMixerPacketVersion::FBXJointOrderChange);
case PacketType::BulkAvatarData:
case PacketType::KillAvatar:
return static_cast<PacketVersion>(AvatarMixerPacketVersion::SendMaxTranslationDimension);
return static_cast<PacketVersion>(AvatarMixerPacketVersion::FBXJointOrderChange);
case PacketType::MessagesData:
return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData);
// ICE packets

View file

@ -267,6 +267,7 @@ enum class EntityVersion : PacketVersion {
ReOrderParentIDProperties,
CertificateTypeProperty,
DisableWebMedia,
ParticleShapeType,
// Add new versions above here
NUM_PACKET_TYPE,
@ -328,7 +329,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
CollisionFlag,
AvatarTraitsAck,
FasterAvatarEntities,
SendMaxTranslationDimension
SendMaxTranslationDimension,
FBXJointOrderChange
};
enum class DomainConnectRequestVersion : PacketVersion {

View file

@ -97,7 +97,7 @@ bool OctreeEntitiesFileParser::parseEntities(QVariantMap& parsedEntities) {
_errorString = "Duplicate Id entries";
return false;
}
gotId = true;
if (nextToken() != '"') {
_errorString = "Invalid Id value";
return false;
@ -107,14 +107,21 @@ bool OctreeEntitiesFileParser::parseEntities(QVariantMap& parsedEntities) {
_errorString = "Invalid Id string";
return false;
}
QUuid idValue = QUuid::fromString(QLatin1String(idString.c_str()) );
if (idValue.isNull()) {
_errorString = "Id value invalid UUID string: " + idString;
return false;
}
parsedEntities["Id"] = idValue;
gotId = true;
// some older archives may have a null string id, so
// return success without setting parsedEntities,
// which will result in a new uuid for the restored
// archive. (not parsing and using isNull as parsing
// results in null if there is a corrupt string)
if (idString != "{00000000-0000-0000-0000-000000000000}") {
QUuid idValue = QUuid::fromString(QLatin1String(idString.c_str()) );
if (idValue.isNull()) {
_errorString = "Id value invalid UUID string: " + idString;
return false;
}
parsedEntities["Id"] = idValue;
}
} else if (key == "Version") {
if (gotVersion) {
_errorString = "Duplicate Version entries";

View file

@ -217,9 +217,8 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const {
}
return MOTION_TYPE_DYNAMIC;
}
if (_entity->isMovingRelativeToParent() ||
_entity->hasActions() ||
_entity->hasGrabs() ||
if (_entity->hasActions() ||
_entity->isMovingRelativeToParent() ||
_entity->hasAncestorOfType(NestableType::Avatar)) {
return MOTION_TYPE_KINEMATIC;
}

View file

@ -47,6 +47,10 @@ void Transaction::queryTransitionOnItem(ItemID id, TransitionQueryFunc func) {
_queriedTransitions.emplace_back(id, func);
}
void Transaction::transitionFinishedOperator(ItemID id, TransitionFinishedFunc func) {
_transitionFinishedOperators.emplace_back(id, func);
}
void Transaction::updateItem(ItemID id, const UpdateFunctorPointer& functor) {
_updatedItems.emplace_back(id, functor);
}
@ -75,6 +79,7 @@ void Transaction::reserve(const std::vector<Transaction>& transactionContainer)
size_t addedTransitionsCount = 0;
size_t queriedTransitionsCount = 0;
size_t reAppliedTransitionsCount = 0;
size_t transitionFinishedOperatorsCount = 0;
size_t highlightResetsCount = 0;
size_t highlightRemovesCount = 0;
size_t highlightQueriesCount = 0;
@ -85,6 +90,7 @@ void Transaction::reserve(const std::vector<Transaction>& transactionContainer)
updatedItemsCount += transaction._updatedItems.size();
resetSelectionsCount += transaction._resetSelections.size();
addedTransitionsCount += transaction._addedTransitions.size();
transitionFinishedOperatorsCount += transaction._transitionFinishedOperators.size();
queriedTransitionsCount += transaction._queriedTransitions.size();
reAppliedTransitionsCount += transaction._reAppliedTransitions.size();
highlightResetsCount += transaction._highlightResets.size();
@ -99,6 +105,7 @@ void Transaction::reserve(const std::vector<Transaction>& transactionContainer)
_addedTransitions.reserve(addedTransitionsCount);
_queriedTransitions.reserve(queriedTransitionsCount);
_reAppliedTransitions.reserve(reAppliedTransitionsCount);
_transitionFinishedOperators.reserve(transitionFinishedOperatorsCount);
_highlightResets.reserve(highlightResetsCount);
_highlightRemoves.reserve(highlightRemovesCount);
_highlightQueries.reserve(highlightQueriesCount);
@ -142,6 +149,7 @@ void Transaction::merge(Transaction&& transaction) {
moveElements(_resetSelections, transaction._resetSelections);
moveElements(_addedTransitions, transaction._addedTransitions);
moveElements(_queriedTransitions, transaction._queriedTransitions);
moveElements(_transitionFinishedOperators, transaction._transitionFinishedOperators);
moveElements(_reAppliedTransitions, transaction._reAppliedTransitions);
moveElements(_highlightResets, transaction._highlightResets);
moveElements(_highlightRemoves, transaction._highlightRemoves);
@ -156,6 +164,7 @@ void Transaction::merge(const Transaction& transaction) {
copyElements(_addedTransitions, transaction._addedTransitions);
copyElements(_queriedTransitions, transaction._queriedTransitions);
copyElements(_reAppliedTransitions, transaction._reAppliedTransitions);
copyElements(_transitionFinishedOperators, transaction._transitionFinishedOperators);
copyElements(_highlightResets, transaction._highlightResets);
copyElements(_highlightRemoves, transaction._highlightRemoves);
copyElements(_highlightQueries, transaction._highlightQueries);
@ -168,6 +177,7 @@ void Transaction::clear() {
_resetSelections.clear();
_addedTransitions.clear();
_queriedTransitions.clear();
_transitionFinishedOperators.clear();
_reAppliedTransitions.clear();
_highlightResets.clear();
_highlightRemoves.clear();
@ -271,6 +281,7 @@ void Scene::processTransactionFrame(const Transaction& transaction) {
transitionItems(transaction._addedTransitions);
reApplyTransitions(transaction._reAppliedTransitions);
queryTransitionItems(transaction._queriedTransitions);
resetTransitionFinishedOperator(transaction._transitionFinishedOperators);
// Update the numItemsAtomic counter AFTER the pending changes went through
_numAllocatedItems.exchange(maxID);
@ -394,7 +405,7 @@ void Scene::transitionItems(const Transaction::TransitionAdds& transactions) {
// Only remove if:
// transitioning to something other than none or we're transitioning to none from ELEMENT_LEAVE_DOMAIN or USER_LEAVE_DOMAIN
const auto& oldTransitionType = transitionStage->getTransition(transitionId).eventType;
if (transitionType != Transition::NONE || !(oldTransitionType == Transition::ELEMENT_LEAVE_DOMAIN || oldTransitionType == Transition::USER_LEAVE_DOMAIN)) {
if (transitionType != oldTransitionType) {
resetItemTransition(itemId);
}
}
@ -440,6 +451,23 @@ void Scene::queryTransitionItems(const Transaction::TransitionQueries& transacti
}
}
void Scene::resetTransitionFinishedOperator(const Transaction::TransitionFinishedOperators& operators) {
for (auto& finishedOperator : operators) {
auto itemId = std::get<0>(finishedOperator);
const auto& item = _items[itemId];
auto func = std::get<1>(finishedOperator);
if (item.exist() && func != nullptr) {
TransitionStage::Index transitionId = item.getTransitionId();
if (!TransitionStage::isIndexInvalid(transitionId)) {
_transitionFinishedOperatorMap[transitionId].emplace_back(func);
} else if (func) {
func();
}
}
}
}
void Scene::resetHighlights(const Transaction::HighlightResets& transactions) {
auto outlineStage = getStage<HighlightStage>(HighlightStage::getName());
if (outlineStage) {
@ -526,9 +554,18 @@ void Scene::setItemTransition(ItemID itemId, Index transitionId) {
void Scene::resetItemTransition(ItemID itemId) {
auto& item = _items[itemId];
if (!render::TransitionStage::isIndexInvalid(item.getTransitionId())) {
TransitionStage::Index transitionId = item.getTransitionId();
if (!render::TransitionStage::isIndexInvalid(transitionId)) {
auto transitionStage = getStage<TransitionStage>(TransitionStage::getName());
transitionStage->removeTransition(item.getTransitionId());
auto finishedOperators = _transitionFinishedOperatorMap[transitionId];
for (auto finishedOperator : finishedOperators) {
if (finishedOperator) {
finishedOperator();
}
}
_transitionFinishedOperatorMap.erase(transitionId);
transitionStage->removeTransition(transitionId);
setItemTransition(itemId, render::TransitionStage::INVALID_INDEX);
}
}
@ -587,4 +624,4 @@ void Scene::resetStage(const Stage::Name& name, const StagePointer& stage) {
} else {
(*found).second = stage;
}
}
}

View file

@ -32,12 +32,15 @@ class Scene;
// These changes must be expressed through the corresponding command from the Transaction
// THe Transaction is then queued on the Scene so all the pending transactions can be consolidated and processed at the time
// of updating the scene before it s rendered.
//
//
class Transaction {
friend class Scene;
public:
typedef std::function<void(ItemID, const Transition*)> TransitionQueryFunc;
typedef std::function<void()> TransitionFinishedFunc;
typedef std::function<void(HighlightStyle const*)> SelectionHighlightQueryFunc;
Transaction() {}
@ -52,6 +55,7 @@ public:
void removeTransitionFromItem(ItemID id);
void reApplyTransitionToItem(ItemID id);
void queryTransitionOnItem(ItemID id, TransitionQueryFunc func);
void transitionFinishedOperator(ItemID id, TransitionFinishedFunc func);
template <class T> void updateItem(ItemID id, std::function<void(T&)> func) {
updateItem(id, std::make_shared<UpdateFunctor<T>>(func));
@ -84,6 +88,7 @@ protected:
using Update = std::tuple<ItemID, UpdateFunctorPointer>;
using TransitionAdd = std::tuple<ItemID, Transition::Type, ItemID>;
using TransitionQuery = std::tuple<ItemID, TransitionQueryFunc>;
using TransitionFinishedOperator = std::tuple<ItemID, TransitionFinishedFunc>;
using TransitionReApply = ItemID;
using SelectionReset = Selection;
using HighlightReset = std::tuple<std::string, HighlightStyle>;
@ -95,6 +100,7 @@ protected:
using Updates = std::vector<Update>;
using TransitionAdds = std::vector<TransitionAdd>;
using TransitionQueries = std::vector<TransitionQuery>;
using TransitionFinishedOperators = std::vector<TransitionFinishedOperator>;
using TransitionReApplies = std::vector<TransitionReApply>;
using SelectionResets = std::vector<SelectionReset>;
using HighlightResets = std::vector<HighlightReset>;
@ -107,6 +113,7 @@ protected:
TransitionAdds _addedTransitions;
TransitionQueries _queriedTransitions;
TransitionReApplies _reAppliedTransitions;
TransitionFinishedOperators _transitionFinishedOperators;
SelectionResets _resetSelections;
HighlightResets _highlightResets;
HighlightRemoves _highlightRemoves;
@ -208,6 +215,7 @@ protected:
ItemIDSet _masterNonspatialSet;
void resetItems(const Transaction::Resets& transactions);
void resetTransitionFinishedOperator(const Transaction::TransitionFinishedOperators& transactions);
void removeItems(const Transaction::Removes& transactions);
void updateItems(const Transaction::Updates& transactions);
void transitionItems(const Transaction::TransitionAdds& transactions);
@ -223,6 +231,8 @@ protected:
mutable std::mutex _selectionsMutex; // mutable so it can be used in the thread safe getSelection const method
SelectionMap _selections;
std::unordered_map<int32_t, std::vector<Transaction::TransitionFinishedFunc>> _transitionFinishedOperatorMap;
void resetSelections(const Transaction::SelectionResets& transactions);
// More actions coming to selections soon:
// void removeFromSelection(const Selection& selection);

View file

@ -50,4 +50,4 @@ namespace render {
typedef std::vector<Transition::Type> TransitionTypes;
}
#endif // hifi_render_Transition_h
#endif // hifi_render_Transition_h

View file

@ -358,6 +358,12 @@ glm::vec3 Triangle::getNormal() const {
return glm::normalize(glm::cross(edge1, edge2));
}
float Triangle::getArea() const {
glm::vec3 edge1 = v1 - v0;
glm::vec3 edge2 = v2 - v0;
return 0.5f * glm::length(glm::cross(edge1, edge2));
}
Triangle Triangle::operator*(const glm::mat4& transform) const {
return {
glm::vec3(transform * glm::vec4(v0, 1.0f)),

View file

@ -125,6 +125,7 @@ public:
glm::vec3 v1;
glm::vec3 v2;
glm::vec3 getNormal() const;
float getArea() const;
Triangle operator*(const glm::mat4& transform) const;
};

View file

@ -0,0 +1,138 @@
"use strict";
(function(){
var AppUi = Script.require("appUi");
var ui;
var onCreateAvatarInputsBarEntity = false;
var micBarEntity = null;
var bubbleIconEntity = null;
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var AVATAR_INPUTS_EDIT_QML_SOURCE = "hifi/EditAvatarInputsBar.qml";
// DPI
var ENTITY_DPI = 60.0;
// QML NATURAL DIMENSIONS
var MIC_BAR_DIMENSIONS = Vec3.multiply(30.0 / ENTITY_DPI, {x: 0.036, y: 0.048, z: 0.3});
var BUBBLE_ICON_DIMENSIONS = Vec3.multiply(30.0 / ENTITY_DPI, {x: 0.036, y: 0.036, z: 0.3});
// ENTITY NAMES
var MIC_BAR_NAME = "AvatarInputsMicBarEntity";
var BUBBLE_ICON_NAME = "AvatarInputsBubbleIconEntity";
// CONSTANTS
var LOCAL_POSITION_X_OFFSET = -0.2;
var LOCAL_POSITION_Y_OFFSET = -0.125;
var LOCAL_POSITION_Z_OFFSET = -0.5;
function fromQml(message) {
if (message.method === "reposition") {
var micBarLocalPosition = Entities.getEntityProperties(micBarEntity).localPosition;
var bubbleIconLocalPosition = Entities.getEntityProperties(bubbleIconEntity).localPosition;
var newMicBarLocalPosition, newBubbleIconLocalPosition;
if (message.x !== undefined) {
newMicBarLocalPosition = { x: -((MIC_BAR_DIMENSIONS.x) / 2) + message.x, y: micBarLocalPosition.y, z: micBarLocalPosition.z };
newBubbleIconLocalPosition = { x: ((MIC_BAR_DIMENSIONS.x) * 1.2 / 2) + message.x, y: bubbleIconLocalPosition.y, z: bubbleIconLocalPosition.z };
} else if (message.y !== undefined) {
newMicBarLocalPosition = { x: micBarLocalPosition.x, y: message.y, z: micBarLocalPosition.z };
newBubbleIconLocalPosition = { x: bubbleIconLocalPosition.x, y: ((MIC_BAR_DIMENSIONS.y - BUBBLE_ICON_DIMENSIONS.y) / 2 + message.y), z: bubbleIconLocalPosition.z };
} else if (message.z !== undefined) {
newMicBarLocalPosition = { x: micBarLocalPosition.x, y: micBarLocalPosition.y, z: message.z };
newBubbleIconLocalPosition = { x: bubbleIconLocalPosition.x, y: bubbleIconLocalPosition.y, z: message.z };
}
var micBarProps = {
localPosition: newMicBarLocalPosition
};
var bubbleIconProps = {
localPosition: newBubbleIconLocalPosition
};
Entities.editEntity(micBarEntity, micBarProps);
Entities.editEntity(bubbleIconEntity, bubbleIconProps);
} else if (message.method === "setVisible") {
if (message.visible !== undefined) {
var props = {
visible: message.visible
};
Entities.editEntity(micBarEntity, props);
Entities.editEntity(bubbleIconEntity, props);
}
} else if (message.method === "print") {
// prints the local position into the hifi log.
var micBarLocalPosition = Entities.getEntityProperties(micBarEntity).localPosition;
var bubbleIconLocalPosition = Entities.getEntityProperties(bubbleIconEntity).localPosition;
console.log("mic bar local position is at " + JSON.stringify(micBarLocalPosition));
console.log("bubble icon local position is at " + JSON.stringify(bubbleIconLocalPosition));
}
};
function createEntities() {
if (micBarEntity != null && bubbleIconEntity != null) {
return;
}
// POSITIONS
var micBarLocalPosition = {x: (-(MIC_BAR_DIMENSIONS.x / 2)) + LOCAL_POSITION_X_OFFSET, y: LOCAL_POSITION_Y_OFFSET, z: LOCAL_POSITION_Z_OFFSET};
var bubbleIconLocalPosition = {x: (MIC_BAR_DIMENSIONS.x * 1.2 / 2) + LOCAL_POSITION_X_OFFSET, y: ((MIC_BAR_DIMENSIONS.y - BUBBLE_ICON_DIMENSIONS.y) / 2 + LOCAL_POSITION_Y_OFFSET), z: LOCAL_POSITION_Z_OFFSET};
var props = {
type: "Web",
name: MIC_BAR_NAME,
parentID: MyAvatar.SELF_ID,
parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX"),
localPosition: micBarLocalPosition,
localRotation: Quat.cancelOutRollAndPitch(Quat.lookAtSimple(Camera.orientation, micBarLocalPosition)),
sourceUrl: Script.resourcesPath() + "qml/hifi/audio/MicBarApplication.qml",
// cutoff alpha for detecting transparency
alpha: 0.98,
dimensions: MIC_BAR_DIMENSIONS,
dpi: ENTITY_DPI,
drawInFront: true,
userData: {
grabbable: false
},
};
micBarEntity = Entities.addEntity(props, "local");
var props = {
type: "Web",
name: BUBBLE_ICON_NAME,
parentID: MyAvatar.SELF_ID,
parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX"),
localPosition: bubbleIconLocalPosition,
localRotation: Quat.cancelOutRollAndPitch(Quat.lookAtSimple(Camera.orientation, bubbleIconLocalPosition)),
sourceUrl: Script.resourcesPath() + "qml/BubbleIcon.qml",
// cutoff alpha for detecting transparency
alpha: 0.98,
dimensions: BUBBLE_ICON_DIMENSIONS,
dpi: ENTITY_DPI,
drawInFront: true,
userData: {
grabbable: false
},
};
bubbleIconEntity = Entities.addEntity(props, "local");
tablet.loadQMLSource(AVATAR_INPUTS_EDIT_QML_SOURCE);
};
function cleanup() {
if (micBarEntity) {
Entities.deleteEntity(micBarEntity);
}
if (bubbleIconEntity) {
Entities.deleteEntity(bubbleIconEntity);
}
};
function setup() {
ui = new AppUi({
buttonName: "AVBAR",
home: Script.resourcesPath() + "qml/hifi/EditAvatarInputsBar.qml",
onMessage: fromQml,
onOpened: createEntities,
// onClosed: cleanup,
normalButton: "icons/tablet-icons/edit-i.svg",
activeButton: "icons/tablet-icons/edit-a.svg",
});
};
setup();
Script.scriptEnding.connect(cleanup);
}());

View file

@ -227,6 +227,14 @@
"speedSpread": {
"tooltip": "The spread in speeds at which particles are emitted at, resulting in a variety of speeds."
},
"particleShapeType": {
"tooltip": "The shape of the surface from which to emit particles.",
"jsPropertyName": "shapeType"
},
"particleCompoundShapeURL": {
"tooltip": "The model file to use for the particle emitter if Shape Type is \"Use Compound Shape URL\".",
"jsPropertyName": "compoundShapeURL"
},
"emitDimensions": {
"tooltip": "The outer limit radius in dimensions that the particles can be emitted from."
},

View file

@ -75,6 +75,7 @@ button.clicked.connect(onClicked);
tablet.screenChanged.connect(onScreenChanged);
Audio.mutedChanged.connect(onMuteToggled);
Audio.pushToTalkChanged.connect(onMuteToggled);
HMD.displayModeChanged.connect(onMuteToggled);
Script.scriptEnding.connect(function () {
if (onAudioScreen) {
@ -84,6 +85,7 @@ Script.scriptEnding.connect(function () {
tablet.screenChanged.disconnect(onScreenChanged);
Audio.mutedChanged.disconnect(onMuteToggled);
Audio.pushToTalkChanged.disconnect(onMuteToggled);
HMD.displayModeChanged.disconnect(onMuteToggled);
tablet.removeButton(button);
});

View file

@ -43,7 +43,7 @@
if (HMD.active) {
warningOverlayID = Overlays.addOverlay("text3d", {
name: "Muted-Warning",
localPosition: { x: 0.0, y: -0.5, z: -1.0 },
localPosition: { x: 0.0, y: -0.45, z: -1.0 },
localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }),
text: warningText,
textAlpha: 1,
@ -58,20 +58,6 @@
parentID: MyAvatar.SELF_ID,
parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX")
});
} else {
var textDimensions = { x: 100, y: 50 };
warningOverlayID = Overlays.addOverlay("text", {
name: "Muted-Warning",
font: { size: 36 },
text: warningText,
x: (Window.innerWidth - textDimensions.x) / 2,
y: (Window.innerHeight - textDimensions.y),
width: textDimensions.x,
height: textDimensions.y,
textColor: { red: 226, green: 51, blue: 77 },
backgroundAlpha: 0,
visible: true
});
}
}
@ -141,4 +127,4 @@
Audio.mutedChanged.connect(startOrStopPoll);
Audio.warnWhenMutedChanged.connect(startOrStopPoll);
}()); // END LOCAL_SCOPE
}()); // END LOCAL_SCOPE

View file

@ -171,7 +171,7 @@ function goAway(fromStartup) {
if (!previousBubbleState) {
Users.toggleIgnoreRadius();
}
UserActivityLogger.bubbleToggled(Users.getIgnoreRadiusEnabled());
UserActivityLogger.privacyShieldToggled(Users.getIgnoreRadiusEnabled());
UserActivityLogger.toggledAway(true);
MyAvatar.isAway = true;
}
@ -186,7 +186,7 @@ function goActive() {
if (Users.getIgnoreRadiusEnabled() !== previousBubbleState) {
Users.toggleIgnoreRadius();
UserActivityLogger.bubbleToggled(Users.getIgnoreRadiusEnabled());
UserActivityLogger.privacyShieldToggled(Users.getIgnoreRadiusEnabled());
}
if (!Window.hasFocus()) {

View file

@ -90,7 +90,7 @@
// Called from the C++ scripting interface to show the bubble overlay
function enteredIgnoreRadius() {
createOverlays();
UserActivityLogger.bubbleActivated();
UserActivityLogger.privacyShieldActivated();
}
// Used to set the state of the bubble HUD button
@ -160,7 +160,7 @@
function onBubbleToggled(enabled, doNotLog) {
writeButtonProperties(enabled);
if (doNotLog !== true) {
UserActivityLogger.bubbleToggled(enabled);
UserActivityLogger.privacyShieldToggled(enabled);
}
if (enabled) {
createOverlays();
@ -174,7 +174,7 @@
}
// Setup the bubble button
var buttonName = "BUBBLE";
var buttonName = "SHIELD";
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
button = tablet.addButton({
icon: "icons/tablet-icons/bubble-i.svg",

View file

@ -282,6 +282,28 @@ function checkEditPermissionsAndUpdate() {
}
}
// Copies the properties in `b` into `a`. `a` will be modified.
function copyProperties(a, b) {
for (var key in b) {
a[key] = b[key];
}
return a;
}
const DEFAULT_DYNAMIC_PROPERTIES = {
dynamic: true,
damping: 0.39347,
angularDamping: 0.39347,
gravity: { x: 0, y: -9.8, z: 0 },
};
const DEFAULT_NON_DYNAMIC_PROPERTIES = {
dynamic: false,
damping: 0,
angularDamping: 0,
gravity: { x: 0, y: 0, z: 0 },
};
const DEFAULT_ENTITY_PROPERTIES = {
All: {
description: "",
@ -299,26 +321,14 @@ const DEFAULT_ENTITY_PROPERTIES = {
y: 0,
z: 0
},
damping: 0,
angularVelocity: {
x: 0,
y: 0,
z: 0
},
angularDamping: 0,
restitution: 0.5,
friction: 0.5,
density: 1000,
gravity: {
x: 0,
y: 0,
z: 0
},
acceleration: {
x: 0,
y: 0,
z: 0
},
dynamic: false,
},
Shape: {
@ -484,11 +494,6 @@ var toolBar = (function () {
dialogWindow = null,
tablet = null;
function applyProperties(originalProperties, newProperties) {
for (var key in newProperties) {
originalProperties[key] = newProperties[key];
}
}
function createNewEntity(requestedProperties) {
var dimensions = requestedProperties.dimensions ? requestedProperties.dimensions : DEFAULT_DIMENSIONS;
var position = getPositionToCreateEntity();
@ -496,17 +501,23 @@ var toolBar = (function () {
var properties = {};
applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.All);
copyProperties(properties, DEFAULT_ENTITY_PROPERTIES.All);
var type = requestedProperties.type;
if (type === "Box" || type === "Sphere") {
applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Shape);
copyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Shape);
} else {
applyProperties(properties, DEFAULT_ENTITY_PROPERTIES[type]);
copyProperties(properties, DEFAULT_ENTITY_PROPERTIES[type]);
}
// We apply the requested properties first so that they take priority over any default properties.
applyProperties(properties, requestedProperties);
copyProperties(properties, requestedProperties);
if (properties.dynamic) {
copyProperties(properties, DEFAULT_DYNAMIC_PROPERTIES);
} else {
copyProperties(properties, DEFAULT_NON_DYNAMIC_PROPERTIES);
}
if (position !== null && position !== undefined) {
@ -675,7 +686,6 @@ var toolBar = (function () {
grabbable: result.grabbable
},
dynamic: dynamic,
gravity: dynamic ? { x: 0, y: -10, z: 0 } : { x: 0, y: 0, z: 0 }
});
}
}

View file

@ -807,6 +807,21 @@ const GROUPS = [
decimals: 2,
propertyID: "speedSpread",
},
{
label: "Shape Type",
type: "dropdown",
options: { "box": "Box", "ellipsoid": "Ellipsoid",
"cylinder-y": "Cylinder", "circle": "Circle", "plane": "Plane",
"compound": "Use Compound Shape URL" },
propertyID: "particleShapeType",
propertyName: "shapeType",
},
{
label: "Compound Shape URL",
type: "string",
propertyID: "particleCompoundShapeURL",
propertyName: "compoundShapeURL",
},
{
label: "Emit Dimensions",
type: "vec3",

View file

@ -17,9 +17,10 @@ using System.Text.RegularExpressions;
class AvatarExporter : MonoBehaviour {
// update version number for every PR that changes this file, also set updated version in README file
static readonly string AVATAR_EXPORTER_VERSION = "0.4.0";
static readonly string AVATAR_EXPORTER_VERSION = "0.4.1";
static readonly float HIPS_GROUND_MIN_Y = 0.01f;
static readonly float HIPS_MIN_Y_PERCENT_OF_HEIGHT = 0.03f;
static readonly float BELOW_GROUND_THRESHOLD_PERCENT_OF_HEIGHT = -0.15f;
static readonly float HIPS_SPINE_CHEST_MIN_SEPARATION = 0.001f;
static readonly int MAXIMUM_USER_BONE_COUNT = 256;
static readonly string EMPTY_WARNING_TEXT = "None";
@ -231,7 +232,8 @@ class AvatarExporter : MonoBehaviour {
HeadMapped,
HeadDescendantOfChest,
EyesMapped,
HipsNotOnGround,
HipsNotAtBottom,
ExtentsNotBelowGround,
HipsSpineChestNotCoincident,
TotalBoneCountUnderLimit,
AvatarRuleEnd,
@ -247,18 +249,26 @@ class AvatarExporter : MonoBehaviour {
class UserBoneInformation {
public string humanName; // bone name in Humanoid if it is mapped, otherwise ""
public string parentName; // parent user bone name
public BoneTreeNode boneTreeNode; // node within the user bone tree
public int mappingCount; // number of times this bone is mapped in Humanoid
public Vector3 position; // absolute position
public Quaternion rotation; // absolute rotation
public BoneTreeNode boneTreeNode;
public UserBoneInformation() {
humanName = "";
parentName = "";
boneTreeNode = new BoneTreeNode();
mappingCount = 0;
position = new Vector3();
rotation = new Quaternion();
boneTreeNode = new BoneTreeNode();
}
public UserBoneInformation(string parent, BoneTreeNode treeNode, Vector3 pos) {
humanName = "";
parentName = parent;
boneTreeNode = treeNode;
mappingCount = 0;
position = pos;
rotation = new Quaternion();
}
public bool HasHumanMapping() { return !string.IsNullOrEmpty(humanName); }
@ -266,11 +276,13 @@ class AvatarExporter : MonoBehaviour {
class BoneTreeNode {
public string boneName;
public string parentName;
public List<BoneTreeNode> children = new List<BoneTreeNode>();
public BoneTreeNode() {}
public BoneTreeNode(string name) {
public BoneTreeNode(string name, string parent) {
boneName = name;
parentName = parent;
}
}
@ -732,9 +744,11 @@ class AvatarExporter : MonoBehaviour {
// instantiate a game object of the user avatar to traverse the bone tree to gather
// bone parents and positions as well as build a bone tree, then destroy it
GameObject assetGameObject = (GameObject)Instantiate(avatarResource);
TraverseUserBoneTree(assetGameObject.transform);
DestroyImmediate(assetGameObject);
GameObject avatarGameObject = (GameObject)Instantiate(avatarResource, Vector3.zero, Quaternion.identity);
TraverseUserBoneTree(avatarGameObject.transform, userBoneTree);
Bounds bounds = AvatarUtilities.GetAvatarBounds(avatarGameObject);
float height = AvatarUtilities.GetAvatarHeight(avatarGameObject);
DestroyImmediate(avatarGameObject);
// iterate over Humanoid bones and update user bone info to increase human mapping counts for each bone
// as well as set their Humanoid name and build a Humanoid to user bone mapping
@ -753,10 +767,10 @@ class AvatarExporter : MonoBehaviour {
}
// generate the list of avatar rule failure strings for any avatar rules that are not satisfied by this avatar
SetFailedAvatarRules();
SetFailedAvatarRules(bounds, height);
}
static void TraverseUserBoneTree(Transform modelBone) {
static void TraverseUserBoneTree(Transform modelBone, BoneTreeNode boneTreeNode) {
GameObject gameObject = modelBone.gameObject;
// check if this transform is a node containing mesh, light, or camera instead of a bone
@ -770,33 +784,52 @@ class AvatarExporter : MonoBehaviour {
if (mesh) {
Material[] materials = skinnedMeshRenderer != null ? skinnedMeshRenderer.sharedMaterials : meshRenderer.sharedMaterials;
StoreMaterialData(materials);
// ensure branches within the transform hierarchy that contain meshes are removed from the user bone tree
Transform ancestorBone = modelBone;
string previousBoneName = "";
// find the name of the root child bone that this mesh is underneath
while (ancestorBone != null) {
if (ancestorBone.parent == null) {
break;
}
previousBoneName = ancestorBone.name;
ancestorBone = ancestorBone.parent;
}
// remove the bone tree node from root's children for the root child bone that has mesh children
if (!string.IsNullOrEmpty(previousBoneName)) {
foreach (BoneTreeNode rootChild in userBoneTree.children) {
if (rootChild.boneName == previousBoneName) {
userBoneTree.children.Remove(rootChild);
break;
}
}
}
} else if (!light && !camera) {
// if it is in fact a bone, add it to the bone tree as well as user bone infos list with position and parent name
UserBoneInformation userBoneInfo = new UserBoneInformation();
userBoneInfo.position = modelBone.position; // bone's absolute position
string boneName = modelBone.name;
if (modelBone.parent == null) {
// if no parent then this is actual root bone node of the user avatar, so consider it's parent as "root"
boneName = GetRootBoneName(); // ensure we use the root bone name from the skeleton list for consistency
userBoneTree = new BoneTreeNode(boneName); // initialize root of tree
userBoneInfo.parentName = "root";
userBoneInfo.boneTreeNode = userBoneTree;
boneTreeNode.boneName = boneName;
boneTreeNode.parentName = "root";
} else {
// otherwise add this bone node as a child to it's parent's children list
// if its a child of the root bone, use the root bone name from the skeleton list as the parent for consistency
string parentName = modelBone.parent.parent == null ? GetRootBoneName() : modelBone.parent.name;
BoneTreeNode boneTreeNode = new BoneTreeNode(boneName);
userBoneInfos[parentName].boneTreeNode.children.Add(boneTreeNode);
userBoneInfo.parentName = parentName;
BoneTreeNode node = new BoneTreeNode(boneName, parentName);
boneTreeNode.children.Add(node);
boneTreeNode = node;
}
Vector3 bonePosition = modelBone.position; // bone's absolute position in avatar space
UserBoneInformation userBoneInfo = new UserBoneInformation(boneTreeNode.parentName, boneTreeNode, bonePosition);
userBoneInfos.Add(boneName, userBoneInfo);
}
// recurse over transform node's children
for (int i = 0; i < modelBone.childCount; ++i) {
TraverseUserBoneTree(modelBone.GetChild(i));
TraverseUserBoneTree(modelBone.GetChild(i), boneTreeNode);
}
}
@ -840,7 +873,7 @@ class AvatarExporter : MonoBehaviour {
return "";
}
static void SetFailedAvatarRules() {
static void SetFailedAvatarRules(Bounds avatarBounds, float avatarHeight) {
failedAvatarRules.Clear();
string hipsUserBone = "";
@ -905,18 +938,29 @@ class AvatarExporter : MonoBehaviour {
break;
case AvatarRule.ChestMapped:
if (!humanoidToUserBoneMappings.TryGetValue("Chest", out chestUserBone)) {
// check to see if there is a child of Spine that we can suggest to be mapped to Chest
string spineChild = "";
// check to see if there is an unmapped child of Spine that we can suggest to be mapped to Chest
string chestMappingCandidate = "";
if (!string.IsNullOrEmpty(spineUserBone)) {
BoneTreeNode spineTreeNode = userBoneInfos[spineUserBone].boneTreeNode;
if (spineTreeNode.children.Count == 1) {
spineChild = spineTreeNode.children[0].boneName;
foreach (BoneTreeNode spineChildTreeNode in spineTreeNode.children) {
string spineChildBone = spineChildTreeNode.boneName;
if (userBoneInfos[spineChildBone].HasHumanMapping()) {
continue;
}
// a suitable candidate for Chest should have Neck/Head or Shoulder mappings in its descendants
if (IsHumanBoneInHierarchy(spineChildTreeNode, "Neck") ||
IsHumanBoneInHierarchy(spineChildTreeNode, "Head") ||
IsHumanBoneInHierarchy(spineChildTreeNode, "LeftShoulder") ||
IsHumanBoneInHierarchy(spineChildTreeNode, "RightShoulder")) {
chestMappingCandidate = spineChildBone;
break;
}
}
}
failedAvatarRules.Add(avatarRule, "There is no Chest bone mapped in Humanoid for the selected avatar.");
// if the only found child of Spine is not yet mapped then add it as a suggestion for Chest mapping
if (!string.IsNullOrEmpty(spineChild) && !userBoneInfos[spineChild].HasHumanMapping()) {
failedAvatarRules[avatarRule] += " It is suggested that you map bone " + spineChild +
if (!string.IsNullOrEmpty(chestMappingCandidate)) {
failedAvatarRules[avatarRule] += " It is suggested that you map bone " + chestMappingCandidate +
" to Chest in Humanoid.";
}
}
@ -949,15 +993,34 @@ class AvatarExporter : MonoBehaviour {
}
}
break;
case AvatarRule.HipsNotOnGround:
// ensure the absolute Y position for the bone mapped to Hips (if its mapped) is at least HIPS_GROUND_MIN_Y
case AvatarRule.HipsNotAtBottom:
// ensure that Hips is not below a proportional percentage of the avatar's height in avatar space
if (!string.IsNullOrEmpty(hipsUserBone)) {
UserBoneInformation hipsBoneInfo = userBoneInfos[hipsUserBone];
hipsPosition = hipsBoneInfo.position;
if (hipsPosition.y < HIPS_GROUND_MIN_Y) {
failedAvatarRules.Add(avatarRule, "The bone mapped to Hips in Humanoid (" + hipsUserBone +
") should not be at ground level.");
// find the lowest y position of the bones
float minBoneYPosition = float.MaxValue;
foreach (var userBoneInfo in userBoneInfos) {
Vector3 position = userBoneInfo.Value.position;
if (position.y < minBoneYPosition) {
minBoneYPosition = position.y;
}
}
// check that Hips is within a percentage of avatar's height from the lowest Y point of the avatar
float bottomYRange = HIPS_MIN_Y_PERCENT_OF_HEIGHT * avatarHeight;
if (Mathf.Abs(hipsPosition.y - minBoneYPosition) < bottomYRange) {
failedAvatarRules.Add(avatarRule, "The bone mapped to Hips in Humanoid (" + hipsUserBone +
") should not be at the bottom of the selected avatar.");
}
}
break;
case AvatarRule.ExtentsNotBelowGround:
// ensure the minimum Y extent of the model's bounds is not below a proportional threshold of avatar's height
float belowGroundThreshold = BELOW_GROUND_THRESHOLD_PERCENT_OF_HEIGHT * avatarHeight;
if (avatarBounds.min.y < belowGroundThreshold) {
failedAvatarRules.Add(avatarRule, "The bottom extents of the selected avatar go below ground level.");
}
break;
case AvatarRule.HipsSpineChestNotCoincident:
@ -989,6 +1052,23 @@ class AvatarExporter : MonoBehaviour {
}
}
static bool IsHumanBoneInHierarchy(BoneTreeNode boneTreeNode, string humanBoneName) {
UserBoneInformation userBoneInfo;
if (userBoneInfos.TryGetValue(boneTreeNode.boneName, out userBoneInfo) && userBoneInfo.humanName == humanBoneName) {
// this bone matches the human bone name being searched for
return true;
}
// recursively check downward through children bones for target human bone
foreach (BoneTreeNode childNode in boneTreeNode.children) {
if (IsHumanBoneInHierarchy(childNode, humanBoneName)) {
return true;
}
}
return false;
}
static string CheckHumanBoneMappingRule(AvatarRule avatarRule, string humanBoneName) {
string userBoneName = "";
// avatar rule fails if bone is not mapped in Humanoid
@ -999,8 +1079,8 @@ class AvatarExporter : MonoBehaviour {
return userBoneName;
}
static void CheckUserBoneDescendantOfHumanRule(AvatarRule avatarRule, string userBoneName, string descendantOfHumanName) {
if (string.IsNullOrEmpty(userBoneName)) {
static void CheckUserBoneDescendantOfHumanRule(AvatarRule avatarRule, string descendantUserBoneName, string descendantOfHumanName) {
if (string.IsNullOrEmpty(descendantUserBoneName)) {
return;
}
@ -1009,27 +1089,26 @@ class AvatarExporter : MonoBehaviour {
return;
}
string userBone = userBoneName;
string ancestorUserBone = "";
UserBoneInformation userBoneInfo = new UserBoneInformation();
string userBoneName = descendantUserBoneName;
UserBoneInformation userBoneInfo = userBoneInfos[userBoneName];
string descendantHumanName = userBoneInfo.humanName;
// iterate upward from user bone through user bone info parent names until root
// is reached or the ancestor bone name matches the target descendant of name
while (ancestorUserBone != "root") {
if (userBoneInfos.TryGetValue(userBone, out userBoneInfo)) {
ancestorUserBone = userBoneInfo.parentName;
if (ancestorUserBone == descendantOfUserBoneName) {
return;
}
userBone = ancestorUserBone;
while (userBoneName != "root") {
if (userBoneName == descendantOfUserBoneName) {
return;
}
if (userBoneInfos.TryGetValue(userBoneName, out userBoneInfo)) {
userBoneName = userBoneInfo.parentName;
} else {
break;
}
}
// avatar rule fails if no ancestor of given user bone matched the descendant of name (no early return)
failedAvatarRules.Add(avatarRule, "The bone mapped to " + userBoneInfo.humanName + " in Humanoid (" + userBoneName +
") is not a child of the bone mapped to " + descendantOfHumanName + " in Humanoid (" +
descendantOfUserBoneName + ").");
failedAvatarRules.Add(avatarRule, "The bone mapped to " + descendantHumanName + " in Humanoid (" +
descendantUserBoneName + ") is not a descendant of the bone mapped to " +
descendantOfHumanName + " in Humanoid (" + descendantOfUserBoneName + ").");
}
static void CheckAsymmetricalMappingRule(AvatarRule avatarRule, string[] mappingSuffixes, string appendage) {
@ -1296,9 +1375,8 @@ class ExportProjectWindow : EditorWindow {
const float MAX_SCALE_SLIDER = 2.0f;
const int SLIDER_SCALE_EXPONENT = 10;
const float ACTUAL_SCALE_OFFSET = 1.0f;
const float DEFAULT_AVATAR_HEIGHT = 1.755f;
const float MAXIMUM_RECOMMENDED_HEIGHT = DEFAULT_AVATAR_HEIGHT * 1.5f;
const float MINIMUM_RECOMMENDED_HEIGHT = DEFAULT_AVATAR_HEIGHT * 0.25f;
const float MAXIMUM_RECOMMENDED_HEIGHT = AvatarUtilities.DEFAULT_AVATAR_HEIGHT * 1.5f;
const float MINIMUM_RECOMMENDED_HEIGHT = AvatarUtilities.DEFAULT_AVATAR_HEIGHT * 0.25f;
const float SLIDER_DIFFERENCE_REMOVE_TEXT = 0.01f;
readonly Color COLOR_YELLOW = Color.yellow; //new Color(0.9176f, 0.8274f, 0.0f);
readonly Color COLOR_BACKGROUND = new Color(0.5f, 0.5f, 0.5f);
@ -1339,9 +1417,9 @@ class ExportProjectWindow : EditorWindow {
ShowUtility();
// if the avatar's starting height is outside of the recommended ranges, auto-adjust the scale to default height
float height = GetAvatarHeight();
float height = AvatarUtilities.GetAvatarHeight(avatarPreviewObject);
if (height < MINIMUM_RECOMMENDED_HEIGHT || height > MAXIMUM_RECOMMENDED_HEIGHT) {
float newScale = DEFAULT_AVATAR_HEIGHT / height;
float newScale = AvatarUtilities.DEFAULT_AVATAR_HEIGHT / height;
SetAvatarScale(newScale);
scaleWarningText = "Avatar's scale automatically adjusted to be within the recommended range.";
}
@ -1524,7 +1602,7 @@ class ExportProjectWindow : EditorWindow {
void UpdateScaleWarning() {
// called on any scale changes
float height = GetAvatarHeight();
float height = AvatarUtilities.GetAvatarHeight(avatarPreviewObject);
if (height < MINIMUM_RECOMMENDED_HEIGHT) {
scaleWarningText = "The height of the avatar is below the recommended minimum.";
} else if (height > MAXIMUM_RECOMMENDED_HEIGHT) {
@ -1535,23 +1613,6 @@ class ExportProjectWindow : EditorWindow {
}
}
float GetAvatarHeight() {
// height of an avatar model can be determined to be the max Y extents of the combined bounds for all its mesh renderers
if (avatarPreviewObject != null) {
Bounds bounds = new Bounds();
var meshRenderers = avatarPreviewObject.GetComponentsInChildren<MeshRenderer>();
var skinnedMeshRenderers = avatarPreviewObject.GetComponentsInChildren<SkinnedMeshRenderer>();
foreach (var renderer in meshRenderers) {
bounds.Encapsulate(renderer.bounds);
}
foreach (var renderer in skinnedMeshRenderers) {
bounds.Encapsulate(renderer.bounds);
}
return bounds.max.y;
}
return 0.0f;
}
void SetAvatarScale(float actualScale) {
// set the new scale uniformly on the preview avatar's transform to show the resulting avatar size
avatarPreviewObject.transform.localScale = new Vector3(actualScale, actualScale, actualScale);
@ -1571,3 +1632,28 @@ class ExportProjectWindow : EditorWindow {
onCloseCallback();
}
}
class AvatarUtilities {
public const float DEFAULT_AVATAR_HEIGHT = 1.755f;
public static Bounds GetAvatarBounds(GameObject avatarObject) {
Bounds bounds = new Bounds();
if (avatarObject != null) {
var meshRenderers = avatarObject.GetComponentsInChildren<MeshRenderer>();
var skinnedMeshRenderers = avatarObject.GetComponentsInChildren<SkinnedMeshRenderer>();
foreach (var renderer in meshRenderers) {
bounds.Encapsulate(renderer.bounds);
}
foreach (var renderer in skinnedMeshRenderers) {
bounds.Encapsulate(renderer.bounds);
}
}
return bounds;
}
public static float GetAvatarHeight(GameObject avatarObject) {
// height of an avatar model can be determined to be the max Y extents of the combined bounds for all its mesh renderers
Bounds avatarBounds = GetAvatarBounds(avatarObject);
return avatarBounds.max.y - avatarBounds.min.y;
}
}

View file

@ -1,6 +1,6 @@
High Fidelity, Inc.
Avatar Exporter
Version 0.4.0
Version 0.4.1
Note: It is recommended to use Unity versions between 2017.4.15f1 and 2018.2.12f1 for this Avatar Exporter.