diff --git a/.gitignore b/.gitignore index c268e8a4bd..dfffe2ac51 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,7 @@ android/app/src/main/res/values/libs.xml android/app/src/main/assets/bundled # VSCode -# List taken from Github Global Ignores master@435c4d92 +# List taken from Github Global Ignores master@435c4d92 # https://github.com/github/gitignore/commits/master/Global/VisualStudioCode.gitignore .vscode/* !.vscode/settings.json @@ -69,7 +69,7 @@ gvr-interface/libs/* # ignore files for various dev environments TAGS *.sw[po] -*.qmlc +*.jsc # ignore QML compilation output *.qmlc diff --git a/BUILD_LINUX_CHEATSHEET.md b/BUILD_LINUX_CHEATSHEET.md new file mode 100644 index 0000000000..7d77f5d685 --- /dev/null +++ b/BUILD_LINUX_CHEATSHEET.md @@ -0,0 +1,27 @@ + # this guide is specific to Ubuntu 16.04. + # deb packages of High Fidelity domain server and assignment client are stored on debian.highfidelity.com +sudo su - +apt-get -y update +apt-get install -y software-properties-common +apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 15FF1AAE +add-apt-repository "deb http://debian.highfidelity.com stable main" +apt-get -y update +apt-get install -y hifi-domain-server +apt-get install -y hifi-assignment-client + + # When installing master/dev builds, the packages are slightly different and you just need to change the last 2 steps to: +apt-get install -y hifi-dev-domain-server +apt-get install -y hifi-dev-assignment-client + + # domain server and assignment clients should already be running. The processes are controlled via: +systemctl start hifi-domain-server +systemctl stop hifi-domain-server + + # Once the machine is setup and processes are running you should ensure that your firewall exposes port 40100 on TCP and all UDP ports. This will get your domain up and running and you could connect to it (for now) by using High Fidelity Interface and typing in the IP for the place name. (further customizations can be done via http://IPAddress:40100). + + # The server always depends on both hifi-domain-server and hifi-assignment-client running at the same time. + # As an additional step, you should ensure that your packages are automatically updated when a new version goes out. You could, for example, set the automatic update checks to happen every hour (though this could potentially result in the domain being unreachable for a whole hour by new clients when they are released - adjust the update checks accordingly). +To do this you can modify /etc/crontab by adding the following lines +0 */1 * * * root apt-get update +1 */1 * * * root apt-get install --only-upgrade -y hifi-domain-server +2 */1 * * * root apt-get install --only-upgrade -y hifi-assignment-client diff --git a/android/build.gradle b/android/build.gradle index 903c3831bc..c7eefd051b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -454,6 +454,8 @@ task cleanDependencies(type: Delete) { delete 'app/src/main/res/values/libs.xml' } +// FIXME this code is prototyping the desired functionality for doing build time binary dependency resolution. +// See the comment on the qtBundle task above /* // FIXME derive the path from the gradle environment def toolchain = [ diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index f166a780ff..2fc905c6fd 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -94,7 +94,6 @@ Agent::Agent(ReceivedMessage& message) : packetReceiver.registerListenerForTypes( { PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase }, this, "handleOctreePacket"); - packetReceiver.registerListener(PacketType::Jurisdiction, this, "handleJurisdictionPacket"); packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat"); @@ -149,17 +148,6 @@ void Agent::handleOctreePacket(QSharedPointer message, SharedNo } } -void Agent::handleJurisdictionPacket(QSharedPointer message, SharedNodePointer senderNode) { - NodeType_t nodeType; - message->peekPrimitive(&nodeType); - - // PacketType_JURISDICTION, first byte is the node type... - if (nodeType == NodeType::EntityServer) { - DependencyManager::get()->getJurisdictionListener()-> - queueReceivedPacket(message, senderNode); - } -} - void Agent::handleAudioPacket(QSharedPointer message) { _receivedAudioStream.parseData(*message); _lastReceivedAudioLoudness = _receivedAudioStream.getNextOutputFrameLoudness(); @@ -483,10 +471,7 @@ void Agent::executeScript() { auto recordingInterface = DependencyManager::get(); _scriptEngine->registerGlobalObject("Recording", recordingInterface.data()); - // we need to make sure that init has been called for our EntityScriptingInterface - // so that it actually has a jurisdiction listener when we ask it for it next entityScriptingInterface->init(); - _entityViewer.setJurisdictionListener(entityScriptingInterface->getJurisdictionListener()); _entityViewer.init(); diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 168da185b6..1229f06276 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -73,7 +73,6 @@ private slots: void handleAudioPacket(QSharedPointer message); void handleOctreePacket(QSharedPointer message, SharedNodePointer senderNode); - void handleJurisdictionPacket(QSharedPointer message, SharedNodePointer senderNode); void handleSelectedAudioFormat(QSharedPointer message); void nodeActivated(SharedNodePointer activatedNode); diff --git a/assignment-client/src/AssignmentClientApp.cpp b/assignment-client/src/AssignmentClientApp.cpp index dd3050ba4e..b37784cddc 100644 --- a/assignment-client/src/AssignmentClientApp.cpp +++ b/assignment-client/src/AssignmentClientApp.cpp @@ -46,8 +46,14 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : const QCommandLineOption helpOption = parser.addHelpOption(); - const QCommandLineOption clientTypeOption(ASSIGNMENT_TYPE_OVERRIDE_OPTION, - "run single assignment client of given type", "type"); + QString typeDescription = "run single assignment client of given type\n# | Type\n============================"; + for (Assignment::Type type = Assignment::FirstType; + type != Assignment::AllTypes; + type = static_cast(static_cast(type) + 1)) { + typeDescription.append(QStringLiteral("\n%1 | %2").arg(QString::number(type), Assignment::typeToString(type))); + } + const QCommandLineOption clientTypeOption(ASSIGNMENT_TYPE_OVERRIDE_OPTION, typeDescription, "type"); + parser.addOption(clientTypeOption); const QCommandLineOption poolOption(ASSIGNMENT_POOL_OPTION, "set assignment pool", "pool-name"); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 47a81ba1fe..fb4b65726a 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -214,7 +214,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) uint64_t getTimestamp() const override { return _lastEncodeTime; } - const AvatarSharedPointer& getAvatar() const { return _avatar; } + AvatarSharedPointer getAvatar() const { return _avatar; } private: AvatarSharedPointer _avatar; @@ -326,7 +326,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int remainingAvatars = (int)sortedAvatars.size(); while (!sortedAvatars.empty()) { - const auto& avatarData = sortedAvatars.top().getAvatar(); + const auto avatarData = sortedAvatars.top().getAvatar(); sortedAvatars.pop(); remainingAvatars--; diff --git a/assignment-client/src/octree/OctreeHeadlessViewer.cpp b/assignment-client/src/octree/OctreeHeadlessViewer.cpp index 4e885da7a5..d3b20fb623 100644 --- a/assignment-client/src/octree/OctreeHeadlessViewer.cpp +++ b/assignment-client/src/octree/OctreeHeadlessViewer.cpp @@ -23,23 +23,6 @@ void OctreeHeadlessViewer::queryOctree() { char serverType = getMyNodeType(); PacketType packetType = getMyQueryMessageType(); - NodeToJurisdictionMap& jurisdictions = *_jurisdictionListener->getJurisdictions(); - - bool wantExtraDebugging = false; - - if (wantExtraDebugging) { - qCDebug(octree) << "OctreeHeadlessViewer::queryOctree() _jurisdictionListener=" << _jurisdictionListener; - qCDebug(octree) << "---------------"; - qCDebug(octree) << "_jurisdictionListener=" << _jurisdictionListener; - qCDebug(octree) << "Jurisdictions..."; - jurisdictions.withReadLock([&] { - for (NodeToJurisdictionMapIterator i = jurisdictions.begin(); i != jurisdictions.end(); ++i) { - qCDebug(octree) << i.key() << ": " << &i.value(); - } - }); - qCDebug(octree) << "---------------"; - } - _octreeQuery.setCameraPosition(_viewFrustum.getPosition()); _octreeQuery.setCameraOrientation(_viewFrustum.getOrientation()); _octreeQuery.setCameraFov(_viewFrustum.getFieldOfView()); @@ -51,159 +34,22 @@ void OctreeHeadlessViewer::queryOctree() { _octreeQuery.setOctreeSizeScale(_voxelSizeScale); _octreeQuery.setBoundaryLevelAdjust(_boundaryLevelAdjust); - // Iterate all of the nodes, and get a count of how many voxel servers we have... - int totalServers = 0; - int inViewServers = 0; - int unknownJurisdictionServers = 0; - - DependencyManager::get()->eachNode([&](const SharedNodePointer& node){ - // only send to the NodeTypes that are serverType - if (node->getActiveSocket() && node->getType() == serverType) { - totalServers++; - - // get the server bounds for this server - QUuid nodeUUID = node->getUUID(); - - // if we haven't heard from this voxel server, go ahead and send it a query, so we - // can get the jurisdiction... - VoxelPositionSize rootDetails; - bool foundRootDetails = false; - jurisdictions.withReadLock([&] { - if (jurisdictions.find(nodeUUID) == jurisdictions.end()) { - unknownJurisdictionServers++; - return; - } - const JurisdictionMap& map = (jurisdictions)[nodeUUID]; - - auto rootCode = map.getRootOctalCode(); - if (!rootCode) { - return; - } - - voxelDetailsForCode(rootCode.get(), rootDetails); - foundRootDetails = true; - }); - - if (foundRootDetails) { - AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s); - if ((bool)(_viewFrustum.calculateCubeKeyholeIntersection(serverBounds))) { - inViewServers++; - } - } - } - }); - - if (wantExtraDebugging) { - qCDebug(octree, "Servers: total %d, in view %d, unknown jurisdiction %d", - totalServers, inViewServers, unknownJurisdictionServers); - } - - int perServerPPS = 0; - const int SMALL_BUDGET = 10; - int perUnknownServer = SMALL_BUDGET; - int totalPPS = getMaxPacketsPerSecond(); - - // determine PPS based on number of servers - if (inViewServers >= 1) { - // set our preferred PPS to be exactly evenly divided among all of the voxel servers... and allocate 1 PPS - // for each unknown jurisdiction server - perServerPPS = (totalPPS / inViewServers) - (unknownJurisdictionServers * perUnknownServer); - } else { - if (unknownJurisdictionServers > 0) { - perUnknownServer = (totalPPS / unknownJurisdictionServers); - } - } - - if (wantExtraDebugging) { - qCDebug(octree, "perServerPPS: %d perUnknownServer: %d", perServerPPS, perUnknownServer); - } - auto nodeList = DependencyManager::get(); - nodeList->eachNode([&](const SharedNodePointer& node){ - // only send to the NodeTypes that are serverType - if (node->getActiveSocket() && node->getType() == serverType) { - // get the server bounds for this server - QUuid nodeUUID = node->getUUID(); + auto node = nodeList->soloNodeOfType(serverType); + if (node && node->getActiveSocket()) { + _octreeQuery.setMaxQueryPacketsPerSecond(getMaxPacketsPerSecond()); - bool inView = false; - bool unknownView = false; + auto queryPacket = NLPacket::create(packetType); - // if we haven't heard from this voxel server, go ahead and send it a query, so we - // can get the jurisdiction... - VoxelPositionSize rootDetails; - bool foundRootDetails = false; - jurisdictions.withReadLock([&] { - if (jurisdictions.find(nodeUUID) == jurisdictions.end()) { - unknownView = true; // assume it's in view - if (wantExtraDebugging) { - qCDebug(octree) << "no known jurisdiction for node " << *node << ", assume it's visible."; - } - return; - } + // encode the query data + auto packetData = reinterpret_cast(queryPacket->getPayload()); + int packetSize = _octreeQuery.getBroadcastData(packetData); + queryPacket->setPayloadSize(packetSize); - const JurisdictionMap& map = (jurisdictions)[nodeUUID]; - auto rootCode = map.getRootOctalCode(); - - if (!rootCode) { - if (wantExtraDebugging) { - qCDebug(octree) << "Jurisdiction without RootCode for node " << *node << ". That's unusual!"; - } - return; - } - voxelDetailsForCode(rootCode.get(), rootDetails); - foundRootDetails = true; - }); - - if (foundRootDetails) { - AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s); - inView = (bool)(_viewFrustum.calculateCubeKeyholeIntersection(serverBounds)); - } - - if (inView) { - _octreeQuery.setMaxQueryPacketsPerSecond(perServerPPS); - if (wantExtraDebugging) { - qCDebug(octree) << "inView for node " << *node << ", give it budget of " << perServerPPS; - } - } else if (unknownView) { - if (wantExtraDebugging) { - qCDebug(octree) << "no known jurisdiction for node " << *node << ", give it budget of " - << perUnknownServer << " to send us jurisdiction."; - } - - // set the query's position/orientation to be degenerate in a manner that will get the scene quickly - // If there's only one server, then don't do this, and just let the normal voxel query pass through - // as expected... this way, we will actually get a valid scene if there is one to be seen - if (totalServers > 1) { - _octreeQuery.setCameraPosition(glm::vec3(-0.1,-0.1,-0.1)); - const glm::quat OFF_IN_NEGATIVE_SPACE = glm::quat(-0.5, 0, -0.5, 1.0); - _octreeQuery.setCameraOrientation(OFF_IN_NEGATIVE_SPACE); - _octreeQuery.setCameraNearClip(0.1f); - _octreeQuery.setCameraFarClip(0.1f); - if (wantExtraDebugging) { - qCDebug(octree) << "Using 'minimal' camera position for node" << *node; - } - } else { - if (wantExtraDebugging) { - qCDebug(octree) << "Using regular camera position for node" << *node; - } - } - _octreeQuery.setMaxQueryPacketsPerSecond(perUnknownServer); - } else { - _octreeQuery.setMaxQueryPacketsPerSecond(0); - } - - // setup the query packet - auto queryPacket = NLPacket::create(packetType); - - // read the data to our packet and set the payload size to fit the query - int querySize = _octreeQuery.getBroadcastData(reinterpret_cast(queryPacket->getPayload())); - queryPacket->setPayloadSize(querySize); - - // ask the NodeList to send it - nodeList->sendPacket(std::move(queryPacket), *node); - } - }); + // make sure we still have an active socket + nodeList->sendUnreliablePacket(*queryPacket, *node); + } } diff --git a/assignment-client/src/octree/OctreeHeadlessViewer.h b/assignment-client/src/octree/OctreeHeadlessViewer.h index 5a7544498d..feb8211c39 100644 --- a/assignment-client/src/octree/OctreeHeadlessViewer.h +++ b/assignment-client/src/octree/OctreeHeadlessViewer.h @@ -13,7 +13,6 @@ #define hifi_OctreeHeadlessViewer_h #include -#include #include @@ -23,8 +22,6 @@ class OctreeHeadlessViewer : public OctreeProcessor { public: OctreeHeadlessViewer(); virtual ~OctreeHeadlessViewer() {}; - - void setJurisdictionListener(JurisdictionListener* jurisdictionListener) { _jurisdictionListener = jurisdictionListener; } OctreeQuery& getOctreeQuery() { return _octreeQuery; } @@ -57,7 +54,6 @@ public slots: unsigned getOctreeElementsCount() const { return _tree->getOctreeElementsCount(); } private: - JurisdictionListener* _jurisdictionListener = nullptr; OctreeQuery _octreeQuery; ViewFrustum _viewFrustum; diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 3ae653307f..d5b9da7353 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -391,8 +391,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData->sceneStart(usecTimestampNow() - CHANGE_FUDGE); // start tracking our stats - nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, - _myServer->getOctree()->getRoot(), _myServer->getJurisdiction()); + nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, _myServer->getOctree()->getRoot()); preStartNewScene(nodeData, isFullScene); } @@ -507,7 +506,7 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre float octreeSizeScale = nodeData->getOctreeSizeScale(); EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP, viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale, - isFullScene, _myServer->getJurisdiction(), nodeData); + isFullScene, nodeData); // Our trackSend() function is implemented by the server subclass, and will be called back as new entities/data elements are sent params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) { _myServer->trackSend(dataID, dataEdited, _nodeUuid); diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index c535c48dda..42494ea7ee 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -237,8 +237,6 @@ OctreeServer::OctreeServer(ReceivedMessage& message) : _debugSending(false), _debugReceiving(false), _verboseDebug(false), - _jurisdiction(NULL), - _jurisdictionSender(NULL), _octreeInboundPacketProcessor(NULL), _persistThread(NULL), _started(time(0)), @@ -257,12 +255,6 @@ OctreeServer::~OctreeServer() { delete[] _parsedArgV; } - if (_jurisdictionSender) { - _jurisdictionSender->terminating(); - _jurisdictionSender->terminate(); - _jurisdictionSender->deleteLater(); - } - if (_octreeInboundPacketProcessor) { _octreeInboundPacketProcessor->terminating(); _octreeInboundPacketProcessor->terminate(); @@ -275,9 +267,6 @@ OctreeServer::~OctreeServer() { _persistThread->deleteLater(); } - delete _jurisdiction; - _jurisdiction = NULL; - // cleanup our tree here... qDebug() << qPrintable(_safeServerName) << "server START cleaning up octree... [" << this << "]"; _tree.reset(); @@ -933,10 +922,6 @@ void OctreeServer::handleOctreeDataNackPacket(QSharedPointer me } } -void OctreeServer::handleJurisdictionRequestPacket(QSharedPointer message, SharedNodePointer senderNode) { - _jurisdictionSender->queueReceivedPacket(message, senderNode); -} - void OctreeServer::handleOctreeFileReplacement(QSharedPointer message) { if (!_isFinished && !_isShuttingDown) { // these messages are only allowed to come from the domain server, so make sure that is the case @@ -1111,23 +1096,6 @@ void OctreeServer::readConfiguration() { qDebug() << "statusPort= DISABLED"; } - QString jurisdictionFile; - if (readOptionString(QString("jurisdictionFile"), settingsSectionObject, jurisdictionFile)) { - qDebug("jurisdictionFile=%s", qPrintable(jurisdictionFile)); - qDebug("about to readFromFile().... jurisdictionFile=%s", qPrintable(jurisdictionFile)); - _jurisdiction = new JurisdictionMap(qPrintable(jurisdictionFile)); - qDebug("after readFromFile().... jurisdictionFile=%s", qPrintable(jurisdictionFile)); - } else { - QString jurisdictionRoot; - bool hasRoot = readOptionString(QString("jurisdictionRoot"), settingsSectionObject, jurisdictionRoot); - QString jurisdictionEndNodes; - bool hasEndNodes = readOptionString(QString("jurisdictionEndNodes"), settingsSectionObject, jurisdictionEndNodes); - - if (hasRoot || hasEndNodes) { - _jurisdiction = new JurisdictionMap(qPrintable(jurisdictionRoot), qPrintable(jurisdictionEndNodes)); - } - } - readOptionBool(QString("verboseDebug"), settingsSectionObject, _verboseDebug); qDebug("verboseDebug=%s", debug::valueOf(_verboseDebug)); @@ -1241,7 +1209,6 @@ void OctreeServer::domainSettingsRequestComplete() { auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListener(getMyQueryMessageType(), this, "handleOctreeQueryPacket"); packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket"); - packetReceiver.registerListener(PacketType::JurisdictionRequest, this, "handleJurisdictionRequestPacket"); packetReceiver.registerListener(PacketType::OctreeFileReplacement, this, "handleOctreeFileReplacement"); packetReceiver.registerListener(PacketType::OctreeFileReplacementFromUrl, this, "handleOctreeFileReplacementFromURL"); @@ -1365,13 +1332,6 @@ void OctreeServer::domainSettingsRequestComplete() { _persistThread->initialize(true); } - // set up our jurisdiction broadcaster... - if (_jurisdiction) { - _jurisdiction->setNodeType(getMyNodeType()); - } - _jurisdictionSender = new JurisdictionSender(_jurisdiction, getMyNodeType()); - _jurisdictionSender->initialize(true); - // set up our OctreeServerPacketProcessor _octreeInboundPacketProcessor = new OctreeInboundPacketProcessor(this); _octreeInboundPacketProcessor->initialize(true); @@ -1441,10 +1401,6 @@ void OctreeServer::aboutToFinish() { _octreeInboundPacketProcessor->terminating(); } - if (_jurisdictionSender) { - _jurisdictionSender->terminating(); - } - // Shut down all the send threads for (auto& it : _sendThreads) { auto& sendThread = *it.second; diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index f930f299f3..0eba914064 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -44,7 +44,6 @@ public: bool wantsVerboseDebug() const { return _verboseDebug; } OctreePointer getOctree() { return _tree; } - JurisdictionMap* getJurisdiction() { return _jurisdiction; } int getPacketsPerClientPerInterval() const { return std::min(_packetsPerClientPerInterval, std::max(1, getPacketsTotalPerInterval() / std::max(1, getCurrentClientCount()))); } @@ -138,7 +137,6 @@ private slots: void domainSettingsRequestComplete(); void handleOctreeQueryPacket(QSharedPointer message, SharedNodePointer senderNode); void handleOctreeDataNackPacket(QSharedPointer message, SharedNodePointer senderNode); - void handleJurisdictionRequestPacket(QSharedPointer message, SharedNodePointer senderNode); void handleOctreeFileReplacement(QSharedPointer message); void handleOctreeFileReplacementFromURL(QSharedPointer message); void removeSendThread(); @@ -190,8 +188,6 @@ protected: bool _debugReceiving; bool _debugTimestampNow; bool _verboseDebug; - JurisdictionMap* _jurisdiction; - JurisdictionSender* _jurisdictionSender; OctreeInboundPacketProcessor* _octreeInboundPacketProcessor; OctreePersistThread* _persistThread; diff --git a/assignment-client/src/octree/OctreeServerConsts.h b/assignment-client/src/octree/OctreeServerConsts.h index 30b55b8786..2706b3ef73 100644 --- a/assignment-client/src/octree/OctreeServerConsts.h +++ b/assignment-client/src/octree/OctreeServerConsts.h @@ -14,7 +14,6 @@ #include #include // for MAX_PACKET_SIZE -#include const int MAX_FILENAME_LENGTH = 1024; diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index c8067ce81f..b4a6b3af93 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -79,7 +79,6 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase }, this, "handleOctreePacket"); - packetReceiver.registerListener(PacketType::Jurisdiction, this, "handleJurisdictionPacket"); packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat"); auto avatarHashMap = DependencyManager::set(); @@ -283,11 +282,8 @@ void EntityScriptServer::run() { // Setup Script Engine resetEntitiesScriptEngine(); - // we need to make sure that init has been called for our EntityScriptingInterface - // so that it actually has a jurisdiction listener when we ask it for it next auto entityScriptingInterface = DependencyManager::get(); entityScriptingInterface->init(); - _entityViewer.setJurisdictionListener(entityScriptingInterface->getJurisdictionListener()); _entityViewer.init(); @@ -566,17 +562,6 @@ void EntityScriptServer::handleOctreePacket(QSharedPointer mess } } -void EntityScriptServer::handleJurisdictionPacket(QSharedPointer message, SharedNodePointer senderNode) { - NodeType_t nodeType; - message->peekPrimitive(&nodeType); - - // PacketType_JURISDICTION, first byte is the node type... - if (nodeType == NodeType::EntityServer) { - DependencyManager::get()->getJurisdictionListener()-> - queueReceivedPacket(message, senderNode); - } -} - void EntityScriptServer::aboutToFinish() { shutdownScriptEngine(); diff --git a/assignment-client/src/scripts/EntityScriptServer.h b/assignment-client/src/scripts/EntityScriptServer.h index f9c5e921f0..9c6c4c752e 100644 --- a/assignment-client/src/scripts/EntityScriptServer.h +++ b/assignment-client/src/scripts/EntityScriptServer.h @@ -41,7 +41,6 @@ public slots: private slots: void handleOctreePacket(QSharedPointer message, SharedNodePointer senderNode); - void handleJurisdictionPacket(QSharedPointer message, SharedNodePointer senderNode); void handleSelectedAudioFormat(QSharedPointer message); void handleReloadEntityServerScriptPacket(QSharedPointer message, SharedNodePointer senderNode); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 2d8bf7418a..290f4a7f53 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -945,7 +945,7 @@ void DomainServer::createStaticAssignmentsForType(Assignment::Type type, const Q void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet& excludedTypes) { // enumerate over all assignment types and see if we've already excluded it - for (Assignment::Type defaultedType = Assignment::AudioMixerType; + for (Assignment::Type defaultedType = Assignment::FirstType; defaultedType != Assignment::AllTypes; defaultedType = static_cast(static_cast(defaultedType) + 1)) { if (!excludedTypes.contains(defaultedType) && defaultedType != Assignment::AgentType) { diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 7625d69211..e6b3392aad 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -12,8 +12,10 @@ function(JOIN VALUES GLUE OUTPUT) endfunction() -set(INTERFACE_QML_QRC ${CMAKE_CURRENT_BINARY_DIR}/qml.qrc) -generate_qrc(OUTPUT ${INTERFACE_QML_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *.qml *.qss *.js *.html *.ttf *.gif *.svg *.png *.jpg) +if (NOT DEV_BUILD) + set(INTERFACE_QML_QRC ${CMAKE_CURRENT_BINARY_DIR}/qml.qrc) + generate_qrc(OUTPUT ${INTERFACE_QML_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *.qml *.qss *.js *.html *.ttf *.gif *.svg *.png *.jpg) +endif() # set a default root dir for each of our optional externals if it was not passed set(OPTIONAL_EXTERNALS "LeapMotion") @@ -72,7 +74,9 @@ qt5_wrap_ui(QT_UI_HEADERS "${QT_UI_FILES}") # add them to the interface source files set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}") +if (NOT DEV_BUILD) list(APPEND INTERFACE_SRCS ${INTERFACE_QML_QRC}) +endif() if (UNIX) install( diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 02fc09c815..8a7744efb3 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -56,29 +56,28 @@ { "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot", - "filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}] + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}] }, { "from": "Vive.RightFoot", "to" : "Standard.RightFoot", - "filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}] + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}] }, { "from": "Vive.Hips", "to" : "Standard.Hips", - "filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}] + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}] }, { "from": "Vive.Spine2", "to" : "Standard.Spine2", - "filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}] + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}] }, { "from": "Vive.Head", "to" : "Standard.Head"}, - { "from": "Vive.RightArm", "to" : "Standard.RightArm" }, { "from": "Vive.LeftArm", "to" : "Standard.LeftArm" }, - + { "from": "Vive.TrackedObject00", "to" : "Standard.TrackedObject00" }, { "from": "Vive.TrackedObject01", "to" : "Standard.TrackedObject01" }, { "from": "Vive.TrackedObject02", "to" : "Standard.TrackedObject02" }, diff --git a/interface/resources/qml/AudioScope.qml b/interface/resources/qml/AudioScope.qml index aea1473c3d..aa181dbf8d 100644 --- a/interface/resources/qml/AudioScope.qml +++ b/interface/resources/qml/AudioScope.qml @@ -110,7 +110,7 @@ Item { } function pullFreshValues() { - if (Audio.getRecording()) { + if (AudioScriptingInterface.getRecording()) { updateRecordingLabel(); } @@ -129,14 +129,14 @@ Item { _wavFilePath = _wavFilePath.replace(/[\-:]|\.\d*Z$/g, "").replace("T", "-") + ".wav"; // Using controller recording default directory _wavFilePath = Recording.getDefaultRecordingSaveDirectory() + _wavFilePath; - if (!Audio.startRecording(_wavFilePath)) { + if (!AudioScriptingInterface.startRecording(_wavFilePath)) { Messages.sendMessage("Hifi-Notifications", JSON.stringify({message:"Error creating: "+_wavFilePath})); updateRecordingUI(false); } } function stopRecording() { - Audio.stopRecording(); + AudioScriptingInterface.stopRecording(); setRecordingLabelOpacity(0.0); Messages.sendMessage("Hifi-Notifications", JSON.stringify({message:"Saved: "+_wavFilePath})); } @@ -158,7 +158,7 @@ Item { } function toggleRecording() { - if (Audio.getRecording()) { + if (AudioScriptingInterface.getRecording()) { updateRecordingUI(false); stopRecording(); } else { diff --git a/interface/resources/qml/OverlayWindowTest.qml b/interface/resources/qml/OverlayWindowTest.qml new file mode 100644 index 0000000000..7b82b2f705 --- /dev/null +++ b/interface/resources/qml/OverlayWindowTest.qml @@ -0,0 +1,18 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +Rectangle { + width: 100 + height: 100 + color: "white" + Rectangle { + width: 10 + height: 10 + color: "red" + } + + Label { + text: OverlayWindowTestString + anchors.centerIn: parent + } +} diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml index 9a84418b3a..7c1ce704c3 100644 --- a/interface/resources/qml/QmlWindow.qml +++ b/interface/resources/qml/QmlWindow.qml @@ -22,7 +22,6 @@ Windows.Window { // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer destroyOnCloseButton: false property var source; - property var component; property var dynamicContent; // Keyboard control properties in case needed by QML content. @@ -35,28 +34,9 @@ Windows.Window { dynamicContent.destroy(); dynamicContent = null; } - component = Qt.createComponent(source); - console.log("Created component " + component + " from source " + source); - } - - onComponentChanged: { - console.log("Component changed to " + component) - populate(); - } - - function populate() { - console.log("Populate called: dynamicContent " + dynamicContent + " component " + component); - if (!dynamicContent && component) { - if (component.status == Component.Error) { - console.log("Error loading component:", component.errorString()); - } else if (component.status == Component.Ready) { - console.log("Building dynamic content"); - dynamicContent = component.createObject(contentHolder); - } else { - console.log("Component not yet ready, connecting to status change"); - component.statusChanged.connect(populate); - } - } + QmlSurface.load(source, contentHolder, function(newObject) { + dynamicContent = newObject; + }); } // Handle message traffic from the script that launched us to the loaded QML diff --git a/interface/resources/qml/controls-uit/Button.qml b/interface/resources/qml/controls-uit/Button.qml index c068fdcfaf..02c6181952 100644 --- a/interface/resources/qml/controls-uit/Button.qml +++ b/interface/resources/qml/controls-uit/Button.qml @@ -29,12 +29,12 @@ Original.Button { onHoveredChanged: { if (hovered) { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); } } onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } style: ButtonStyle { diff --git a/interface/resources/qml/controls-uit/CheckBox.qml b/interface/resources/qml/controls-uit/CheckBox.qml index 22b25671c3..e60f646327 100644 --- a/interface/resources/qml/controls-uit/CheckBox.qml +++ b/interface/resources/qml/controls-uit/CheckBox.qml @@ -31,12 +31,12 @@ Original.CheckBox { activeFocusOnPress: true onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } // TODO: doesnt works for QQC1. check with QQC2 // onHovered: { -// tabletInterface.playSound(TabletEnums.ButtonHover); +// Tablet.playSound(TabletEnums.ButtonHover); // } style: CheckBoxStyle { diff --git a/interface/resources/qml/controls-uit/CheckBoxQQC2.qml b/interface/resources/qml/controls-uit/CheckBoxQQC2.qml index 32d69cf339..12b8c80003 100644 --- a/interface/resources/qml/controls-uit/CheckBoxQQC2.qml +++ b/interface/resources/qml/controls-uit/CheckBoxQQC2.qml @@ -36,12 +36,12 @@ CheckBox { hoverEnabled: true onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } onHoveredChanged: { if (hovered) { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); } } diff --git a/interface/resources/qml/controls-uit/GlyphButton.qml b/interface/resources/qml/controls-uit/GlyphButton.qml index bc7bc636fe..9c23171ee1 100644 --- a/interface/resources/qml/controls-uit/GlyphButton.qml +++ b/interface/resources/qml/controls-uit/GlyphButton.qml @@ -27,12 +27,12 @@ Original.Button { onHoveredChanged: { if (hovered) { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); } } onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } style: ButtonStyle { diff --git a/interface/resources/qml/controls-uit/Key.qml b/interface/resources/qml/controls-uit/Key.qml index 4ca6cd1b53..b2c720368d 100644 --- a/interface/resources/qml/controls-uit/Key.qml +++ b/interface/resources/qml/controls-uit/Key.qml @@ -41,13 +41,13 @@ Item { onContainsMouseChanged: { if (containsMouse) { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); } } onClicked: { mouse.accepted = true; - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); webEntity.synthesizeKeyPress(glyph); webEntity.synthesizeKeyPress(glyph, mirrorText); diff --git a/interface/resources/qml/controls-uit/RadioButton.qml b/interface/resources/qml/controls-uit/RadioButton.qml index 65d36d2dcb..a818be072b 100644 --- a/interface/resources/qml/controls-uit/RadioButton.qml +++ b/interface/resources/qml/controls-uit/RadioButton.qml @@ -30,12 +30,12 @@ Original.RadioButton { readonly property int checkRadius: 2 onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } // TODO: doesnt works for QQC1. check with QQC2 // onHovered: { -// tabletInterface.playSound(TabletEnums.ButtonHover); +// Tablet.playSound(TabletEnums.ButtonHover); // } style: RadioButtonStyle { diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml index 6ab4fd9758..efc8519c1e 100644 --- a/interface/resources/qml/controls/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/FlickableWebViewCore.qml @@ -49,7 +49,7 @@ Item { } if (WebEngineView.LoadFailedStatus === loadRequest.status) { - console.log(" Tablet WebEngineView failed to load url: " + loadRequest.url.toString()); + console.log("Tablet WebEngineView failed to load url: " + loadRequest.url.toString()); } if (WebEngineView.LoadSucceededStatus === loadRequest.status) { diff --git a/interface/resources/qml/dialogs/preferences/ButtonPreference.qml b/interface/resources/qml/dialogs/preferences/ButtonPreference.qml index 3a5c850031..454a9124ae 100644 --- a/interface/resources/qml/dialogs/preferences/ButtonPreference.qml +++ b/interface/resources/qml/dialogs/preferences/ButtonPreference.qml @@ -25,13 +25,13 @@ Preference { id: button onHoveredChanged: { if (hovered) { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); } } onClicked: { preference.trigger(); - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } width: 180 anchors.bottom: parent.bottom diff --git a/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml b/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml index 8904896ab7..73819839a1 100644 --- a/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml @@ -41,12 +41,12 @@ Preference { id: checkBox onHoveredChanged: { if (hovered) { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); } } onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } anchors { diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index fc7b8c6200..83bf1e2c54 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -246,12 +246,12 @@ Item { anchors.fill: parent; acceptedButtons: Qt.LeftButton; onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); goFunction("hifi://" + hifiUrl); } hoverEnabled: true; onEntered: { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); hoverThunk(); } onExited: unhoverThunk(); @@ -269,7 +269,7 @@ Item { } } function go() { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); goFunction(drillDownToPlace ? ("/places/" + placeName) : ("/user_stories/" + storyId)); } MouseArea { diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 96bb359aea..896b3cf10e 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -45,11 +45,13 @@ OriginalDesktop.Desktop { Toolbar { id: sysToolbar; objectName: "com.highfidelity.interface.toolbar.system"; + property var tablet: Tablet.getTablet("com.highfidelity.interface.tablet.system"); anchors.horizontalCenter: settings.constrainToolbarToCenterX ? desktop.horizontalCenter : undefined; // Literal 50 is overwritten by settings from previous session, and sysToolbar.x comes from settings when not constrained. x: sysToolbar.x y: 50 - shown: true + buttonModel: tablet.buttons; + shown: tablet.toolbarMode; } Settings { diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index 1f9caf747a..3f5cad655d 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -597,18 +597,11 @@ Item { // Function body by Howard Stearns 2017-01-08 function goToUserInDomain(avatarUuid) { var avatar = AvatarList.getAvatar(avatarUuid); - if (!avatar) { + if (!avatar || !avatar.position || !avatar.orientation) { console.log("This avatar is no longer present. goToUserInDomain() failed."); return; } - // FIXME: We would like the avatar to recompute the avatar's "maybe fly" test at the new position, so that if high enough up, - // the avatar goes into fly mode rather than falling. However, that is not exposed to Javascript right now. - // FIXME: it would be nice if this used the same teleport steps and smoothing as in the teleport.js script. - // Note, however, that this script allows teleporting to a person in the air, while teleport.js is going to a grounded target. - // Position avatar 2 metres from the target in the direction that target avatar was facing. - MyAvatar.position = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.orientation, {x: 0, y: 0, z: -2})); - - // Rotate avatar on Y axis to face target avatar and cancel out any inherited roll and pitch. - MyAvatar.orientation = Quat.cancelOutRollAndPitch(Quat.multiply(avatar.orientation, {y: 1})); + // This is the last step of what AddressManager.goToUser does, but we don't need to resolve the username. + MyAvatar.goToLocation(avatar.position, true, Quat.cancelOutRollAndPitch(avatar.orientation), true); } } diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 87ddce49ca..eab1e40af0 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -26,7 +26,7 @@ Rectangle { HifiConstants { id: hifi; } property var eventBridge; - property string title: "Audio Settings - " + Audio.context; + property string title: "Audio Settings - " + AudioScriptingInterface.context; signal sendToScript(var message); color: hifi.colors.baseGray; @@ -37,7 +37,7 @@ Rectangle { } - property bool isVR: Audio.context === "VR" + property bool isVR: AudioScriptingInterface.context === "VR" property real rightMostInputLevelPos: 0 //placeholder for control sizes and paddings //recalculates dynamically in case of UI size is changed @@ -72,17 +72,17 @@ Rectangle { property bool showPeaks: true; function enablePeakValues() { - Audio.devices.input.peakValuesEnabled = true; - Audio.devices.input.peakValuesEnabledChanged.connect(function(enabled) { + AudioScriptingInterface.devices.input.peakValuesEnabled = true; + AudioScriptingInterface.devices.input.peakValuesEnabledChanged.connect(function(enabled) { if (!enabled && root.showPeaks) { - Audio.devices.input.peakValuesEnabled = true; + AudioScriptingInterface.devices.input.peakValuesEnabled = true; } }); } function disablePeakValues() { root.showPeaks = false; - Audio.devices.input.peakValuesEnabled = false; + AudioScriptingInterface.devices.input.peakValuesEnabled = false; } Component.onCompleted: enablePeakValues(); @@ -117,10 +117,10 @@ Rectangle { text: qsTr("Mute microphone"); spacing: margins.sizeCheckBox - boxSize isRedCheck: true; - checked: Audio.muted; + checked: AudioScriptingInterface.muted; onClicked: { - Audio.muted = checked; - checked = Qt.binding(function() { return Audio.muted; }); // restore binding + AudioScriptingInterface.muted = checked; + checked = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding } } } @@ -130,10 +130,10 @@ Rectangle { AudioControls.CheckBox { spacing: muteMic.spacing text: qsTr("Enable noise reduction"); - checked: Audio.noiseReduction; + checked: AudioScriptingInterface.noiseReduction; onClicked: { - Audio.noiseReduction = checked; - checked = Qt.binding(function() { return Audio.noiseReduction; }); // restore binding + AudioScriptingInterface.noiseReduction = checked; + checked = Qt.binding(function() { return AudioScriptingInterface.noiseReduction; }); // restore binding } } AudioControls.CheckBox { @@ -184,7 +184,7 @@ Rectangle { spacing: 4; snapMode: ListView.SnapToItem; clip: true; - model: Audio.devices.input; + model: AudioScriptingInterface.devices.input; delegate: Item { width: rightMostInputLevelPos height: margins.sizeCheckBox > checkBoxInput.implicitHeight ? @@ -204,7 +204,7 @@ Rectangle { text: devicename onPressed: { if (!checked) { - Audio.setInputDevice(info, bar.currentIndex === 1); + AudioScriptingInterface.setInputDevice(info, bar.currentIndex === 1); } } } @@ -215,7 +215,7 @@ Rectangle { anchors.verticalCenter: parent.verticalCenter visible: ((bar.currentIndex === 1 && isVR) || (bar.currentIndex === 0 && !isVR)) && - Audio.devices.input.peakValuesAvailable; + AudioScriptingInterface.devices.input.peakValuesAvailable; } } } @@ -256,7 +256,7 @@ Rectangle { spacing: 4; snapMode: ListView.SnapToItem; clip: true; - model: Audio.devices.output; + model: AudioScriptingInterface.devices.output; delegate: Item { width: rightMostInputLevelPos height: margins.sizeCheckBox > checkBoxOutput.implicitHeight ? @@ -273,7 +273,7 @@ Rectangle { text: devicename onPressed: { if (!checked) { - Audio.setOutputDevice(info, bar.currentIndex === 1); + AudioScriptingInterface.setOutputDevice(info, bar.currentIndex === 1); } } } diff --git a/interface/resources/qml/hifi/audio/InputPeak.qml b/interface/resources/qml/hifi/audio/InputPeak.qml index 4ff49091b1..be58c9536b 100644 --- a/interface/resources/qml/hifi/audio/InputPeak.qml +++ b/interface/resources/qml/hifi/audio/InputPeak.qml @@ -40,7 +40,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - visible: Audio.muted; + visible: AudioScriptingInterface.muted; color: colors.muted; text: "MUTED"; diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index b6699d6ceb..e798b35e29 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -17,7 +17,7 @@ import QtGraphicalEffects 1.0 import TabletScriptingInterface 1.0 Rectangle { - readonly property var level: Audio.inputLevel; + readonly property var level: AudioScriptingInterface.inputLevel; property bool standalone: false; property var dragTarget: null; @@ -60,13 +60,13 @@ Rectangle { hoverEnabled: true; scrollGestureEnabled: false; onClicked: { - Audio.muted = !Audio.muted; - tabletInterface.playSound(TabletEnums.ButtonClick); + AudioScriptingInterface.muted = !AudioScriptingInterface.muted; + Tablet.playSound(TabletEnums.ButtonClick); } drag.target: dragTarget; onContainsMouseChanged: { if (containsMouse) { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); } } } @@ -82,7 +82,7 @@ Rectangle { readonly property string red: colors.muted; readonly property string fill: "#55000000"; readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF"; - readonly property string icon: Audio.muted ? muted : unmuted; + readonly property string icon: AudioScriptingInterface.muted ? muted : unmuted; } Item { @@ -103,7 +103,7 @@ Rectangle { readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg"; id: image; - source: Audio.muted ? mutedIcon : unmutedIcon; + source: AudioScriptingInterface.muted ? mutedIcon : unmutedIcon; width: 30; height: 30; @@ -126,9 +126,9 @@ Rectangle { Item { id: status; - readonly property string color: Audio.muted ? colors.muted : colors.unmuted; + readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; - visible: Audio.muted; + visible: AudioScriptingInterface.muted; anchors { left: parent.left; @@ -147,7 +147,7 @@ Rectangle { color: parent.color; - text: Audio.muted ? "MUTED" : "MUTE"; + text: AudioScriptingInterface.muted ? "MUTED" : "MUTE"; font.pointSize: 12; } diff --git a/interface/resources/qml/hifi/audio/PlaySampleSound.qml b/interface/resources/qml/hifi/audio/PlaySampleSound.qml index dec2e9bfc9..6d7eb80974 100644 --- a/interface/resources/qml/hifi/audio/PlaySampleSound.qml +++ b/interface/resources/qml/hifi/audio/PlaySampleSound.qml @@ -27,9 +27,9 @@ RowLayout { } function playSound() { // FIXME: MyAvatar is not properly exposed to QML; MyAvatar.qmlPosition is a stopgap - // FIXME: Audio.playSystemSound should not require position + // FIXME: AudioScriptingInterface.playSystemSound should not require position if (sample === null && !isPlaying) { - sample = Audio.playSystemSound(sound, MyAvatar.qmlPosition); + sample = AudioScriptingInterface.playSystemSound(sound, MyAvatar.qmlPosition); isPlaying = true; sample.finished.connect(reset); } diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index a058f32994..4de09c1bf3 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -33,6 +33,7 @@ Rectangle { property string itemName; property string itemId; property string itemHref; + property string itemAuthor; property double balanceAfterPurchase; property bool alreadyOwned: false; property int itemPrice: -1; @@ -41,10 +42,11 @@ Rectangle { property bool debugCheckoutSuccess: false; property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified(); property bool isWearable; + property string referrer; // Style color: hifi.colors.white; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onWalletStatusResult: { if (walletStatus === 0) { @@ -59,6 +61,7 @@ Rectangle { } else if (walletStatus === 2) { if (root.activeView !== "passphraseModal") { root.activeView = "passphraseModal"; + UserActivityLogger.commercePassphraseEntry("marketplace checkout"); } } else if (walletStatus === 3) { authSuccessStep(); @@ -71,7 +74,7 @@ Rectangle { if (!isLoggedIn && root.activeView !== "needsLogIn") { root.activeView = "needsLogIn"; } else { - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } } @@ -79,12 +82,12 @@ Rectangle { if (result.status !== 'success') { failureErrorText.text = result.message; root.activeView = "checkoutFailure"; - UserActivityLogger.commercePurchaseFailure(root.itemId, root.itemPrice, !root.alreadyOwned, result.message); + UserActivityLogger.commercePurchaseFailure(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned, result.message); } else { root.itemHref = result.data.download_url; root.isWearable = result.data.categories.indexOf("Wearables") > -1; root.activeView = "checkoutSuccess"; - UserActivityLogger.commercePurchaseSuccess(root.itemId, root.itemPrice, !root.alreadyOwned); + UserActivityLogger.commercePurchaseSuccess(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned); } } @@ -114,7 +117,7 @@ Rectangle { } onItemIdChanged: { - commerce.inventory(); + Commerce.inventory(); itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg"; } @@ -123,14 +126,14 @@ Rectangle { } onItemPriceChanged: { - commerce.balance(); + Commerce.balance(); } Timer { id: notSetUpTimer; interval: 200; onTriggered: { - sendToScript({method: 'checkout_walletNotSetUp', itemId: itemId}); + sendToScript({method: 'checkout_walletNotSetUp', itemId: itemId, referrer: referrer}); } } @@ -202,7 +205,7 @@ Rectangle { Component.onCompleted: { purchasesReceived = false; balanceReceived = false; - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } } @@ -223,7 +226,7 @@ Rectangle { Connections { target: GlobalServices onMyUsernameChanged: { - commerce.getLoginStatus(); + Commerce.getLoginStatus(); } } @@ -408,7 +411,8 @@ Rectangle { Rectangle { id: buyTextContainer; visible: buyText.text !== ""; - anchors.top: parent.top; + anchors.top: cancelPurchaseButton.bottom; + anchors.topMargin: 16; anchors.left: parent.left; anchors.right: parent.right; height: buyText.height + 30; @@ -463,8 +467,8 @@ Rectangle { enabled: (root.balanceAfterPurchase >= 0 && purchasesReceived && balanceReceived) || !itemIsJson; color: hifi.buttons.blue; colorScheme: hifi.colorSchemes.light; - anchors.top: buyTextContainer.visible ? buyTextContainer.bottom : checkoutActionButtonsContainer.top; - anchors.topMargin: buyTextContainer.visible ? 12 : 16; + anchors.top: checkoutActionButtonsContainer.top; + anchors.topMargin: 16; height: 40; anchors.left: parent.left; anchors.right: parent.right; @@ -473,9 +477,9 @@ Rectangle { if (itemIsJson) { buyButton.enabled = false; if (!root.shouldBuyWithControlledFailure) { - commerce.buy(itemId, itemPrice); + Commerce.buy(itemId, itemPrice); } else { - commerce.buy(itemId, itemPrice, true); + Commerce.buy(itemId, itemPrice, true); } } else { if (urlHandler.canHandleUrl(itemHref)) { @@ -876,6 +880,8 @@ Rectangle { itemName = message.params.itemName; root.itemPrice = message.params.itemPrice; itemHref = message.params.itemHref; + referrer = message.params.referrer; + itemAuthor = message.params.itemAuthor; setBuyText(); break; default: @@ -923,11 +929,11 @@ Rectangle { buyText.text = ""; } } else { - buyText.text = "This free item will not be added to your Purchases. Non-entities can't yet be purchased for HFC."; - buyTextContainer.color = "#FFD6AD"; - buyTextContainer.border.color = "#FAC07D"; - buyGlyph.text = hifi.glyphs.alert; - buyGlyph.size = 46; + buyText.text = 'This type of item cannot currently be certified, so it will not show up in "My Purchases". You can access it again for free from the Marketplace.'; + buyTextContainer.color = hifi.colors.white; + buyTextContainer.border.color = hifi.colors.white; + buyGlyph.text = ""; + buyGlyph.size = 0; } } @@ -939,8 +945,8 @@ Rectangle { } root.balanceReceived = false; root.purchasesReceived = false; - commerce.inventory(); - commerce.balance(); + Commerce.inventory(); + Commerce.balance(); } // diff --git a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml index 8b3f017fd2..a4b0f57e8f 100644 --- a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml +++ b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml @@ -31,14 +31,14 @@ Item { height: mainContainer.height + additionalDropdownHeight; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onWalletStatusResult: { if (walletStatus === 0) { sendToParent({method: "needsLogIn"}); } else if (walletStatus === 3) { - commerce.getSecurityImage(); + Commerce.getSecurityImage(); } else if (walletStatus > 3) { console.log("ERROR in EmulatedMarketplaceHeader.qml: Unknown wallet status: " + walletStatus); } @@ -48,7 +48,7 @@ Item { if (!isLoggedIn) { sendToParent({method: "needsLogIn"}); } else { - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } } @@ -61,13 +61,13 @@ Item { } Component.onCompleted: { - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } Connections { target: GlobalServices onMyUsernameChanged: { - commerce.getLoginStatus(); + Commerce.getLoginStatus(); } } diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index d98c453a0c..28c32c59de 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -36,8 +36,8 @@ Rectangle { property bool isCertificateInvalid: false; // Style color: hifi.colors.faintGray; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onCertificateInfoResult: { if (result.status !== 'success') { @@ -109,7 +109,7 @@ Rectangle { onCertificateIdChanged: { if (certificateId !== "") { - commerce.certificateInfo(certificateId); + Commerce.certificateInfo(certificateId); } } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index ff9ce16e93..de66be4a88 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -38,8 +38,8 @@ Rectangle { property bool isDebuggingFirstUseTutorial: false; // Style color: hifi.colors.white; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onWalletStatusResult: { if (walletStatus === 0) { @@ -54,13 +54,14 @@ Rectangle { } else if (walletStatus === 2) { if (root.activeView !== "passphraseModal") { root.activeView = "passphraseModal"; + UserActivityLogger.commercePassphraseEntry("marketplace purchases"); } } else if (walletStatus === 3) { if ((Settings.getValue("isFirstUseOfPurchases", true) || root.isDebuggingFirstUseTutorial) && root.activeView !== "firstUseTutorial") { root.activeView = "firstUseTutorial"; } else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") { root.activeView = "purchasesMain"; - commerce.inventory(); + Commerce.inventory(); } } else { console.log("ERROR in Purchases.qml: Unknown wallet status: " + walletStatus); @@ -71,7 +72,7 @@ Rectangle { if (!isLoggedIn && root.activeView !== "needsLogIn") { root.activeView = "needsLogIn"; } else { - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } } @@ -197,7 +198,7 @@ Rectangle { Component.onCompleted: { securityImageResultReceived = false; purchasesReceived = false; - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } } @@ -218,7 +219,7 @@ Rectangle { Connections { target: GlobalServices onMyUsernameChanged: { - commerce.getLoginStatus(); + Commerce.getLoginStatus(); } } @@ -233,7 +234,7 @@ Rectangle { onSendSignalToParent: { if (msg.method === "authSuccess") { root.activeView = "initialize"; - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } else { sendToScript(msg); } @@ -254,7 +255,7 @@ Rectangle { case 'tutorial_finished': Settings.setValue("isFirstUseOfPurchases", false); root.activeView = "purchasesMain"; - commerce.inventory(); + Commerce.inventory(); break; } } @@ -594,7 +595,7 @@ Rectangle { if (root.activeView === "purchasesMain" && !root.pendingInventoryReply) { console.log("Refreshing Purchases..."); root.pendingInventoryReply = true; - commerce.inventory(); + Commerce.inventory(); } } } diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index ebba2b87c6..409833df98 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -27,8 +27,8 @@ Item { property string keyFilePath; property bool showDebugButtons: true; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onKeyFilePathIfExistsResult: { root.keyFilePath = path; @@ -37,7 +37,7 @@ Item { onVisibleChanged: { if (visible) { - commerce.getKeyFilePathIfExists(); + Commerce.getKeyFilePathIfExists(); } } @@ -55,6 +55,37 @@ Item { // Style color: hifi.colors.blueHighlight; } + HifiControlsUit.Button { + id: clearCachedPassphraseButton; + visible: root.showDebugButtons; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.left: helpTitleText.right; + anchors.leftMargin: 20; + height: 40; + width: 150; + text: "DBG: Clear Pass"; + onClicked: { + Commerce.setPassphrase(""); + sendSignalToWallet({method: 'passphraseReset'}); + } + } + HifiControlsUit.Button { + id: resetButton; + visible: root.showDebugButtons; + color: hifi.buttons.red; + colorScheme: hifi.colorSchemes.dark; + anchors.top: clearCachedPassphraseButton.top; + anchors.left: clearCachedPassphraseButton.right; + height: 40; + width: 150; + text: "DBG: RST Wallet"; + onClicked: { + Commerce.reset(); + sendSignalToWallet({method: 'walletReset'}); + } + } ListModel { id: helpModel; diff --git a/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml b/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml index 7ce0cf3853..404d7e84cf 100644 --- a/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml +++ b/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml @@ -30,8 +30,8 @@ Item { source: "images/wallet-bg.jpg"; } - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; } // diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml index a75d511793..91d2ab9f7f 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml @@ -25,10 +25,6 @@ Item { id: root; - SecurityImageModel { - id: securityImageModel; - } - // Username Text RalewayRegular { id: usernameText; diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml index 582052c4c3..87430246f3 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml @@ -36,8 +36,8 @@ Item { source: "images/wallet-bg.jpg"; } - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onSecurityImageResult: { titleBarSecurityImage.source = ""; @@ -50,8 +50,12 @@ Item { submitPassphraseInputButton.enabled = true; if (!isAuthenticated) { errorText.text = "Authentication failed - please try again."; + passphraseField.error = true; + UserActivityLogger.commercePassphraseAuthenticationStatus("auth failure"); } else { - sendSignalToParent({method: 'authSuccess'});; + sendSignalToParent({method: 'authSuccess'}); + passphraseField.error = false; + UserActivityLogger.commercePassphraseAuthenticationStatus("auth success"); } } } @@ -70,6 +74,7 @@ Item { // TODO: Fix this unlikely bug onVisibleChanged: { if (visible) { + passphraseField.error = false; passphraseField.focus = true; sendSignalToParent({method: 'disableHmdPreview'}); } else { @@ -208,7 +213,7 @@ Item { onAccepted: { submitPassphraseInputButton.enabled = false; - commerce.setPassphrase(passphraseField.text); + Commerce.setPassphrase(passphraseField.text); } } @@ -248,7 +253,7 @@ Item { source: "image://security/securityImage"; cache: false; onVisibleChanged: { - commerce.getSecurityImage(); + Commerce.getSecurityImage(); } } Item { @@ -316,7 +321,7 @@ Item { text: "Submit" onClicked: { submitPassphraseInputButton.enabled = false; - commerce.setPassphrase(passphraseField.text); + Commerce.setPassphrase(passphraseField.text); } } @@ -336,6 +341,7 @@ Item { text: "Cancel" onClicked: { sendSignalToParent({method: 'passphrasePopup_cancelClicked'}); + UserActivityLogger.commercePassphraseAuthenticationStatus("passphrase modal cancelled"); } } } diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml index 9ba066a9fd..50e58f8cc4 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml @@ -36,8 +36,8 @@ Item { propagateComposedEvents: false; } - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onSecurityImageResult: { passphrasePageSecurityImage.source = ""; passphrasePageSecurityImage.source = "image://security/securityImage"; @@ -54,6 +54,9 @@ Item { // TODO: Fix this unlikely bug onVisibleChanged: { if (visible) { + passphraseField.error = false; + passphraseFieldAgain.error = false; + currentPassphraseField.error = false; if (root.shouldImmediatelyFocus) { focusFirstTextField(); } @@ -160,7 +163,7 @@ Item { source: "image://security/securityImage"; cache: false; onVisibleChanged: { - commerce.getSecurityImage(); + Commerce.getSecurityImage(); } } Item { @@ -283,7 +286,7 @@ Item { passphraseFieldAgain.error = false; currentPassphraseField.error = false; setErrorText(""); - commerce.changePassphrase(currentPassphraseField.text, passphraseField.text); + Commerce.changePassphrase(currentPassphraseField.text, passphraseField.text); return true; } } diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml index 485b0ec086..d825196655 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Security.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml @@ -27,8 +27,8 @@ Item { id: root; property string keyFilePath; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onKeyFilePathIfExistsResult: { root.keyFilePath = path; @@ -234,7 +234,7 @@ Item { onVisibleChanged: { if (visible) { - commerce.getKeyFilePathIfExists(); + Commerce.getKeyFilePathIfExists(); } } diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml index 7f767060f6..2ad2b75553 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml @@ -26,12 +26,8 @@ Item { id: root; property bool justSubmitted: false; - SecurityImageModel { - id: securityImageModel; - } - - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onSecurityImageResult: { securityImageChangePageSecurityImage.source = ""; @@ -70,7 +66,7 @@ Item { source: "image://security/securityImage"; cache: false; onVisibleChanged: { - commerce.getSecurityImage(); + Commerce.getSecurityImage(); } } Item { @@ -198,7 +194,7 @@ Item { securityImageSubmitButton.text = "Submitting..."; securityImageSubmitButton.enabled = false; var securityImagePath = securityImageSelection.getImagePathFromImageID(securityImageSelection.getSelectedImageIndex()) - commerce.chooseSecurityImage(securityImagePath); + Commerce.chooseSecurityImage(securityImagePath); } } } @@ -213,4 +209,8 @@ Item { securityImageSubmitButton.enabled = true; securityImageSubmitButton.text = "Submit"; } + + function initModel() { + securityImageSelection.initModel(); + } } diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml index 7b1434aa3c..b8e9db09ab 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml @@ -15,29 +15,28 @@ import QtQuick 2.5 ListModel { id: root; - ListElement{ - sourcePath: "images/01.jpg" - securityImageEnumValue: 1; + + function initModel() { + var array = []; + while (array.length < 6) { + // We currently have 34 security images to choose from + var randomNumber = Math.floor(Math.random() * 34) + 1; + if (array.indexOf(randomNumber) > -1) { + continue; + } + array[array.length] = randomNumber; + } + + var modelElement; + + for (var i = 0; i < 6; i++) { + modelElement = { "sourcePath":"images/" + addLeadingZero(array[i]) + ".jpg", "securityImageEnumValue": (i + 1) } + root.insert(i, modelElement); + } } - ListElement{ - sourcePath: "images/02.jpg" - securityImageEnumValue: 2; - } - ListElement{ - sourcePath: "images/03.jpg" - securityImageEnumValue: 3; - } - ListElement{ - sourcePath: "images/04.jpg" - securityImageEnumValue: 4; - } - ListElement{ - sourcePath: "images/05.jpg" - securityImageEnumValue: 5; - } - ListElement{ - sourcePath: "images/06.jpg" - securityImageEnumValue: 6; + + function addLeadingZero(n) { + return n < 10 ? '0' + n : '' + n; } function getImagePathFromImageID(imageID) { diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml index e12332cd0c..1f5e67eaa5 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml @@ -95,6 +95,10 @@ Item { function getSelectedImageIndex() { return gridModel.get(securityImageGrid.currentIndex).securityImageEnumValue; } + + function initModel() { + gridModel.initModel(); + } // // FUNCTION DEFINITIONS END // diff --git a/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml b/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml index 75334b1686..11a6c99b0c 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml @@ -25,8 +25,8 @@ Item { id: root; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; } // "Unavailable" diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index ac05bf7c84..ef2b007dc8 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -31,13 +31,15 @@ Rectangle { property bool keyboardRaised: false; property bool isPassword: false; + anchors.fill: (typeof parent === undefined) ? undefined : parent; + Image { anchors.fill: parent; source: "images/wallet-bg.jpg"; } - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onWalletStatusResult: { if (walletStatus === 0) { @@ -47,7 +49,7 @@ Rectangle { } else if (walletStatus === 1) { if (root.activeView !== "walletSetup") { root.activeView = "walletSetup"; - commerce.resetLocalWalletOnly(); + Commerce.resetLocalWalletOnly(); var timestamp = new Date(); walletSetup.startingTimestamp = timestamp; walletSetup.setupAttemptID = generateUUID(); @@ -57,10 +59,13 @@ Rectangle { } else if (walletStatus === 2) { if (root.activeView !== "passphraseModal") { root.activeView = "passphraseModal"; + UserActivityLogger.commercePassphraseEntry("wallet app"); } } else if (walletStatus === 3) { - root.activeView = "walletHome"; - commerce.getSecurityImage(); + if (root.activeView !== "walletSetup") { + root.activeView = "walletHome"; + Commerce.getSecurityImage(); + } } else { console.log("ERROR in Wallet.qml: Unknown wallet status: " + walletStatus); } @@ -70,7 +75,7 @@ Rectangle { if (!isLoggedIn && root.activeView !== "needsLogIn") { root.activeView = "needsLogIn"; } else if (isLoggedIn) { - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } } @@ -82,10 +87,6 @@ Rectangle { } } - SecurityImageModel { - id: securityImageModel; - } - HifiCommerceCommon.CommerceLightbox { id: lightboxPopup; visible: false; @@ -180,7 +181,7 @@ Rectangle { if (msg.method === 'walletSetup_finished') { if (msg.referrer === '' || msg.referrer === 'marketplace cta') { root.activeView = "initialize"; - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } else if (msg.referrer === 'purchases') { sendToScript({method: 'goToPurchases'}); } else { @@ -209,17 +210,19 @@ Rectangle { Connections { onSendSignalToWallet: { - if (msg.method === 'walletSetup_raiseKeyboard') { - root.keyboardRaised = true; - root.isPassword = msg.isPasswordField; - } else if (msg.method === 'walletSetup_lowerKeyboard') { - root.keyboardRaised = false; - } else if (msg.method === 'walletSecurity_changePassphraseCancelled') { - root.activeView = "security"; - } else if (msg.method === 'walletSecurity_changePassphraseSuccess') { - root.activeView = "security"; - } else { - sendToScript(msg); + if (passphraseChange.visible) { + if (msg.method === 'walletSetup_raiseKeyboard') { + root.keyboardRaised = true; + root.isPassword = msg.isPasswordField; + } else if (msg.method === 'walletSetup_lowerKeyboard') { + root.keyboardRaised = false; + } else if (msg.method === 'walletSecurity_changePassphraseCancelled') { + root.activeView = "security"; + } else if (msg.method === 'walletSecurity_changePassphraseSuccess') { + root.activeView = "security"; + } else { + sendToScript(msg); + } } } } @@ -260,7 +263,7 @@ Rectangle { color: hifi.colors.baseGray; Component.onCompleted: { - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } } @@ -281,7 +284,7 @@ Rectangle { Connections { target: GlobalServices onMyUsernameChanged: { - commerce.getLoginStatus(); + Commerce.getLoginStatus(); } } @@ -295,7 +298,7 @@ Rectangle { Connections { onSendSignalToParent: { if (msg.method === "authSuccess") { - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } else { sendToScript(msg); } @@ -342,6 +345,7 @@ Rectangle { passphraseChange.clearPassphraseFields(); passphraseChange.resetSubmitButton(); } else if (msg.method === 'walletSecurity_changeSecurityImage') { + securityImageChange.initModel(); root.activeView = "securityImageChange"; } } diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index d23079d3f3..42ee44d584 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -28,8 +28,8 @@ Item { property bool historyReceived: false; property int pendingCount: 0; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onBalanceResult : { balanceText.text = result.data.balance; @@ -135,8 +135,8 @@ Item { onVisibleChanged: { if (visible) { historyReceived = false; - commerce.balance(); - commerce.history(); + Commerce.balance(); + Commerce.history(); } else { refreshTimer.stop(); } @@ -165,8 +165,8 @@ Item { interval: 4000; onTriggered: { console.log("Refreshing Wallet Home..."); - commerce.balance(); - commerce.history(); + Commerce.balance(); + Commerce.history(); } } @@ -197,14 +197,36 @@ Item { anchors.topMargin: 26; anchors.left: parent.left; anchors.leftMargin: 20; - anchors.right: parent.right; - anchors.rightMargin: 30; + width: paintedWidth; height: 30; // Text size size: 22; // Style color: hifi.colors.baseGrayHighlight; } + + RalewaySemiBold { + id: myPurchasesLink; + text: 'My Purchases'; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 26; + anchors.right: parent.right; + anchors.rightMargin: 20; + width: paintedWidth; + height: 30; + y: 4; + // Text size + size: 18; + // Style + color: hifi.colors.baseGrayHighlight; + horizontalAlignment: Text.AlignRight; + + onLinkActivated: { + sendSignalToWallet({method: 'goToPurchases_fromWalletHome'}); + } + } + ListModel { id: tempTransactionHistoryModel; } diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml index d7859d2800..21da38def0 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml @@ -41,8 +41,8 @@ Item { source: "images/wallet-bg.jpg"; } - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onSecurityImageResult: { if (!exists && root.lastPage === "step_2") { @@ -236,6 +236,7 @@ Item { height: 50; text: "Set Up Wallet"; onClicked: { + securityImageSelection.initModel(); root.activeView = "step_2"; } } @@ -251,7 +252,7 @@ Item { height: 50; text: "Cancel"; onClicked: { - sendSignalToWallet({method: 'walletSetup_cancelClicked'}); + sendSignalToWallet({method: 'walletSetup_cancelClicked', referrer: root.referrer }); } } } @@ -365,7 +366,7 @@ Item { onClicked: { root.lastPage = "step_2"; var securityImagePath = securityImageSelection.getImagePathFromImageID(securityImageSelection.getSelectedImageIndex()) - commerce.chooseSecurityImage(securityImagePath); + Commerce.chooseSecurityImage(securityImagePath); root.activeView = "step_3"; passphraseSelection.clearPassphraseFields(); } @@ -448,7 +449,7 @@ Item { onVisibleChanged: { if (visible) { - commerce.getWalletAuthenticatedStatus(); + Commerce.getWalletAuthenticatedStatus(); } } @@ -534,7 +535,7 @@ Item { onClicked: { if (passphraseSelection.validateAndSubmitPassphrase()) { root.lastPage = "step_3"; - commerce.generateKeyPair(); + Commerce.generateKeyPair(); root.activeView = "step_4"; } } @@ -667,7 +668,7 @@ Item { onVisibleChanged: { if (visible) { - commerce.getKeyFilePathIfExists(); + Commerce.getKeyFilePathIfExists(); } } } diff --git a/interface/resources/qml/hifi/commerce/wallet/images/01.jpg b/interface/resources/qml/hifi/commerce/wallet/images/01.jpg index 821be6c584..199920a32d 100644 Binary files a/interface/resources/qml/hifi/commerce/wallet/images/01.jpg and b/interface/resources/qml/hifi/commerce/wallet/images/01.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/02.jpg b/interface/resources/qml/hifi/commerce/wallet/images/02.jpg index cfcd3e38bf..821be6c584 100644 Binary files a/interface/resources/qml/hifi/commerce/wallet/images/02.jpg and b/interface/resources/qml/hifi/commerce/wallet/images/02.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/04.jpg b/interface/resources/qml/hifi/commerce/wallet/images/04.jpg index 82e3e64be3..1acedf8b10 100644 Binary files a/interface/resources/qml/hifi/commerce/wallet/images/04.jpg and b/interface/resources/qml/hifi/commerce/wallet/images/04.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/05.jpg b/interface/resources/qml/hifi/commerce/wallet/images/05.jpg index c6ce0ac0a1..da73727dea 100644 Binary files a/interface/resources/qml/hifi/commerce/wallet/images/05.jpg and b/interface/resources/qml/hifi/commerce/wallet/images/05.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/06.jpg b/interface/resources/qml/hifi/commerce/wallet/images/06.jpg index 1acedf8b10..bbd0399d3f 100644 Binary files a/interface/resources/qml/hifi/commerce/wallet/images/06.jpg and b/interface/resources/qml/hifi/commerce/wallet/images/06.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/07.jpg b/interface/resources/qml/hifi/commerce/wallet/images/07.jpg new file mode 100644 index 0000000000..e5f86a4a80 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/07.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/08.jpg b/interface/resources/qml/hifi/commerce/wallet/images/08.jpg new file mode 100644 index 0000000000..9d49866c91 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/08.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/09.jpg b/interface/resources/qml/hifi/commerce/wallet/images/09.jpg new file mode 100644 index 0000000000..a1c31bce7d Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/09.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/10.jpg b/interface/resources/qml/hifi/commerce/wallet/images/10.jpg new file mode 100644 index 0000000000..1f6fc1de27 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/10.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/11.jpg b/interface/resources/qml/hifi/commerce/wallet/images/11.jpg new file mode 100644 index 0000000000..37c3b937a5 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/11.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/12.jpg b/interface/resources/qml/hifi/commerce/wallet/images/12.jpg new file mode 100644 index 0000000000..39f653630c Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/12.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/13.jpg b/interface/resources/qml/hifi/commerce/wallet/images/13.jpg new file mode 100644 index 0000000000..3ae86be465 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/13.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/14.jpg b/interface/resources/qml/hifi/commerce/wallet/images/14.jpg new file mode 100644 index 0000000000..8de661e58e Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/14.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/15.jpg b/interface/resources/qml/hifi/commerce/wallet/images/15.jpg new file mode 100644 index 0000000000..f0ac801b89 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/15.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/16.jpg b/interface/resources/qml/hifi/commerce/wallet/images/16.jpg new file mode 100644 index 0000000000..f7c32a696b Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/16.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/17.jpg b/interface/resources/qml/hifi/commerce/wallet/images/17.jpg new file mode 100644 index 0000000000..75e811ee36 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/17.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/18.jpg b/interface/resources/qml/hifi/commerce/wallet/images/18.jpg new file mode 100644 index 0000000000..e4ca0b6f28 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/18.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/19.jpg b/interface/resources/qml/hifi/commerce/wallet/images/19.jpg new file mode 100644 index 0000000000..4c25f3dda2 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/19.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/20.jpg b/interface/resources/qml/hifi/commerce/wallet/images/20.jpg new file mode 100644 index 0000000000..a5a4565a87 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/20.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/21.jpg b/interface/resources/qml/hifi/commerce/wallet/images/21.jpg new file mode 100644 index 0000000000..3747c9558e Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/21.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/22.jpg b/interface/resources/qml/hifi/commerce/wallet/images/22.jpg new file mode 100644 index 0000000000..66003db9c7 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/22.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/23.jpg b/interface/resources/qml/hifi/commerce/wallet/images/23.jpg new file mode 100644 index 0000000000..72e02c6db0 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/23.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/24.jpg b/interface/resources/qml/hifi/commerce/wallet/images/24.jpg new file mode 100644 index 0000000000..e481a587a5 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/24.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/25.jpg b/interface/resources/qml/hifi/commerce/wallet/images/25.jpg new file mode 100644 index 0000000000..1300edf984 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/25.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/26.jpg b/interface/resources/qml/hifi/commerce/wallet/images/26.jpg new file mode 100644 index 0000000000..9e3aaf9383 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/26.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/27.jpg b/interface/resources/qml/hifi/commerce/wallet/images/27.jpg new file mode 100644 index 0000000000..f64c0c6a3d Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/27.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/28.jpg b/interface/resources/qml/hifi/commerce/wallet/images/28.jpg new file mode 100644 index 0000000000..8f1580ab21 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/28.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/29.jpg b/interface/resources/qml/hifi/commerce/wallet/images/29.jpg new file mode 100644 index 0000000000..fdfb4438ec Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/29.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/30.jpg b/interface/resources/qml/hifi/commerce/wallet/images/30.jpg new file mode 100644 index 0000000000..785ea5a999 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/30.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/31.jpg b/interface/resources/qml/hifi/commerce/wallet/images/31.jpg new file mode 100644 index 0000000000..06dc95afab Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/31.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/32.jpg b/interface/resources/qml/hifi/commerce/wallet/images/32.jpg new file mode 100644 index 0000000000..b987c44e13 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/32.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/33.jpg b/interface/resources/qml/hifi/commerce/wallet/images/33.jpg new file mode 100644 index 0000000000..19b05a5f73 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/33.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/34.jpg b/interface/resources/qml/hifi/commerce/wallet/images/34.jpg new file mode 100644 index 0000000000..39f2a5f4ce Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/34.jpg differ diff --git a/interface/resources/qml/hifi/tablet/NewEntityButton.qml b/interface/resources/qml/hifi/tablet/NewEntityButton.qml index 7f838717df..1952ef7ee8 100644 --- a/interface/resources/qml/hifi/tablet/NewEntityButton.qml +++ b/interface/resources/qml/hifi/tablet/NewEntityButton.qml @@ -123,11 +123,11 @@ Item { hoverEnabled: true enabled: true onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); newEntityButton.clicked(); } onEntered: { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); newEntityButton.state = "hover state"; } onExited: { diff --git a/interface/resources/qml/hifi/tablet/Tablet.qml b/interface/resources/qml/hifi/tablet/Tablet.qml deleted file mode 100644 index 2a086bae94..0000000000 --- a/interface/resources/qml/hifi/tablet/Tablet.qml +++ /dev/null @@ -1,261 +0,0 @@ -import QtQuick 2.5 -import QtGraphicalEffects 1.0 -import QtQuick.Layouts 1.3 - -import "../../styles-uit" -import "../audio" as HifiAudio - -Item { - id: tablet - objectName: "tablet" - property int rowIndex: 6 // by default - property int columnIndex: 1 // point to 'go to location' - property int count: (flowMain.children.length - 1) - - // used to look up a button by its uuid - function findButtonIndex(uuid) { - if (!uuid) { - return -1; - } - - for (var i in flowMain.children) { - var child = flowMain.children[i]; - if (child.uuid === uuid) { - return i; - } - } - return -1; - } - - function sortButtons() { - var children = []; - for (var i = 0; i < flowMain.children.length; i++) { - children[i] = flowMain.children[i]; - } - - children.sort(function (a, b) { - if (a.sortOrder === b.sortOrder) { - // subsort by stableOrder, because JS sort is not stable in qml. - return a.stableOrder - b.stableOrder; - } else { - return a.sortOrder - b.sortOrder; - } - }); - - flowMain.children = children; - } - - // called by C++ code when a button should be added to the tablet - function addButtonProxy(properties) { - var component = Qt.createComponent("TabletButton.qml"); - var button = component.createObject(flowMain); - - // copy all properites to button - var keys = Object.keys(properties).forEach(function (key) { - button[key] = properties[key]; - }); - - // pass a reference to the tabletRoot object to the button. - if (tabletRoot) { - button.tabletRoot = tabletRoot; - } else { - button.tabletRoot = parent.parent; - } - - sortButtons(); - - return button; - } - - // called by C++ code when a button should be removed from the tablet - function removeButtonProxy(properties) { - var index = findButtonIndex(properties.uuid); - if (index < 0) { - console.log("Warning: Tablet.qml could not find button with uuid = " + properties.uuid); - } else { - flowMain.children[index].destroy(); - } - } - - Rectangle { - id: bgTopBar - height: 90 - - anchors { - top: parent.top - topMargin: 0 - left: parent.left - leftMargin: 0 - right: parent.right - rightMargin: 0 - } - - gradient: Gradient { - GradientStop { - position: 0 - color: "#2b2b2b" - } - - GradientStop { - position: 1 - color: "#1e1e1e" - } - } - - HifiAudio.MicBar { - anchors { - left: parent.left - leftMargin: 30 - verticalCenter: parent.verticalCenter - } - } - - Item { - width: 150 - height: 50 - anchors.right: parent.right - anchors.rightMargin: 30 - anchors.verticalCenter: parent.verticalCenter - - ColumnLayout { - anchors.fill: parent - - RalewaySemiBold { - text: Account.loggedIn ? qsTr("Log out") : qsTr("Log in") - horizontalAlignment: Text.AlignRight - anchors.right: parent.right - font.pixelSize: 20 - color: "#afafaf" - } - - RalewaySemiBold { - visible: Account.loggedIn - height: Account.loggedIn ? parent.height/2 - parent.spacing/2 : 0 - text: Account.loggedIn ? "[" + tabletRoot.usernameShort + "]" : "" - horizontalAlignment: Text.AlignRight - anchors.right: parent.right - font.pixelSize: 20 - color: "#afafaf" - } - } - - MouseArea { - anchors.fill: parent - onClicked: { - if (!Account.loggedIn) { - DialogsManager.showLoginDialog() - } else { - Account.logOut() - } - } - } - } - } - - Rectangle { - id: bgMain - gradient: Gradient { - GradientStop { - position: 0 - color: "#2b2b2b" - } - - GradientStop { - position: 1 - color: "#0f212e" - } - } - anchors.bottom: parent.bottom - anchors.bottomMargin: 0 - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.top: bgTopBar.bottom - anchors.topMargin: 0 - - Flickable { - id: flickable - width: parent.width - height: parent.height - contentWidth: parent.width - contentHeight: flowMain.childrenRect.height + flowMain.anchors.topMargin + flowMain.anchors.bottomMargin + flowMain.spacing - clip: true - Flow { - id: flowMain - spacing: 16 - anchors.right: parent.right - anchors.rightMargin: 30 - anchors.left: parent.left - anchors.leftMargin: 30 - anchors.bottom: parent.bottom - anchors.bottomMargin: 30 - anchors.top: parent.top - anchors.topMargin: 30 - } - } - } - - function setCurrentItemState(state) { - var index = rowIndex + columnIndex; - - if (index >= 0 && index <= count ) { - flowMain.children[index].state = state; - } - } - - function nextItem() { - setCurrentItemState("base state"); - var nextColumnIndex = (columnIndex + 3 + 1) % 3; - var nextIndex = rowIndex + nextColumnIndex; - if(nextIndex <= count) { - columnIndex = nextColumnIndex; - }; - setCurrentItemState("hover state"); - } - - function previousItem() { - setCurrentItemState("base state"); - var prevIndex = (columnIndex + 3 - 1) % 3; - if((rowIndex + prevIndex) <= count){ - columnIndex = prevIndex; - } - setCurrentItemState("hover state"); - } - - function upItem() { - setCurrentItemState("base state"); - rowIndex = rowIndex - 3; - if (rowIndex < 0 ) { - rowIndex = (count - (count % 3)); - var index = rowIndex + columnIndex; - if(index > count) { - rowIndex = rowIndex - 3; - } - } - setCurrentItemState("hover state"); - } - - function downItem() { - setCurrentItemState("base state"); - rowIndex = rowIndex + 3; - var index = rowIndex + columnIndex; - if (index > count ) { - rowIndex = 0; - } - setCurrentItemState("hover state"); - } - - function selectItem() { - flowMain.children[rowIndex + columnIndex].clicked(); - if (tabletRoot) { - tabletRoot.playButtonClickSound(); - } - } - - Keys.onRightPressed: nextItem(); - Keys.onLeftPressed: previousItem(); - Keys.onDownPressed: downItem(); - Keys.onUpPressed: upItem(); - Keys.onReturnPressed: selectItem(); -} diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 160151144b..217adc5083 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -72,10 +72,14 @@ StackView { Component { id: tabletWebView; TabletWebView {} } Component.onCompleted: { updateLocationText(false); - addressLine.focus = !HMD.active; root.parentChanged.connect(center); center(); tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + + Qt.callLater(function() { + addressBarDialog.keyboardEnabled = HMD.active; + addressLine.forceActiveFocus(); + }) } Component.onDestruction: { root.parentChanged.disconnect(center); diff --git a/interface/resources/qml/hifi/tablet/TabletButton.qml b/interface/resources/qml/hifi/tablet/TabletButton.qml index 8fc31d1cd6..4d443fb97c 100644 --- a/interface/resources/qml/hifi/tablet/TabletButton.qml +++ b/interface/resources/qml/hifi/tablet/TabletButton.qml @@ -23,11 +23,26 @@ Item { property double sortOrder: 100 property int stableOrder: 0 property var tabletRoot; + property var flickable: null + property var gridView: null + + property int buttonIndex: -1 + width: 129 height: 129 signal clicked() + Connections { + target: flickable + onMovingChanged: { + //when flick/move started, and hover is on, clean hove state + if (flickable.moving && tabletButton.state.indexOf("hover") !== -1) { + tabletButton.state = (tabletButton.isActive) ? "active state" : "base state"; + } + } + } + function changeProperty(key, value) { tabletButton[key] = value; } @@ -121,9 +136,10 @@ Item { anchors.fill: parent hoverEnabled: true enabled: true - preventStealing: true + preventStealing: false onClicked: { - console.log("Tablet Button Clicked!"); + gridView.currentIndex = buttonIndex + if (tabletButton.inDebugMode) { if (tabletButton.isActive) { tabletButton.isActive = false; @@ -131,14 +147,17 @@ Item { tabletButton.isActive = true; } } + tabletButton.clicked(); if (tabletRoot) { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } } + onEntered: { + gridView.currentIndex = buttonIndex tabletButton.isEntered = true; - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); if (tabletButton.isActive) { tabletButton.state = "hover active state"; diff --git a/interface/resources/qml/hifi/tablet/TabletHome.qml b/interface/resources/qml/hifi/tablet/TabletHome.qml new file mode 100644 index 0000000000..e934f18ab6 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/TabletHome.qml @@ -0,0 +1,296 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtGraphicalEffects 1.0 +import QtQuick.Layouts 1.3 + +import TabletScriptingInterface 1.0 + +import "." +import "../../styles-uit" +import "../audio" as HifiAudio + +Item { + id: tablet + objectName: "tablet" + property var tabletProxy: Tablet.getTablet("com.highfidelity.interface.tablet.system"); + + property var currentGridItems: null + + focus: true + + Rectangle { + id: bgTopBar + height: 90 + + anchors { + top: parent.top + left: parent.left + right: parent.right + } + + gradient: Gradient { + GradientStop { + position: 0 + color: "#2b2b2b" + } + + GradientStop { + position: 1 + color: "#1e1e1e" + } + } + + HifiAudio.MicBar { + anchors { + left: parent.left + leftMargin: 30 + verticalCenter: parent.verticalCenter + } + } + + Item { + width: 150 + height: 50 + anchors.right: parent.right + anchors.rightMargin: 30 + anchors.verticalCenter: parent.verticalCenter + + ColumnLayout { + anchors.fill: parent + + RalewaySemiBold { + text: Account.loggedIn ? qsTr("Log out") : qsTr("Log in") + horizontalAlignment: Text.AlignRight + anchors.right: parent.right + font.pixelSize: 20 + color: "#afafaf" + } + + RalewaySemiBold { + visible: Account.loggedIn + height: Account.loggedIn ? parent.height/2 - parent.spacing/2 : 0 + text: Account.loggedIn ? "[" + tabletRoot.usernameShort + "]" : "" + horizontalAlignment: Text.AlignRight + anchors.right: parent.right + font.pixelSize: 20 + color: "#afafaf" + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + if (!Account.loggedIn) { + DialogsManager.showLoginDialog() + } else { + Account.logOut() + } + } + } + } + } + + Rectangle { + id: bgMain + gradient: Gradient { + GradientStop { + position: 0 + color: "#2b2b2b" + } + + GradientStop { + position: 1 + color: "#0f212e" + } + } + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + anchors.top: bgTopBar.bottom + + SwipeView { + id: swipeView + clip: false + currentIndex: -1 + property int previousIndex: -1 + Repeater { + id: pageRepeater + model: Math.ceil(tabletProxy.buttons.rowCount() / TabletEnums.ButtonsOnPage) + onItemAdded: { + item.proxyModel.sourceModel = tabletProxy.buttons; + item.proxyModel.pageIndex = index; + } + + delegate: Item { + id: page + property TabletButtonsProxyModel proxyModel: TabletButtonsProxyModel {} + + GridView { + id: gridView + keyNavigationEnabled: false + highlightFollowsCurrentItem: false + property int previousGridIndex: -1 + anchors { + fill: parent + topMargin: 20 + leftMargin: 30 + rightMargin: 30 + bottomMargin: 0 + } + + function setButtonState(buttonIndex, buttonstate) { + if (buttonIndex < 0 || gridView.contentItem === undefined + || gridView.contentItem.children.length - 1 < buttonIndex) { + return; + } + var itemat = gridView.contentItem.children[buttonIndex].children[0]; + if (itemat.isActive) { + itemat.state = "active state"; + } else { + itemat.state = buttonstate; + } + } + + onCurrentIndexChanged: { + setButtonState(previousGridIndex, "base state"); + setButtonState(currentIndex, "hover state"); + previousGridIndex = currentIndex + } + + cellWidth: width/3 + cellHeight: cellWidth + flow: GridView.LeftToRight + model: page.proxyModel + + delegate: Item { + id: wrapper + width: gridView.cellWidth + height: gridView.cellHeight + + property var proxy: modelData + + TabletButton { + id: tabletButton + anchors.centerIn: parent + gridView: wrapper.GridView.view + buttonIndex: page.proxyModel.buttonIndex(uuid); + flickable: swipeView.contentItem; + onClicked: modelData.clicked() + } + + Connections { + target: modelData; + onPropertiesChanged: { + updateProperties(); + } + } + + Component.onCompleted: updateProperties() + + function updateProperties() { + var keys = Object.keys(modelData.properties).forEach(function (key) { + if (tabletButton[key] !== modelData.properties[key]) { + tabletButton[key] = modelData.properties[key]; + } + }); + } + } + } + } + } + + onCurrentIndexChanged: { + if (swipeView.currentIndex < 0 + || swipeView.itemAt(swipeView.currentIndex) === null + || swipeView.itemAt(swipeView.currentIndex).children[0] === null) { + return; + } + + currentGridItems = swipeView.itemAt(swipeView.currentIndex).children[0]; + + currentGridItems.currentIndex = (previousIndex > swipeView.currentIndex ? currentGridItems.count - 1 : 0); + previousIndex = currentIndex; + } + + hoverEnabled: true + anchors { + left: parent.left + right: parent.right + top: parent.top + bottom: pageIndicator.top + } + } + + PageIndicator { + id: pageIndicator + currentIndex: swipeView.currentIndex + + delegate: Item { + width: 15 + height: 15 + + Rectangle { + anchors.centerIn: parent + opacity: index === pageIndicator.currentIndex ? 0.95 : 0.45 + implicitWidth: index === pageIndicator.currentIndex ? 15 : 10 + implicitHeight: implicitWidth + radius: width/2 + color: "white" + Behavior on opacity { + OpacityAnimator { + duration: 100 + } + } + } + } + + interactive: false + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + count: swipeView.count + } + } + + Component.onCompleted: { + focus = true; + forceActiveFocus(); + } + + Keys.onRightPressed: { + if (!currentGridItems) { + return; + } + + var index = currentGridItems.currentIndex; + currentGridItems.moveCurrentIndexRight(); + if (index === currentGridItems.count - 1 && index === currentGridItems.currentIndex) { + if (swipeView.currentIndex < swipeView.count - 1) { + swipeView.incrementCurrentIndex(); + } + } + } + + Keys.onLeftPressed: { + if (!currentGridItems) { + return; + } + + var index = currentGridItems.currentIndex; + currentGridItems.moveCurrentIndexLeft(); + if (index === 0 && index === currentGridItems.currentIndex) { + if (swipeView.currentIndex > 0) { + swipeView.decrementCurrentIndex(); + } + } + } + Keys.onDownPressed: currentGridItems.moveCurrentIndexDown(); + Keys.onUpPressed: currentGridItems.moveCurrentIndexUp(); + Keys.onReturnPressed: { + if (currentGridItems.currentItem) { + currentGridItems.currentItem.proxy.clicked(); + if (tabletRoot) { + tabletRoot.playButtonClickSound(); + } + } + } +} diff --git a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml index 8cd696a41b..ff653b6457 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml @@ -48,11 +48,15 @@ Item { } function pushSource(path) { - d.push(Qt.resolvedUrl(path)); - d.currentItem.sendToScript.connect(tabletMenu.sendToScript); + d.push(Qt.resolvedUrl("../../" + path)); + if (d.currentItem.sendToScript !== undefined) { + d.currentItem.sendToScript.connect(tabletMenu.sendToScript); + } d.currentItem.focus = true; d.currentItem.forceActiveFocus(); - breadcrumbText.text = d.currentItem.title; + if (d.currentItem.title !== undefined) { + breadcrumbText.text = d.currentItem.title; + } if (typeof bgNavBar !== "undefined") { d.currentItem.y = bgNavBar.height; d.currentItem.height -= bgNavBar.height; diff --git a/interface/resources/qml/hifi/tablet/TabletMenuView.qml b/interface/resources/qml/hifi/tablet/TabletMenuView.qml index 4a4a6b7f87..636ebfe71f 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuView.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuView.qml @@ -77,12 +77,12 @@ FocusScope { anchors.fill: parent hoverEnabled: true onEntered: { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); listView.currentIndex = index } onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); root.selected(item); } } diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index a161741049..da544c2114 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -68,37 +68,36 @@ Item { function loadSource(url) { tabletApps.clear(); - loader.source = ""; // make sure we load the qml fresh each time. - loader.source = url; tabletApps.append({"appUrl": url, "isWebUrl": false, "scriptUrl": "", "appWebUrl": ""}); + loader.load(url) } function loadQMLOnTop(url) { tabletApps.append({"appUrl": url, "isWebUrl": false, "scriptUrl": "", "appWebUrl": ""}); - loader.source = ""; - loader.source = tabletApps.get(currentApp).appUrl; - if (loader.item.hasOwnProperty("gotoPreviousApp")) { - loader.item.gotoPreviousApp = true; - } + loader.load(tabletApps.get(currentApp).appUrl, function(){ + if (loader.item.hasOwnProperty("gotoPreviousApp")) { + loader.item.gotoPreviousApp = true; + } + }) } - function loadWebOnTop(url, injectJavaScriptUrl) { - tabletApps.append({"appUrl": loader.source, "isWebUrl": true, "scriptUrl": injectJavaScriptUrl, "appWebUrl": url}); - loader.item.url = tabletApps.get(currentApp).appWebUrl; - loader.item.scriptUrl = tabletApps.get(currentApp).scriptUrl; - if (loader.item.hasOwnProperty("gotoPreviousApp")) { - loader.item.gotoPreviousApp = true; - } + function loadWebContent(source, url, injectJavaScriptUrl) { + tabletApps.append({"appUrl": source, "isWebUrl": true, "scriptUrl": injectJavaScriptUrl, "appWebUrl": url}); + loader.load(source, function() { + loader.item.scriptURL = injectJavaScriptUrl; + loader.item.url = url; + if (loader.item.hasOwnProperty("gotoPreviousApp")) { + loader.item.gotoPreviousApp = true; + } + }); } - function loadWebBase() { - loader.source = ""; - loader.source = "TabletWebView.qml"; + function loadWebBase(url, injectJavaScriptUrl) { + loadWebContent("hifi/tablet/TabletWebView.qml", url, injectJavaScriptUrl); } - function loadTabletWebBase() { - loader.source = ""; - loader.source = "./BlocksWebView.qml"; + function loadTabletWebBase(url, injectJavaScriptUrl) { + loadWebContent("hifi/tablet/BlocksWebView.qml", url, injectJavaScriptUrl); } function returnToPreviousApp() { @@ -110,7 +109,7 @@ Item { loadSource("TabletWebView.qml"); loadWebUrl(webUrl, scriptUrl); } else { - loader.source = tabletApps.get(currentApp).appUrl; + loader.load(tabletApps.get(currentApp).appUrl); } } @@ -173,47 +172,79 @@ Item { } } - Loader { - id: loader - objectName: "loader" - asynchronous: false - - width: parent.width - height: parent.height - - // Hook up callback for clara.io download from the marketplace. - Connections { - id: eventBridgeConnection - target: eventBridge - onWebEventReceived: { - if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { - ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); - } - } - } - - onLoaded: { - if (loader.item.hasOwnProperty("sendToScript")) { - loader.item.sendToScript.connect(tabletRoot.sendToScript); - } - if (loader.item.hasOwnProperty("setRootMenu")) { - loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu); - } - loader.item.forceActiveFocus(); - - if (openModal) { - openModal.canceled(); - openModal.destroy(); - openModal = null; - } - - if (openBrowser) { - openBrowser.destroy(); - openBrowser = null; + // Hook up callback for clara.io download from the marketplace. + Connections { + id: eventBridgeConnection + target: eventBridge + onWebEventReceived: { + if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { + ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); } } } + Item { + id: loader + objectName: "loader"; + anchors.fill: parent; + property string source: ""; + property var item: null; + signal loaded; + + onWidthChanged: { + if (loader.item) { + loader.item.width = loader.width; + } + } + + onHeightChanged: { + if (loader.item) { + loader.item.height = loader.height; + } + } + + function load(newSource, callback) { + if (loader.source == newSource) { + loader.loaded(); + return; + } + + if (loader.item) { + loader.item.destroy(); + loader.item = null; + } + + QmlSurface.load(newSource, loader, function(newItem) { + loader.item = newItem; + loader.item.width = loader.width; + loader.item.height = loader.height; + loader.loaded(); + if (loader.item.hasOwnProperty("sendToScript")) { + loader.item.sendToScript.connect(tabletRoot.sendToScript); + } + if (loader.item.hasOwnProperty("setRootMenu")) { + loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu); + } + loader.item.forceActiveFocus(); + + if (openModal) { + openModal.canceled(); + openModal.destroy(); + openModal = null; + } + + if (openBrowser) { + openBrowser.destroy(); + openBrowser = null; + } + + if (callback) { + callback(); + } + }); + } + } + width: 480 height: 706 diff --git a/interface/resources/qml/hifi/tablet/WindowRoot.qml b/interface/resources/qml/hifi/tablet/WindowRoot.qml index e3170f85ef..9c027308b8 100644 --- a/interface/resources/qml/hifi/tablet/WindowRoot.qml +++ b/interface/resources/qml/hifi/tablet/WindowRoot.qml @@ -59,26 +59,25 @@ Windows.ScrollingWindow { } function loadSource(url) { - loader.source = ""; // make sure we load the qml fresh each time. - loader.source = url; + loader.load(url) } - function loadWebBase() { - loader.source = ""; - loader.source = "WindowWebView.qml"; + function loadWebContent(source, url, injectJavaScriptUrl) { + loader.load(source, function() { + loader.item.scriptURL = injectJavaScriptUrl; + loader.item.url = url; + if (loader.item.hasOwnProperty("closeButtonVisible")) { + loader.item.closeButtonVisible = false; + } + }); } - function loadTabletWebBase() { - loader.source = ""; - loader.source = "./BlocksWebView.qml"; + function loadWebBase(url, injectJavaScriptUrl) { + loadWebContent("hifi/tablet/TabletWebView.qml", url, injectJavaScriptUrl); } - function loadWebUrl(url, injectedJavaScriptUrl) { - loader.item.url = url; - loader.item.scriptURL = injectedJavaScriptUrl; - if (loader.item.hasOwnProperty("closeButtonVisible")) { - loader.item.closeButtonVisible = false; - } + function loadTabletWebBase(url, injectJavaScriptUrl) { + loadWebContent("hifi/tablet/BlocksWebView.qml", url, injectJavaScriptUrl); } // used to send a message from qml to interface script. @@ -111,38 +110,68 @@ Windows.ScrollingWindow { username = newUsername; } - Loader { + // Hook up callback for clara.io download from the marketplace. + Connections { + id: eventBridgeConnection + target: eventBridge + onWebEventReceived: { + if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { + ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); + } + } + } + + Item { id: loader - objectName: "loader" - asynchronous: false + objectName: "loader"; + property string source: ""; + property var item: null; height: pane.scrollHeight width: pane.contentWidth anchors.left: parent.left anchors.top: parent.top - - // Hook up callback for clara.io download from the marketplace. - Connections { - id: eventBridgeConnection - target: eventBridge - onWebEventReceived: { - if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { - ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); - } + signal loaded; + + onWidthChanged: { + if (loader.item) { + loader.item.width = loader.width; } } - - onLoaded: { - if (loader.item.hasOwnProperty("sendToScript")) { - loader.item.sendToScript.connect(tabletRoot.sendToScript); + + onHeightChanged: { + if (loader.item) { + loader.item.height = loader.height; } - if (loader.item.hasOwnProperty("setRootMenu")) { - loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu); + } + + function load(newSource, callback) { + if (loader.item) { + loader.item.destroy(); + loader.item = null; } - loader.item.forceActiveFocus(); + + QmlSurface.load(newSource, loader, function(newItem) { + loader.item = newItem; + loader.item.width = loader.width; + loader.item.height = loader.height; + loader.loaded(); + if (loader.item.hasOwnProperty("sendToScript")) { + loader.item.sendToScript.connect(tabletRoot.sendToScript); + } + if (loader.item.hasOwnProperty("setRootMenu")) { + loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu); + } + loader.item.forceActiveFocus(); + + if (callback) { + callback(); + } + }); } } + implicitWidth: 480 implicitHeight: 706 } diff --git a/interface/resources/qml/hifi/toolbars/Toolbar.qml b/interface/resources/qml/hifi/toolbars/Toolbar.qml index 01aa29f665..ff7e835690 100644 --- a/interface/resources/qml/hifi/toolbars/Toolbar.qml +++ b/interface/resources/qml/hifi/toolbars/Toolbar.qml @@ -11,6 +11,8 @@ Window { horizontalSpacers: horizontal verticalSpacers: !horizontal } + property var tabletProxy; + property var buttonModel: ListModel {} hideBackground: true resizable: false destroyOnCloseButton: false @@ -23,24 +25,32 @@ Window { activator: Item {} property bool horizontal: true property real buttonSize: 50; - property var buttons: [] - property var container: horizontal ? row : column Settings { category: "toolbar/" + window.objectName property alias x: window.x property alias y: window.y } + + Component { + id: buttonComponent + ToolbarButton { + id: toolbarButton + property var proxy: modelData; + onClicked: proxy.clicked() + Component.onCompleted: updateProperties() - onHorizontalChanged: { - var newParent = horizontal ? row : column; - for (var i in buttons) { - var child = buttons[i]; - child.parent = newParent; - if (horizontal) { - child.y = 0 - } else { - child.x = 0 + Connections { + target: proxy; + onPropertiesChanged: updateProperties(); + } + + function updateProperties() { + Object.keys(proxy.properties).forEach(function (key) { + if (toolbarButton[key] !== proxy.properties[key]) { + toolbarButton[key] = proxy.properties[key]; + } + }); } } } @@ -52,97 +62,22 @@ Window { Row { id: row + visible: window.horizontal spacing: 6 + Repeater { + model: buttonModel + delegate: buttonComponent + } } - + Column { id: column + visible: !window.horizontal spacing: 6 - } - - Component { id: toolbarButtonBuilder; ToolbarButton { } } - } - - - function findButtonIndex(name) { - if (!name) { - return -1; - } - - for (var i in buttons) { - var child = buttons[i]; - if (child.objectName === name) { - return i; + Repeater { + model: buttonModel + delegate: buttonComponent } } - return -1; - } - - function findButton(name) { - var index = findButtonIndex(name); - if (index < 0) { - return; - } - return buttons[index]; - } - - function sortButtons() { - var children = []; - for (var i = 0; i < container.children.length; i++) { - children[i] = container.children[i]; - } - - children.sort(function (a, b) { - if (a.sortOrder === b.sortOrder) { - // subsort by stableOrder, because JS sort is not stable in qml. - return a.stableOrder - b.stableOrder; - } else { - return a.sortOrder - b.sortOrder; - } - }); - - container.children = children; - } - - function addButton(properties) { - properties = properties || {} - - // If a name is specified, then check if there's an existing button with that name - // and return it if so. This will allow multiple clients to listen to a single button, - // and allow scripts to be idempotent so they don't duplicate buttons if they're reloaded - var result = findButton(properties.objectName); - if (result) { - for (var property in properties) { - result[property] = properties[property]; - } - return result; - } - properties.toolbar = this; - properties.opacity = 0; - result = toolbarButtonBuilder.createObject(container, properties); - buttons.push(result); - - result.opacity = 1; - - sortButtons(); - - fadeIn(null); - - return result; - } - - function removeButton(name) { - var index = findButtonIndex(name); - if (index < -1) { - console.warn("Tried to remove non-existent button " + name); - return; - } - - buttons[index].destroy(); - buttons.splice(index, 1); - - if (buttons.length === 0) { - fadeOut(null); - } } } diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml index f27ba6bb24..7781d1140b 100644 --- a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -27,7 +27,8 @@ StateImage { property string activeHoverIcon: button.activeIcon property int sortOrder: 100 - property int stableSortOrder: 0 + property int stableOrder: 0 + property var uuid; signal clicked() diff --git a/interface/resources/styles/filter.png b/interface/resources/styles/filter.png new file mode 100644 index 0000000000..867cfe19bc Binary files /dev/null and b/interface/resources/styles/filter.png differ diff --git a/interface/resources/styles/log_dialog.qss b/interface/resources/styles/log_dialog.qss index d3ae4e0a00..33473d2903 100644 --- a/interface/resources/styles/log_dialog.qss +++ b/interface/resources/styles/log_dialog.qss @@ -67,8 +67,22 @@ QPushButton#revealLogButton { font-size: 11px; } +QPushButton#showAllButton { + font-family: Helvetica, Arial, sans-serif; + background-color: #333333; + color: #BBBBBB; + border-width: 0; + border-radius: 9px; + font-size: 11px; +} + QCheckBox { font-family: Helvetica, Arial, sans-serif; + text-align: center; + color: #3d3d3d; + border-width: 0; + border-radius: 9px; + font-size: 11px; } QCheckBox::indicator:unchecked { @@ -77,4 +91,25 @@ QCheckBox::indicator:unchecked { QCheckBox::indicator:checked { image: url(styles/checked.svg); +} + +QComboBox { + font-family: Helvetica, Arial, sans-serif; + text-align: center; + background-color: #CCCCCC; + color: #3d3d3d; + border-width: 0; + border-radius: 9px; + font-size: 11px; + padding-left: 7px; +} + +QComboBox::drop-down { + border-width: 0px; + padding-right: 7px; +} + +QComboBox::down-arrow { + image: url(styles/filter.png); + border-width: 0px; } \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3e4ab16908..f3c41565f8 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -154,7 +154,6 @@ #include "scripting/Audio.h" #include "networking/CloseEventSender.h" #include "scripting/TestScriptingInterface.h" -#include "scripting/AccountScriptingInterface.h" #include "scripting/AssetMappingsScriptingInterface.h" #include "scripting/ClipboardScriptingInterface.h" #include "scripting/DesktopScriptingInterface.h" @@ -191,6 +190,7 @@ #include #include #include +#include #include #include @@ -698,6 +698,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(nullptr, qApp->getOcteeSceneStats()); DependencyManager::set(); DependencyManager::set(); @@ -737,6 +738,7 @@ const float DEFAULT_HMD_TABLET_SCALE_PERCENT = 100.0f; const float DEFAULT_DESKTOP_TABLET_SCALE_PERCENT = 75.0f; const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true; const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false; +const bool DEFAULT_PREFER_STYLUS_OVER_LASER = false; const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = false; const QString DEFAULT_CURSOR_NAME = "DEFAULT"; @@ -756,6 +758,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _desktopTabletScale("desktopTabletScale", DEFAULT_DESKTOP_TABLET_SCALE_PERCENT), _desktopTabletBecomesToolbarSetting("desktopTabletBecomesToolbar", DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR), _hmdTabletBecomesToolbarSetting("hmdTabletBecomesToolbar", DEFAULT_HMD_TABLET_BECOMES_TOOLBAR), + _preferStylusOverLaserSetting("preferStylusOverLaser", DEFAULT_PREFER_STYLUS_OVER_LASER), _preferAvatarFingerOverStylusSetting("preferAvatarFingerOverStylus", DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS), _constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true), _preferredCursor("preferredCursor", DEFAULT_CURSOR_NAME), @@ -1189,8 +1192,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo userActivityLogger.logAction("launch", properties); } - // Tell our entity edit sender about our known jurisdictions - _entityEditSender.setServerJurisdictions(&_entityServerJurisdictions); _entityEditSender.setMyAvatar(myAvatar.get()); // The entity octree will have to know about MyAvatar for the parentJointName import @@ -1443,7 +1444,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(audioIO.data(), &AudioClient::noiseGateClosed, audioScriptingInterface.data(), &AudioScriptingInterface::noiseGateClosed); connect(audioIO.data(), &AudioClient::inputReceived, audioScriptingInterface.data(), &AudioScriptingInterface::inputReceived); - this->installEventFilter(this); #ifdef HAVE_DDE @@ -1467,8 +1467,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo applicationUpdater->checkForUpdate(); } - // Now that menu is initialized we can sync myAvatar with it's state. - myAvatar->updateMotionBehaviorFromMenu(); + Menu::getInstance()->setIsOptionChecked(MenuOption::ActionMotorControl, true); // FIXME spacemouse code still needs cleanup #if 0 @@ -1496,6 +1495,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo } }); + connect(getEntities()->getTree().get(), &EntityTree::deletingEntity, [=](const EntityItemID& entityItemID) { + auto avatarManager = DependencyManager::get(); + auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; + if (myAvatar) { + myAvatar->clearAvatarEntity(entityItemID); + } + }); + // Keyboard focus handling for Web overlays. auto overlays = &(qApp->getOverlays()); connect(overlays, &Overlays::overlayDeleted, [=](const OverlayID& overlayID) { @@ -2240,27 +2247,65 @@ extern void setupPreferences(); void Application::initializeUi() { // Make sure all QML surfaces share the main thread GL context OffscreenQmlSurface::setSharedContext(_offscreenContext->getContext()); + OffscreenQmlSurface::addWhitelistContextHandler(QUrl{ "OverlayWindowTest.qml" }, + [](QQmlContext* context) { + qDebug() << "Whitelist OverlayWindow worked"; + context->setContextProperty("OverlayWindowTestString", "TestWorked"); + }); + OffscreenQmlSurface::addWhitelistContextHandler(QUrl{ "hifi/audio/Audio.qml" }, + [](QQmlContext* context) { + qDebug() << "QQQ" << __FUNCTION__ << "Whitelist Audio worked"; + }); + AddressBarDialog::registerType(); ErrorDialog::registerType(); LoginDialog::registerType(); Tooltip::registerType(); UpdateDialog::registerType(); - QmlCommerce::registerType(); + QmlContextCallback callback = [](QQmlContext* context) { + context->setContextProperty("Commerce", new QmlCommerce()); + }; + OffscreenQmlSurface::addWhitelistContextHandler({ + QUrl{ "hifi/commerce/checkout/Checkout.qml" }, + QUrl{ "hifi/commerce/common/CommerceLightbox.qml" }, + QUrl{ "hifi/commerce/common/EmulatedMarketplaceHeader.qml" }, + QUrl{ "hifi/commerce/common/FirstUseTutorial.qml" }, + QUrl{ "hifi/commerce/common/SortableListModel.qml" }, + QUrl{ "hifi/commerce/inspectionCertificate/InspectionCertificate.qml" }, + QUrl{ "hifi/commerce/purchases/PurchasedItem.qml" }, + QUrl{ "hifi/commerce/purchases/Purchases.qml" }, + QUrl{ "hifi/commerce/wallet/Help.qml" }, + QUrl{ "hifi/commerce/wallet/NeedsLogIn.qml" }, + QUrl{ "hifi/commerce/wallet/PassphraseChange.qml" }, + QUrl{ "hifi/commerce/wallet/PassphraseModal.qml" }, + QUrl{ "hifi/commerce/wallet/PassphraseSelection.qml" }, + QUrl{ "hifi/commerce/wallet/Security.qml" }, + QUrl{ "hifi/commerce/wallet/SecurityImageChange.qml" }, + QUrl{ "hifi/commerce/wallet/SecurityImageModel.qml" }, + QUrl{ "hifi/commerce/wallet/SecurityImageSelection.qml" }, + QUrl{ "hifi/commerce/wallet/SendMoney.qml" }, + QUrl{ "hifi/commerce/wallet/Wallet.qml" }, + QUrl{ "hifi/commerce/wallet/WalletHome.qml" }, + QUrl{ "hifi/commerce/wallet/WalletSetup.qml" }, + }, callback); qmlRegisterType("Hifi", 1, 0, "ResourceImageItem"); qmlRegisterType("Hifi", 1, 0, "Preference"); qmlRegisterType("HifiWeb", 1, 0, "WebBrowserSuggestionsEngine"); + { + auto tabletScriptingInterface = DependencyManager::get(); + tabletScriptingInterface->getTablet(SYSTEM_TABLET); + } auto offscreenUi = DependencyManager::get(); offscreenUi->create(); auto surfaceContext = offscreenUi->getSurfaceContext(); offscreenUi->setProxyWindow(_window->windowHandle()); - offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); // OffscreenUi is a subclass of OffscreenQmlSurface specifically designed to // support the window management and scripting proxies for VR use - offscreenUi->createDesktop(QString("qrc:///qml/hifi/Desktop.qml")); + offscreenUi->createDesktop(QString("hifi/Desktop.qml")); // FIXME either expose so that dialogs can set this themselves or // do better detection in the offscreen UI of what has focus @@ -2274,9 +2319,10 @@ void Application::initializeUi() { setupPreferences(); - // For some reason there is already an "Application" object in the QML context, - // though I can't find it. Hence, "ApplicationInterface" - surfaceContext->setContextProperty("Audio", DependencyManager::get().data()); + // in Qt 5.10.0 there is already an "Audio" object in the QML context + // though I failed to find it (from QtMultimedia??). So.. let it be "AudioScriptingInterface" + surfaceContext->setContextProperty("AudioScriptingInterface", DependencyManager::get().data()); + surfaceContext->setContextProperty("AudioStats", DependencyManager::get()->getStats().data()); surfaceContext->setContextProperty("AudioScope", DependencyManager::get().data()); @@ -2327,10 +2373,7 @@ void Application::initializeUi() { surfaceContext->setContextProperty("SoundCache", DependencyManager::get().data()); surfaceContext->setContextProperty("InputConfiguration", DependencyManager::get().data()); - surfaceContext->setContextProperty("Account", AccountScriptingInterface::getInstance()); - surfaceContext->setContextProperty("Tablet", DependencyManager::get().data()); - // Tablet inteference with Tablet.qml. Need to avoid this in QML space - surfaceContext->setContextProperty("tabletInterface", DependencyManager::get().data()); + surfaceContext->setContextProperty("Account", GlobalServicesScriptingInterface::getInstance()); surfaceContext->setContextProperty("DialogsManager", _dialogsManagerScriptingInterface); surfaceContext->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance()); surfaceContext->setContextProperty("FaceTracker", DependencyManager::get().data()); @@ -2539,6 +2582,10 @@ void Application::setHmdTabletBecomesToolbarSetting(bool value) { updateSystemTabletMode(); } +void Application::setPreferStylusOverLaser(bool value) { + _preferStylusOverLaserSetting.set(value); +} + void Application::setPreferAvatarFingerOverStylus(bool value) { _preferAvatarFingerOverStylusSetting.set(value); } @@ -3222,8 +3269,6 @@ void Application::keyPressEvent(QKeyEvent* event) { } } - - void Application::keyReleaseEvent(QKeyEvent* event) { _keysPressed.remove(event->key()); @@ -4238,7 +4283,7 @@ void Application::init() { getEntities()->init(); getEntities()->setEntityLoadingPriorityFunction([this](const EntityItem& item) { - auto dims = item.getDimensions(); + auto dims = item.getScaledDimensions(); auto maxSize = glm::compMax(dims); if (maxSize <= 0.0f) { @@ -4282,9 +4327,11 @@ void Application::updateLOD(float deltaTime) const { PerformanceTimer perfTimer("LOD"); // adjust it unless we were asked to disable this feature, or if we're currently in throttleRendering mode if (!isThrottleRendering()) { - float batchTime = (float)_gpuContext->getFrameTimerBatchAverage(); + float presentTime = getActiveDisplayPlugin()->getAveragePresentTime(); float engineRunTime = (float)(_renderEngine->getConfiguration().get()->getCPURunTime()); - DependencyManager::get()->autoAdjustLOD(batchTime, engineRunTime, deltaTime); + float gpuTime = getGPUContext()->getFrameTimerGPUAverage(); + float maxRenderTime = glm::max(gpuTime, glm::max(presentTime, engineRunTime)); + DependencyManager::get()->autoAdjustLOD(maxRenderTime, deltaTime); } else { DependencyManager::get()->resetLODAdjust(); } @@ -4514,7 +4561,7 @@ void Application::reloadResourceCaches() { _lastQueriedTime = 0; _octreeQuery.incrementConnectionID(); - queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions); + queryOctree(NodeType::EntityServer, PacketType::EntityQuery); DependencyManager::get()->clearCache(); @@ -4590,7 +4637,7 @@ void Application::setKeyboardFocusEntity(const EntityItemID& entityItemID) { _lastAcceptedKeyPress = usecTimestampNow(); setKeyboardFocusHighlight(entity->getWorldPosition(), entity->getWorldOrientation(), - entity->getDimensions() * FOCUS_HIGHLIGHT_EXPANSION_FACTOR); + entity->getScaledDimensions() * FOCUS_HIGHLIGHT_EXPANSION_FACTOR); } } } @@ -4848,8 +4895,7 @@ void Application::update(float deltaTime) { if (_physicsEnabled) { { PROFILE_RANGE(simulation_physics, "PreStep"); - - PerformanceTimer perfTimer("updateStates)"); + PerformanceTimer perfTimer("preStep)"); static VectorOfMotionStates motionStates; _entitySimulation->getObjectsToRemoveFromPhysics(motionStates); _physicsEngine->removeObjects(motionStates); @@ -4882,22 +4928,22 @@ void Application::update(float deltaTime) { } { PROFILE_RANGE(simulation_physics, "Step"); - PerformanceTimer perfTimer("stepSimulation"); + PerformanceTimer perfTimer("step"); getEntities()->getTree()->withWriteLock([&] { _physicsEngine->stepSimulation(); }); } { PROFILE_RANGE(simulation_physics, "PostStep"); - PerformanceTimer perfTimer("harvestChanges"); + PerformanceTimer perfTimer("postStep"); if (_physicsEngine->hasOutgoingChanges()) { // grab the collision events BEFORE handleOutgoingChanges() because at this point // we have a better idea of which objects we own or should own. auto& collisionEvents = _physicsEngine->getCollisionEvents(); getEntities()->getTree()->withWriteLock([&] { - PROFILE_RANGE(simulation_physics, "Harvest"); - PerformanceTimer perfTimer("handleOutgoingChanges"); + PROFILE_RANGE(simulation_physics, "HandleChanges"); + PerformanceTimer perfTimer("handleChanges"); const VectorOfMotionStates& outgoingChanges = _physicsEngine->getChangedMotionStates(); _entitySimulation->handleChangedMotionStates(outgoingChanges); @@ -4908,17 +4954,15 @@ void Application::update(float deltaTime) { }); if (!_aboutToQuit) { - // handleCollisionEvents() AFTER handleOutgoinChanges() + // handleCollisionEvents() AFTER handleOutgoingChanges() { PROFILE_RANGE(simulation_physics, "CollisionEvents"); - PerformanceTimer perfTimer("entities"); avatarManager->handleCollisionEvents(collisionEvents); // Collision events (and their scripts) must not be handled when we're locked, above. (That would risk // deadlock.) _entitySimulation->handleCollisionEvents(collisionEvents); } - PROFILE_RANGE(simulation_physics, "UpdateEntities"); // NOTE: the getEntities()->update() call below will wait for lock // and will simulate entity motion (the EntityTree has been given an EntitySimulation). getEntities()->update(true); // update the models... @@ -4929,7 +4973,8 @@ void Application::update(float deltaTime) { myAvatar->harvestResultsFromPhysicsSimulation(deltaTime); } - if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails) && + if (PerformanceTimer::isActive() && + Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails) && Menu::getInstance()->isOptionChecked(MenuOption::ExpandPhysicsSimulationTiming)) { _physicsEngine->harvestPerformanceStats(); } @@ -5009,7 +5054,7 @@ void Application::update(float deltaTime) { if (queryIsDue || viewIsDifferentEnough) { _lastQueriedTime = now; if (DependencyManager::get()->shouldRenderEntities()) { - queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions); + queryOctree(NodeType::EntityServer, PacketType::EntityQuery); } sendAvatarViewFrustum(); _lastQueriedViewFrustum = _viewFrustum; @@ -5230,15 +5275,12 @@ int Application::sendNackPackets() { return packetsSent; } -void Application::queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions) { +void Application::queryOctree(NodeType_t serverType, PacketType packetType) { if (!_settingsLoaded) { return; // bail early if settings are not loaded } - //qCDebug(interfaceapp) << ">>> inside... queryOctree()... _viewFrustum.getFieldOfView()=" << _viewFrustum.getFieldOfView(); - bool wantExtraDebugging = getLogger()->extraDebugging(); - ViewFrustum viewFrustum; copyViewFrustum(viewFrustum); _octreeQuery.setCameraPosition(viewFrustum.getPosition()); @@ -5253,147 +5295,22 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node _octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale()); _octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust()); - // Iterate all of the nodes, and get a count of how many octree servers we have... - int totalServers = 0; - int inViewServers = 0; - int unknownJurisdictionServers = 0; - auto nodeList = DependencyManager::get(); - nodeList->eachNode([&](const SharedNodePointer& node) { - // only send to the NodeTypes that are serverType - if (node->getActiveSocket() && node->getType() == serverType) { - totalServers++; + auto node = nodeList->soloNodeOfType(serverType); + if (node && node->getActiveSocket()) { + _octreeQuery.setMaxQueryPacketsPerSecond(getMaxOctreePacketsPerSecond()); - // get the server bounds for this server - QUuid nodeUUID = node->getUUID(); + auto queryPacket = NLPacket::create(packetType); - // if we haven't heard from this voxel server, go ahead and send it a query, so we - // can get the jurisdiction... - if (jurisdictions.find(nodeUUID) == jurisdictions.end()) { - unknownJurisdictionServers++; - } else { - const JurisdictionMap& map = (jurisdictions)[nodeUUID]; + // encode the query data + auto packetData = reinterpret_cast(queryPacket->getPayload()); + int packetSize = _octreeQuery.getBroadcastData(packetData); + queryPacket->setPayloadSize(packetSize); - auto rootCode = map.getRootOctalCode(); - - if (rootCode) { - VoxelPositionSize rootDetails; - voxelDetailsForCode(rootCode.get(), rootDetails); - AACube serverBounds(glm::vec3(rootDetails.x * TREE_SCALE, - rootDetails.y * TREE_SCALE, - rootDetails.z * TREE_SCALE) - glm::vec3(HALF_TREE_SCALE), - rootDetails.s * TREE_SCALE); - if (viewFrustum.cubeIntersectsKeyhole(serverBounds)) { - inViewServers++; - } - } - } - } - }); - - if (wantExtraDebugging) { - qCDebug(interfaceapp, "Servers: total %d, in view %d, unknown jurisdiction %d", - totalServers, inViewServers, unknownJurisdictionServers); + // make sure we still have an active socket + nodeList->sendUnreliablePacket(*queryPacket, *node); } - - int perServerPPS = 0; - const int SMALL_BUDGET = 10; - int perUnknownServer = SMALL_BUDGET; - int totalPPS = getMaxOctreePacketsPerSecond(); - - // determine PPS based on number of servers - if (inViewServers >= 1) { - // set our preferred PPS to be exactly evenly divided among all of the voxel servers... and allocate 1 PPS - // for each unknown jurisdiction server - perServerPPS = (totalPPS / inViewServers) - (unknownJurisdictionServers * perUnknownServer); - } else { - if (unknownJurisdictionServers > 0) { - perUnknownServer = (totalPPS / unknownJurisdictionServers); - } - } - - if (wantExtraDebugging) { - qCDebug(interfaceapp, "perServerPPS: %d perUnknownServer: %d", perServerPPS, perUnknownServer); - } - - auto queryPacket = NLPacket::create(packetType); - - nodeList->eachNode([&](const SharedNodePointer& node) { - // only send to the NodeTypes that are serverType - if (node->getActiveSocket() && node->getType() == serverType) { - - // get the server bounds for this server - QUuid nodeUUID = node->getUUID(); - - bool inView = false; - bool unknownView = false; - - // if we haven't heard from this voxel server, go ahead and send it a query, so we - // can get the jurisdiction... - if (jurisdictions.find(nodeUUID) == jurisdictions.end()) { - unknownView = true; // assume it's in view - if (wantExtraDebugging) { - qCDebug(interfaceapp) << "no known jurisdiction for node " << *node << ", assume it's visible."; - } - } else { - const JurisdictionMap& map = (jurisdictions)[nodeUUID]; - - auto rootCode = map.getRootOctalCode(); - - if (rootCode) { - VoxelPositionSize rootDetails; - voxelDetailsForCode(rootCode.get(), rootDetails); - AACube serverBounds(glm::vec3(rootDetails.x * TREE_SCALE, - rootDetails.y * TREE_SCALE, - rootDetails.z * TREE_SCALE) - glm::vec3(HALF_TREE_SCALE), - rootDetails.s * TREE_SCALE); - - - inView = viewFrustum.cubeIntersectsKeyhole(serverBounds); - } else if (wantExtraDebugging) { - qCDebug(interfaceapp) << "Jurisdiction without RootCode for node " << *node << ". That's unusual!"; - } - } - - if (inView) { - _octreeQuery.setMaxQueryPacketsPerSecond(perServerPPS); - } else if (unknownView) { - if (wantExtraDebugging) { - qCDebug(interfaceapp) << "no known jurisdiction for node " << *node << ", give it budget of " - << perUnknownServer << " to send us jurisdiction."; - } - - // set the query's position/orientation to be degenerate in a manner that will get the scene quickly - // If there's only one server, then don't do this, and just let the normal voxel query pass through - // as expected... this way, we will actually get a valid scene if there is one to be seen - if (totalServers > 1) { - _octreeQuery.setCameraPosition(glm::vec3(-0.1,-0.1,-0.1)); - const glm::quat OFF_IN_NEGATIVE_SPACE = glm::quat(-0.5, 0, -0.5, 1.0); - _octreeQuery.setCameraOrientation(OFF_IN_NEGATIVE_SPACE); - _octreeQuery.setCameraNearClip(0.1f); - _octreeQuery.setCameraFarClip(0.1f); - if (wantExtraDebugging) { - qCDebug(interfaceapp) << "Using 'minimal' camera position for node" << *node; - } - } else { - if (wantExtraDebugging) { - qCDebug(interfaceapp) << "Using regular camera position for node" << *node; - } - } - _octreeQuery.setMaxQueryPacketsPerSecond(perUnknownServer); - } else { - _octreeQuery.setMaxQueryPacketsPerSecond(0); - } - - // encode the query data - int packetSize = _octreeQuery.getBroadcastData(reinterpret_cast(queryPacket->getPayload())); - queryPacket->setPayloadSize(packetSize); - - // make sure we still have an active socket - nodeList->sendUnreliablePacket(*queryPacket, *node); - } - }); } @@ -5508,11 +5425,6 @@ void Application::clearDomainOctreeDetails() { resetPhysicsReadyInformation(); - // reset our node to stats and node to jurisdiction maps... since these must be changing... - _entityServerJurisdictions.withWriteLock([&] { - _entityServerJurisdictions.clear(); - }); - _octreeServerSceneStats.withWriteLock([&] { _octreeServerSceneStats.clear(); }); @@ -5714,8 +5626,6 @@ bool Application::nearbyEntitiesAreReadyForPhysics() { } int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer sendingNode) { - // But, also identify the sender, and keep track of the contained jurisdiction root for this server - // parse the incoming stats datas stick it in a temporary object for now, while we // determine which server it belongs to int statsMessageLength = 0; @@ -5730,42 +5640,6 @@ int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer if (octreeStats.isFullScene()) { _fullSceneReceivedCounter++; } - - // see if this is the first we've heard of this node... - NodeToJurisdictionMap* jurisdiction = nullptr; - QString serverType; - if (sendingNode->getType() == NodeType::EntityServer) { - jurisdiction = &_entityServerJurisdictions; - serverType = "Entity"; - } - - bool found = false; - - jurisdiction->withReadLock([&] { - if (jurisdiction->find(nodeUUID) != jurisdiction->end()) { - found = true; - return; - } - - VoxelPositionSize rootDetails; - voxelDetailsForCode(octreeStats.getJurisdictionRoot().get(), rootDetails); - - qCDebug(interfaceapp, "stats from new %s server... [%f, %f, %f, %f]", - qPrintable(serverType), - (double)rootDetails.x, (double)rootDetails.y, (double)rootDetails.z, (double)rootDetails.s); - }); - - if (!found) { - // store jurisdiction details for later use - // This is bit of fiddling is because JurisdictionMap assumes it is the owner of the values used to construct it - // but OctreeSceneStats thinks it's just returning a reference to its contents. So we need to make a copy of the - // details from the OctreeSceneStats to construct the JurisdictionMap - JurisdictionMap jurisdictionMap; - jurisdictionMap.copyContents(octreeStats.getJurisdictionRoot(), octreeStats.getJurisdictionEndNodes()); - jurisdiction->withWriteLock([&] { - (*jurisdiction)[nodeUUID] = jurisdictionMap; - }); - } }); return statsMessageLength; @@ -5786,7 +5660,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe return !entityServerNode || isPhysicsEnabled(); }); - // setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so + // setup the packet sender of the script engine's scripting interfaces so // we can use the same ones from the application. auto entityScriptingInterface = DependencyManager::get(); entityScriptingInterface->setPacketSender(&_entityEditSender); @@ -5832,9 +5706,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe qScriptRegisterMetaType(scriptEngine.data(), wrapperToScriptValue, wrapperFromScriptValue); qScriptRegisterMetaType(scriptEngine.data(), wrapperToScriptValue, wrapperFromScriptValue); - // Tablet inteference with Tablet.qml. Need to avoid this in QML space - scriptEngine->registerGlobalObject("tabletInterface", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Tablet", DependencyManager::get().data()); + // FIXME remove these deprecated names for the tablet scripting interface + scriptEngine->registerGlobalObject("tabletInterface", DependencyManager::get().data()); auto toolbarScriptingInterface = DependencyManager::get().data(); DependencyManager::get().data()->setToolbarScriptingInterface(toolbarScriptingInterface); @@ -5870,7 +5744,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGlobalObject("ModelCache", DependencyManager::get().data()); scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get().data()); - scriptEngine->registerGlobalObject("Account", AccountScriptingInterface::getInstance()); + scriptEngine->registerGlobalObject("Account", GlobalServicesScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("DialogsManager", _dialogsManagerScriptingInterface); scriptEngine->registerGlobalObject("GlobalServices", GlobalServicesScriptingInterface::getInstance()); @@ -5900,6 +5774,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGlobalObject("Users", DependencyManager::get().data()); scriptEngine->registerGlobalObject("LimitlessSpeechRecognition", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("GooglePoly", DependencyManager::get().data()); if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { scriptEngine->registerGlobalObject("Steam", new SteamScriptingInterface(scriptEngine.data(), steamClient.get())); @@ -6272,7 +6147,7 @@ void Application::showAssetServerWidget(QString filePath) { if (!hmd->getShouldShowTablet() && !isHMDMode()) { DependencyManager::get()->show(url, "AssetServer", startUpload); } else { - static const QUrl url("../../hifi/dialogs/TabletAssetServer.qml"); + static const QUrl url("hifi/dialogs/TabletAssetServer.qml"); tablet->pushOntoStack(url); } } @@ -6845,7 +6720,7 @@ void Application::loadLODToolsDialog() { auto dialogsManager = DependencyManager::get(); dialogsManager->lodTools(); } else { - tablet->pushOntoStack("../../hifi/dialogs/TabletLODTools.qml"); + tablet->pushOntoStack("hifi/dialogs/TabletLODTools.qml"); } } @@ -6857,7 +6732,7 @@ void Application::loadEntityStatisticsDialog() { auto dialogsManager = DependencyManager::get(); dialogsManager->octreeStatsDetails(); } else { - tablet->pushOntoStack("../../hifi/dialogs/TabletEntityStatistics.qml"); + tablet->pushOntoStack("hifi/dialogs/TabletEntityStatistics.qml"); } } @@ -6868,7 +6743,7 @@ void Application::loadDomainConnectionDialog() { auto dialogsManager = DependencyManager::get(); dialogsManager->showDomainConnectionDialog(); } else { - tablet->pushOntoStack("../../hifi/dialogs/TabletDCDialog.qml"); + tablet->pushOntoStack("hifi/dialogs/TabletDCDialog.qml"); } } @@ -7241,13 +7116,17 @@ void Application::updateDisplayMode() { } auto offscreenUi = DependencyManager::get(); + auto desktop = offscreenUi->getDesktop(); // Make the switch atomic from the perspective of other threads { std::unique_lock lock(_displayPluginLock); - // Tell the desktop to no reposition (which requires plugin info), until we have set the new plugin, below. - bool wasRepositionLocked = offscreenUi->getDesktop()->property("repositionLocked").toBool(); - offscreenUi->getDesktop()->setProperty("repositionLocked", true); + bool wasRepositionLocked = false; + if (desktop) { + // Tell the desktop to no reposition (which requires plugin info), until we have set the new plugin, below. + wasRepositionLocked = offscreenUi->getDesktop()->property("repositionLocked").toBool(); + offscreenUi->getDesktop()->setProperty("repositionLocked", true); + } if (_displayPlugin) { disconnect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent); @@ -7293,7 +7172,6 @@ void Application::updateDisplayMode() { getApplicationCompositor().setDisplayPlugin(newDisplayPlugin); _displayPlugin = newDisplayPlugin; connect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent, Qt::DirectConnection); - auto desktop = offscreenUi->getDesktop(); if (desktop) { desktop->setProperty("repositionLocked", wasRepositionLocked); } @@ -7506,4 +7384,9 @@ void Application::setAvatarOverrideUrl(const QUrl& url, bool save) { _avatarOverrideUrl = url; _saveAvatarOverrideUrl = save; } + +void Application::saveNextPhysicsStats(QString filename) { + _physicsEngine->saveNextPhysicsStats(filename); +} + #include "Application.moc" diff --git a/interface/src/Application.h b/interface/src/Application.h index 9542c5ccb6..479ce919a3 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -207,7 +207,11 @@ public: void setDesktopTabletBecomesToolbarSetting(bool value); bool getHmdTabletBecomesToolbarSetting() { return _hmdTabletBecomesToolbarSetting.get(); } void setHmdTabletBecomesToolbarSetting(bool value); - bool getPreferAvatarFingerOverStylus() { return _preferAvatarFingerOverStylusSetting.get(); } + bool getPreferStylusOverLaser() { return _preferStylusOverLaserSetting.get(); } + void setPreferStylusOverLaser(bool value); + // FIXME: Remove setting completely or make available through JavaScript API? + //bool getPreferAvatarFingerOverStylus() { return _preferAvatarFingerOverStylusSetting.get(); } + bool getPreferAvatarFingerOverStylus() { return false; } void setPreferAvatarFingerOverStylus(bool value); float getSettingConstrainToolbarPosition() { return _constrainToolbarPosition.get(); } @@ -228,8 +232,6 @@ public: FileLogger* getLogger() const { return _logger; } - NodeToJurisdictionMap& getEntityServerJurisdictions() { return _entityServerJurisdictions; } - float getRenderResolutionScale() const; qint64 getCurrentSessionRuntime() const { return _sessionRunTimer.elapsed(); } @@ -280,6 +282,7 @@ public: void clearAvatarOverrideUrl() { _avatarOverrideUrl = QUrl(); _saveAvatarOverrideUrl = false; } QUrl getAvatarOverrideUrl() { return _avatarOverrideUrl; } bool getSaveAvatarOverrideUrl() { return _saveAvatarOverrideUrl; } + void saveNextPhysicsStats(QString filename); signals: void svoImportRequested(const QString& url); @@ -432,6 +435,7 @@ private slots: void handleSandboxStatus(QNetworkReply* reply); void switchDisplayMode(); + private: static void initDisplay(); void init(); @@ -448,7 +452,7 @@ private: void updateThreads(float deltaTime); void updateDialogs(float deltaTime) const; - void queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions); + void queryOctree(NodeType_t serverType, PacketType packetType); int sendNackPackets(); void sendAvatarViewFrustum(); @@ -551,6 +555,7 @@ private: Setting::Handle _desktopTabletScale; Setting::Handle _desktopTabletBecomesToolbarSetting; Setting::Handle _hmdTabletBecomesToolbarSetting; + Setting::Handle _preferStylusOverLaserSetting; Setting::Handle _preferAvatarFingerOverStylusSetting; Setting::Handle _constrainToolbarPosition; Setting::Handle _preferredCursor; @@ -569,7 +574,6 @@ private: StDev _idleLoopStdev; float _idleLoopMeasuredJitter; - NodeToJurisdictionMap _entityServerJurisdictions; NodeToOctreeSceneStats _octreeServerSceneStats; ControllerScriptingInterface* _controllerScriptingInterface{ nullptr }; QPointer _logDialog; diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 01ccbd0d9a..9e6fabd439 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -19,6 +19,7 @@ #include "LODManager.h" + Setting::Handle desktopLODDecreaseFPS("desktopLODDecreaseFPS", DEFAULT_DESKTOP_LOD_DOWN_FPS); Setting::Handle hmdLODDecreaseFPS("hmdLODDecreaseFPS", DEFAULT_HMD_LOD_DOWN_FPS); @@ -39,156 +40,95 @@ float LODManager::getLODIncreaseFPS() { return getDesktopLODIncreaseFPS(); } -void LODManager::autoAdjustLOD(float batchTime, float engineRunTime, float deltaTimeSec) { +// We use a "time-weighted running average" of the renderTime and compare it against min/max thresholds +// to determine if we should adjust the level of detail (LOD). +// +// A time-weighted running average has a timescale which determines how fast the average tracks the measured +// value in real-time. Given a step-function in the mesured value, and assuming measurements happen +// faster than the runningAverage is computed, the error between the value and its runningAverage will be +// reduced by 1/e every timescale of real-time that passes. +const float LOD_ADJUST_RUNNING_AVG_TIMESCALE = 0.1f; // sec +// +// Assuming the measured value is affected by logic invoked by the runningAverage bumping up against its +// thresholds, we expect the adjustment to introduce a step-function. We want the runningAverage settle +// to the new value BEFORE we test it aginst its thresholds again. Hence we test on a period that is a few +// multiples of the running average timescale: +const uint64_t LOD_AUTO_ADJUST_PERIOD = 5 * (uint64_t)(LOD_ADJUST_RUNNING_AVG_TIMESCALE * (float)USECS_PER_MSEC); // usec - // NOTE: our first ~100 samples at app startup are completely all over the place, and we don't - // really want to count them in our average, so we will ignore the real frame rates and stuff - // our moving average with simulated good data - const int IGNORE_THESE_SAMPLES = 100; - if (_fpsAverageUpWindow.getSampleCount() < IGNORE_THESE_SAMPLES) { - _lastStable = _lastUpShift = _lastDownShift = usecTimestampNow(); - } +const float LOD_AUTO_ADJUST_DECREMENT_FACTOR = 0.8f; +const float LOD_AUTO_ADJUST_INCREMENT_FACTOR = 1.2f; +void LODManager::autoAdjustLOD(float renderTime, float realTimeDelta) { // compute time-weighted running average renderTime - const float OVERLAY_AND_SWAP_TIME_BUDGET = 2.0f; // msec - float renderTime = batchTime + OVERLAY_AND_SWAP_TIME_BUDGET; - float maxTime = glm::max(renderTime, engineRunTime); - const float BLEND_TIMESCALE = 0.3f; // sec - const float MIN_DELTA_TIME = 0.001f; - const float safeDeltaTime = glm::max(deltaTimeSec, MIN_DELTA_TIME); - float blend = BLEND_TIMESCALE / safeDeltaTime; - if (blend > 1.0f) { - blend = 1.0f; + // Note: we MUST clamp the blend to 1.0 for stability + float blend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE) ? realTimeDelta / LOD_ADJUST_RUNNING_AVG_TIMESCALE : 1.0f; + _avgRenderTime = (1.0f - blend) * _avgRenderTime + blend * renderTime; // msec + if (!_automaticLODAdjust) { + // early exit + return; } - _avgRenderTime = (1.0f - blend) * _avgRenderTime + blend * maxTime; // msec - // translate into fps for legacy implementation + float oldOctreeSizeScale = _octreeSizeScale; float currentFPS = (float)MSECS_PER_SECOND / _avgRenderTime; - - _fpsAverageStartWindow.updateAverage(currentFPS); - _fpsAverageDownWindow.updateAverage(currentFPS); - _fpsAverageUpWindow.updateAverage(currentFPS); - - quint64 now = usecTimestampNow(); - - quint64 elapsedSinceDownShift = now - _lastDownShift; - quint64 elapsedSinceUpShift = now - _lastUpShift; - - quint64 lastStableOrUpshift = glm::max(_lastUpShift, _lastStable); - quint64 elapsedSinceStableOrUpShift = now - lastStableOrUpshift; - - if (_automaticLODAdjust) { - bool changed = false; - - // LOD Downward adjustment - // If we've been downshifting, we watch a shorter downshift window so that we will quickly move toward our - // target frame rate. But if we haven't just done a downshift (either because our last shift was an upshift, - // or because we've just started out) then we look at a much longer window to consider whether or not to start - // downshifting. - bool doDownShift = false; - - if (_isDownshifting) { - // only consider things if our DOWN_SHIFT time has elapsed... - if (elapsedSinceDownShift > DOWN_SHIFT_ELPASED) { - doDownShift = _fpsAverageDownWindow.getAverage() < getLODDecreaseFPS(); - - if (!doDownShift) { - qCDebug(interfaceapp) << "---- WE APPEAR TO BE DONE DOWN SHIFTING -----"; - _isDownshifting = false; - _lastStable = now; - } - } - } else { - doDownShift = (elapsedSinceStableOrUpShift > START_SHIFT_ELPASED - && _fpsAverageStartWindow.getAverage() < getLODDecreaseFPS()); - } - - if (doDownShift) { - - // Octree items... stepwise adjustment + uint64_t now = usecTimestampNow(); + if (currentFPS < getLODDecreaseFPS()) { + if (now > _decreaseFPSExpiry) { + _decreaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; if (_octreeSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) { - _octreeSizeScale *= ADJUST_LOD_DOWN_BY; + _octreeSizeScale *= LOD_AUTO_ADJUST_DECREMENT_FACTOR; if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; } - changed = true; - } - - if (changed) { - if (_isDownshifting) { - // subsequent downshift - qCDebug(interfaceapp) << "adjusting LOD DOWN..." - << "average fps for last "<< DOWN_SHIFT_WINDOW_IN_SECS <<"seconds was " - << _fpsAverageDownWindow.getAverage() - << "minimum is:" << getLODDecreaseFPS() - << "elapsedSinceDownShift:" << elapsedSinceDownShift - << " NEW _octreeSizeScale=" << _octreeSizeScale; - } else { - // first downshift - qCDebug(interfaceapp) << "adjusting LOD DOWN after initial delay..." - << "average fps for last "<< START_DELAY_WINDOW_IN_SECS <<"seconds was " - << _fpsAverageStartWindow.getAverage() - << "minimum is:" << getLODDecreaseFPS() - << "elapsedSinceUpShift:" << elapsedSinceUpShift - << " NEW _octreeSizeScale=" << _octreeSizeScale; - } - - _lastDownShift = now; - _isDownshifting = true; - + qCDebug(interfaceapp) << "adjusting LOD DOWN" + << "fps =" << currentFPS + << "targetFPS =" << getLODDecreaseFPS() + << "octreeSizeScale =" << _octreeSizeScale; emit LODDecreased(); } - } else { - - // LOD Upward adjustment - if (elapsedSinceUpShift > UP_SHIFT_ELPASED) { - - if (_fpsAverageUpWindow.getAverage() > getLODIncreaseFPS()) { - - // Octee items... stepwise adjustment - if (_octreeSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) { - if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { - _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; - } else { - _octreeSizeScale *= ADJUST_LOD_UP_BY; - } - if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) { - _octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE; - } - changed = true; - } - } - - if (changed) { - qCDebug(interfaceapp) << "adjusting LOD UP... average fps for last "<< UP_SHIFT_WINDOW_IN_SECS <<"seconds was " - << _fpsAverageUpWindow.getAverage() - << "upshift point is:" << getLODIncreaseFPS() - << "elapsedSinceUpShift:" << elapsedSinceUpShift - << " NEW _octreeSizeScale=" << _octreeSizeScale; - - _lastUpShift = now; - _isDownshifting = false; - - emit LODIncreased(); - } - } + _decreaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; } - - if (changed) { - auto lodToolsDialog = DependencyManager::get()->getLodToolsDialog(); - if (lodToolsDialog) { - lodToolsDialog->reloadSliders(); + _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; + } else if (currentFPS > getLODIncreaseFPS()) { + if (now > _increaseFPSExpiry) { + _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; + if (_octreeSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) { + if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { + _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; + } else { + _octreeSizeScale *= LOD_AUTO_ADJUST_INCREMENT_FACTOR; + } + if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) { + _octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE; + } + qCDebug(interfaceapp) << "adjusting LOD UP" + << "fps =" << currentFPS + << "targetFPS =" << getLODDecreaseFPS() + << "octreeSizeScale =" << _octreeSizeScale; + emit LODIncreased(); } + _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; } + _decreaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; + } else { + _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; + _decreaseFPSExpiry = _increaseFPSExpiry; + } + + if (oldOctreeSizeScale != _octreeSizeScale) { + auto lodToolsDialog = DependencyManager::get()->getLodToolsDialog(); + if (lodToolsDialog) { + lodToolsDialog->reloadSliders(); + } + // Assuming the LOD adjustment will work: we optimistically reset _avgRenderTime + // to be at middle of target zone. It will drift close to its true value within + // about three few LOD_ADJUST_TIMESCALEs and we'll adjust again as necessary. + float expectedFPS = 0.5f * (getLODIncreaseFPS() + getLODDecreaseFPS()); + _avgRenderTime = MSECS_PER_SECOND / expectedFPS; } } void LODManager::resetLODAdjust() { - _fpsAverageStartWindow.reset(); - _fpsAverageDownWindow.reset(); - _fpsAverageUpWindow.reset(); - _lastUpShift = _lastDownShift = usecTimestampNow(); - _isDownshifting = false; + _decreaseFPSExpiry = _increaseFPSExpiry = usecTimestampNow() + LOD_AUTO_ADJUST_PERIOD; } const float MIN_DECREASE_FPS = 0.5f; @@ -206,7 +146,7 @@ float LODManager::getDesktopLODDecreaseFPS() const { } float LODManager::getDesktopLODIncreaseFPS() const { - return glm::max(((float)MSECS_PER_SECOND / _desktopMaxRenderTime) + INCREASE_LOD_GAP, MAX_LIKELY_DESKTOP_FPS); + return glm::max(((float)MSECS_PER_SECOND / _desktopMaxRenderTime) + INCREASE_LOD_GAP_FPS, MAX_LIKELY_DESKTOP_FPS); } void LODManager::setHMDLODDecreaseFPS(float fps) { @@ -222,7 +162,7 @@ float LODManager::getHMDLODDecreaseFPS() const { } float LODManager::getHMDLODIncreaseFPS() const { - return glm::max(((float)MSECS_PER_SECOND / _hmdMaxRenderTime) + INCREASE_LOD_GAP, MAX_LIKELY_HMD_FPS); + return glm::max(((float)MSECS_PER_SECOND / _hmdMaxRenderTime) + INCREASE_LOD_GAP_FPS, MAX_LIKELY_HMD_FPS); } QString LODManager::getLODFeedbackText() { diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 1b3797a0ca..cf38342db0 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -19,29 +19,13 @@ #include #include -const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 20.0; -const float DEFAULT_HMD_LOD_DOWN_FPS = 20.0; +const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 30.0f; +const float DEFAULT_HMD_LOD_DOWN_FPS = 45.0f; const float DEFAULT_DESKTOP_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_DESKTOP_LOD_DOWN_FPS; // msec const float DEFAULT_HMD_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_HMD_LOD_DOWN_FPS; // msec -const float MAX_LIKELY_DESKTOP_FPS = 59.0; // this is essentially, V-synch - 1 fps -const float MAX_LIKELY_HMD_FPS = 74.0; // this is essentially, V-synch - 1 fps -const float INCREASE_LOD_GAP = 15.0f; - -const float START_DELAY_WINDOW_IN_SECS = 3.0f; // wait at least this long after steady state/last upshift to consider downshifts -const float DOWN_SHIFT_WINDOW_IN_SECS = 0.5f; -const float UP_SHIFT_WINDOW_IN_SECS = 2.5f; - -const int ASSUMED_FPS = 60; -const quint64 START_SHIFT_ELPASED = USECS_PER_SECOND * START_DELAY_WINDOW_IN_SECS; -const quint64 DOWN_SHIFT_ELPASED = USECS_PER_SECOND * DOWN_SHIFT_WINDOW_IN_SECS; // Consider adjusting LOD down after half a second -const quint64 UP_SHIFT_ELPASED = USECS_PER_SECOND * UP_SHIFT_WINDOW_IN_SECS; - -const int START_DELAY_SAMPLES_OF_FRAMES = ASSUMED_FPS * START_DELAY_WINDOW_IN_SECS; -const int DOWN_SHIFT_SAMPLES_OF_FRAMES = ASSUMED_FPS * DOWN_SHIFT_WINDOW_IN_SECS; -const int UP_SHIFT_SAMPLES_OF_FRAMES = ASSUMED_FPS * UP_SHIFT_WINDOW_IN_SECS; - -const float ADJUST_LOD_DOWN_BY = 0.9f; -const float ADJUST_LOD_UP_BY = 1.1f; +const float MAX_LIKELY_DESKTOP_FPS = 59.0f; // this is essentially, V-synch - 1 fps +const float MAX_LIKELY_HMD_FPS = 74.0f; // this is essentially, V-synch - 1 fps +const float INCREASE_LOD_GAP_FPS = 15.0f; // fps // The default value DEFAULT_OCTREE_SIZE_SCALE means you can be 400 meters away from a 1 meter object in order to see it (which is ~20:20 vision). const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE; @@ -78,7 +62,7 @@ public: Q_INVOKABLE float getLODIncreaseFPS(); static bool shouldRender(const RenderArgs* args, const AABox& bounds); - void autoAdjustLOD(float batchTime, float engineRunTime, float deltaTimeSec); + void autoAdjustLOD(float renderTime, float realTimeDelta); void loadSettings(); void saveSettings(); @@ -92,21 +76,15 @@ private: LODManager(); bool _automaticLODAdjust = true; - float _avgRenderTime { 0.0 }; + float _avgRenderTime { 0.0f }; float _desktopMaxRenderTime { DEFAULT_DESKTOP_MAX_RENDER_TIME }; float _hmdMaxRenderTime { DEFAULT_HMD_MAX_RENDER_TIME }; float _octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE; int _boundaryLevelAdjust = 0; - - quint64 _lastDownShift = 0; - quint64 _lastUpShift = 0; - quint64 _lastStable = 0; - bool _isDownshifting = false; // start out as if we're not downshifting - - SimpleMovingAverage _fpsAverageStartWindow = START_DELAY_SAMPLES_OF_FRAMES; - SimpleMovingAverage _fpsAverageDownWindow = DOWN_SHIFT_SAMPLES_OF_FRAMES; - SimpleMovingAverage _fpsAverageUpWindow = UP_SHIFT_SAMPLES_OF_FRAMES; + + uint64_t _decreaseFPSExpiry { 0 }; + uint64_t _increaseFPSExpiry { 0 }; }; #endif // hifi_LODManager_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 9bbb72357b..d295e96867 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -101,7 +101,7 @@ Menu::Menu() { auto action = addActionToQMenuAndActionHash(editMenu, MenuOption::RunningScripts, Qt::CTRL | Qt::Key_J); connect(action, &QAction::triggered, [] { static const QUrl widgetUrl("hifi/dialogs/RunningScripts.qml"); - static const QUrl tabletUrl("../../hifi/dialogs/TabletRunningScripts.qml"); + static const QUrl tabletUrl("hifi/dialogs/TabletRunningScripts.qml"); static const QString name("RunningScripts"); qApp->showDialog(widgetUrl, tabletUrl, name); }); @@ -166,7 +166,7 @@ Menu::Menu() { action = addActionToQMenuAndActionHash(avatarMenu, MenuOption::Attachments); connect(action, &QAction::triggered, [] { qApp->showDialog(QString("hifi/dialogs/AttachmentsDialog.qml"), - QString("../../hifi/tablet/TabletAttachmentsDialog.qml"), "AttachmentsDialog"); + QString("hifi/tablet/TabletAttachmentsDialog.qml"), "AttachmentsDialog"); }); // Avatar > Size @@ -309,13 +309,13 @@ Menu::Menu() { action = addActionToQMenuAndActionHash(settingsMenu, MenuOption::Preferences, Qt::CTRL | Qt::Key_Comma, nullptr, nullptr, QAction::PreferencesRole); connect(action, &QAction::triggered, [] { qApp->showDialog(QString("hifi/dialogs/GeneralPreferencesDialog.qml"), - QString("../../hifi/tablet/TabletGeneralPreferences.qml"), "GeneralPreferencesDialog"); + QString("hifi/tablet/TabletGeneralPreferences.qml"), "GeneralPreferencesDialog"); }); action = addActionToQMenuAndActionHash(settingsMenu, "Audio..."); connect(action, &QAction::triggered, [] { static const QUrl widgetUrl("hifi/dialogs/Audio.qml"); - static const QUrl tabletUrl("../../hifi/audio/Audio.qml"); + static const QUrl tabletUrl("hifi/audio/Audio.qml"); static const QString name("AudioDialog"); qApp->showDialog(widgetUrl, tabletUrl, name); }); @@ -324,21 +324,21 @@ Menu::Menu() { action = addActionToQMenuAndActionHash(settingsMenu, "Avatar..."); connect(action, &QAction::triggered, [] { qApp->showDialog(QString("hifi/dialogs/AvatarPreferencesDialog.qml"), - QString("../../hifi/tablet/TabletAvatarPreferences.qml"), "AvatarPreferencesDialog"); + QString("hifi/tablet/TabletAvatarPreferences.qml"), "AvatarPreferencesDialog"); }); // Settings > LOD... action = addActionToQMenuAndActionHash(settingsMenu, "LOD..."); connect(action, &QAction::triggered, [] { qApp->showDialog(QString("hifi/dialogs/LodPreferencesDialog.qml"), - QString("../../hifi/tablet/TabletLodPreferences.qml"), "LodPreferencesDialog"); + QString("hifi/tablet/TabletLodPreferences.qml"), "LodPreferencesDialog"); }); action = addActionToQMenuAndActionHash(settingsMenu, "Controller Settings..."); connect(action, &QAction::triggered, [] { auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); auto hmd = DependencyManager::get(); - tablet->loadQMLSource("ControllerSettings.qml"); + tablet->loadQMLSource("hifi/tablet/ControllerSettings.qml"); if (!hmd->getShouldShowTablet()) { hmd->toggleShouldShowTablet(); @@ -364,7 +364,7 @@ Menu::Menu() { action = addActionToQMenuAndActionHash(developerMenu, "Graphics..."); connect(action, &QAction::triggered, [] { qApp->showDialog(QString("hifi/dialogs/GraphicsPreferencesDialog.qml"), - QString("../../hifi/tablet/TabletGraphicsPreferences.qml"), "GraphicsPreferencesDialog"); + QString("hifi/tablet/TabletGraphicsPreferences.qml"), "GraphicsPreferencesDialog"); }); // Developer > UI >>> @@ -589,8 +589,8 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderDetailedCollision, 0, false, avatar.get(), SLOT(setEnableDebugDrawDetailedCollision(bool))); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ActionMotorControl, - Qt::CTRL | Qt::SHIFT | Qt::Key_K, true, avatar.get(), SLOT(updateMotionBehaviorFromMenu()), + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ActionMotorControl, 0, true, + avatar.get(), SLOT(updateMotionBehaviorFromMenu()), UNSPECIFIED_POSITION, "Developer"); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ScriptedMotorControl, 0, true, @@ -615,7 +615,7 @@ Menu::Menu() { action = addActionToQMenuAndActionHash(networkMenu, MenuOption::Networking); connect(action, &QAction::triggered, [] { qApp->showDialog(QString("hifi/dialogs/NetworkingPreferencesDialog.qml"), - QString("../../hifi/tablet/TabletNetworkingPreferences.qml"), "NetworkingPreferencesDialog"); + QString("hifi/tablet/TabletNetworkingPreferences.qml"), "NetworkingPreferencesDialog"); }); addActionToQMenuAndActionHash(networkMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches())); addActionToQMenuAndActionHash(networkMenu, MenuOption::ClearDiskCache, 0, @@ -645,7 +645,8 @@ Menu::Menu() { // Developer > Timing >>> MenuWrapper* timingMenu = developerMenu->addMenu("Timing"); MenuWrapper* perfTimerMenu = timingMenu->addMenu("Performance Timer"); - addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayDebugTimingDetails, 0, false); + addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayDebugTimingDetails, 0, false, + qApp, SLOT(enablePerfStats(bool))); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::OnlyDisplayTopTen, 0, true); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandUpdateTiming, 0, false); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandMyAvatarTiming, 0, false); @@ -675,7 +676,7 @@ Menu::Menu() { action = addActionToQMenuAndActionHash(audioDebugMenu, "Buffers..."); connect(action, &QAction::triggered, [] { qApp->showDialog(QString("hifi/dialogs/AudioBuffers.qml"), - QString("../../hifi/tablet/TabletAudioBuffers.qml"), "AudioBuffersDialog"); + QString("hifi/tablet/TabletAudioBuffers.qml"), "AudioBuffersDialog"); }); auto audioIO = DependencyManager::get(); diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index aad7768b99..e5e94da68a 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -407,7 +407,7 @@ void shapeInfoCalculator(const ShapeEntityItem * const shapeEntity, ShapeInfo &s ShapeInfo::PointList points; pointCollection.push_back(points); - GeometryCache::computeSimpleHullPointListForShape((int)shapeEntity->getShape(), shapeEntity->getDimensions(), pointCollection.back()); + GeometryCache::computeSimpleHullPointListForShape((int)shapeEntity->getShape(), shapeEntity->getScaledDimensions(), pointCollection.back()); shapeInfo.setPointCollection(pointCollection); } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 8a294182bd..93caef555f 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -150,7 +150,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); } float getRadius() const override { return std::static_pointer_cast(_avatar)->getBoundingRadius(); } uint64_t getTimestamp() const override { return std::static_pointer_cast(_avatar)->getLastRenderUpdateTime(); } - const AvatarSharedPointer& getAvatar() const { return _avatar; } + AvatarSharedPointer getAvatar() const { return _avatar; } private: AvatarSharedPointer _avatar; }; @@ -185,7 +185,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { render::Transaction transaction; while (!sortedAvatars.empty()) { const SortableAvatar& sortData = sortedAvatars.top(); - const auto& avatar = std::static_pointer_cast(sortData.getAvatar()); + const auto avatar = std::static_pointer_cast(sortData.getAvatar()); bool ignoring = DependencyManager::get()->isPersonalMutingNode(avatar->getID()); if (ignoring) { @@ -239,7 +239,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { sortedAvatars.pop(); while (inView && !sortedAvatars.empty()) { const SortableAvatar& newSortData = sortedAvatars.top(); - const auto& newAvatar = std::static_pointer_cast(newSortData.getAvatar()); + const auto newAvatar = std::static_pointer_cast(newSortData.getAvatar()); inView = newSortData.getPriority() > OUT_OF_VIEW_THRESHOLD; if (inView && newAvatar->hasNewJointData()) { numAVatarsNotUpdated++; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 02a1959a95..832ec7d00e 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -424,6 +424,7 @@ void MyAvatar::update(float deltaTime) { emit positionGoneTo(); // Run safety tests as soon as we can after goToLocation, or clear if we're not colliding. _physicsSafetyPending = getCollisionsEnabled(); + _characterController.recomputeFlying(); // In case we've gone to into the sky. } if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) { // When needed and ready, arrange to check and fix. @@ -2315,6 +2316,19 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, bool hasOrientation, const glm::quat& newOrientation, bool shouldFaceLocation) { + // Most cases of going to a place or user go through this now. Some possible improvements to think about in the future: + // - It would be nice if this used the same teleport steps and smoothing as in the teleport.js script, as long as it + // still worked if the target is in the air. + // - Sometimes (such as the response from /api/v1/users/:username/location), the location can be stale, but there is a + // node_id supplied by which we could update the information after going to the stale location first and "looking around". + // This could be passed through AddressManager::goToAddressFromObject => AddressManager::handleViewpoint => here. + // The trick is that you have to yield enough time to resolve the node_id. + // - Instead of always doing the same thing for shouldFaceLocation -- which places users uncomfortabley on top of each other -- + // it would be nice to see how many users are already "at" a person or place, and place ourself in semicircle or other shape + // around the target. Avatars and entities (specified by the node_id) could define an adjustable "face me" method that would + // compute the position (e.g., so that if I'm on stage, going to me would compute an available seat in the audience rather than + // being in my face on-stage). Note that this could work for going to an entity as well as to a person. + qCDebug(interfaceapp).nospace() << "MyAvatar goToLocation - moving to " << newPosition.x << ", " << newPosition.y << ", " << newPosition.z; diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 3257a634c7..d7d36dabf6 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -61,7 +61,7 @@ void Ledger::send(const QString& endpoint, const QString& success, const QString void Ledger::signedSend(const QString& propertyName, const QByteArray& text, const QString& key, const QString& endpoint, const QString& success, const QString& fail, const bool controlled_failure) { auto wallet = DependencyManager::get(); - QString signature = key.isEmpty() ? "" : wallet->signWithKey(text, key); + QString signature = wallet->signWithKey(text, key); QJsonObject request; request[propertyName] = QString(text); if (!controlled_failure) { diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 69089df9c2..62e87f9c66 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -16,9 +16,7 @@ #include "Wallet.h" #include -HIFI_QML_DEF(QmlCommerce) - -QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) { +QmlCommerce::QmlCommerce() { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); connect(ledger.data(), &Ledger::buyResult, this, &QmlCommerce::buyResult); diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index fd8dd0d395..c53e73d565 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -16,16 +16,14 @@ #define hifi_QmlCommerce_h #include -#include #include -class QmlCommerce : public OffscreenQmlDialog { +class QmlCommerce : public QObject { Q_OBJECT - HIFI_QML_DECL public: - QmlCommerce(QQuickItem* parent = nullptr); + QmlCommerce(); signals: void walletStatusResult(uint walletStatus); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 00941d6c50..c3c91e82a8 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -548,13 +548,16 @@ QStringList Wallet::listPublicKeys() { // the horror of code pages and so on (changing the bytes) by just returning a base64 // encoded string representing the signature (suitable for http, etc...) QString Wallet::signWithKey(const QByteArray& text, const QString& key) { - qCInfo(commerce) << "Signing text" << text << "with key" << key; EC_KEY* ecPrivateKey = NULL; + + auto keyFilePathString = keyFilePath().toStdString(); if ((ecPrivateKey = readPrivateKey(keyFilePath().toStdString().c_str()))) { unsigned char* sig = new unsigned char[ECDSA_size(ecPrivateKey)]; unsigned int signatureBytes = 0; + qCInfo(commerce) << "Hashing and signing plaintext" << text << "with key at address" << ecPrivateKey; + QByteArray hashedPlaintext = QCryptographicHash::hash(text, QCryptographicHash::Sha256); @@ -747,12 +750,10 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer pack } EC_KEY_free(ec); - QByteArray ba = sig.toLocal8Bit(); - const char *sigChar = ba.data(); QByteArray textByteArray; if (status > -1) { - textByteArray = QByteArray(sigChar, (int) strlen(sigChar)); + textByteArray = sig.toUtf8(); } textByteArraySize = textByteArray.size(); int certIDSize = certID.size(); diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 35f3a44227..a4fe516590 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -218,14 +218,40 @@ Pointer::PickedObject LaserPointer::getHoveredObject(const PickResultPointer& pi return PickedObject(rayPickResult->objectID, rayPickResult->type); } -Pointer::Buttons LaserPointer::getPressedButtons() { +Pointer::Buttons LaserPointer::getPressedButtons(const PickResultPointer& pickResult) { std::unordered_set toReturn; - for (const PointerTrigger& trigger : _triggers) { - // TODO: right now, LaserPointers don't support axes, only on/off buttons - if (trigger.getEndpoint()->peek() >= 1.0f) { - toReturn.insert(trigger.getButton()); + auto rayPickResult = std::static_pointer_cast(pickResult); + + if (rayPickResult) { + for (const PointerTrigger& trigger : _triggers) { + std::string button = trigger.getButton(); + TriggerState& state = _states[button]; + // TODO: right now, LaserPointers don't support axes, only on/off buttons + if (trigger.getEndpoint()->peek() >= 1.0f) { + toReturn.insert(button); + + if (_previousButtons.find(button) == _previousButtons.end()) { + // start triggering for buttons that were just pressed + state.triggeredObject = PickedObject(rayPickResult->objectID, rayPickResult->type); + state.intersection = rayPickResult->intersection; + state.triggerPos2D = findPos2D(state.triggeredObject, rayPickResult->intersection); + state.triggerStartTime = usecTimestampNow(); + state.surfaceNormal = rayPickResult->surfaceNormal; + state.deadspotExpired = false; + state.wasTriggering = true; + state.triggering = true; + _latestState = state; + } + } else { + // stop triggering for buttons that aren't pressed + state.wasTriggering = state.triggering; + state.triggering = false; + _latestState = state; + } } + _previousButtons = toReturn; } + return toReturn; } @@ -303,7 +329,7 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) { return RenderState(startID, pathID, endID); } -PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover) const { +PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) { QUuid pickedID; glm::vec3 intersection, surfaceNormal, direction, origin; auto rayPickResult = std::static_pointer_cast(pickResult); @@ -316,20 +342,48 @@ PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const P pickedID = rayPickResult->objectID; } - glm::vec2 pos2D; if (pickedID != target.objectID) { - if (target.type == ENTITY) { - intersection = RayPick::intersectRayWithEntityXYPlane(target.objectID, origin, direction); - } else if (target.type == OVERLAY) { - intersection = RayPick::intersectRayWithOverlayXYPlane(target.objectID, origin, direction); - } + intersection = findIntersection(target, origin, direction); } - if (target.type == ENTITY) { - pos2D = RayPick::projectOntoEntityXYPlane(target.objectID, intersection); - } else if (target.type == OVERLAY) { - pos2D = RayPick::projectOntoOverlayXYPlane(target.objectID, intersection); - } else if (target.type == HUD) { - pos2D = DependencyManager::get()->calculatePos2DFromHUD(intersection); + glm::vec2 pos2D = findPos2D(target, intersection); + + // If we just started triggering and we haven't moved too much, don't update intersection and pos2D + TriggerState& state = hover ? _latestState : _states[button]; + float sensorToWorldScale = DependencyManager::get()->getMyAvatar()->getSensorToWorldScale(); + float deadspotSquared = TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED * sensorToWorldScale * sensorToWorldScale; + bool withinDeadspot = usecTimestampNow() - state.triggerStartTime < POINTER_MOVE_DELAY && glm::distance2(pos2D, state.triggerPos2D) < deadspotSquared; + if ((state.triggering || state.wasTriggering) && !state.deadspotExpired && withinDeadspot) { + pos2D = state.triggerPos2D; + intersection = state.intersection; + surfaceNormal = state.surfaceNormal; } + if (!withinDeadspot) { + state.deadspotExpired = true; + } + return PointerEvent(pos2D, intersection, surfaceNormal, direction); +} + +glm::vec3 LaserPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction) { + switch (pickedObject.type) { + case ENTITY: + return RayPick::intersectRayWithEntityXYPlane(pickedObject.objectID, origin, direction); + case OVERLAY: + return RayPick::intersectRayWithOverlayXYPlane(pickedObject.objectID, origin, direction); + default: + return glm::vec3(NAN); + } +} + +glm::vec2 LaserPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) { + switch (pickedObject.type) { + case ENTITY: + return RayPick::projectOntoEntityXYPlane(pickedObject.objectID, origin); + case OVERLAY: + return RayPick::projectOntoOverlayXYPlane(pickedObject.objectID, origin); + case HUD: + return DependencyManager::get()->calculatePos2DFromHUD(origin); + default: + return glm::vec2(NAN); + } } \ No newline at end of file diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index 5cc83749bd..efc5c02729 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -80,10 +80,10 @@ public: static RenderState buildRenderState(const QVariantMap& propMap); protected: - PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover = true) const override; + PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override; PickedObject getHoveredObject(const PickResultPointer& pickResult) override; - Pointer::Buttons getPressedButtons() override; + Pointer::Buttons getPressedButtons(const PickResultPointer& pickResult) override; bool shouldHover(const PickResultPointer& pickResult) override { return _currentRenderState != ""; } bool shouldTrigger(const PickResultPointer& pickResult) override { return _currentRenderState != ""; } @@ -105,6 +105,23 @@ private: void updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay, bool defaultState); void disableRenderState(const RenderState& renderState); + struct TriggerState { + PickedObject triggeredObject; + glm::vec3 intersection { NAN }; + glm::vec3 surfaceNormal { NAN }; + glm::vec2 triggerPos2D { NAN }; + quint64 triggerStartTime { 0 }; + bool deadspotExpired { true }; + bool triggering { false }; + bool wasTriggering { false }; + }; + + Pointer::Buttons _previousButtons; + std::unordered_map _states; + TriggerState _latestState; + static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction); + static glm::vec2 findPos2D(const PickedObject& pickedObject, const glm::vec3& origin); + }; #endif // hifi_LaserPointer_h diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index 501e8d1e42..4a39d5df59 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -92,4 +92,4 @@ glm::vec2 RayPick::projectOntoOverlayXYPlane(const QUuid& overlayID, const glm:: glm::vec2 RayPick::projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized) { auto props = DependencyManager::get()->getEntityProperties(entityID); return projectOntoXYPlane(worldPos, props.getPosition(), props.getRotation(), props.getDimensions(), props.getRegistrationPoint(), unNormalized); -} +} \ No newline at end of file diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index 5afbc2058a..fad6fe47ae 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -28,10 +28,6 @@ static const float TABLET_MAX_TOUCH_DISTANCE = 0.005f; static const float HOVER_HYSTERESIS = 0.01f; static const float TOUCH_HYSTERESIS = 0.001f; -static const float STYLUS_MOVE_DELAY = 0.33f * USECS_PER_SECOND; -static const float TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0481f; -static const float TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED = TOUCH_PRESS_TO_MOVE_DEADSPOT * TOUCH_PRESS_TO_MOVE_DEADSPOT; - StylusPointer::StylusPointer(const QVariant& props, const OverlayID& stylusOverlay, bool hover, bool enabled) : Pointer(DependencyManager::get()->createStylusPick(props), enabled, hover), _stylusOverlay(stylusOverlay) @@ -112,37 +108,37 @@ bool StylusPointer::shouldHover(const PickResultPointer& pickResult) { bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) { auto stylusPickResult = std::static_pointer_cast(pickResult); + bool wasTriggering = false; if (_renderState == EVENTS_ON && stylusPickResult) { auto sensorScaleFactor = DependencyManager::get()->getMyAvatar()->getSensorToWorldScale(); float distance = stylusPickResult->distance; // If we're triggering on an object, recalculate the distance instead of using the pickResult glm::vec3 origin = vec3FromVariant(stylusPickResult->pickVariant["position"]); - glm::vec3 direction = -_state.surfaceNormal; - if (!_state.triggeredObject.objectID.isNull() && stylusPickResult->objectID != _state.triggeredObject.objectID) { + glm::vec3 direction = _state.triggering ? -_state.surfaceNormal : -stylusPickResult->surfaceNormal; + if ((_state.triggering || _state.wasTriggering) && stylusPickResult->objectID != _state.triggeredObject.objectID) { distance = glm::dot(findIntersection(_state.triggeredObject, origin, direction) - origin, direction); } float hysteresis = _state.triggering ? TOUCH_HYSTERESIS * sensorScaleFactor : 0.0f; if (isWithinBounds(distance, TABLET_MIN_TOUCH_DISTANCE * sensorScaleFactor, - TABLET_MAX_TOUCH_DISTANCE * sensorScaleFactor, hysteresis)) { - if (_state.triggeredObject.objectID.isNull()) { + TABLET_MAX_TOUCH_DISTANCE * sensorScaleFactor, hysteresis)) { + _state.wasTriggering = _state.triggering; + if (!_state.triggering) { _state.triggeredObject = PickedObject(stylusPickResult->objectID, stylusPickResult->type); - _state.intersection = findIntersection(_state.triggeredObject, origin, direction); + _state.intersection = stylusPickResult->intersection; _state.triggerPos2D = findPos2D(_state.triggeredObject, origin); _state.triggerStartTime = usecTimestampNow(); _state.surfaceNormal = stylusPickResult->surfaceNormal; + _state.deadspotExpired = false; _state.triggering = true; } return true; } + wasTriggering = _state.triggering; } - _state.triggeredObject = PickedObject(); - _state.intersection = glm::vec3(NAN); - _state.triggerPos2D = glm::vec2(NAN); - _state.triggerStartTime = 0; - _state.surfaceNormal = glm::vec3(NAN); + _state.wasTriggering = wasTriggering; _state.triggering = false; return false; } @@ -155,13 +151,13 @@ Pointer::PickedObject StylusPointer::getHoveredObject(const PickResultPointer& p return PickedObject(stylusPickResult->objectID, stylusPickResult->type); } -Pointer::Buttons StylusPointer::getPressedButtons() { +Pointer::Buttons StylusPointer::getPressedButtons(const PickResultPointer& pickResult) { // TODO: custom buttons for styluses Pointer::Buttons toReturn({ "Primary", "Focus" }); return toReturn; } -PointerEvent StylusPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover) const { +PointerEvent StylusPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) { QUuid pickedID; glm::vec2 pos2D; glm::vec3 intersection, surfaceNormal, direction, origin; @@ -177,18 +173,22 @@ PointerEvent StylusPointer::buildPointerEvent(const PickedObject& target, const } // If we just started triggering and we haven't moved too much, don't update intersection and pos2D - if (!_state.triggeredObject.objectID.isNull() && usecTimestampNow() - _state.triggerStartTime < STYLUS_MOVE_DELAY && - glm::distance2(pos2D, _state.triggerPos2D) < TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED) { + float sensorToWorldScale = DependencyManager::get()->getMyAvatar()->getSensorToWorldScale(); + float deadspotSquared = TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED * sensorToWorldScale * sensorToWorldScale; + bool withinDeadspot = usecTimestampNow() - _state.triggerStartTime < POINTER_MOVE_DELAY && glm::distance2(pos2D, _state.triggerPos2D) < deadspotSquared; + if ((_state.triggering || _state.wasTriggering) && !_state.deadspotExpired && withinDeadspot) { pos2D = _state.triggerPos2D; intersection = _state.intersection; } else if (pickedID != target.objectID) { intersection = findIntersection(target, origin, direction); } + if (!withinDeadspot) { + _state.deadspotExpired = true; + } return PointerEvent(pos2D, intersection, surfaceNormal, direction); } - bool StylusPointer::isWithinBounds(float distance, float min, float max, float hysteresis) { return (distance == glm::clamp(distance, min - hysteresis, max + hysteresis)); } diff --git a/interface/src/raypick/StylusPointer.h b/interface/src/raypick/StylusPointer.h index 9c69915108..950b03b7c9 100644 --- a/interface/src/raypick/StylusPointer.h +++ b/interface/src/raypick/StylusPointer.h @@ -37,11 +37,11 @@ public: protected: PickedObject getHoveredObject(const PickResultPointer& pickResult) override; - Buttons getPressedButtons() override; + Buttons getPressedButtons(const PickResultPointer& pickResult) override; bool shouldHover(const PickResultPointer& pickResult) override; bool shouldTrigger(const PickResultPointer& pickResult) override; - PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover = true) const override; + PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override; private: void show(const StylusTip& tip); @@ -53,7 +53,9 @@ private: glm::vec2 triggerPos2D { NAN }; glm::vec3 surfaceNormal { NAN }; quint64 triggerStartTime { 0 }; + bool deadspotExpired { true }; bool triggering { false }; + bool wasTriggering { false }; bool hovering { false }; }; diff --git a/interface/src/scripting/AccountScriptingInterface.cpp b/interface/src/scripting/AccountScriptingInterface.cpp index 068cccdc50..fd54198b3c 100644 --- a/interface/src/scripting/AccountScriptingInterface.cpp +++ b/interface/src/scripting/AccountScriptingInterface.cpp @@ -12,47 +12,25 @@ #include "AccountManager.h" #include "AccountScriptingInterface.h" +#include "GlobalServicesScriptingInterface.h" AccountScriptingInterface* AccountScriptingInterface::getInstance() { static AccountScriptingInterface sharedInstance; - auto accountManager = DependencyManager::get(); - QObject::connect(accountManager.data(), &AccountManager::profileChanged, - &sharedInstance, &AccountScriptingInterface::usernameChanged); - QObject::connect(accountManager.data(), &AccountManager::usernameChanged, - &sharedInstance, &AccountScriptingInterface::onUsernameChanged); return &sharedInstance; } bool AccountScriptingInterface::isLoggedIn() { - auto accountManager = DependencyManager::get(); - return accountManager->isLoggedIn(); -} - -bool AccountScriptingInterface::checkAndSignalForAccessToken() { - auto accountManager = DependencyManager::get(); - return accountManager->checkAndSignalForAccessToken(); + return GlobalServicesScriptingInterface::getInstance()->isLoggedIn(); } void AccountScriptingInterface::logOut() { - auto accountManager = DependencyManager::get(); - return accountManager->logout(); + GlobalServicesScriptingInterface::getInstance()->logOut(); } -AccountScriptingInterface::AccountScriptingInterface(QObject *parent): QObject(parent) { - m_loggedIn = isLoggedIn(); - emit loggedInChanged(m_loggedIn); -} - -void AccountScriptingInterface::onUsernameChanged(QString username) { - m_loggedIn = (username != QString()); - emit loggedInChanged(m_loggedIn); +bool AccountScriptingInterface::loggedIn() const { + return GlobalServicesScriptingInterface::getInstance()->loggedIn(); } QString AccountScriptingInterface::getUsername() { - auto accountManager = DependencyManager::get(); - if (accountManager->isLoggedIn()) { - return accountManager->getAccountInfo().getUsername(); - } else { - return "Unknown user"; - } + return GlobalServicesScriptingInterface::getInstance()->getUsername(); } diff --git a/interface/src/scripting/AccountScriptingInterface.h b/interface/src/scripting/AccountScriptingInterface.h index 4ff250e926..10d33ffa36 100644 --- a/interface/src/scripting/AccountScriptingInterface.h +++ b/interface/src/scripting/AccountScriptingInterface.h @@ -17,18 +17,12 @@ class AccountScriptingInterface : public QObject { Q_OBJECT - Q_PROPERTY(QString username READ getUsername NOTIFY usernameChanged) - Q_PROPERTY(bool loggedIn READ loggedIn NOTIFY loggedInChanged) - /**jsdoc * @namespace Account * @property username {String} username if user is logged in, otherwise it returns "Unknown user" */ - -public: - - Q_PROPERTY(QUrl metaverseServerURL READ getMetaverseServerURL) - QUrl getMetaverseServerURL() { return DependencyManager::get()->getMetaverseServerURL(); } + Q_PROPERTY(QString username READ getUsername) + Q_PROPERTY(bool loggedIn READ loggedIn) signals: @@ -56,21 +50,11 @@ public slots: * @return {bool} true when user is logged into the High Fidelity metaverse. */ bool isLoggedIn(); - bool checkAndSignalForAccessToken(); void logOut(); public: - AccountScriptingInterface(QObject* parent = nullptr); - bool loggedIn() const { - return m_loggedIn; - } - -private slots: - void onUsernameChanged(QString username); - -private: - bool m_loggedIn { false }; - + AccountScriptingInterface(QObject* parent = nullptr) {} + bool loggedIn() const; }; #endif // hifi_AccountScriptingInterface_h diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.cpp b/interface/src/scripting/AssetMappingsScriptingInterface.cpp index a1b2a9ccfc..72b6f401e9 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.cpp +++ b/interface/src/scripting/AssetMappingsScriptingInterface.cpp @@ -31,6 +31,8 @@ AssetMappingsScriptingInterface::AssetMappingsScriptingInterface() { _proxyModel.setSourceModel(&_assetMappingModel); _proxyModel.setSortRole(Qt::DisplayRole); _proxyModel.setDynamicSortFilter(true); + _proxyModel.setSortLocaleAware(true); + _proxyModel.setFilterCaseSensitivity(Qt::CaseInsensitive); _proxyModel.sort(0); } diff --git a/interface/src/scripting/GlobalServicesScriptingInterface.cpp b/interface/src/scripting/GlobalServicesScriptingInterface.cpp index f4a5ffb39c..305bfa3e79 100644 --- a/interface/src/scripting/GlobalServicesScriptingInterface.cpp +++ b/interface/src/scripting/GlobalServicesScriptingInterface.cpp @@ -18,7 +18,7 @@ GlobalServicesScriptingInterface::GlobalServicesScriptingInterface() { auto accountManager = DependencyManager::get(); - connect(accountManager.data(), &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged); + connect(accountManager.data(), &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::onUsernameChanged); connect(accountManager.data(), &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut); connect(accountManager.data(), &AccountManager::loginComplete, this, &GlobalServicesScriptingInterface::connected); @@ -31,11 +31,14 @@ GlobalServicesScriptingInterface::GlobalServicesScriptingInterface() { auto discoverabilityManager = DependencyManager::get(); connect(discoverabilityManager.data(), &DiscoverabilityManager::discoverabilityModeChanged, this, &GlobalServicesScriptingInterface::discoverabilityModeChanged); + + _loggedIn = isLoggedIn(); + emit loggedInChanged(_loggedIn); } GlobalServicesScriptingInterface::~GlobalServicesScriptingInterface() { auto accountManager = DependencyManager::get(); - disconnect(accountManager.data(), &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged); + disconnect(accountManager.data(), &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::onUsernameChanged); disconnect(accountManager.data(), &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut); disconnect(accountManager.data(), &AccountManager::loginComplete, this, &GlobalServicesScriptingInterface::connected); } @@ -45,8 +48,28 @@ GlobalServicesScriptingInterface* GlobalServicesScriptingInterface::getInstance( return &sharedInstance; } -const QString& GlobalServicesScriptingInterface::getUsername() const { - return DependencyManager::get()->getAccountInfo().getUsername(); +const QString GlobalServicesScriptingInterface::getUsername() const { + auto accountManager = DependencyManager::get(); + if (accountManager->isLoggedIn()) { + return accountManager->getAccountInfo().getUsername(); + } else { + return "Unknown user"; + } +} + +bool GlobalServicesScriptingInterface::isLoggedIn() { + auto accountManager = DependencyManager::get(); + return accountManager->isLoggedIn(); +} + +bool GlobalServicesScriptingInterface::checkAndSignalForAccessToken() { + auto accountManager = DependencyManager::get(); + return accountManager->checkAndSignalForAccessToken(); +} + +void GlobalServicesScriptingInterface::logOut() { + auto accountManager = DependencyManager::get(); + return accountManager->logout(); } void GlobalServicesScriptingInterface::loggedOut() { @@ -77,6 +100,12 @@ void GlobalServicesScriptingInterface::discoverabilityModeChanged(Discoverabilit emit findableByChanged(DiscoverabilityManager::findableByString(discoverabilityMode)); } +void GlobalServicesScriptingInterface::onUsernameChanged(const QString& username) { + _loggedIn = (username != QString()); + emit myUsernameChanged(username); + emit loggedInChanged(_loggedIn); +} + DownloadInfoResult::DownloadInfoResult() : downloading(QList()), pending(0.0f) diff --git a/interface/src/scripting/GlobalServicesScriptingInterface.h b/interface/src/scripting/GlobalServicesScriptingInterface.h index 63294fc656..93d35e9ce8 100644 --- a/interface/src/scripting/GlobalServicesScriptingInterface.h +++ b/interface/src/scripting/GlobalServicesScriptingInterface.h @@ -35,17 +35,25 @@ void DownloadInfoResultFromScriptValue(const QScriptValue& object, DownloadInfoR class GlobalServicesScriptingInterface : public QObject { Q_OBJECT - Q_PROPERTY(QString username READ getUsername) + Q_PROPERTY(QString username READ getUsername NOTIFY myUsernameChanged) + Q_PROPERTY(bool loggedIn READ loggedIn NOTIFY loggedInChanged) Q_PROPERTY(QString findableBy READ getFindableBy WRITE setFindableBy NOTIFY findableByChanged) + Q_PROPERTY(QUrl metaverseServerURL READ getMetaverseServerURL) public: static GlobalServicesScriptingInterface* getInstance(); - const QString& getUsername() const; + const QString getUsername() const; + bool loggedIn() const { return _loggedIn; } + QUrl getMetaverseServerURL() { return DependencyManager::get()->getMetaverseServerURL(); } public slots: DownloadInfoResult getDownloadInfo(); void updateDownloadInfo(); + + bool isLoggedIn(); + bool checkAndSignalForAccessToken(); + void logOut(); private slots: void loggedOut(); @@ -55,18 +63,22 @@ private slots: void setFindableBy(const QString& discoverabilityMode); void discoverabilityModeChanged(Discoverability::Mode discoverabilityMode); + void onUsernameChanged(const QString& username); + signals: void connected(); void disconnected(const QString& reason); void myUsernameChanged(const QString& username); void downloadInfoChanged(DownloadInfoResult info); void findableByChanged(const QString& discoverabilityMode); + void loggedInChanged(bool loggedIn); private: GlobalServicesScriptingInterface(); ~GlobalServicesScriptingInterface(); bool _downloading; + bool _loggedIn{ false }; }; #endif // hifi_GlobalServicesScriptingInterface_h diff --git a/interface/src/scripting/GooglePolyScriptingInterface.cpp b/interface/src/scripting/GooglePolyScriptingInterface.cpp new file mode 100644 index 0000000000..8ed5d59d07 --- /dev/null +++ b/interface/src/scripting/GooglePolyScriptingInterface.cpp @@ -0,0 +1,181 @@ +// +// GooglePolyScriptingInterface.cpp +// interface/src/scripting +// +// Created by Elisa Lupin-Jimenez on 12/3/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 +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "GooglePolyScriptingInterface.h" +#include "ScriptEngineLogging.h" + +const QString LIST_POLY_URL = "https://poly.googleapis.com/v1/assets?"; +const QString GET_POLY_URL = "https://poly.googleapis.com/v1/assets/model?"; + +const QStringList VALID_FORMATS = QStringList() << "BLOCKS" << "FBX" << "GLTF" << "GLTF2" << "OBJ" << "TILT" << ""; +const QStringList VALID_CATEGORIES = QStringList() << "animals" << "architecture" << "art" << "food" << + "nature" << "objects" << "people" << "scenes" << "technology" << "transport" << ""; + +GooglePolyScriptingInterface::GooglePolyScriptingInterface() { + // nothing to be implemented +} + +void GooglePolyScriptingInterface::setAPIKey(const QString& key) { + _authCode = key; +} + +QString GooglePolyScriptingInterface::getAssetList(const QString& keyword, const QString& category, const QString& format) { + QUrl url = formatURLQuery(keyword, category, format); + if (!url.isEmpty()) { + QByteArray json = parseJSON(url, 0).toJsonDocument().toJson(); + return (QString) json; + } else { + qCDebug(scriptengine) << "Invalid filters were specified."; + return ""; + } +} + +QString GooglePolyScriptingInterface::getFBX(const QString& keyword, const QString& category) { + QUrl url = formatURLQuery(keyword, category, "FBX"); + return getModelURL(url); +} + +QString GooglePolyScriptingInterface::getOBJ(const QString& keyword, const QString& category) { + QUrl url = formatURLQuery(keyword, category, "OBJ"); + return getModelURL(url); +} + +QString GooglePolyScriptingInterface::getBlocks(const QString& keyword, const QString& category) { + QUrl url = formatURLQuery(keyword, category, "BLOCKS"); + return getModelURL(url); +} + +QString GooglePolyScriptingInterface::getGLTF(const QString& keyword, const QString& category) { + QUrl url = formatURLQuery(keyword, category, "GLTF"); + return getModelURL(url); +} + +QString GooglePolyScriptingInterface::getGLTF2(const QString& keyword, const QString& category) { + QUrl url = formatURLQuery(keyword, category, "GLTF2"); + return getModelURL(url); +} + +// This method will not be useful until we support Tilt models +QString GooglePolyScriptingInterface::getTilt(const QString& keyword, const QString& category) { + QUrl url = formatURLQuery(keyword, category, "TILT"); + return getModelURL(url); +} + +// Can provide asset name or full URL to model +QString GooglePolyScriptingInterface::getModelInfo(const QString& input) { + QString name(input); + if (input.contains("poly.googleapis") || input.contains("poly.google.com")) { + QStringList list = input.split("/"); + if (input.contains("poly.googleapis")) { + name = list[4]; + } else { + name = list.last(); + } + } + QString urlString(GET_POLY_URL); + urlString = urlString.replace("model", name) + "key=" + _authCode; + qCDebug(scriptengine) << "Google URL request: " << urlString; + QUrl url(urlString); + QString json = parseJSON(url, 2).toString(); + return json; +} + +int GooglePolyScriptingInterface::getRandIntInRange(int length) { + QTime time = QTime::currentTime(); + qsrand((uint)time.msec()); + return qrand() % length; +} + +QUrl GooglePolyScriptingInterface::formatURLQuery(const QString& keyword, const QString& category, const QString& format) { + QString queries; + if (!VALID_FORMATS.contains(format, Qt::CaseInsensitive) || !VALID_CATEGORIES.contains(category, Qt::CaseInsensitive)) { + return QUrl(""); + } else { + if (!keyword.isEmpty()) { + QString keywords(keyword); + keywords.replace(" ", "+"); + queries.append("&keywords=" + keywords); + } + if (!category.isEmpty()) { + queries.append("&category=" + category); + } + if (!format.isEmpty()) { + queries.append("&format=" + format); + } + QString urlString(LIST_POLY_URL + "key=" + _authCode + queries); + return QUrl(urlString); + } +} + +QString GooglePolyScriptingInterface::getModelURL(const QUrl& url) { + qCDebug(scriptengine) << "Google URL request: " << url; + if (!url.isEmpty()) { + return parseJSON(url, 1).toString(); + } else { + qCDebug(scriptengine) << "Invalid filters were specified."; + return ""; + } +} + +// FIXME: synchronous +QByteArray GooglePolyScriptingInterface::getHTTPRequest(const QUrl& url) { + QNetworkAccessManager manager; + QNetworkReply *response = manager.get(QNetworkRequest(url)); + QEventLoop event; + connect(response, SIGNAL(finished()), &event, SLOT(quit())); + event.exec(); + + return response->readAll(); +} + +// 0 = asset list, 1 = model from asset list, 2 = specific model +QVariant GooglePolyScriptingInterface::parseJSON(const QUrl& url, int fileType) { + QByteArray jsonString = getHTTPRequest(url); + QJsonDocument doc = QJsonDocument::fromJson(jsonString); + QJsonObject obj = doc.object(); + if (obj.isEmpty()) { + qCDebug(scriptengine) << "Assets with specified filters not found"; + return ""; + } + if (obj.keys().first() == "error") { + QString error = obj.value("error").toObject().value("message").toString(); + qCDebug(scriptengine) << error; + return ""; + } + if (fileType == 0 || fileType == 1) { + QJsonArray arr = obj.value("assets").toArray(); + // return model url + if (fileType == 1) { + int random = getRandIntInRange(arr.size()); + QJsonObject json = arr.at(random).toObject(); + // nested JSONs + return json.value("formats").toArray().at(0).toObject().value("root").toObject().value("url"); + } + // return whole asset list + return QJsonDocument(arr); + // return specific object + } else { + return jsonString; + } +} diff --git a/interface/src/scripting/GooglePolyScriptingInterface.h b/interface/src/scripting/GooglePolyScriptingInterface.h new file mode 100644 index 0000000000..fdd3597bec --- /dev/null +++ b/interface/src/scripting/GooglePolyScriptingInterface.h @@ -0,0 +1,47 @@ +// +// GooglePolyScriptingInterface.h +// interface/src/scripting +// +// Created by Elisa Lupin-Jimenez on 12/3/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 +// + +#ifndef hifi_GooglePolyScriptingInterface_h +#define hifi_GooglePolyScriptingInterface_h + +#include +#include + +class GooglePolyScriptingInterface : public QObject, public Dependency { + Q_OBJECT + +public: + GooglePolyScriptingInterface(); + +public slots: + void setAPIKey(const QString& key); + + QString getAssetList(const QString& keyword, const QString& category, const QString& format); + QString getFBX(const QString& keyword, const QString& category); + QString getOBJ(const QString& keyword, const QString& category); + QString getBlocks(const QString& keyword, const QString& categoryy); + QString getGLTF(const QString& keyword, const QString& category); + QString getGLTF2(const QString& keyword, const QString& category); + QString getTilt(const QString& keyword, const QString& category); + QString getModelInfo(const QString& input); + +private: + QString _authCode; + + QUrl formatURLQuery(const QString& keyword, const QString& category, const QString& format); + QString getModelURL(const QUrl& url); + QByteArray getHTTPRequest(const QUrl& url); + QVariant parseJSON(const QUrl& url, int fileType); + int getRandIntInRange(int length); + +}; + +#endif // hifi_GooglePolyScriptingInterface_h diff --git a/interface/src/scripting/TestScriptingInterface.cpp b/interface/src/scripting/TestScriptingInterface.cpp index d4b4ba1480..74a15db0ce 100644 --- a/interface/src/scripting/TestScriptingInterface.cpp +++ b/interface/src/scripting/TestScriptingInterface.cpp @@ -11,11 +11,12 @@ #include #include +#include #include #include -#include -#include #include +#include +#include #include "Application.h" @@ -141,6 +142,15 @@ void TestScriptingInterface::endTraceEvent(QString name) { tracing::traceEvent(trace_test(), name, tracing::DurationEnd); } +void TestScriptingInterface::savePhysicsSimulationStats(QString originalPath) { + QString path = FileUtils::replaceDateTimeTokens(originalPath); + path = FileUtils::computeDocumentPath(path); + if (!FileUtils::canCreateFile(path)) { + return; + } + qApp->saveNextPhysicsStats(path); +} + void TestScriptingInterface::profileRange(const QString& name, QScriptValue fn) { PROFILE_RANGE(script, name); fn.call(); diff --git a/interface/src/scripting/TestScriptingInterface.h b/interface/src/scripting/TestScriptingInterface.h index 73b8f0ac93..aca07d110b 100644 --- a/interface/src/scripting/TestScriptingInterface.h +++ b/interface/src/scripting/TestScriptingInterface.h @@ -71,6 +71,11 @@ public slots: void endTraceEvent(QString name); + /**jsdoc + * Write detailed timing stats of next physics stepSimulation() to filename + */ + void savePhysicsSimulationStats(QString filename); + Q_INVOKABLE void profileRange(const QString& name, QScriptValue function); private: diff --git a/interface/src/ui/BaseLogDialog.cpp b/interface/src/ui/BaseLogDialog.cpp index 571d3ac403..6830de6e35 100644 --- a/interface/src/ui/BaseLogDialog.cpp +++ b/interface/src/ui/BaseLogDialog.cpp @@ -16,11 +16,10 @@ #include #include #include -#include #include -const int TOP_BAR_HEIGHT = 46; +const int TOP_BAR_HEIGHT = 124; const int INITIAL_WIDTH = 720; const int INITIAL_HEIGHT = 480; const int MINIMAL_WIDTH = 700; @@ -34,21 +33,6 @@ const QColor HIGHLIGHT_COLOR = QColor("#3366CC"); const QColor BOLD_COLOR = QColor("#445c8c"); const QString BOLD_PATTERN = "\\[\\d*\\/.*:\\d*:\\d*\\]"; -class Highlighter : public QSyntaxHighlighter { -public: - Highlighter(QTextDocument* parent = nullptr); - void setBold(int indexToBold); - QString keyword; - -protected: - void highlightBlock(const QString& text) override; - -private: - QTextCharFormat boldFormat; - QTextCharFormat keywordFormat; - -}; - BaseLogDialog::BaseLogDialog(QWidget* parent) : QDialog(parent, Qt::Window) { setWindowTitle("Base Log"); setAttribute(Qt::WA_DeleteOnClose); @@ -77,7 +61,7 @@ void BaseLogDialog::initControls() { _searchButton->setGeometry(_leftPad, ELEMENT_MARGIN, SEARCH_BUTTON_WIDTH, ELEMENT_HEIGHT); _leftPad += SEARCH_BUTTON_WIDTH; _searchButton->show(); - connect(_searchButton, SIGNAL(clicked()), SLOT(handleSearchButton())); + connect(_searchButton, &QPushButton::clicked, this, &BaseLogDialog::handleSearchButton); _searchTextBox = new QLineEdit(this); // disable blue outline in Mac @@ -85,8 +69,8 @@ void BaseLogDialog::initControls() { _searchTextBox->setGeometry(_leftPad, ELEMENT_MARGIN, SEARCH_TEXT_WIDTH, ELEMENT_HEIGHT); _leftPad += SEARCH_TEXT_WIDTH + BUTTON_MARGIN; _searchTextBox->show(); - connect(_searchTextBox, SIGNAL(textChanged(QString)), SLOT(handleSearchTextChanged(QString))); - connect(_searchTextBox, SIGNAL(returnPressed()), SLOT(toggleSearchNext())); + connect(_searchTextBox, &QLineEdit::textChanged, this, &BaseLogDialog::handleSearchTextChanged); + connect(_searchTextBox, &QLineEdit::returnPressed, this, &BaseLogDialog::toggleSearchNext); _searchPrevButton = new QPushButton(this); _searchPrevButton->setObjectName("searchPrevButton"); @@ -94,7 +78,7 @@ void BaseLogDialog::initControls() { _searchPrevButton->setText("Prev"); _leftPad += SEARCH_TOGGLE_BUTTON_WIDTH + BUTTON_MARGIN; _searchPrevButton->show(); - connect(_searchPrevButton, SIGNAL(clicked()), SLOT(toggleSearchPrev())); + connect(_searchPrevButton, &QPushButton::clicked, this, &BaseLogDialog::toggleSearchPrev); _searchNextButton = new QPushButton(this); _searchNextButton->setObjectName("searchNextButton"); @@ -102,16 +86,16 @@ void BaseLogDialog::initControls() { _searchNextButton->setText("Next"); _leftPad += SEARCH_TOGGLE_BUTTON_WIDTH + CHECKBOX_MARGIN; _searchNextButton->show(); - connect(_searchNextButton, SIGNAL(clicked()), SLOT(toggleSearchNext())); + connect(_searchNextButton, &QPushButton::clicked, this, &BaseLogDialog::toggleSearchNext); _logTextBox = new QPlainTextEdit(this); _logTextBox->setReadOnly(true); _logTextBox->show(); _highlighter = new Highlighter(_logTextBox->document()); - connect(_logTextBox, SIGNAL(selectionChanged()), SLOT(updateSelection())); + connect(_logTextBox, &QPlainTextEdit::selectionChanged, this, &BaseLogDialog::updateSelection); } -void BaseLogDialog::showEvent(QShowEvent* event) { +void BaseLogDialog::showEvent(QShowEvent* event) { showLogData(); } @@ -132,15 +116,12 @@ void BaseLogDialog::handleSearchButton() { } void BaseLogDialog::handleSearchTextChanged(QString searchText) { - if (searchText.isEmpty()) { - return; - } - QTextCursor cursor = _logTextBox->textCursor(); + if (cursor.hasSelection()) { QString selectedTerm = cursor.selectedText(); if (selectedTerm == searchText) { - return; + return; } } @@ -158,6 +139,10 @@ void BaseLogDialog::handleSearchTextChanged(QString searchText) { _highlighter->rehighlight(); } +void BaseLogDialog::clearSearch() { + _searchTextBox->setText(""); +} + void BaseLogDialog::toggleSearchPrev() { QTextCursor searchCursor = _logTextBox->textCursor(); if (searchCursor.hasSelection()) { diff --git a/interface/src/ui/BaseLogDialog.h b/interface/src/ui/BaseLogDialog.h index e18d23937f..a2f890914d 100644 --- a/interface/src/ui/BaseLogDialog.h +++ b/interface/src/ui/BaseLogDialog.h @@ -13,17 +13,33 @@ #define hifi_BaseLogDialog_h #include +#include const int ELEMENT_MARGIN = 7; const int ELEMENT_HEIGHT = 32; const int CHECKBOX_MARGIN = 12; -const int CHECKBOX_WIDTH = 140; +const int CHECKBOX_WIDTH = 110; +const int COMBOBOX_WIDTH = 160; const int BUTTON_MARGIN = 8; class QPushButton; class QLineEdit; class QPlainTextEdit; -class Highlighter; + +class Highlighter : public QSyntaxHighlighter { +public: + Highlighter(QTextDocument* parent = nullptr); + void setBold(int indexToBold); + QString keyword; + +protected: + void highlightBlock(const QString& text) override; + +private: + QTextCharFormat boldFormat; + QTextCharFormat keywordFormat; + +}; class BaseLogDialog : public QDialog { Q_OBJECT @@ -33,7 +49,7 @@ public: ~BaseLogDialog(); public slots: - void appendLogLine(QString logLine); + virtual void appendLogLine(QString logLine); private slots: void updateSelection(); @@ -43,21 +59,22 @@ private slots: void toggleSearchNext(); protected: - int _leftPad { 0 }; + int _leftPad{ 0 }; + QString _searchTerm; + QPlainTextEdit* _logTextBox{ nullptr }; + Highlighter* _highlighter{ nullptr }; void resizeEvent(QResizeEvent* event) override; void showEvent(QShowEvent* event) override; virtual QString getCurrentLog() = 0; + void clearSearch(); private: - QPushButton* _searchButton { nullptr }; - QLineEdit* _searchTextBox { nullptr }; - QPlainTextEdit* _logTextBox { nullptr }; - QPushButton* _searchPrevButton { nullptr }; - QPushButton* _searchNextButton { nullptr }; - QString _searchTerm; - Highlighter* _highlighter { nullptr }; - + QPushButton* _searchButton{ nullptr }; + QLineEdit* _searchTextBox{ nullptr }; + QPushButton* _searchPrevButton{ nullptr }; + QPushButton* _searchNextButton{ nullptr }; + void initControls(); void showLogData(); }; diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index f216bb4edc..ff2d4868df 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -31,7 +31,7 @@ #include "scripting/HMDScriptingInterface.h" -static const QVariant TABLET_ADDRESS_DIALOG = "TabletAddressDialog.qml"; +static const QVariant TABLET_ADDRESS_DIALOG = "hifi/tablet/TabletAddressDialog.qml"; template void DialogsManager::maybeCreateDialog(QPointer& member) { if (!member) { @@ -91,7 +91,7 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) { ConnectionFailureDialog::hide(); } } else { - static const QUrl url("../../dialogs/TabletConnectionFailureDialog.qml"); + static const QUrl url("dialogs/TabletConnectionFailureDialog.qml"); auto hmd = DependencyManager::get(); if (visible) { tablet->initialScreen(url); diff --git a/interface/src/ui/LogDialog.cpp b/interface/src/ui/LogDialog.cpp index cce7879e94..00dc9be959 100644 --- a/interface/src/ui/LogDialog.cpp +++ b/interface/src/ui/LogDialog.cpp @@ -13,48 +13,287 @@ #include #include +#include +#include #include const int REVEAL_BUTTON_WIDTH = 122; +const int CLEAR_FILTER_BUTTON_WIDTH = 80; +const int MARGIN_LEFT = 25; +const int DEBUG_CHECKBOX_WIDTH = 70; +const int INFO_CHECKBOX_WIDTH = 65; +const int CRITICAL_CHECKBOX_WIDTH = 85; +const int WARNING_CHECKBOX_WIDTH = 80; +const int SUPPRESS_CHECKBOX_WIDTH = 87; +const int FATAL_CHECKBOX_WIDTH = 70; +const int UNKNOWN_CHECKBOX_WIDTH = 80; +const int SECOND_ROW = ELEMENT_MARGIN + ELEMENT_MARGIN + ELEMENT_HEIGHT; +const int THIRD_ROW = SECOND_ROW + ELEMENT_MARGIN + ELEMENT_HEIGHT; +const QString DEBUG_TEXT = "[DEBUG]"; +const QString INFO_TEXT = "[INFO]"; +const QString CRITICAL_TEXT = "[CRITICAL]"; +const QString WARNING_TEXT = "[WARNING]"; +const QString FATAL_TEXT = "[FATAL]"; +const QString SUPPRESS_TEXT = "[SUPPRESS]"; +const QString UNKNOWN_TEXT = "[UNKNOWN]"; LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : BaseLogDialog(parent) { _logger = logger; setWindowTitle("Log"); - _extraDebuggingBox = new QCheckBox("Extra debugging", this); - _extraDebuggingBox->setGeometry(_leftPad, ELEMENT_MARGIN, CHECKBOX_WIDTH, ELEMENT_HEIGHT); - if (_logger->extraDebugging()) { - _extraDebuggingBox->setCheckState(Qt::Checked); - } - _extraDebuggingBox->show(); - connect(_extraDebuggingBox, SIGNAL(stateChanged(int)), SLOT(handleExtraDebuggingCheckbox(int))); - _revealLogButton = new QPushButton("Reveal log file", this); // set object name for css styling _revealLogButton->setObjectName("revealLogButton"); _revealLogButton->show(); - connect(_revealLogButton, SIGNAL(clicked()), SLOT(handleRevealButton())); + connect(_revealLogButton, &QPushButton::clicked, this, &LogDialog::handleRevealButton); - connect(_logger, SIGNAL(logReceived(QString)), this, SLOT(appendLogLine(QString)), Qt::QueuedConnection); + connect(_logger, &AbstractLoggerInterface::logReceived, this, &LogDialog::appendLogLine); + + _leftPad = MARGIN_LEFT; + _debugPrintBox = new QCheckBox("DEBUG", this); + _debugPrintBox->setGeometry(_leftPad, SECOND_ROW, DEBUG_CHECKBOX_WIDTH, ELEMENT_HEIGHT); + if (_logger->debugPrint()) { + _debugPrintBox->setCheckState(Qt::Checked); + } + _debugPrintBox->show(); + connect(_debugPrintBox, &QCheckBox::stateChanged, this, &LogDialog::handleDebugPrintBox); + + _leftPad += DEBUG_CHECKBOX_WIDTH + BUTTON_MARGIN; + _infoPrintBox = new QCheckBox("INFO", this); + _infoPrintBox->setGeometry(_leftPad, SECOND_ROW, INFO_CHECKBOX_WIDTH, ELEMENT_HEIGHT); + if (_logger->infoPrint()) { + _infoPrintBox->setCheckState(Qt::Checked); + } + _infoPrintBox->show(); + connect(_infoPrintBox, &QCheckBox::stateChanged, this, &LogDialog::handleInfoPrintBox); + + _leftPad += INFO_CHECKBOX_WIDTH + BUTTON_MARGIN; + _criticalPrintBox = new QCheckBox("CRITICAL", this); + _criticalPrintBox->setGeometry(_leftPad, SECOND_ROW, CRITICAL_CHECKBOX_WIDTH, ELEMENT_HEIGHT); + if (_logger->criticalPrint()) { + _criticalPrintBox->setCheckState(Qt::Checked); + } + _criticalPrintBox->show(); + connect(_criticalPrintBox, &QCheckBox::stateChanged, this, &LogDialog::handleCriticalPrintBox); + + _leftPad += CRITICAL_CHECKBOX_WIDTH + BUTTON_MARGIN; + _warningPrintBox = new QCheckBox("WARNING", this); + _warningPrintBox->setGeometry(_leftPad, SECOND_ROW, WARNING_CHECKBOX_WIDTH, ELEMENT_HEIGHT); + if (_logger->warningPrint()) { + _warningPrintBox->setCheckState(Qt::Checked); + } + _warningPrintBox->show(); + connect(_warningPrintBox, &QCheckBox::stateChanged, this, &LogDialog::handleWarningPrintBox); + + _leftPad += WARNING_CHECKBOX_WIDTH + BUTTON_MARGIN; + _suppressPrintBox = new QCheckBox("SUPPRESS", this); + _suppressPrintBox->setGeometry(_leftPad, SECOND_ROW, SUPPRESS_CHECKBOX_WIDTH, ELEMENT_HEIGHT); + if (_logger->suppressPrint()) { + _suppressPrintBox->setCheckState(Qt::Checked); + } + _suppressPrintBox->show(); + connect(_suppressPrintBox, &QCheckBox::stateChanged, this, &LogDialog::handleSuppressPrintBox); + + _leftPad += SUPPRESS_CHECKBOX_WIDTH + BUTTON_MARGIN; + _fatalPrintBox = new QCheckBox("FATAL", this); + _fatalPrintBox->setGeometry(_leftPad, SECOND_ROW, FATAL_CHECKBOX_WIDTH, ELEMENT_HEIGHT); + if (_logger->fatalPrint()) { + _fatalPrintBox->setCheckState(Qt::Checked); + } + _fatalPrintBox->show(); + connect(_fatalPrintBox, &QCheckBox::stateChanged, this, &LogDialog::handleFatalPrintBox); + + _leftPad += FATAL_CHECKBOX_WIDTH + BUTTON_MARGIN; + _unknownPrintBox = new QCheckBox("UNKNOWN", this); + _unknownPrintBox->setGeometry(_leftPad, SECOND_ROW, UNKNOWN_CHECKBOX_WIDTH, ELEMENT_HEIGHT); + if (_logger->unknownPrint()) { + _unknownPrintBox->setCheckState(Qt::Checked); + } + _unknownPrintBox->show(); + connect(_unknownPrintBox, &QCheckBox::stateChanged, this, &LogDialog::handleUnknownPrintBox); + + _leftPad = MARGIN_LEFT; + _filterDropdown = new QComboBox(this); + _filterDropdown->setGeometry(_leftPad, THIRD_ROW, COMBOBOX_WIDTH, ELEMENT_HEIGHT); + _filterDropdown->addItem("No secondary filter..."); + _filterDropdown->addItem("default"); + _filterDropdown->addItem("hifi.audio"); + _filterDropdown->addItem("hifi.audioclient"); + _filterDropdown->addItem("hifi.animation"); + _filterDropdown->addItem("hifi.avatars"); + _filterDropdown->addItem("hifi.commerce"); + _filterDropdown->addItem("hifi.controllers"); + _filterDropdown->addItem("hifi.entities"); + _filterDropdown->addItem("hifi.gl"); + _filterDropdown->addItem("hifi.gpu.gl"); + _filterDropdown->addItem("hifi.interface"); + _filterDropdown->addItem("hifi.interface.deadlock"); + _filterDropdown->addItem("hifi.modelformat"); + _filterDropdown->addItem("hifi.networking"); + _filterDropdown->addItem("hifi.networking.resource"); + _filterDropdown->addItem("hifi.plugins"); + _filterDropdown->addItem("hifi.render"); + _filterDropdown->addItem("hifi.scriptengine"); + _filterDropdown->addItem("hifi.scriptengine.module"); + _filterDropdown->addItem("hifi.scriptengine.script"); + _filterDropdown->addItem("hifi.shared"); + _filterDropdown->addItem("hifi.ui"); + _filterDropdown->addItem("qml"); + connect(_filterDropdown, static_cast(&QComboBox::currentIndexChanged), this, &LogDialog::handleFilterDropdownChanged); + + _extraDebuggingBox = new QCheckBox("Extra debugging", this); + if (_logger->extraDebugging()) { + _extraDebuggingBox->setCheckState(Qt::Checked); + } + _extraDebuggingBox->show(); + connect(_extraDebuggingBox, &QCheckBox::stateChanged, this, &LogDialog::handleExtraDebuggingCheckbox); + + _clearFilterButton = new QPushButton("Clear Filters", this); + // set object name for css styling + _clearFilterButton->setObjectName("showAllButton"); + _clearFilterButton->show(); + connect(_clearFilterButton, &QPushButton::clicked, this, &LogDialog::handleClearFilterButton); + handleClearFilterButton(); } void LogDialog::resizeEvent(QResizeEvent* event) { BaseLogDialog::resizeEvent(event); _revealLogButton->setGeometry(width() - ELEMENT_MARGIN - REVEAL_BUTTON_WIDTH, - ELEMENT_MARGIN, - REVEAL_BUTTON_WIDTH, - ELEMENT_HEIGHT); + ELEMENT_MARGIN, + REVEAL_BUTTON_WIDTH, + ELEMENT_HEIGHT); + _clearFilterButton->setGeometry(width() - ELEMENT_MARGIN - CLEAR_FILTER_BUTTON_WIDTH, + THIRD_ROW, + CLEAR_FILTER_BUTTON_WIDTH, + ELEMENT_HEIGHT); + _extraDebuggingBox->setGeometry(width() - ELEMENT_MARGIN - COMBOBOX_WIDTH - ELEMENT_MARGIN - CLEAR_FILTER_BUTTON_WIDTH, + THIRD_ROW, + COMBOBOX_WIDTH, + ELEMENT_HEIGHT); } void LogDialog::handleRevealButton() { _logger->locateLog(); } +void LogDialog::handleClearFilterButton() { + _logger->setExtraDebugging(false); + _extraDebuggingBox->setCheckState(Qt::Unchecked); + _logger->setDebugPrint(false); + _debugPrintBox->setCheckState(Qt::Unchecked); + _logger->setInfoPrint(false); + _infoPrintBox->setCheckState(Qt::Unchecked); + _logger->setCriticalPrint(true); + _criticalPrintBox->setCheckState(Qt::Checked); + _logger->setWarningPrint(true); + _warningPrintBox->setCheckState(Qt::Checked); + _logger->setSuppressPrint(true); + _suppressPrintBox->setCheckState(Qt::Checked); + _logger->setFatalPrint(true); + _fatalPrintBox->setCheckState(Qt::Checked); + _logger->setUnknownPrint(true); + _unknownPrintBox->setCheckState(Qt::Checked); + clearSearch(); + _filterDropdown->setCurrentIndex(0); + printLogFile(); +} + void LogDialog::handleExtraDebuggingCheckbox(int state) { _logger->setExtraDebugging(state != 0); } +void LogDialog::handleDebugPrintBox(int state) { + _logger->setDebugPrint(state != 0); + printLogFile(); +} + +void LogDialog::handleInfoPrintBox(int state) { + _logger->setInfoPrint(state != 0); + printLogFile(); +} + +void LogDialog::handleCriticalPrintBox(int state) { + _logger->setCriticalPrint(state != 0); + printLogFile(); +} + +void LogDialog::handleWarningPrintBox(int state) { + _logger->setWarningPrint(state != 0); + printLogFile(); +} + +void LogDialog::handleSuppressPrintBox(int state) { + _logger->setSuppressPrint(state != 0); + printLogFile(); +} + +void LogDialog::handleFatalPrintBox(int state) { + _logger->setFatalPrint(state != 0); + printLogFile(); +} + +void LogDialog::handleUnknownPrintBox(int state) { + _logger->setUnknownPrint(state != 0); + printLogFile(); +} + +void LogDialog::handleFilterDropdownChanged(int selection) { + if (selection != 0) { + _filterSelection = "[" + _filterDropdown->currentText() + "]"; + } else { + _filterSelection = ""; + } + printLogFile(); +} + QString LogDialog::getCurrentLog() { return _logger->getLogData(); } + +void LogDialog::appendLogLine(QString logLine) { + if (logLine.contains(_searchTerm, Qt::CaseInsensitive) && + logLine.contains(_filterSelection, Qt::CaseSensitive)) { + int indexToBold = _logTextBox->document()->characterCount(); + _highlighter->setBold(indexToBold); + + if (logLine.contains(DEBUG_TEXT, Qt::CaseSensitive)) { + if (_logger->debugPrint()) { + _logTextBox->appendPlainText(logLine.trimmed()); + } + } else if (logLine.contains(INFO_TEXT, Qt::CaseSensitive)) { + if (_logger->infoPrint()) { + _logTextBox->appendPlainText(logLine.trimmed()); + } + } else if (logLine.contains(CRITICAL_TEXT, Qt::CaseSensitive)) { + if (_logger->criticalPrint()) { + _logTextBox->appendPlainText(logLine.trimmed()); + } + } else if (logLine.contains(WARNING_TEXT, Qt::CaseSensitive)) { + if (_logger->warningPrint()) { + _logTextBox->appendPlainText(logLine.trimmed()); + } + } else if (logLine.contains(SUPPRESS_TEXT, Qt::CaseSensitive)) { + if (_logger->suppressPrint()) { + _logTextBox->appendPlainText(logLine.trimmed()); + } + } else if (logLine.contains(FATAL_TEXT, Qt::CaseSensitive)) { + if (_logger->fatalPrint()) { + _logTextBox->appendPlainText(logLine.trimmed()); + } + } else { + if (_logger->unknownPrint()) { + _logTextBox->appendPlainText(logLine.trimmed()); + } + } + } +} + +void LogDialog::printLogFile() { + _logTextBox->clear(); + QString log = getCurrentLog(); + QStringList logList = log.split('\n'); + for (const auto& message : logList) { + appendLogLine(message); + } +} diff --git a/interface/src/ui/LogDialog.h b/interface/src/ui/LogDialog.h index c8794b57ea..5e4e084d5a 100644 --- a/interface/src/ui/LogDialog.h +++ b/interface/src/ui/LogDialog.h @@ -16,6 +16,7 @@ class QCheckBox; class QPushButton; +class QComboBox; class QResizeEvent; class AbstractLoggerInterface; @@ -25,17 +26,40 @@ class LogDialog : public BaseLogDialog { public: LogDialog(QWidget* parent, AbstractLoggerInterface* logger); +public slots: + void appendLogLine(QString logLine) override; + private slots: void handleRevealButton(); void handleExtraDebuggingCheckbox(int); + void handleDebugPrintBox(int); + void handleInfoPrintBox(int); + void handleCriticalPrintBox(int); + void handleWarningPrintBox(int); + void handleSuppressPrintBox(int); + void handleFatalPrintBox(int); + void handleUnknownPrintBox(int); + void handleFilterDropdownChanged(int); + void handleClearFilterButton(); protected: void resizeEvent(QResizeEvent* event) override; QString getCurrentLog() override; - + void printLogFile(); + private: QCheckBox* _extraDebuggingBox; QPushButton* _revealLogButton; + QPushButton* _clearFilterButton; + QCheckBox* _debugPrintBox; + QCheckBox* _infoPrintBox; + QCheckBox* _criticalPrintBox; + QCheckBox* _warningPrintBox; + QCheckBox* _suppressPrintBox; + QCheckBox* _fatalPrintBox; + QCheckBox* _unknownPrintBox; + QComboBox* _filterDropdown; + QString _filterSelection; AbstractLoggerInterface* _logger; }; diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 7ce2a0146d..2e40d3c087 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -46,7 +46,7 @@ void LoginDialog::showWithSelection() if (tablet->getToolbarMode()) { LoginDialog::show(); } else { - static const QUrl url("../../dialogs/TabletLoginDialog.qml"); + static const QUrl url("dialogs/TabletLoginDialog.qml"); tablet->initialScreen(url); if (!hmd->getShouldShowTablet()) { hmd->openTablet(); diff --git a/interface/src/ui/OctreeStatsDialog.cpp b/interface/src/ui/OctreeStatsDialog.cpp index fc0e6781d7..ec5d800042 100644 --- a/interface/src/ui/OctreeStatsDialog.cpp +++ b/interface/src/ui/OctreeStatsDialog.cpp @@ -26,27 +26,17 @@ OctreeStatsDialog::OctreeStatsDialog(QWidget* parent, NodeToOctreeSceneStats* model) : QDialog(parent, Qt::Window | Qt::WindowCloseButtonHint | Qt::WindowStaysOnTopHint), - _model(model), - _averageUpdatesPerSecond(SAMPLES_PER_SECOND) + _model(model) { - - _statCount = 0; - _octreeServerLabelsCount = 0; - - for (int i = 0; i < MAX_VOXEL_SERVERS; i++) { - _octreeServerLables[i] = 0; - _extraServerDetails[i] = LESS; - } - for (int i = 0; i < MAX_STATS; i++) { - _labels[i] = NULL; + _labels[i] = nullptr; } - this->setWindowTitle("Octree Server Statistics"); + setWindowTitle("Octree Server Statistics"); // Create layouter _form = new QFormLayout(); - this->QDialog::setLayout(_form); + setLayout(_form); // Setup stat items _serverElements = AddStatItem("Elements on Servers"); @@ -63,7 +53,14 @@ OctreeStatsDialog::OctreeStatsDialog(QWidget* parent, NodeToOctreeSceneStats* mo _entityUpdateTime = AddStatItem("Entity Update Time"); _entityUpdates = AddStatItem("Entity Updates"); - + + + _octreeServerLabel = AddStatItem("Entity Server"); + _labels[_octreeServerLabel]->setTextFormat(Qt::RichText); + _labels[_octreeServerLabel]->setTextInteractionFlags(Qt::TextBrowserInteraction); + connect(_labels[_octreeServerLabel], SIGNAL(linkActivated(const QString&)), + this, SLOT(moreless(const QString&))); + layout()->setSizeConstraint(QLayout::SetFixedSize); } @@ -74,23 +71,16 @@ void OctreeStatsDialog::RemoveStatItem(int item) { _form->removeWidget(automaticLabel); automaticLabel->deleteLater(); myLabel->deleteLater(); - _labels[item] = NULL; + _labels[item] = nullptr; } void OctreeStatsDialog::moreless(const QString& link) { - QStringList linkDetails = link.split("-"); - const int COMMAND_ITEM = 0; - const int SERVER_NUMBER_ITEM = 1; - QString serverNumberString = linkDetails[SERVER_NUMBER_ITEM]; - QString command = linkDetails[COMMAND_ITEM]; - int serverNumber = serverNumberString.toInt(); - - if (command == "more") { - _extraServerDetails[serverNumber-1] = MORE; - } else if (command == "most") { - _extraServerDetails[serverNumber-1] = MOST; + if (link == "more") { + _extraServerDetails = MORE; + } else if (link == "most") { + _extraServerDetails = MOST; } else { - _extraServerDetails[serverNumber-1] = LESS; + _extraServerDetails = LESS; } } @@ -376,91 +366,34 @@ void OctreeStatsDialog::paintEvent(QPaintEvent* event) { QDialog::paintEvent(event); } void OctreeStatsDialog::showAllOctreeServers() { - int serverCount = 0; - - showOctreeServersOfType(serverCount, NodeType::EntityServer, "Entity", - qApp->getEntityServerJurisdictions()); - - if (_octreeServerLabelsCount > serverCount) { - for (int i = serverCount; i < _octreeServerLabelsCount; i++) { - int serverLabel = _octreeServerLables[i]; - RemoveStatItem(serverLabel); - _octreeServerLables[i] = 0; - } - _octreeServerLabelsCount = serverCount; - } + showOctreeServersOfType(NodeType::EntityServer); } -void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t serverType, const char* serverTypeName, - NodeToJurisdictionMap& serverJurisdictions) { - +void OctreeStatsDialog::showOctreeServersOfType(NodeType_t serverType) { QLocale locale(QLocale::English); - auto nodeList = DependencyManager::get(); - nodeList->eachNode([&](const SharedNodePointer& node){ - - // only send to the NodeTypes that are NodeType_t_VOXEL_SERVER - if (node->getType() == serverType) { - serverCount++; - - if (serverCount > _octreeServerLabelsCount) { - QString label = QString("%1 Server %2").arg(serverTypeName).arg(serverCount); - int thisServerRow = _octreeServerLables[serverCount-1] = AddStatItem(label.toUtf8().constData()); - _labels[thisServerRow]->setTextFormat(Qt::RichText); - _labels[thisServerRow]->setTextInteractionFlags(Qt::TextBrowserInteraction); - connect(_labels[thisServerRow], SIGNAL(linkActivated(const QString&)), this, SLOT(moreless(const QString&))); - _octreeServerLabelsCount++; - } - - std::stringstream serverDetails(""); - std::stringstream extraDetails(""); - std::stringstream linkDetails(""); - - if (node->getActiveSocket()) { - serverDetails << "active "; - } else { - serverDetails << "inactive "; - } - - QUuid nodeUUID = node->getUUID(); - - // lookup our nodeUUID in the jurisdiction map, if it's missing then we're - // missing at least one jurisdiction - serverJurisdictions.withReadLock([&] { - if (serverJurisdictions.find(nodeUUID) == serverJurisdictions.end()) { - serverDetails << " unknown jurisdiction "; - return; - } - const JurisdictionMap& map = serverJurisdictions[nodeUUID]; + auto node = DependencyManager::get()->soloNodeOfType(serverType); + if (node) { + std::stringstream serverDetails(""); + std::stringstream extraDetails(""); + std::stringstream linkDetails(""); - auto rootCode = map.getRootOctalCode(); + if (node->getActiveSocket()) { + serverDetails << "active "; + } else { + serverDetails << "inactive "; + } - if (rootCode) { - QString rootCodeHex = octalCodeToHexString(rootCode.get()); + QUuid nodeUUID = node->getUUID(); - VoxelPositionSize rootDetails; - voxelDetailsForCode(rootCode.get(), rootDetails); - AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s); - serverDetails << " jurisdiction: " - << qPrintable(rootCodeHex) - << " [" - << rootDetails.x << ", " - << rootDetails.y << ", " - << rootDetails.z << ": " - << rootDetails.s << "] "; - } else { - serverDetails << " jurisdiction has no rootCode"; - } // root code - }); - - // now lookup stats details for this server... - if (_extraServerDetails[serverCount-1] != LESS) { - NodeToOctreeSceneStats* sceneStats = qApp->getOcteeSceneStats(); - sceneStats->withReadLock([&] { - if (sceneStats->find(nodeUUID) != sceneStats->end()) { - OctreeSceneStats& stats = sceneStats->at(nodeUUID); + // now lookup stats details for this server... + if (_extraServerDetails != LESS) { + NodeToOctreeSceneStats* sceneStats = qApp->getOcteeSceneStats(); + sceneStats->withReadLock([&] { + if (sceneStats->find(nodeUUID) != sceneStats->end()) { + OctreeSceneStats& stats = sceneStats->at(nodeUUID); - switch (_extraServerDetails[serverCount - 1]) { + switch (_extraServerDetails) { case MOST: { extraDetails << "
"; @@ -538,7 +471,7 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser " Average Ping Time: " << qPrintable(incomingPingTimeString) << " msecs"; serverDetails << "
" << - " Average Clock Skew: " << qPrintable(incomingClockSkewString) << " msecs" << + " Average Clock Skew: " << qPrintable(incomingClockSkewString) << " msecs" << " [" << qPrintable(formattedClockSkewString) << "]"; @@ -547,38 +480,37 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser " Wasted Bytes: " << qPrintable(incomingWastedBytesString); serverDetails << extraDetails.str(); - if (_extraServerDetails[serverCount - 1] == MORE) { - linkDetails << " " << " [most...]"; - linkDetails << " " << " [less...]"; + if (_extraServerDetails == MORE) { + linkDetails << " [most...]"; + linkDetails << " [less...]"; } else { - linkDetails << " " << " [less...]"; - linkDetails << " " << " [least...]"; + linkDetails << " [less...]"; + linkDetails << " [least...]"; } } break; case LESS: { // nothing } break; - } } - }); - } else { - linkDetails << " " << " [more...]"; - linkDetails << " " << " [most...]"; - } - serverDetails << linkDetails.str(); - _labels[_octreeServerLables[serverCount - 1]]->setText(serverDetails.str().c_str()); - } // is VOXEL_SERVER - }); + } + }); + } else { + linkDetails << " [more...]"; + linkDetails << " [most...]"; + } + serverDetails << linkDetails.str(); + _labels[_octreeServerLabel]->setText(serverDetails.str().c_str()); + } } void OctreeStatsDialog::reject() { // Just regularly close upon ESC - this->QDialog::close(); + QDialog::close(); } void OctreeStatsDialog::closeEvent(QCloseEvent* event) { - this->QDialog::closeEvent(event); + QDialog::closeEvent(event); emit closed(); } diff --git a/interface/src/ui/OctreeStatsDialog.h b/interface/src/ui/OctreeStatsDialog.h index 67f5c01f65..81bf5f251f 100644 --- a/interface/src/ui/OctreeStatsDialog.h +++ b/interface/src/ui/OctreeStatsDialog.h @@ -19,7 +19,6 @@ #include #define MAX_STATS 100 -#define MAX_VOXEL_SERVERS 50 #define DEFAULT_COLOR 0 class OctreeStatsDialog : public QDialog { @@ -47,18 +46,22 @@ protected: void RemoveStatItem(int item); void showAllOctreeServers(); - void showOctreeServersOfType(int& serverNumber, NodeType_t serverType, - const char* serverTypeName, NodeToJurisdictionMap& serverJurisdictions); + void showOctreeServersOfType(NodeType_t serverType); private: + enum details { + LESS, + MORE, + MOST + }; - typedef enum { LESS, MORE, MOST } details; - - QFormLayout* _form; + QFormLayout* _form { nullptr }; QLabel* _labels[MAX_STATS]; - NodeToOctreeSceneStats* _model; - int _statCount; - + NodeToOctreeSceneStats* _model { nullptr }; + int _statCount { 0 }; + + int _octreeServerLabel; + int _sendingMode; int _serverElements; int _localElements; @@ -72,16 +75,14 @@ private: int _processedPacketsTiming; int _outboundEditPackets; - const int SAMPLES_PER_SECOND = 10; - SimpleMovingAverage _averageUpdatesPerSecond; - quint64 _lastWindowAt = usecTimestampNow(); - quint64 _lastKnownTrackedEdits = 0; + const int SAMPLES_PER_SECOND { 10 }; + SimpleMovingAverage _averageUpdatesPerSecond { SAMPLES_PER_SECOND }; + quint64 _lastWindowAt { usecTimestampNow() }; + quint64 _lastKnownTrackedEdits { 0 }; - quint64 _lastRefresh = 0; + quint64 _lastRefresh { 0 }; - int _octreeServerLables[MAX_VOXEL_SERVERS]; - int _octreeServerLabelsCount; - details _extraServerDetails[MAX_VOXEL_SERVERS]; + details _extraServerDetails { LESS }; }; #endif // hifi_OctreeStatsDialog_h diff --git a/interface/src/ui/OctreeStatsProvider.cpp b/interface/src/ui/OctreeStatsProvider.cpp index 5f40b9916d..a393660c17 100644 --- a/interface/src/ui/OctreeStatsProvider.cpp +++ b/interface/src/ui/OctreeStatsProvider.cpp @@ -16,9 +16,9 @@ OctreeStatsProvider::OctreeStatsProvider(QObject* parent, NodeToOctreeSceneStats* model) : QObject(parent), - _model(model) - , _statCount(0) - , _averageUpdatesPerSecond(SAMPLES_PER_SECOND) + _model(model), + _statCount(0), + _averageUpdatesPerSecond(SAMPLES_PER_SECOND) { //schedule updates connect(&_updateTimer, &QTimer::timeout, this, &OctreeStatsProvider::updateOctreeStatsData); @@ -237,140 +237,105 @@ void OctreeStatsProvider::updateOctreeStatsData() { } void OctreeStatsProvider::updateOctreeServers() { + showOctreeServersOfType(NodeType::EntityServer); +} + +void OctreeStatsProvider::showOctreeServersOfType(NodeType_t serverType) { + m_servers.clear(); int serverCount = 0; - showOctreeServersOfType(serverCount, NodeType::EntityServer, "Entity", - qApp->getEntityServerJurisdictions()); - if (m_serversNum != serverCount) { + auto node = DependencyManager::get()->soloNodeOfType(serverType); + if (node) { + ++serverCount; + + QString lesserDetails; + QString moreDetails; + QString mostDetails; + + if (node->getActiveSocket()) { + lesserDetails += "active "; + } else { + lesserDetails += "inactive "; + } + + QUuid nodeUUID = node->getUUID(); + + // now lookup stats details for this server... + NodeToOctreeSceneStats* sceneStats = qApp->getOcteeSceneStats(); + sceneStats->withReadLock([&] { + if (sceneStats->find(nodeUUID) != sceneStats->end()) { + OctreeSceneStats& stats = sceneStats->at(nodeUUID); + + float lastFullEncode = stats.getLastFullTotalEncodeTime() / USECS_PER_MSEC; + float lastFullSend = stats.getLastFullElapsedTime() / USECS_PER_MSEC; + float lastFullSendInSeconds = stats.getLastFullElapsedTime() / USECS_PER_SECOND; + float lastFullPackets = stats.getLastFullTotalPackets(); + float lastFullPPS = lastFullPackets; + if (lastFullSendInSeconds > 0) { + lastFullPPS = lastFullPackets / lastFullSendInSeconds; + } + + mostDetails += QString("

Last Full Scene... Encode: %1 ms Send: %2 ms Packets: %3 Bytes: %4 Rate: %5 PPS") + .arg(lastFullEncode) + .arg(lastFullSend) + .arg(lastFullPackets) + .arg(stats.getLastFullTotalBytes()) + .arg(lastFullPPS); + + for (int i = 0; i < OctreeSceneStats::ITEM_COUNT; i++) { + OctreeSceneStats::Item item = (OctreeSceneStats::Item)(i); + OctreeSceneStats::ItemInfo& itemInfo = stats.getItemInfo(item); + mostDetails += QString("
%1 %2") + .arg(itemInfo.caption).arg(stats.getItemValue(item)); + } + + moreDetails += "
Node UUID: " +nodeUUID.toString() + " "; + + moreDetails += QString("
Elements: %1 total %2 internal %3 leaves ") + .arg(stats.getTotalElements()) + .arg(stats.getTotalInternal()) + .arg(stats.getTotalLeaves()); + + const SequenceNumberStats& seqStats = stats.getIncomingOctreeSequenceNumberStats(); + qint64 clockSkewInUsecs = node->getClockSkewUsec(); + qint64 clockSkewInMS = clockSkewInUsecs / (qint64)USECS_PER_MSEC; + + moreDetails += QString("
Incoming Packets: %1/ Lost: %2/ Recovered: %3") + .arg(stats.getIncomingPackets()) + .arg(seqStats.getLost()) + .arg(seqStats.getRecovered()); + + moreDetails += QString("
Out of Order: %1/ Early: %2/ Late: %3/ Unreasonable: %4") + .arg(seqStats.getOutOfOrder()) + .arg(seqStats.getEarly()) + .arg(seqStats.getLate()) + .arg(seqStats.getUnreasonable()); + + moreDetails += QString("
Average Flight Time: %1 msecs") + .arg(stats.getIncomingFlightTimeAverage()); + + moreDetails += QString("
Average Ping Time: %1 msecs") + .arg(node->getPingMs()); + + moreDetails += QString("
Average Clock Skew: %1 msecs [%2]") + .arg(clockSkewInMS) + .arg(formatUsecTime(clockSkewInUsecs)); + + + moreDetails += QString("
Incoming Bytes: %1 Wasted Bytes: %2") + .arg(stats.getIncomingBytes()) + .arg(stats.getIncomingWastedBytes()); + } + }); + m_servers.append(lesserDetails); + m_servers.append(moreDetails); + m_servers.append(mostDetails); + } + + if (serverCount != m_serversNum) { m_serversNum = serverCount; emit serversNumChanged(m_serversNum); } -} - -void OctreeStatsProvider::showOctreeServersOfType(int& serverCount, NodeType_t serverType, const char* serverTypeName, - NodeToJurisdictionMap& serverJurisdictions) { - - m_servers.clear(); - - auto nodeList = DependencyManager::get(); - nodeList->eachNode([&](const SharedNodePointer& node) { - - // only send to the NodeTypes that are NodeType_t_VOXEL_SERVER - if (node->getType() == serverType) { - serverCount++; - - QString lesserDetails; - QString moreDetails; - QString mostDetails; - - if (node->getActiveSocket()) { - lesserDetails += "active "; - } else { - lesserDetails += "inactive "; - } - - QUuid nodeUUID = node->getUUID(); - - // lookup our nodeUUID in the jurisdiction map, if it's missing then we're - // missing at least one jurisdiction - serverJurisdictions.withReadLock([&] { - if (serverJurisdictions.find(nodeUUID) == serverJurisdictions.end()) { - lesserDetails += " unknown jurisdiction "; - return; - } - const JurisdictionMap& map = serverJurisdictions[nodeUUID]; - - auto rootCode = map.getRootOctalCode(); - - if (rootCode) { - QString rootCodeHex = octalCodeToHexString(rootCode.get()); - - VoxelPositionSize rootDetails; - voxelDetailsForCode(rootCode.get(), rootDetails); - AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s); - lesserDetails += QString(" jurisdiction: %1 [%2, %3, %4: %5]") - .arg(rootCodeHex) - .arg(rootDetails.x) - .arg(rootDetails.y) - .arg(rootDetails.z) - .arg(rootDetails.s); - } else { - lesserDetails += " jurisdiction has no rootCode"; - } // root code - }); - - // now lookup stats details for this server... - NodeToOctreeSceneStats* sceneStats = qApp->getOcteeSceneStats(); - sceneStats->withReadLock([&] { - if (sceneStats->find(nodeUUID) != sceneStats->end()) { - OctreeSceneStats& stats = sceneStats->at(nodeUUID); - - float lastFullEncode = stats.getLastFullTotalEncodeTime() / USECS_PER_MSEC; - float lastFullSend = stats.getLastFullElapsedTime() / USECS_PER_MSEC; - float lastFullSendInSeconds = stats.getLastFullElapsedTime() / USECS_PER_SECOND; - float lastFullPackets = stats.getLastFullTotalPackets(); - float lastFullPPS = lastFullPackets; - if (lastFullSendInSeconds > 0) { - lastFullPPS = lastFullPackets / lastFullSendInSeconds; - } - - mostDetails += QString("

Last Full Scene... Encode: %1 ms Send: %2 ms Packets: %3 Bytes: %4 Rate: %5 PPS") - .arg(lastFullEncode) - .arg(lastFullSend) - .arg(lastFullPackets) - .arg(stats.getLastFullTotalBytes()) - .arg(lastFullPPS); - - for (int i = 0; i < OctreeSceneStats::ITEM_COUNT; i++) { - OctreeSceneStats::Item item = (OctreeSceneStats::Item)(i); - OctreeSceneStats::ItemInfo& itemInfo = stats.getItemInfo(item); - mostDetails += QString("
%1 %2") - .arg(itemInfo.caption).arg(stats.getItemValue(item)); - } - - moreDetails += "
Node UUID: " +nodeUUID.toString() + " "; - - moreDetails += QString("
Elements: %1 total %2 internal %3 leaves ") - .arg(stats.getTotalElements()) - .arg(stats.getTotalInternal()) - .arg(stats.getTotalLeaves()); - - const SequenceNumberStats& seqStats = stats.getIncomingOctreeSequenceNumberStats(); - qint64 clockSkewInUsecs = node->getClockSkewUsec(); - qint64 clockSkewInMS = clockSkewInUsecs / (qint64)USECS_PER_MSEC; - - moreDetails += QString("
Incoming Packets: %1/ Lost: %2/ Recovered: %3") - .arg(stats.getIncomingPackets()) - .arg(seqStats.getLost()) - .arg(seqStats.getRecovered()); - - moreDetails += QString("
Out of Order: %1/ Early: %2/ Late: %3/ Unreasonable: %4") - .arg(seqStats.getOutOfOrder()) - .arg(seqStats.getEarly()) - .arg(seqStats.getLate()) - .arg(seqStats.getUnreasonable()); - - moreDetails += QString("
Average Flight Time: %1 msecs") - .arg(stats.getIncomingFlightTimeAverage()); - - moreDetails += QString("
Average Ping Time: %1 msecs") - .arg(node->getPingMs()); - - moreDetails += QString("
Average Clock Skew: %1 msecs [%2]") - .arg(clockSkewInMS) - .arg(formatUsecTime(clockSkewInUsecs)); - - - moreDetails += QString("
Incoming Bytes: %1 Wasted Bytes: %2") - .arg(stats.getIncomingBytes()) - .arg(stats.getIncomingWastedBytes()); - } - }); - m_servers.append(lesserDetails); - m_servers.append(moreDetails); - m_servers.append(mostDetails); - } // is VOXEL_SERVER - }); emit serversChanged(m_servers); } diff --git a/interface/src/ui/OctreeStatsProvider.h b/interface/src/ui/OctreeStatsProvider.h index c919ca102f..5b3d4d735c 100644 --- a/interface/src/ui/OctreeStatsProvider.h +++ b/interface/src/ui/OctreeStatsProvider.h @@ -18,10 +18,6 @@ #include "DependencyManager.h" -#define MAX_STATS 100 -#define MAX_VOXEL_SERVERS 50 -#define DEFAULT_COLOR 0 - class OctreeStatsProvider : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY @@ -121,8 +117,7 @@ private slots: void updateOctreeStatsData(); protected: void updateOctreeServers(); - void showOctreeServersOfType(int& serverNumber, NodeType_t serverType, - const char* serverTypeName, NodeToJurisdictionMap& serverJurisdictions); + void showOctreeServersOfType(NodeType_t serverType); private: NodeToOctreeSceneStats* _model; @@ -136,7 +131,7 @@ private: quint64 _lastRefresh = 0; QTimer _updateTimer; - int m_serversNum {0}; + int m_serversNum { 0 }; QString m_serverElements; QString m_localElements; QString m_localElementsMemory; diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 023d3e6e17..1a09af07ab 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -90,11 +90,19 @@ void setupPreferences() { preference->setMax(500); preferences->addPreference(preference); } + { + auto getter = []()->bool { return qApp->getPreferStylusOverLaser(); }; + auto setter = [](bool value) { qApp->setPreferStylusOverLaser(value); }; + preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Stylus Over Laser", getter, setter)); + } + // FIXME: Remove setting completely or make available through JavaScript API? + /* { auto getter = []()->bool { return qApp->getPreferAvatarFingerOverStylus(); }; auto setter = [](bool value) { qApp->setPreferAvatarFingerOverStylus(value); }; preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Avatar Finger Over Stylus", getter, setter)); } + */ { static const QString RETICLE_ICON_NAME = { Cursor::Manager::getIconName(Cursor::Icon::RETICLE) }; auto getter = []()->bool { return qApp->getPreferredCursor() == RETICLE_ICON_NAME; }; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index e194551add..f991420fe8 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -78,6 +78,8 @@ bool Stats::includeTimingRecord(const QString& name) { return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming); } else if (name.startsWith("/paintGL/")) { return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming); + } else if (name.startsWith("step/")) { + return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPhysicsSimulationTiming); } return true; } diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 5b29279dba..84a6fe1da4 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -42,7 +42,7 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) : setTransform(base3DOverlay->getTransform()); } -QVariantMap convertOverlayLocationFromScriptSemantics(const QVariantMap& properties) { +QVariantMap convertOverlayLocationFromScriptSemantics(const QVariantMap& properties, bool scalesWithParent) { // the position and rotation in _transform are relative to the parent (aka local). The versions coming from // scripts are in world-frame, unless localPosition or localRotation are used. Patch up the properties // so that "position" and "rotation" are relative-to-parent values. @@ -56,7 +56,7 @@ QVariantMap convertOverlayLocationFromScriptSemantics(const QVariantMap& propert result["position"] = result["localPosition"]; } else if (result["position"].isValid()) { glm::vec3 localPosition = SpatiallyNestable::worldToLocal(vec3FromVariant(result["position"]), - parentID, parentJointIndex, success); + parentID, parentJointIndex, scalesWithParent, success); if (success) { result["position"] = vec3toVariant(localPosition); } @@ -66,7 +66,7 @@ QVariantMap convertOverlayLocationFromScriptSemantics(const QVariantMap& propert result["orientation"] = result["localOrientation"]; } else if (result["orientation"].isValid()) { glm::quat localOrientation = SpatiallyNestable::worldToLocal(quatFromVariant(result["orientation"]), - parentID, parentJointIndex, success); + parentID, parentJointIndex, scalesWithParent, success); if (success) { result["orientation"] = quatToVariant(localOrientation); } @@ -118,7 +118,7 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { } } - properties = convertOverlayLocationFromScriptSemantics(properties); + properties = convertOverlayLocationFromScriptSemantics(properties, getScalesWithParent()); Overlay::setProperties(properties); bool needRenderItemUpdate = false; @@ -212,8 +212,6 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { * parentID set, otherwise the same value as rotation. * @property {boolean} isSolid=false - Synonyms: solid, isFilled, * filled, and filed. Antonyms: isWire and wire. - * Deprecated: The erroneous property spelling "filed" is deprecated and support for it will - * be removed. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. * @property {boolean} ignoreRayIntersection=false - If true, @@ -241,7 +239,7 @@ QVariant Base3DOverlay::getProperty(const QString& property) { if (property == "localRotation" || property == "localOrientation") { return quatToVariant(getLocalOrientation()); } - if (property == "isSolid" || property == "isFilled" || property == "solid" || property == "filled" || property == "filed") { + if (property == "isSolid" || property == "isFilled" || property == "solid" || property == "filled") { return _isSolid; } if (property == "isWire" || property == "wire") { @@ -335,4 +333,4 @@ SpatialParentTree* Base3DOverlay::getParentTree() const { void Base3DOverlay::setVisible(bool visible) { Parent::setVisible(visible); notifyRenderVariableChange(); -} \ No newline at end of file +} diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index c41663fcc9..d690880f99 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -151,7 +151,7 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& glm::vec3 normal; boundingBox.findRayIntersection(cameraPosition, direction, distance, face, normal); float offsetAngle = -CONTEXT_OVERLAY_OFFSET_ANGLE; - if (DependencyManager::get()->isLeftHand(event.getID())) { + if (event.getID() == 1) { // "1" is left hand offsetAngle *= -1.0f; } contextOverlayPosition = cameraPosition + @@ -266,7 +266,7 @@ void ContextOverlayInterface::contextOverlays_hoverLeaveEntity(const EntityItemI } } -static const QString INSPECTION_CERTIFICATE_QML_PATH = qApp->applicationDirPath() + "../../../qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml"; +static const QString INSPECTION_CERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml"; void ContextOverlayInterface::openInspectionCertificate() { // lets open the tablet to the inspection certificate QML if (!_currentEntityWithContextOverlay.isNull() && _entityMarketplaceID.length() > 0) { diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index 2cbf4308ae..62675bacea 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -56,12 +56,12 @@ glm::vec3 Line3DOverlay::getEnd() const { if (_endParentID != QUuid()) { glm::vec3 localOffset = _direction * _length; bool success; - worldEnd = localToWorld(localOffset, _endParentID, _endParentJointIndex, success); + worldEnd = localToWorld(localOffset, _endParentID, _endParentJointIndex, getScalesWithParent(), success); return worldEnd; } localEnd = getLocalEnd(); - worldEnd = localToWorld(localEnd, getParentID(), getParentJointIndex(), success); + worldEnd = localToWorld(localEnd, getParentID(), getParentJointIndex(), getScalesWithParent(), success); if (!success) { qDebug() << "Line3DOverlay::getEnd failed"; } @@ -79,10 +79,10 @@ void Line3DOverlay::setEnd(const glm::vec3& end) { glm::vec3 offset; if (_endParentID != QUuid()) { - offset = worldToLocal(end, _endParentID, _endParentJointIndex, success); + offset = worldToLocal(end, _endParentID, _endParentJointIndex, getScalesWithParent(), success); } else { localStart = getLocalStart(); - localEnd = worldToLocal(end, getParentID(), getParentJointIndex(), success); + localEnd = worldToLocal(end, getParentID(), getParentJointIndex(), getScalesWithParent(), success); offset = localEnd - localStart; } if (!success) { diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index ba8bf8cbef..3bd4f84cd6 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -24,7 +24,6 @@ ModelOverlay::ModelOverlay() : _model(std::make_shared(nullptr, this)), _modelTextures(QVariantMap()) { - _model->init(); _model->setLoadingPriority(_loadPriority); _isLoaded = false; } @@ -38,7 +37,6 @@ ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) : _scaleToFit(modelOverlay->_scaleToFit), _loadPriority(modelOverlay->_loadPriority) { - _model->init(); _model->setLoadingPriority(_loadPriority); if (_url.isValid()) { _updateModel = true; @@ -99,6 +97,11 @@ void ModelOverlay::update(float deltatime) { _model->setLayeredInHUD(getDrawHUDLayer(), scene); } scene->enqueueTransaction(transaction); + + if (!_texturesLoaded && _model->getGeometry() && _model->getGeometry()->areTexturesLoaded()) { + _texturesLoaded = true; + _model->updateRenderItems(); + } } bool ModelOverlay::addToScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) { @@ -172,10 +175,12 @@ void ModelOverlay::setProperties(const QVariantMap& properties) { _url = urlValue.toString(); _updateModel = true; _isLoaded = false; + _texturesLoaded = false; } auto texturesValue = properties["textures"]; if (texturesValue.isValid() && texturesValue.canConvert(QVariant::Map)) { + _texturesLoaded = false; QVariantMap textureMap = texturesValue.toMap(); QMetaObject::invokeMethod(_model.get(), "setTextures", Qt::AutoConnection, Q_ARG(const QVariantMap&, textureMap)); diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 4f7f1e0cae..a39f762210 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -79,6 +79,7 @@ private: ModelPointer _model; QVariantMap _modelTextures; + bool _texturesLoaded { false }; render::ItemIDs _subRenderItemIDs; diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 71d1c4b842..a5da5e99b6 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -36,7 +36,6 @@ #include #include #include -#include "scripting/AccountScriptingInterface.h" #include "scripting/HMDScriptingInterface.h" #include "scripting/AssetMappingsScriptingInterface.h" #include "scripting/MenuScriptingInterface.h" @@ -193,14 +192,17 @@ void Web3DOverlay::setupQmlSurface() { _webSurface->getSurfaceContext()->setContextProperty("offscreenFlags", flags); _webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("Account", AccountScriptingInterface::getInstance()); - _webSurface->getSurfaceContext()->setContextProperty("Audio", DependencyManager::get().data()); + _webSurface->getSurfaceContext()->setContextProperty("Account", GlobalServicesScriptingInterface::getInstance()); + + // in Qt 5.10.0 there is already an "Audio" object in the QML context + // though I failed to find it (from QtMultimedia??). So.. let it be "AudioScriptingInterface" + _webSurface->getSurfaceContext()->setContextProperty("AudioScriptingInterface", DependencyManager::get().data()); + _webSurface->getSurfaceContext()->setContextProperty("AudioStats", DependencyManager::get()->getStats().data()); _webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("fileDialogHelper", new FileDialogHelper()); _webSurface->getSurfaceContext()->setContextProperty("MyAvatar", DependencyManager::get()->getMyAvatar().get()); _webSurface->getSurfaceContext()->setContextProperty("ScriptDiscoveryService", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("Tablet", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("Assets", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("LODManager", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("OctreeStats", DependencyManager::get().data()); @@ -220,9 +222,6 @@ void Web3DOverlay::setupQmlSurface() { _webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../"); - // Tablet inteference with Tablet.qml. Need to avoid this in QML space - _webSurface->getSurfaceContext()->setContextProperty("tabletInterface", DependencyManager::get().data()); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data()); // mark the TabletProxy object as cpp ownership. QObject* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"); @@ -518,7 +517,6 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { * * @property {string} url - The URL of the Web page to display. * @property {string} scriptURL="" - The URL of a JavaScript file to inject into the Web page. - * @property {Vec2} resolution - Deprecated: This property has been removed. * @property {number} dpi=30 - The dots per inch to display the Web page at, on the overlay. * @property {Vec2} dimensions=1,1 - The size of the overlay to display the Web page on, in meters. Synonyms: * scale, size. @@ -609,4 +607,4 @@ Web3DOverlay* Web3DOverlay::createClone() const { void Web3DOverlay::emitScriptEvent(const QVariant& message) { QMetaObject::invokeMethod(this, "scriptEventReceived", Q_ARG(QVariant, message)); -} \ No newline at end of file +} diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index bb7f141cd9..c532e7659f 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -138,7 +138,6 @@ Avatar::~Avatar() { void Avatar::init() { getHead()->init(); - _skeletonModel->init(); _initialized = true; } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index c75b54fdc4..39081ba44b 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -268,6 +268,7 @@ public: virtual float getModelScale() const { return _modelScale; } virtual void setModelScale(float scale) { _modelScale = scale; } + virtual glm::vec3 scaleForChildren() const override { return glm::vec3(getModelScale()); } virtual void setAvatarEntityDataChanged(bool value) override; diff --git a/libraries/controllers/src/controllers/impl/Filter.cpp b/libraries/controllers/src/controllers/impl/Filter.cpp index b6c3ed4d27..6e6dc816d0 100644 --- a/libraries/controllers/src/controllers/impl/Filter.cpp +++ b/libraries/controllers/src/controllers/impl/Filter.cpp @@ -30,6 +30,7 @@ #include "filters/PostTransformFilter.h" #include "filters/RotateFilter.h" #include "filters/LowVelocityFilter.h" +#include "filters/ExponentialSmoothingFilter.h" using namespace controller; @@ -49,6 +50,7 @@ REGISTER_FILTER_CLASS_INSTANCE(TransformFilter, "transform") REGISTER_FILTER_CLASS_INSTANCE(PostTransformFilter, "postTransform") REGISTER_FILTER_CLASS_INSTANCE(RotateFilter, "rotate") REGISTER_FILTER_CLASS_INSTANCE(LowVelocityFilter, "lowVelocity") +REGISTER_FILTER_CLASS_INSTANCE(ExponentialSmoothingFilter, "exponentialSmoothing") const QString JSON_FILTER_TYPE = QStringLiteral("type"); const QString JSON_FILTER_PARAMS = QStringLiteral("params"); @@ -93,7 +95,7 @@ bool Filter::parseSingleFloatParameter(const QJsonValue& parameters, const QStri output = objectParameters[name].toDouble(); return true; } - } + } return false; } @@ -117,7 +119,7 @@ bool Filter::parseVec3Parameter(const QJsonValue& parameters, glm::vec3& output) objectParameters["z"].toDouble()); return true; } - } + } return false; } @@ -126,7 +128,7 @@ bool Filter::parseMat4Parameter(const QJsonValue& parameters, glm::mat4& output) auto objectParameters = parameters.toObject(); - if (objectParameters.contains("r0c0") && + if (objectParameters.contains("r0c0") && objectParameters.contains("r1c0") && objectParameters.contains("r2c0") && objectParameters.contains("r3c0") && @@ -169,9 +171,9 @@ bool Filter::parseMat4Parameter(const QJsonValue& parameters, glm::mat4& output) bool Filter::parseQuatParameter(const QJsonValue& parameters, glm::quat& output) { if (parameters.isObject()) { auto objectParameters = parameters.toObject(); - if (objectParameters.contains("w") && - objectParameters.contains("x") && - objectParameters.contains("y") && + if (objectParameters.contains("w") && + objectParameters.contains("x") && + objectParameters.contains("y") && objectParameters.contains("z")) { output = glm::quat(objectParameters["w"].toDouble(), diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp index bc1ef55725..048e23be1c 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp @@ -32,6 +32,7 @@ #include "filters/PostTransformFilter.h" #include "filters/RotateFilter.h" #include "filters/LowVelocityFilter.h" +#include "filters/ExponentialSmoothingFilter.h" #include "conditionals/AndConditional.h" using namespace controller; @@ -134,6 +135,11 @@ QObject* RouteBuilderProxy::lowVelocity(float rotationConstant, float translatio return this; } +QObject* RouteBuilderProxy::exponentialSmoothing(float rotationConstant, float translationConstant) { + addFilter(std::make_shared(rotationConstant, translationConstant)); + return this; +} + QObject* RouteBuilderProxy::constrainToInteger() { addFilter(std::make_shared()); return this; diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h index 75f3747566..92a87e5e39 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h @@ -53,6 +53,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* postTransform(glm::mat4 transform); Q_INVOKABLE QObject* rotate(glm::quat rotation); Q_INVOKABLE QObject* lowVelocity(float rotationConstant, float translationConstant); + Q_INVOKABLE QObject* exponentialSmoothing(float rotationConstant, float translationConstant); Q_INVOKABLE QObject* logicalNot(); private: diff --git a/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.cpp b/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.cpp new file mode 100644 index 0000000000..9cf2673d55 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.cpp @@ -0,0 +1,71 @@ +// +// Created by Anthony Thibault 2017/12/07 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#include "ExponentialSmoothingFilter.h" + +#include +#include +#include "../../UserInputMapper.h" +#include "../../Input.h" +#include + +static const QString JSON_ROTATION = QStringLiteral("rotation"); +static const QString JSON_TRANSLATION = QStringLiteral("translation"); +namespace controller { + + Pose ExponentialSmoothingFilter::apply(Pose value) const { + + if (value.isValid()) { + + // to perform filtering in sensor space, we need to compute the transformations. + auto userInputMapper = DependencyManager::get(); + const InputCalibrationData calibrationData = userInputMapper->getInputCalibrationData(); + glm::mat4 sensorToAvatarMat = glm::inverse(calibrationData.avatarMat) * calibrationData.sensorToWorldMat; + glm::mat4 avatarToSensorMat = glm::inverse(calibrationData.sensorToWorldMat) * calibrationData.avatarMat; + + // transform pose into sensor space. + Pose sensorValue = value.transform(avatarToSensorMat); + + if (_prevSensorValue.isValid()) { + // exponential smoothing filter + sensorValue.translation = _translationConstant * sensorValue.getTranslation() + (1.0f - _translationConstant) * _prevSensorValue.getTranslation(); + sensorValue.rotation = safeMix(sensorValue.getRotation(), _prevSensorValue.getRotation(), _rotationConstant); + + // remember previous sensor space value. + _prevSensorValue = sensorValue; + + // transform back into avatar space + return sensorValue.transform(sensorToAvatarMat); + } else { + // remember previous sensor space value. + _prevSensorValue = sensorValue; + + // no previous value to smooth with, so return value unchanged + return value; + } + } else { + // return invalid value unchanged + return value; + } + } + + bool ExponentialSmoothingFilter::parseParameters(const QJsonValue& parameters) { + + if (parameters.isObject()) { + auto obj = parameters.toObject(); + if (obj.contains(JSON_ROTATION) && obj.contains(JSON_TRANSLATION)) { + _rotationConstant = glm::clamp((float)obj[JSON_ROTATION].toDouble(), 0.0f, 1.0f); + _translationConstant = glm::clamp((float)obj[JSON_TRANSLATION].toDouble(), 0.0f, 1.0f); + return true; + } + } + return false; + } + +} diff --git a/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.h b/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.h new file mode 100644 index 0000000000..134f57243e --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.h @@ -0,0 +1,42 @@ +// +// Created by Anthony Thibault 2017/12/17 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_Controllers_Filters_Exponential_Smoothing_h +#define hifi_Controllers_Filters_Exponential_Smoothing_h + +#include "../Filter.h" + +namespace controller { + + class ExponentialSmoothingFilter : public Filter { + REGISTER_FILTER_CLASS(ExponentialSmoothingFilter); + + public: + ExponentialSmoothingFilter() {} + ExponentialSmoothingFilter(float rotationConstant, float translationConstant) : + _translationConstant(translationConstant), _rotationConstant(rotationConstant) {} + + float apply(float value) const override { return value; } + Pose apply(Pose value) const override; + bool parseParameters(const QJsonValue& parameters) override; + + private: + + // Constant between 0 and 1. + // 1 indicates no smoothing at all, poses are passed through unaltered. + // Values near 1 are less smooth with lower latency. + // Values near 0 are more smooth with higher latency. + float _translationConstant { 0.375f }; + float _rotationConstant { 0.375f }; + + mutable Pose _prevSensorValue { Pose() }; // sensor space + }; + +} + +#endif diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 0993daaa8b..e646ba27f5 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -679,17 +679,13 @@ void OpenGLDisplayPlugin::internalPresent() { void OpenGLDisplayPlugin::present() { auto frameId = (uint64_t)presentCount(); PROFILE_RANGE_EX(render, __FUNCTION__, 0xffffff00, frameId) + uint64_t startPresent = usecTimestampNow(); { PROFILE_RANGE_EX(render, "updateFrameData", 0xff00ff00, frameId) updateFrameData(); } incrementPresentCount(); - { - PROFILE_RANGE_EX(render, "recycle", 0xff00ff00, frameId) - _gpuContext->recycle(); - } - if (_currentFrame) { { withPresentThreadLock([&] { @@ -718,6 +714,7 @@ void OpenGLDisplayPlugin::present() { gpu::Backend::freeGPUMemSize.set(gpu::gl::getFreeDedicatedMemory()); } + _movingAveragePresent.addSample((float)(usecTimestampNow() - startPresent)); } float OpenGLDisplayPlugin::newFramePresentRate() const { diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index cf94ecc37c..19987197b8 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -15,4 +15,4 @@ include_hifi_library_headers(avatars) include_hifi_library_headers(controllers) target_bullet() -target_polyvox() \ No newline at end of file +target_polyvox() diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 7103e72f54..3a737e581b 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -251,10 +251,10 @@ void EntityTreeRenderer::shutdown() { } void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, render::Transaction& transaction) { - PROFILE_RANGE_EX(simulation_physics, "Add", 0xffff00ff, (uint64_t)_entitiesToAdd.size()); + PROFILE_RANGE_EX(simulation_physics, "AddToScene", 0xffff00ff, (uint64_t)_entitiesToAdd.size()); PerformanceTimer pt("add"); - // Clear any expired entities - // FIXME should be able to use std::remove_if, but it fails due to some + // Clear any expired entities + // FIXME should be able to use std::remove_if, but it fails due to some // weird compilation error related to EntityItemID assignment operators for (auto itr = _entitiesToAdd.begin(); _entitiesToAdd.end() != itr; ) { if (itr->second.expired()) { @@ -272,7 +272,7 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r continue; } - // Path to the parent transforms is not valid, + // Path to the parent transforms is not valid, // don't add to the scene graph yet if (!entity->isParentPathComplete()) { continue; @@ -296,7 +296,7 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r } void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene, const ViewFrustum& view, render::Transaction& transaction) { - PROFILE_RANGE_EX(simulation_physics, "Change", 0xffff00ff, (uint64_t)_changedEntities.size()); + PROFILE_RANGE_EX(simulation_physics, "ChangeInScene", 0xffff00ff, (uint64_t)_changedEntities.size()); PerformanceTimer pt("change"); std::unordered_set changedEntities; _changedEntitiesGuard.withWriteLock([&] { @@ -349,7 +349,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene float getRadius() const override { return 0.5f * _renderer->getEntity()->getQueryAACube().getScale(); } uint64_t getTimestamp() const override { return _renderer->getUpdateTime(); } - const EntityRendererPointer& getRenderer() const { return _renderer; } + EntityRendererPointer getRenderer() const { return _renderer; } private: EntityRendererPointer _renderer; }; @@ -382,7 +382,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene std::unordered_map::iterator itr; size_t numSorted = sortedRenderables.size(); while (!sortedRenderables.empty() && usecTimestampNow() < expiry) { - const EntityRendererPointer& renderable = sortedRenderables.top().getRenderer(); + const auto renderable = sortedRenderables.top().getRenderer(); renderable->updateInScene(scene, transaction); _renderablesToUpdate.erase(renderable->getEntity()->getID()); sortedRenderables.pop(); @@ -612,7 +612,7 @@ static glm::vec2 projectOntoEntityXYPlane(EntityItemPointer entity, const PickRa glm::vec3 entityPosition = entity->getWorldPosition(); glm::quat entityRotation = entity->getWorldOrientation(); - glm::vec3 entityDimensions = entity->getDimensions(); + glm::vec3 entityDimensions = entity->getScaledDimensions(); glm::vec3 entityRegistrationPoint = entity->getRegistrationPoint(); // project the intersection point onto the local xy plane of the object. diff --git a/libraries/entities-renderer/src/RenderableLightEntityItem.cpp b/libraries/entities-renderer/src/RenderableLightEntityItem.cpp index 696d7ab8db..fc33ebc498 100644 --- a/libraries/entities-renderer/src/RenderableLightEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableLightEntityItem.cpp @@ -37,7 +37,7 @@ void LightEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint lightPayload.editBound() = render::Item::Bound(); } - glm::vec3 dimensions = entity->getDimensions(); + glm::vec3 dimensions = entity->getScaledDimensions(); float largestDiameter = glm::compMax(dimensions); light->setMaximumRadius(largestDiameter / 2.0f); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index e578e4858d..c756070bc3 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -78,11 +78,11 @@ RenderableModelEntityItem::RenderableModelEntityItem(const EntityItemID& entityI RenderableModelEntityItem::~RenderableModelEntityItem() { } -void RenderableModelEntityItem::setDimensions(const glm::vec3& value) { +void RenderableModelEntityItem::setUnscaledDimensions(const glm::vec3& value) { glm::vec3 newDimensions = glm::max(value, glm::vec3(0.0f)); // can never have negative dimensions - if (getDimensions() != newDimensions) { + if (getUnscaledDimensions() != newDimensions) { _dimensionsInitialized = true; - ModelEntityItem::setDimensions(value); + ModelEntityItem::setUnscaledDimensions(value); } } @@ -124,11 +124,11 @@ void RenderableModelEntityItem::doInitialModelSimulation() { // translation/rotation/scale/registration. The first two are straightforward, but the latter two have guards to // make sure they don't happen after they've already been set. Here we reset those guards. This doesn't cause the // entity values to change -- it just allows the model to match once it comes in. - model->setScaleToFit(false, getDimensions()); + model->setScaleToFit(false, getScaledDimensions()); model->setSnapModelToRegistrationPoint(false, getRegistrationPoint()); // now recalculate the bounds and registration - model->setScaleToFit(true, getDimensions()); + model->setScaleToFit(true, getScaledDimensions()); model->setSnapModelToRegistrationPoint(true, getRegistrationPoint()); model->setRotation(getWorldOrientation()); model->setTranslation(getWorldPosition()); @@ -169,7 +169,7 @@ bool RenderableModelEntityItem::needsUpdateModelBounds() const { return true; } - if (model->getScaleToFitDimensions() != getDimensions()) { + if (model->getScaleToFitDimensions() != getScaledDimensions()) { return true; } @@ -209,17 +209,17 @@ void RenderableModelEntityItem::updateModelBounds() { updateRenderItems = true; } - if (model->getScaleToFitDimensions() != getDimensions() || + if (model->getScaleToFitDimensions() != getScaledDimensions() || model->getRegistrationPoint() != getRegistrationPoint()) { // The machinery for updateModelBounds will give existing models the opportunity to fix their // translation/rotation/scale/registration. The first two are straightforward, but the latter two // have guards to make sure they don't happen after they've already been set. Here we reset those guards. // This doesn't cause the entity values to change -- it just allows the model to match once it comes in. - model->setScaleToFit(false, getDimensions()); + model->setScaleToFit(false, getScaledDimensions()); model->setSnapModelToRegistrationPoint(false, getRegistrationPoint()); // now recalculate the bounds and registration - model->setScaleToFit(true, getDimensions()); + model->setScaleToFit(true, getScaledDimensions()); model->setSnapModelToRegistrationPoint(true, getRegistrationPoint()); updateRenderItems = true; model->scaleToFit(); @@ -372,7 +372,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { const uint32_t QUAD_STRIDE = 4; ShapeType type = getShapeType(); - glm::vec3 dimensions = getDimensions(); + glm::vec3 dimensions = getScaledDimensions(); auto model = getModel(); if (type == SHAPE_TYPE_COMPOUND) { updateModelBounds(); @@ -993,19 +993,15 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { } { - // the current frame is set on the server in update() in ModelEntityItem.cpp - int animationCurrentFrame = (int)(glm::floor(entity->getAnimationCurrentFrame())); - - // in the case where the last frame is greater than the framecount then clamp - // it to the end of the animation until it loops around. - if (animationCurrentFrame < 0 || animationCurrentFrame > frameCount) { - animationCurrentFrame = 0; + float currentFrame = fmod(entity->getAnimationCurrentFrame(), (float)(frameCount)); + if (currentFrame < 0.0f) { + currentFrame += (float)frameCount; } - - if (animationCurrentFrame == _lastKnownCurrentFrame) { + int currentIntegerFrame = (int)(glm::floor(currentFrame)); + if (currentIntegerFrame == _lastKnownCurrentFrame) { return; } - _lastKnownCurrentFrame = animationCurrentFrame; + _lastKnownCurrentFrame = currentIntegerFrame; } if (_jointMapping.size() != _model->getJointStateCount()) { @@ -1084,6 +1080,10 @@ bool ModelEntityRenderer::needsRenderUpdate() const { return true; } + if (!_texturesLoaded && model->getGeometry() && model->getGeometry()->areTexturesLoaded()) { + return true; + } + if (model->needsReload()) { return true; } @@ -1157,7 +1157,7 @@ bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin return true; } - if (model->getScaleToFitDimensions() != entity->getDimensions() || + if (model->getScaleToFitDimensions() != entity->getScaledDimensions() || model->getRegistrationPoint() != entity->getRegistrationPoint()) { return true; } @@ -1213,7 +1213,6 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce connect(model.get(), &Model::requestRenderUpdate, this, &ModelEntityRenderer::requestRenderUpdate); connect(entity.get(), &RenderableModelEntityItem::requestCollisionGeometryUpdate, this, &ModelEntityRenderer::flagForCollisionGeometryUpdate); model->setLoadingPriority(EntityTreeRenderer::getEntityLoadingPriority(*entity)); - model->init(); entity->setModel(model); withWriteLock([&] { _model = model; }); } @@ -1221,6 +1220,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce // From here on, we are guaranteed a populated model withWriteLock([&] { if (_parsedModelURL != model->getURL()) { + _texturesLoaded = false; model->setURL(_parsedModelURL); } }); @@ -1252,6 +1252,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } if (_lastTextures != entity->getTextures()) { + _texturesLoaded = false; _lastTextures = entity->getTextures(); auto newTextures = parseTexturesToMap(_lastTextures, entity->_originalTextures); if (newTextures != _currentTextures) { @@ -1306,12 +1307,17 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } } + if (!_texturesLoaded && model->getGeometry() && model->getGeometry()->areTexturesLoaded()) { + _texturesLoaded = true; + model->updateRenderItems(); + } + // When the individual mesh parts of a model finish fading, they will mark their Model as needing updating // we will watch for that and ask the model to update it's render items if (model->getRenderItemsNeedUpdate()) { model->updateRenderItems(); } - + // The code to deal with the change of properties is now in ModelEntityItem.cpp // That is where _currentFrame and _lastAnimated were updated. if (_animating) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index b4f2665692..b7a339e1e1 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -60,7 +60,7 @@ public: RenderableModelEntityItem(const EntityItemID& entityItemID, bool dimensionsInitialized); virtual ~RenderableModelEntityItem(); - virtual void setDimensions(const glm::vec3& value) override; + virtual void setUnscaledDimensions(const glm::vec3& value) override; virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; void doInitialModelSimulation(); @@ -158,10 +158,11 @@ private: virtual bool isTransparent() const override { return false; } bool _hasModel { false }; - ::ModelPointer _model; + ModelPointer _model; GeometryResource::Pointer _compoundShapeResource; QString _lastTextures; QVariantMap _currentTextures; + bool _texturesLoaded { false }; AnimationPropertyGroup _renderAnimationProperties; int _lastKnownCurrentFrame { -1 }; #ifdef MODEL_ENTITY_USE_FADE_EFFECT diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index fbf85fa8c2..21764dff7f 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -303,6 +303,7 @@ void PolyLineEntityRenderer::doRender(RenderArgs* args) { batch.setInputBuffer(0, _verticesBuffer, 0, sizeof(Vertex)); #ifndef POLYLINE_ENTITY_USE_FADE_EFFECT + // glColor4f must be called after setInputFormat if it must be taken into account if (_isFading) { batch._glColor4f(1.0f, 1.0f, 1.0f, Interpolate::calculateFadeRatio(_fadeStartTime)); } else { diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 356bf3a69c..496f8b7323 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -213,7 +213,7 @@ void RenderablePolyVoxEntityItem::setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxel glm::vec3 RenderablePolyVoxEntityItem::getSurfacePositionAdjustment() const { glm::vec3 result; withReadLock([&] { - glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units + glm::vec3 scale = getScaledDimensions() / _voxelVolumeSize; // meters / voxel-units if (isEdged(_voxelSurfaceStyle)) { result = scale / -2.0f; } @@ -228,7 +228,7 @@ glm::mat4 RenderablePolyVoxEntityItem::voxelToLocalMatrix() const { voxelVolumeSize = _voxelVolumeSize; }); - glm::vec3 dimensions = getDimensions(); + glm::vec3 dimensions = getScaledDimensions(); glm::vec3 scale = dimensions / voxelVolumeSize; // meters / voxel-units bool success; // TODO -- Does this actually have to happen in world space? glm::vec3 center = getCenterPosition(success); // this handles registrationPoint changes @@ -393,7 +393,7 @@ bool RenderablePolyVoxEntityItem::setSphere(const vec3& centerWorldCoords, float glm::mat4 vtwMatrix = voxelToWorldMatrix(); glm::mat4 wtvMatrix = glm::inverse(vtwMatrix); - glm::vec3 dimensions = getDimensions(); + glm::vec3 dimensions = getScaledDimensions(); glm::vec3 voxelSize = dimensions / _voxelVolumeSize; float smallestDimensionSize = voxelSize.x; smallestDimensionSize = glm::min(smallestDimensionSize, voxelSize.y); @@ -454,7 +454,7 @@ bool RenderablePolyVoxEntityItem::setCapsule(const vec3& startWorldCoords, const glm::mat4 vtwMatrix = voxelToWorldMatrix(); glm::mat4 wtvMatrix = glm::inverse(vtwMatrix); - glm::vec3 dimensions = getDimensions(); + glm::vec3 dimensions = getScaledDimensions(); glm::vec3 voxelSize = dimensions / _voxelVolumeSize; float smallestDimensionSize = voxelSize.x; smallestDimensionSize = glm::min(smallestDimensionSize, voxelSize.y); @@ -580,7 +580,7 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o // the PolyVox ray intersection code requires a near and far point. // set ray cast length to long enough to cover all of the voxel space float distanceToEntity = glm::distance(origin, getWorldPosition()); - glm::vec3 dimensions = getDimensions(); + glm::vec3 dimensions = getScaledDimensions(); float largestDimension = glm::compMax(dimensions) * 2.0f; glm::vec3 farPoint = origin + normDirection * (distanceToEntity + largestDimension); @@ -668,7 +668,7 @@ bool RenderablePolyVoxEntityItem::isReadyToComputeShape() const { void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { ShapeType shapeType = getShapeType(); if (shapeType == SHAPE_TYPE_NONE) { - info.setParams(getShapeType(), 0.5f * getDimensions()); + info.setParams(getShapeType(), 0.5f * getScaledDimensions()); return; } diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 3524709395..cdee2c5ec9 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -63,7 +63,7 @@ bool ShapeEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin return true; } - if (_dimensions != entity->getDimensions()) { + if (_dimensions != entity->getScaledDimensions()) { return true; } @@ -82,7 +82,7 @@ void ShapeEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce _shape = entity->getShape(); _position = entity->getWorldPosition(); - _dimensions = entity->getDimensions(); + _dimensions = entity->getScaledDimensions(); _orientation = entity->getWorldOrientation(); _renderTransform = getModelTransform(); @@ -137,11 +137,10 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { }); if (proceduralRender) { - batch._glColor4f(outColor.r, outColor.g, outColor.b, outColor.a); if (render::ShapeKey(args->_globalShapeKey).isWireframe()) { - geometryCache->renderWireShape(batch, geometryShape); + geometryCache->renderWireShape(batch, geometryShape, outColor); } else { - geometryCache->renderShape(batch, geometryShape); + geometryCache->renderShape(batch, geometryShape, outColor); } } else { // FIXME, support instanced multi-shape rendering using multidraw indirect diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index 6c0f4447ae..968c181940 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -54,7 +54,7 @@ bool TextEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint return true; } - if (_dimensions != entity->getDimensions()) { + if (_dimensions != entity->getScaledDimensions()) { return true; } @@ -67,7 +67,7 @@ bool TextEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint void TextEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { _textColor = toGlm(entity->getTextColorX()); _backgroundColor = toGlm(entity->getBackgroundColorX()); - _dimensions = entity->getDimensions(); + _dimensions = entity->getScaledDimensions(); _faceCamera = entity->getFaceCamera(); _lineHeight = entity->getLineHeight(); _text = entity->getText(); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index cdd260e73c..3789cca69a 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -30,6 +30,8 @@ using namespace render; using namespace render::entities; +static const QString WEB_ENTITY_QML = "controls/WebEntityView.qml"; + const float METERS_TO_INCHES = 39.3701f; static uint32_t _currentWebCount{ 0 }; // Don't allow more than 100 concurrent web views @@ -124,7 +126,10 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene withWriteLock([&] { // This work must be done on the main thread if (!hasWebSurface()) { - buildWebSurface(entity); + // If we couldn't create a new web surface, exit + if (!buildWebSurface(entity)) { + return; + } } if (_contextPosition != entity->getWorldPosition()) { @@ -144,7 +149,7 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene glm::vec2 windowSize = getWindowSize(entity); _webSurface->resize(QSize(windowSize.x, windowSize.y)); _renderTransform = getModelTransform(); - _renderTransform.postScale(entity->getDimensions()); + _renderTransform.postScale(entity->getScaledDimensions()); }); } @@ -188,7 +193,6 @@ void WebEntityRenderer::doRender(RenderArgs* args) { }); batch.setResourceTexture(0, _texture); float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; - batch._glColor4f(1.0f, 1.0f, 1.0f, fadeRatio); DependencyManager::get()->bindWebBrowserProgram(batch, fadeRatio < OPAQUE_ALPHA_THRESHOLD); DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, glm::vec4(1.0f, 1.0f, 1.0f, fadeRatio), _geometryId); @@ -218,6 +222,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { }; { + // FIXME use the surface cache instead of explicit creation _webSurface = QSharedPointer(new OffscreenQmlSurface(), deleter); _webSurface->create(); } @@ -274,7 +279,7 @@ void WebEntityRenderer::destroyWebSurface() { } glm::vec2 WebEntityRenderer::getWindowSize(const TypedEntityPointer& entity) const { - glm::vec2 dims = glm::vec2(entity->getDimensions()); + glm::vec2 dims = glm::vec2(entity->getScaledDimensions()); dims *= METERS_TO_INCHES * _lastDPI; // ensure no side is never larger then MAX_WINDOW_SIZE @@ -291,7 +296,6 @@ void WebEntityRenderer::loadSourceURL() { if (sourceUrl.scheme() == "http" || sourceUrl.scheme() == "https" || _lastSourceUrl.toLower().endsWith(".htm") || _lastSourceUrl.toLower().endsWith(".html")) { _contentType = htmlContent; - _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "qml/controls/")); // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. if (sourceUrl.host().endsWith("youtube.com", Qt::CaseInsensitive)) { @@ -300,12 +304,11 @@ void WebEntityRenderer::loadSourceURL() { _webSurface->setMaxFps(DEFAULT_MAX_FPS); } - _webSurface->load("WebEntityView.qml", [this](QQmlContext* context, QObject* item) { + _webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) { item->setProperty("url", _lastSourceUrl); }); } else { _contentType = qmlContent; - _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath())); _webSurface->load(_lastSourceUrl); if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") { auto tabletScriptingInterface = DependencyManager::get(); diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 04da70d733..591bccaabe 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -208,7 +208,7 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen entity->resetRenderingPropertiesChanged(); _lastPosition = entity->getWorldPosition(); _lastRotation = entity->getWorldOrientation(); - _lastDimensions = entity->getDimensions(); + _lastDimensions = entity->getScaledDimensions(); _keyLightProperties = entity->getKeyLightProperties(); _skyboxProperties = entity->getSkyboxProperties(); @@ -280,7 +280,7 @@ bool ZoneEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint if (entity->getWorldPosition() != _lastPosition) { return true; } - if (entity->getDimensions() != _lastDimensions) { + if (entity->getScaledDimensions() != _lastDimensions) { return true; } if (entity->getWorldOrientation() != _lastRotation) { diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 503bdd8349..91e26d0a3c 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include // usecTimestampNow() #include @@ -49,6 +50,7 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) : { setLocalVelocity(ENTITY_ITEM_DEFAULT_VELOCITY); setLocalAngularVelocity(ENTITY_ITEM_DEFAULT_ANGULAR_VELOCITY); + setUnscaledDimensions(ENTITY_ITEM_DEFAULT_DIMENSIONS); // explicitly set transform parts to set dirty flags used by batch rendering locationChanged(); dimensionsChanged(); @@ -114,6 +116,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_EDITION_NUMBER; requestedProperties += PROP_ENTITY_INSTANCE_NUMBER; requestedProperties += PROP_CERTIFICATE_ID; + requestedProperties += PROP_STATIC_CERTIFICATE_VERSION; requestedProperties += PROP_NAME; requestedProperties += PROP_HREF; @@ -241,7 +244,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, getLocalAngularVelocity()); APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, getAcceleration()); - APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getDimensions()); + APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getUnscaledDimensions()); APPEND_ENTITY_PROPERTY(PROP_DENSITY, getDensity()); APPEND_ENTITY_PROPERTY(PROP_GRAVITY, getGravity()); APPEND_ENTITY_PROPERTY(PROP_DAMPING, getDamping()); @@ -271,6 +274,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_EDITION_NUMBER, getEditionNumber()); APPEND_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, getEntityInstanceNumber()); APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, getCertificateID()); + APPEND_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, getStaticCertificateVersion()); APPEND_ENTITY_PROPERTY(PROP_NAME, getName()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, getCollisionSoundURL()); @@ -682,18 +686,18 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef weOwnSimulation = _simulationOwner.matchesValidID(myNodeID); } } else if (_simulationOwner.pendingTake(now - maxPingRoundTrip)) { - // we sent a bid before this packet could have been sent from the server - // so we ignore it and pretend we own the object's simulation + // we sent a bid already but maybe before this packet was sent from the server weOwnSimulation = true; if (newSimOwner.getID().isNull()) { - // entity-server is trying to clear someone else's ownership - // but we want to own it, therefore we ignore this clear event + // the entity-server is trying to clear someone else's ownership if (!_simulationOwner.isNull()) { - // someone else really did own it markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID); somethingChanged = true; _simulationOwner.clearCurrentOwner(); } + } else if (newSimOwner.getID() == myNodeID) { + // the entity-server is awarding us ownership which is what we want + _simulationOwner.set(newSimOwner); } } else if (newSimOwner.matchesValidID(myNodeID) && !_hasBidOnSimulation) { // entity-server tells us that we have simulation ownership while we never requested this for this EntityItem, @@ -776,7 +780,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, customSetAcceleration); } - READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, setDimensions); + READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, setUnscaledDimensions); READ_ENTITY_PROPERTY(PROP_DENSITY, float, setDensity); READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, setGravity); @@ -819,6 +823,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_EDITION_NUMBER, quint32, setEditionNumber); READ_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, quint32, setEntityInstanceNumber); READ_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, QString, setCertificateID); + READ_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, quint32, setStaticCertificateVersion); READ_ENTITY_PROPERTY(PROP_NAME, QString, setName); READ_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); @@ -894,7 +899,7 @@ void EntityItem::debugDump() const { qCDebug(entities) << "EntityItem id:" << getEntityItemID(); qCDebug(entities, " edited ago:%f", (double)getEditedAgo()); qCDebug(entities, " position:%f,%f,%f", (double)position.x, (double)position.y, (double)position.z); - qCDebug(entities) << " dimensions:" << getDimensions(); + qCDebug(entities) << " dimensions:" << getScaledDimensions(); } // adjust any internal timestamps to fix clock skew for this server @@ -919,7 +924,7 @@ void EntityItem::adjustEditPacketForClockSkew(QByteArray& buffer, qint64 clockSk } float EntityItem::computeMass() const { - glm::vec3 dimensions = getDimensions(); + glm::vec3 dimensions = getScaledDimensions(); return getDensity() * _volumeMultiplier * dimensions.x * dimensions.y * dimensions.z; } @@ -938,7 +943,7 @@ void EntityItem::setMass(float mass) { // we must protect the density range to help maintain stability of physics simulation // therefore this method might not accept the mass that is supplied. - glm::vec3 dimensions = getDimensions(); + glm::vec3 dimensions = getScaledDimensions(); float volume = _volumeMultiplier * dimensions.x * dimensions.y * dimensions.z; // compute new density @@ -984,6 +989,7 @@ void EntityItem::setCollisionSoundURL(const QString& value) { } void EntityItem::simulate(const quint64& now) { + DETAILED_PROFILE_RANGE(simulation_physics, "Simulate"); if (getLastSimulated() == 0) { setLastSimulated(now); } @@ -1039,6 +1045,7 @@ void EntityItem::simulate(const quint64& now) { } bool EntityItem::stepKinematicMotion(float timeElapsed) { + DETAILED_PROFILE_RANGE(simulation_physics, "StepKinematicMotion"); // get all the data Transform transform; glm::vec3 linearVelocity; @@ -1172,7 +1179,7 @@ bool EntityItem::wantTerseEditLogging() const { glm::mat4 EntityItem::getEntityToWorldMatrix() const { glm::mat4 translation = glm::translate(getWorldPosition()); glm::mat4 rotation = glm::mat4_cast(getWorldOrientation()); - glm::mat4 scale = glm::scale(getDimensions()); + glm::mat4 scale = glm::scale(getScaledDimensions()); glm::mat4 registration = glm::translate(ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()); return translation * rotation * scale * registration; } @@ -1212,7 +1219,7 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper COPY_ENTITY_PROPERTY_TO_PROPERTIES(simulationOwner, getSimulationOwner); COPY_ENTITY_PROPERTY_TO_PROPERTIES(position, getLocalPosition); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(dimensions, getDimensions); // NOTE: radius is obsolete + COPY_ENTITY_PROPERTY_TO_PROPERTIES(dimensions, getUnscaledDimensions); COPY_ENTITY_PROPERTY_TO_PROPERTIES(rotation, getLocalOrientation); COPY_ENTITY_PROPERTY_TO_PROPERTIES(density, getDensity); COPY_ENTITY_PROPERTY_TO_PROPERTIES(velocity, getLocalVelocity); @@ -1249,6 +1256,7 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper COPY_ENTITY_PROPERTY_TO_PROPERTIES(editionNumber, getEditionNumber); COPY_ENTITY_PROPERTY_TO_PROPERTIES(entityInstanceNumber, getEntityInstanceNumber); COPY_ENTITY_PROPERTY_TO_PROPERTIES(certificateID, getCertificateID); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(staticCertificateVersion, getStaticCertificateVersion); COPY_ENTITY_PROPERTY_TO_PROPERTIES(name, getName); COPY_ENTITY_PROPERTY_TO_PROPERTIES(href, getHref); @@ -1319,7 +1327,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(acceleration, setAcceleration); // these (along with "position" above) affect tree structure - SET_ENTITY_PROPERTY_FROM_PROPERTIES(dimensions, setDimensions); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(dimensions, setUnscaledDimensions); SET_ENTITY_PROPERTY_FROM_PROPERTIES(registrationPoint, setRegistrationPoint); // these (along with all properties above) affect the simulation @@ -1356,6 +1364,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(editionNumber, setEditionNumber); SET_ENTITY_PROPERTY_FROM_PROPERTIES(entityInstanceNumber, setEntityInstanceNumber); SET_ENTITY_PROPERTY_FROM_PROPERTIES(certificateID, setCertificateID); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(staticCertificateVersion, setStaticCertificateVersion); SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName); SET_ENTITY_PROPERTY_FROM_PROPERTIES(href, setHref); @@ -1421,7 +1430,7 @@ void EntityItem::recordCreationTime() { const Transform EntityItem::getTransformToCenter(bool& success) const { Transform result = getTransform(success); if (getRegistrationPoint() != ENTITY_ITEM_HALF_VEC3) { // If it is not already centered, translate to center - result.postTranslate((ENTITY_ITEM_HALF_VEC3 - getRegistrationPoint()) * getDimensions()); // Position to center + result.postTranslate((ENTITY_ITEM_HALF_VEC3 - getRegistrationPoint()) * getScaledDimensions()); // Position to center } return result; } @@ -1437,7 +1446,7 @@ AACube EntityItem::getMaximumAACube(bool& success) const { // we want to compute the furthestExtent that an entity can extend out from its "position" // to do this we compute the max of these two vec3s: registration and 1-registration // and then scale by dimensions - glm::vec3 maxExtents = getDimensions() * glm::max(_registrationPoint, glm::vec3(1.0f) - _registrationPoint); + glm::vec3 maxExtents = getScaledDimensions() * glm::max(_registrationPoint, glm::vec3(1.0f) - _registrationPoint); // there exists a sphere that contains maxExtents for all rotations float radius = glm::length(maxExtents); @@ -1462,7 +1471,7 @@ AACube EntityItem::getMinimumAACube(bool& success) const { glm::vec3 position = getWorldPosition(success); if (success) { _recalcMinAACube = false; - glm::vec3 dimensions = getDimensions(); + glm::vec3 dimensions = getScaledDimensions(); glm::vec3 unrotatedMinRelativeToEntity = - (dimensions * _registrationPoint); glm::vec3 unrotatedMaxRelativeToEntity = dimensions * (glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint); Extents extents = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity }; @@ -1492,7 +1501,7 @@ AABox EntityItem::getAABox(bool& success) const { glm::vec3 position = getWorldPosition(success); if (success) { _recalcAABox = false; - glm::vec3 dimensions = getDimensions(); + glm::vec3 dimensions = getScaledDimensions(); glm::vec3 unrotatedMinRelativeToEntity = - (dimensions * _registrationPoint); glm::vec3 unrotatedMaxRelativeToEntity = dimensions * (glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint); Extents extents = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity }; @@ -1532,12 +1541,12 @@ bool EntityItem::shouldPuffQueryAACube() const { // ... cornerToCornerLength = sqrt(3 x maxDimension ^ 2) // ... radius = sqrt(3 x maxDimension ^ 2) / 2.0f; float EntityItem::getRadius() const { - return 0.5f * glm::length(getDimensions()); + return 0.5f * glm::length(getScaledDimensions()); } void EntityItem::adjustShapeInfoByRegistration(ShapeInfo& info) const { if (_registrationPoint != ENTITY_ITEM_DEFAULT_REGISTRATION_POINT) { - glm::mat4 scale = glm::scale(getDimensions()); + glm::mat4 scale = glm::scale(getScaledDimensions()); glm::mat4 registration = scale * glm::translate(ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()); glm::vec3 regTransVec = glm::vec3(registration[3]); // extract position component from matrix info.setOffset(regTransVec); @@ -1558,12 +1567,12 @@ bool EntityItem::contains(const glm::vec3& point) const { } void EntityItem::computeShapeInfo(ShapeInfo& info) { - info.setParams(getShapeType(), 0.5f * getDimensions()); + info.setParams(getShapeType(), 0.5f * getScaledDimensions()); adjustShapeInfoByRegistration(info); } float EntityItem::getVolumeEstimate() const { - glm::vec3 dimensions = getDimensions(); + glm::vec3 dimensions = getScaledDimensions(); return dimensions.x * dimensions.y * dimensions.z; } @@ -1626,6 +1635,10 @@ void EntityItem::setParentID(const QUuid& value) { newParentNoBootstrapping |= Simulation::NO_BOOTSTRAPPING; } + if (!oldParentID.isNull() && (oldParentID == Physics::getSessionUUID() || oldParentID == AVATAR_SELF_ID)) { + oldParentNoBootstrapping |= Simulation::NO_BOOTSTRAPPING; + } + if ((bool)(oldParentNoBootstrapping ^ newParentNoBootstrapping)) { if ((bool)(newParentNoBootstrapping & Simulation::NO_BOOTSTRAPPING)) { markDirtyFlags(Simulation::NO_BOOTSTRAPPING); @@ -1659,11 +1672,21 @@ void EntityItem::setParentID(const QUuid& value) { } } -void EntityItem::setDimensions(const glm::vec3& value) { +glm::vec3 EntityItem::getScaledDimensions() const { + glm::vec3 scale = getSNScale(); + return _unscaledDimensions * scale; +} + +void EntityItem::setScaledDimensions(const glm::vec3& value) { + glm::vec3 parentScale = getSNScale(); + setUnscaledDimensions(value * parentScale); +} + +void EntityItem::setUnscaledDimensions(const glm::vec3& value) { glm::vec3 newDimensions = glm::max(value, glm::vec3(0.0f)); // can never have negative dimensions - if (getDimensions() != newDimensions) { + if (getUnscaledDimensions() != newDimensions) { withWriteLock([&] { - _dimensions = newDimensions; + _unscaledDimensions = newDimensions; }); locationChanged(); dimensionsChanged(); @@ -2059,17 +2082,6 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi } EntityDynamicPointer action = _objectActions[actionID]; - - action->setOwnerEntity(nullptr); - action->setIsMine(false); - - if (simulation) { - action->removeFromSimulation(simulation); - } - - bool success = true; - serializeActions(success, _allActionsDataCache); - _dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION; auto removedActionType = action->getType(); if ((removedActionType == DYNAMIC_TYPE_HOLD || removedActionType == DYNAMIC_TYPE_FAR_GRAB) && !stillHasGrabActions()) { _dirtyFlags &= ~Simulation::NO_BOOTSTRAPPING; @@ -2086,7 +2098,17 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi // because they should have been set correctly when the action was added // and/or when children were linked } + action->setOwnerEntity(nullptr); + action->setIsMine(false); _objectActions.remove(actionID); + + if (simulation) { + action->removeFromSimulation(simulation); + } + + bool success = true; + serializeActions(success, _allActionsDataCache); + _dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION; setDynamicDataNeedsTransmit(true); return success; } @@ -2354,6 +2376,16 @@ void EntityItem::dimensionsChanged() { somethingChangedNotification(); } +bool EntityItem::getScalesWithParent() const { + // keep this logic the same as in EntityItemProperties::getScalesWithParent + if (getClientOnly()) { + QUuid ancestorID = findAncestorOfType(NestableType::Avatar); + return !ancestorID.isNull(); + } else { + return false; + } +} + void EntityItem::globalizeProperties(EntityItemProperties& properties, const QString& messageTemplate, const glm::vec3& offset) const { // TODO -- combine this with convertLocationToScriptSemantics bool success; @@ -2361,7 +2393,7 @@ void EntityItem::globalizeProperties(EntityItemProperties& properties, const QSt if (success) { properties.setPosition(globalPosition + offset); properties.setRotation(getWorldOrientation()); - properties.setDimensions(getDimensions()); + properties.setDimensions(getScaledDimensions()); // Should we do velocities and accelerations, too? This could end up being quite involved, which is why the method exists. } else { properties.setPosition(getQueryAACube().calcCenter() + offset); // best we can do @@ -2783,6 +2815,7 @@ DEFINE_PROPERTY_ACCESSOR(QString, MarketplaceID, marketplaceID) DEFINE_PROPERTY_ACCESSOR(quint32, EditionNumber, editionNumber) DEFINE_PROPERTY_ACCESSOR(quint32, EntityInstanceNumber, entityInstanceNumber) DEFINE_PROPERTY_ACCESSOR(QString, CertificateID, certificateID) +DEFINE_PROPERTY_ACCESSOR(quint32, StaticCertificateVersion, staticCertificateVersion) uint32_t EntityItem::getDirtyFlags() const { uint32_t result; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 4c7f37bd6a..d8fb3b3ab6 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -133,7 +133,7 @@ public: static EntityItemID readEntityItemIDFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args); - virtual int readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args); + int readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args); virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args, @@ -180,8 +180,11 @@ public: void setDescription(const QString& value); /// Dimensions in meters (0.0 - TREE_SCALE) - inline const glm::vec3 getDimensions() const { return _dimensions; } - virtual void setDimensions(const glm::vec3& value); + glm::vec3 getScaledDimensions() const; + virtual void setScaledDimensions(const glm::vec3& value); + + inline const glm::vec3 getUnscaledDimensions() const { return _unscaledDimensions; } + virtual void setUnscaledDimensions(const glm::vec3& value); float getLocalRenderAlpha() const; void setLocalRenderAlpha(float localRenderAlpha); @@ -328,6 +331,8 @@ public: void setEntityInstanceNumber(const quint32&); QString getCertificateID() const; void setCertificateID(const QString& value); + quint32 getStaticCertificateVersion() const; + void setStaticCertificateVersion(const quint32&); // TODO: get rid of users of getRadius()... float getRadius() const; @@ -454,6 +459,8 @@ public: virtual void locationChanged(bool tellPhysics = true) override; + virtual bool getScalesWithParent() const override; + using ChangeHandlerCallback = std::function; using ChangeHandlerId = QUuid; ChangeHandlerId registerChangeHandler(const ChangeHandlerCallback& handler); @@ -475,7 +482,7 @@ protected: virtual void dimensionsChanged() override; - glm::vec3 _dimensions { ENTITY_ITEM_DEFAULT_DIMENSIONS }; + glm::vec3 _unscaledDimensions { ENTITY_ITEM_DEFAULT_DIMENSIONS }; EntityTypes::EntityType _type { EntityTypes::Unknown }; quint64 _lastSimulated { 0 }; // last time this entity called simulate(), this includes velocity, angular velocity, // and physics changes @@ -547,6 +554,7 @@ protected: quint32 _editionNumber { ENTITY_ITEM_DEFAULT_EDITION_NUMBER }; quint32 _entityInstanceNumber { ENTITY_ITEM_DEFAULT_ENTITY_INSTANCE_NUMBER }; QString _marketplaceID { ENTITY_ITEM_DEFAULT_MARKETPLACE_ID }; + quint32 _staticCertificateVersion { ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION }; // NOTE: Damping is applied like this: v *= pow(1 - damping, dt) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index c7b08f2110..111b851962 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -324,6 +324,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_EDITION_NUMBER, editionNumber); CHECK_PROPERTY_CHANGE(PROP_ENTITY_INSTANCE_NUMBER, entityInstanceNumber); CHECK_PROPERTY_CHANGE(PROP_CERTIFICATE_ID, certificateID); + CHECK_PROPERTY_CHANGE(PROP_STATIC_CERTIFICATE_VERSION, staticCertificateVersion); CHECK_PROPERTY_CHANGE(PROP_NAME, name); CHECK_PROPERTY_CHANGE(PROP_BACKGROUND_MODE, backgroundMode); @@ -364,6 +365,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_LOCAL_ROTATION, localRotation); CHECK_PROPERTY_CHANGE(PROP_LOCAL_VELOCITY, localVelocity); CHECK_PROPERTY_CHANGE(PROP_LOCAL_ANGULAR_VELOCITY, localAngularVelocity); + CHECK_PROPERTY_CHANGE(PROP_LOCAL_DIMENSIONS, localDimensions); CHECK_PROPERTY_CHANGE(PROP_FLYING_ALLOWED, flyingAllowed); CHECK_PROPERTY_CHANGE(PROP_GHOSTING_ALLOWED, ghostingAllowed); @@ -459,6 +461,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EDITION_NUMBER, editionNumber); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ENTITY_INSTANCE_NUMBER, entityInstanceNumber); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CERTIFICATE_ID, certificateID); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_STATIC_CERTIFICATE_VERSION, staticCertificateVersion); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_NAME, name); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLLISION_SOUND_URL, collisionSoundURL); @@ -626,6 +629,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_ROTATION, localRotation); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_VELOCITY, localVelocity); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_ANGULAR_VELOCITY, localAngularVelocity); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_DIMENSIONS, localDimensions); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLIENT_ONLY, clientOnly); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID); @@ -742,6 +746,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(editionNumber, quint32, setEditionNumber); COPY_PROPERTY_FROM_QSCRIPTVALUE(entityInstanceNumber, quint32, setEntityInstanceNumber); COPY_PROPERTY_FROM_QSCRIPTVALUE(certificateID, QString, setCertificateID); + COPY_PROPERTY_FROM_QSCRIPTVALUE(staticCertificateVersion, quint32, setStaticCertificateVersion); COPY_PROPERTY_FROM_QSCRIPTVALUE(name, QString, setName); COPY_PROPERTY_FROM_QSCRIPTVALUE(collisionSoundURL, QString, setCollisionSoundURL); @@ -802,6 +807,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(localRotation, glmQuat, setLocalRotation); COPY_PROPERTY_FROM_QSCRIPTVALUE(localVelocity, glmVec3, setLocalVelocity); COPY_PROPERTY_FROM_QSCRIPTVALUE(localAngularVelocity, glmVec3, setLocalAngularVelocity); + COPY_PROPERTY_FROM_QSCRIPTVALUE(localDimensions, glmVec3, setLocalDimensions); COPY_PROPERTY_FROM_QSCRIPTVALUE(jointRotationsSet, qVectorBool, setJointRotationsSet); COPY_PROPERTY_FROM_QSCRIPTVALUE(jointRotations, qVectorQuat, setJointRotations); @@ -899,6 +905,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(editionNumber); COPY_PROPERTY_IF_CHANGED(entityInstanceNumber); COPY_PROPERTY_IF_CHANGED(certificateID); + COPY_PROPERTY_IF_CHANGED(staticCertificateVersion); COPY_PROPERTY_IF_CHANGED(name); COPY_PROPERTY_IF_CHANGED(collisionSoundURL); @@ -949,6 +956,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(localRotation); COPY_PROPERTY_IF_CHANGED(localVelocity); COPY_PROPERTY_IF_CHANGED(localAngularVelocity); + COPY_PROPERTY_IF_CHANGED(localDimensions); COPY_PROPERTY_IF_CHANGED(jointRotationsSet); COPY_PROPERTY_IF_CHANGED(jointRotations); @@ -1089,6 +1097,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_EDITION_NUMBER, EditionNumber, editionNumber, quint32); ADD_PROPERTY_TO_MAP(PROP_ENTITY_INSTANCE_NUMBER, EntityInstanceNumber, entityInstanceNumber, quint32); ADD_PROPERTY_TO_MAP(PROP_CERTIFICATE_ID, CertificateID, certificateID, QString); + ADD_PROPERTY_TO_MAP(PROP_STATIC_CERTIFICATE_VERSION, StaticCertificateVersion, staticCertificateVersion, quint32); ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_COLOR, KeyLightColor, keyLightColor, xColor); ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_INTENSITY, KeyLightIntensity, keyLightIntensity, float); @@ -1127,6 +1136,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_LOCAL_ROTATION, LocalRotation, localRotation, glm::quat); ADD_PROPERTY_TO_MAP(PROP_LOCAL_VELOCITY, LocalVelocity, localVelocity, glm::vec3); ADD_PROPERTY_TO_MAP(PROP_LOCAL_ANGULAR_VELOCITY, LocalAngularVelocity, localAngularVelocity, glm::vec3); + ADD_PROPERTY_TO_MAP(PROP_LOCAL_DIMENSIONS, LocalDimensions, localDimensions, glm::vec3); ADD_PROPERTY_TO_MAP(PROP_JOINT_ROTATIONS_SET, JointRotationsSet, jointRotationsSet, QVector); ADD_PROPERTY_TO_MAP(PROP_JOINT_ROTATIONS, JointRotations, jointRotations, QVector); @@ -1477,6 +1487,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_EDITION_NUMBER, properties.getEditionNumber()); APPEND_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, properties.getEntityInstanceNumber()); APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, properties.getCertificateID()); + APPEND_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, properties.getStaticCertificateVersion()); } if (propertyCount > 0) { @@ -1828,6 +1839,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EDITION_NUMBER, quint32, setEditionNumber); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ENTITY_INSTANCE_NUMBER, quint32, setEntityInstanceNumber); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CERTIFICATE_ID, QString, setCertificateID); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STATIC_CERTIFICATE_VERSION, quint32, setStaticCertificateVersion); return valid; } @@ -1995,6 +2007,7 @@ void EntityItemProperties::markAllChanged() { _editionNumberChanged = true; _entityInstanceNumberChanged = true; _certificateIDChanged = true; + _staticCertificateVersionChanged = true; _keyLight.markAllChanged(); @@ -2337,6 +2350,9 @@ QList EntityItemProperties::listChangedProperties() { if (certificateIDChanged()) { out += "certificateID"; } + if (staticCertificateVersionChanged()) { + out += "staticCertificateVersion"; + } if (backgroundModeChanged()) { out += "backgroundMode"; @@ -2459,9 +2475,25 @@ bool EntityItemProperties::transformChanged() const { localPositionChanged() || localRotationChanged(); } +bool EntityItemProperties::getScalesWithParent() const { + // keep this logic the same as in EntityItem::getScalesWithParent + bool scalesWithParent { false }; + if (parentIDChanged()) { + bool success; + SpatiallyNestablePointer parent = SpatiallyNestable::findByID(getParentID(), success); + if (success && parent) { + bool avatarAncestor = (parent->getNestableType() == NestableType::Avatar || + parent->hasAncestorOfType(NestableType::Avatar)); + scalesWithParent = getClientOnly() && avatarAncestor; + } + } + return scalesWithParent; +} + bool EntityItemProperties::parentRelatedPropertyChanged() const { return positionChanged() || rotationChanged() || localPositionChanged() || localRotationChanged() || + localDimensionsChanged() || parentIDChanged() || parentJointIndexChanged(); } @@ -2479,6 +2511,9 @@ QByteArray EntityItemProperties::getStaticCertificateJSON() const { // of the entity as reviewed during the certification submission. QJsonObject json; + + quint32 staticCertificateVersion = getStaticCertificateVersion(); + if (!getAnimation().getURL().isEmpty()) { json["animationURL"] = getAnimation().getURL(); } @@ -2495,7 +2530,11 @@ QByteArray EntityItemProperties::getStaticCertificateJSON() const { ADD_STRING_PROPERTY(marketplaceID, MarketplaceID); ADD_STRING_PROPERTY(modelURL, ModelURL); ADD_STRING_PROPERTY(script, Script); + if (staticCertificateVersion >= 1) { + ADD_STRING_PROPERTY(serverScripts, ServerScripts); + } ADD_ENUM_PROPERTY(shapeType, ShapeType); + ADD_INT_PROPERTY(staticCertificateVersion, StaticCertificateVersion); json["type"] = EntityTypes::getEntityTypeName(getType()); return QJsonDocument(json).toJson(QJsonDocument::Compact); @@ -2512,7 +2551,8 @@ bool EntityItemProperties::verifySignature(const QString& publicKey, const QByte return false; } - const unsigned char* key = reinterpret_cast(publicKey.toUtf8().constData()); + auto keyByteArray = publicKey.toUtf8(); + auto key = keyByteArray.constData(); int keyLength = publicKey.length(); BIO *bio = BIO_new_mem_buf((void*)key, keyLength); @@ -2530,19 +2570,23 @@ bool EntityItemProperties::verifySignature(const QString& publicKey, const QByte // ECSDA verification prototype: note that type is currently ignored // int ECDSA_verify(int type, const unsigned char *dgst, int dgstlen, // const unsigned char *sig, int siglen, EC_KEY *eckey); - bool answer = ECDSA_verify(0, + int answer = ECDSA_verify(0, digest, digestLength, signature, signatureLength, ec); long error = ERR_get_error(); - if (error != 0) { - const char* error_str = ERR_error_string(error, NULL); - qCWarning(entities) << "ERROR while verifying signature! EC error:" << error_str + if (error != 0 || answer == -1) { + qCWarning(entities) << "ERROR while verifying signature!" << "\nKey:" << publicKey << "\nutf8 Key Length:" << keyLength << "\nDigest:" << digest << "\nDigest Length:" << digestLength << "\nSignature:" << signature << "\nSignature Length:" << signatureLength; + while (error != 0) { + const char* error_str = ERR_error_string(error, NULL); + qCWarning(entities) << "EC error:" << error_str; + error = ERR_get_error(); + } } EC_KEY_free(ec); if (bio) { @@ -2551,7 +2595,7 @@ bool EntityItemProperties::verifySignature(const QString& publicKey, const QByte if (evp_key) { EVP_PKEY_free(evp_key); } - return answer; + return (answer == 1); } else { if (bio) { BIO_free(bio); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index ec192d7c9f..4887b42138 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -88,6 +88,7 @@ public: EntityPropertyFlags getChangedProperties() const; bool transformChanged() const; + bool getScalesWithParent() const; bool parentRelatedPropertyChanged() const; bool queryAACubeRelatedPropertyChanged() const; @@ -220,12 +221,14 @@ public: DEFINE_PROPERTY_REF(PROP_EDITION_NUMBER, EditionNumber, editionNumber, quint32, ENTITY_ITEM_DEFAULT_EDITION_NUMBER); DEFINE_PROPERTY_REF(PROP_ENTITY_INSTANCE_NUMBER, EntityInstanceNumber, entityInstanceNumber, quint32, ENTITY_ITEM_DEFAULT_ENTITY_INSTANCE_NUMBER); DEFINE_PROPERTY_REF(PROP_CERTIFICATE_ID, CertificateID, certificateID, QString, ENTITY_ITEM_DEFAULT_CERTIFICATE_ID); + DEFINE_PROPERTY_REF(PROP_STATIC_CERTIFICATE_VERSION, StaticCertificateVersion, staticCertificateVersion, quint32, ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION); // these are used when bouncing location data into and out of scripts DEFINE_PROPERTY_REF(PROP_LOCAL_POSITION, LocalPosition, localPosition, glmVec3, ENTITY_ITEM_ZERO_VEC3); DEFINE_PROPERTY_REF(PROP_LOCAL_ROTATION, LocalRotation, localRotation, glmQuat, ENTITY_ITEM_DEFAULT_ROTATION); DEFINE_PROPERTY_REF(PROP_LOCAL_VELOCITY, LocalVelocity, localVelocity, glmVec3, ENTITY_ITEM_ZERO_VEC3); DEFINE_PROPERTY_REF(PROP_LOCAL_ANGULAR_VELOCITY, LocalAngularVelocity, localAngularVelocity, glmVec3, ENTITY_ITEM_ZERO_VEC3); + DEFINE_PROPERTY_REF(PROP_LOCAL_DIMENSIONS, LocalDimensions, localDimensions, glmVec3, ENTITY_ITEM_ZERO_VEC3); DEFINE_PROPERTY_REF(PROP_JOINT_ROTATIONS_SET, JointRotationsSet, jointRotationsSet, QVector, QVector()); DEFINE_PROPERTY_REF(PROP_JOINT_ROTATIONS, JointRotations, jointRotations, QVector, QVector()); @@ -473,6 +476,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, EditionNumber, editionNumber, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, EntityInstanceNumber, entityInstanceNumber, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, CertificateID, certificateID, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, StaticCertificateVersion, staticCertificateVersion, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, BackgroundMode, backgroundMode, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, HazeMode, hazeMode, ""); diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index f4e8a18012..49bce37fbd 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -41,6 +41,7 @@ const QString ENTITY_ITEM_DEFAULT_MARKETPLACE_ID = QString(""); const quint32 ENTITY_ITEM_DEFAULT_EDITION_NUMBER = 0; const quint32 ENTITY_ITEM_DEFAULT_ENTITY_INSTANCE_NUMBER = 0; const QString ENTITY_ITEM_DEFAULT_CERTIFICATE_ID = QString(""); +const quint32 ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION = 0; const float ENTITY_ITEM_DEFAULT_ALPHA = 1.0f; const float ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA = 1.0f; diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 35d40b669a..d515d60535 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -200,6 +200,7 @@ enum EntityPropertyList { PROP_EDITION_NUMBER, PROP_ENTITY_INSTANCE_NUMBER, PROP_CERTIFICATE_ID, + PROP_STATIC_CERTIFICATE_VERSION, PROP_HAZE_MODE, @@ -219,6 +220,8 @@ enum EntityPropertyList { PROP_HAZE_KEYLIGHT_RANGE, PROP_HAZE_KEYLIGHT_ALTITUDE, + PROP_LOCAL_DIMENSIONS, // only used to convert values to and from scripts + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line PROP_AFTER_LAST_ITEM, diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 35941c09d3..206065beeb 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -117,7 +117,8 @@ void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) { } } -EntityItemProperties convertLocationToScriptSemantics(const EntityItemProperties& entitySideProperties) { +EntityItemProperties convertPropertiesToScriptSemantics(const EntityItemProperties& entitySideProperties, + bool scalesWithParent) { // In EntityTree code, properties.position and properties.rotation are relative to the parent. In javascript, // they are in world-space. The local versions are put into localPosition and localRotation and position and // rotation are converted from local to world space. @@ -126,35 +127,48 @@ EntityItemProperties convertLocationToScriptSemantics(const EntityItemProperties scriptSideProperties.setLocalRotation(entitySideProperties.getRotation()); scriptSideProperties.setLocalVelocity(entitySideProperties.getLocalVelocity()); scriptSideProperties.setLocalAngularVelocity(entitySideProperties.getLocalAngularVelocity()); + scriptSideProperties.setLocalDimensions(entitySideProperties.getDimensions()); bool success; glm::vec3 worldPosition = SpatiallyNestable::localToWorld(entitySideProperties.getPosition(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), + scalesWithParent, success); glm::quat worldRotation = SpatiallyNestable::localToWorld(entitySideProperties.getRotation(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), + scalesWithParent, success); glm::vec3 worldVelocity = SpatiallyNestable::localToWorldVelocity(entitySideProperties.getVelocity(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), + scalesWithParent, success); glm::vec3 worldAngularVelocity = SpatiallyNestable::localToWorldAngularVelocity(entitySideProperties.getAngularVelocity(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), + scalesWithParent, success); + glm::vec3 worldDimensions = SpatiallyNestable::localToWorldDimensions(entitySideProperties.getDimensions(), + entitySideProperties.getParentID(), + entitySideProperties.getParentJointIndex(), + scalesWithParent, + success); + scriptSideProperties.setPosition(worldPosition); scriptSideProperties.setRotation(worldRotation); scriptSideProperties.setVelocity(worldVelocity); scriptSideProperties.setAngularVelocity(worldAngularVelocity); + scriptSideProperties.setDimensions(worldDimensions); return scriptSideProperties; } -EntityItemProperties convertLocationFromScriptSemantics(const EntityItemProperties& scriptSideProperties) { +EntityItemProperties convertPropertiesFromScriptSemantics(const EntityItemProperties& scriptSideProperties, + bool scalesWithParent) { // convert position and rotation properties from world-space to local, unless localPosition and localRotation // are set. If they are set, they overwrite position and rotation. EntityItemProperties entitySideProperties = scriptSideProperties; @@ -168,7 +182,7 @@ EntityItemProperties convertLocationFromScriptSemantics(const EntityItemProperti glm::vec3 localPosition = SpatiallyNestable::worldToLocal(entitySideProperties.getPosition(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), - success); + scalesWithParent, success); entitySideProperties.setPosition(localPosition); } @@ -178,7 +192,7 @@ EntityItemProperties convertLocationFromScriptSemantics(const EntityItemProperti glm::quat localRotation = SpatiallyNestable::worldToLocal(entitySideProperties.getRotation(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), - success); + scalesWithParent, success); entitySideProperties.setRotation(localRotation); } @@ -188,7 +202,7 @@ EntityItemProperties convertLocationFromScriptSemantics(const EntityItemProperti glm::vec3 localVelocity = SpatiallyNestable::worldToLocalVelocity(entitySideProperties.getVelocity(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), - success); + scalesWithParent, success); entitySideProperties.setVelocity(localVelocity); } @@ -199,10 +213,20 @@ EntityItemProperties convertLocationFromScriptSemantics(const EntityItemProperti SpatiallyNestable::worldToLocalAngularVelocity(entitySideProperties.getAngularVelocity(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex(), - success); + scalesWithParent, success); entitySideProperties.setAngularVelocity(localAngularVelocity); } + if (scriptSideProperties.localDimensionsChanged()) { + entitySideProperties.setDimensions(scriptSideProperties.getLocalDimensions()); + } else if (scriptSideProperties.dimensionsChanged()) { + glm::vec3 localDimensions = SpatiallyNestable::worldToLocalDimensions(entitySideProperties.getDimensions(), + entitySideProperties.getParentID(), + entitySideProperties.getParentJointIndex(), + scalesWithParent, success); + entitySideProperties.setDimensions(localDimensions); + } + return entitySideProperties; } @@ -212,9 +236,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties _activityTracking.addedEntityCount++; - EntityItemProperties propertiesWithSimID = convertLocationFromScriptSemantics(properties); - propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); - + EntityItemProperties propertiesWithSimID = properties; if (clientOnly) { auto nodeList = DependencyManager::get(); const QUuid myNodeID = nodeList->getSessionUUID(); @@ -222,6 +244,11 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties propertiesWithSimID.setOwningAvatarID(myNodeID); } + bool scalesWithParent = propertiesWithSimID.getScalesWithParent(); + + propertiesWithSimID = convertPropertiesFromScriptSemantics(propertiesWithSimID, scalesWithParent); + propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); + auto dimensions = propertiesWithSimID.getDimensions(); float volume = dimensions.x * dimensions.y * dimensions.z; auto density = propertiesWithSimID.getDensity(); @@ -295,15 +322,20 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity, EntityPropertyFlags desiredProperties) { PROFILE_RANGE(script_entities, __FUNCTION__); + bool scalesWithParent { false }; EntityItemProperties results; if (_entityTree) { _entityTree->withReadLock([&] { EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(identity)); if (entity) { + scalesWithParent = entity->getScalesWithParent(); if (desiredProperties.getHasProperty(PROP_POSITION) || desiredProperties.getHasProperty(PROP_ROTATION) || desiredProperties.getHasProperty(PROP_LOCAL_POSITION) || - desiredProperties.getHasProperty(PROP_LOCAL_ROTATION)) { + desiredProperties.getHasProperty(PROP_LOCAL_ROTATION) || + desiredProperties.getHasProperty(PROP_LOCAL_VELOCITY) || + desiredProperties.getHasProperty(PROP_LOCAL_ANGULAR_VELOCITY) || + desiredProperties.getHasProperty(PROP_LOCAL_DIMENSIONS)) { // if we are explicitly getting position or rotation, we need parent information to make sense of them. desiredProperties.setHasProperty(PROP_PARENT_ID); desiredProperties.setHasProperty(PROP_PARENT_JOINT_INDEX); @@ -316,6 +348,9 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit desiredProperties = entity->getEntityProperties(params); desiredProperties.setHasProperty(PROP_LOCAL_POSITION); desiredProperties.setHasProperty(PROP_LOCAL_ROTATION); + desiredProperties.setHasProperty(PROP_LOCAL_VELOCITY); + desiredProperties.setHasProperty(PROP_LOCAL_ANGULAR_VELOCITY); + desiredProperties.setHasProperty(PROP_LOCAL_DIMENSIONS); } results = entity->getProperties(desiredProperties); @@ -323,7 +358,7 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit }); } - return convertLocationToScriptSemantics(results); + return convertPropertiesToScriptSemantics(results, scalesWithParent); } QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& scriptSideProperties) { @@ -390,10 +425,13 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& if (!scriptSideProperties.localRotationChanged() && !scriptSideProperties.rotationChanged()) { properties.setRotation(entity->getWorldOrientation()); } + if (!scriptSideProperties.localDimensionsChanged() && !scriptSideProperties.dimensionsChanged()) { + properties.setDimensions(entity->getScaledDimensions()); + } } - properties = convertLocationFromScriptSemantics(properties); properties.setClientOnly(entity->getClientOnly()); properties.setOwningAvatarID(entity->getOwningAvatarID()); + properties = convertPropertiesFromScriptSemantics(properties, properties.getScalesWithParent()); float cost = calculateCost(density * volume, oldVelocity, newVelocity); cost *= costMultiplier; @@ -529,7 +567,7 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { return; } - auto dimensions = entity->getDimensions(); + auto dimensions = entity->getScaledDimensions(); float volume = dimensions.x * dimensions.y * dimensions.z; auto density = entity->getDensity(); auto velocity = entity->getWorldVelocity().length(); @@ -1821,6 +1859,19 @@ glm::mat4 EntityScriptingInterface::getEntityLocalTransform(const QUuid& entityI return result; } +QString EntityScriptingInterface::getStaticCertificateJSON(const QUuid& entityID) { + QByteArray result; + if (_entityTree) { + _entityTree->withReadLock([&] { + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID)); + if (entity) { + result = entity->getProperties().getStaticCertificateJSON(); + } + }); + } + return result; +} + bool EntityScriptingInterface::verifyStaticCertificateProperties(const QUuid& entityID) { bool result = false; if (_entityTree) { diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index cc5d8ff16f..095a821482 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -423,6 +423,12 @@ public slots: */ Q_INVOKABLE glm::mat4 getEntityLocalTransform(const QUuid& entityID); + + /**jsdoc + * Return the Static Certificate JSON for the specified {EntityID}. + * @return {QByteArray} The Static Certificate JSON for the specified entity. + */ + Q_INVOKABLE QString getStaticCertificateJSON(const QUuid& entityID); Q_INVOKABLE bool verifyStaticCertificateProperties(const QUuid& entityID); signals: diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index f91d728d78..36b0d8ab2d 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -29,7 +29,6 @@ void EntitySimulation::setEntityTree(EntityTreePointer tree) { } void EntitySimulation::updateEntities() { - PROFILE_RANGE(simulation_physics, "ES::updateEntities"); QMutexLocker lock(&_mutex); quint64 now = usecTimestampNow(); @@ -38,12 +37,7 @@ void EntitySimulation::updateEntities() { callUpdateOnEntitiesThatNeedIt(now); moveSimpleKinematics(now); updateEntitiesInternal(now); - - { - PROFILE_RANGE(simulation_physics, "Sort"); - PerformanceTimer perfTimer("sortingEntities"); - sortEntitiesThatMoved(); - } + sortEntitiesThatMoved(); } void EntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) { @@ -101,6 +95,7 @@ void EntitySimulation::changeEntityInternal(EntityItemPointer entity) { // protected void EntitySimulation::expireMortalEntities(const quint64& now) { if (now > _nextExpiry) { + PROFILE_RANGE_EX(simulation_physics, "ExpireMortals", 0xffff00ff, (uint64_t)_mortalEntities.size()); // only search for expired entities if we expect to find one _nextExpiry = quint64(-1); QMutexLocker lock(&_mutex); @@ -146,6 +141,7 @@ void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) { // protected void EntitySimulation::sortEntitiesThatMoved() { + PROFILE_RANGE_EX(simulation_physics, "SortTree", 0xffff00ff, (uint64_t)_entitiesToSort.size()); // NOTE: this is only for entities that have been moved by THIS EntitySimulation. // External changes to entity position/shape are expected to be sorted outside of the EntitySimulation. MovingEntitiesOperator moveOperator; @@ -265,7 +261,7 @@ void EntitySimulation::clearEntities() { } void EntitySimulation::moveSimpleKinematics(const quint64& now) { - PROFILE_RANGE_EX(simulation_physics, "Kinematics", 0xffff00ff, (uint64_t)_simpleKinematicEntities.size()); + PROFILE_RANGE_EX(simulation_physics, "MoveSimples", 0xffff00ff, (uint64_t)_simpleKinematicEntities.size()); SetOfEntities::iterator itemItr = _simpleKinematicEntities.begin(); while (itemItr != _simpleKinematicEntities.end()) { EntityItemPointer entity = *itemItr; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 0d91f3787e..794e56b262 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -355,11 +355,11 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti } else if (submittedID == senderID) { // the sender is trying to take or continue ownership if (entity->getSimulatorID().isNull()) { - // the sender it taking ownership + // the sender is taking ownership properties.promoteSimulationPriority(RECRUIT_SIMULATION_PRIORITY); simulationBlocked = false; } else if (entity->getSimulatorID() == senderID) { - // the sender is asserting ownership + // the sender is asserting ownership, maybe changing priority simulationBlocked = false; } else { // the sender is trying to steal ownership from another simulator @@ -1189,13 +1189,15 @@ bool EntityTree::verifyNonce(const QString& certID, const QString& nonce, Entity key = sent.second; } - QString annotatedKey = "-----BEGIN PUBLIC KEY-----\n" + key.insert(64, "\n") + "\n-----END PUBLIC KEY-----"; - bool verificationSuccess = EntityItemProperties::verifySignature(annotatedKey.toUtf8(), actualNonce.toUtf8(), nonce.toUtf8()); + QString annotatedKey = "-----BEGIN PUBLIC KEY-----\n" + key.insert(64, "\n") + "\n-----END PUBLIC KEY-----\n"; + QByteArray hashedActualNonce = QCryptographicHash::hash(QByteArray(actualNonce.toUtf8()), QCryptographicHash::Sha256); + bool verificationSuccess = EntityItemProperties::verifySignature(annotatedKey.toUtf8(), hashedActualNonce, QByteArray::fromBase64(nonce.toUtf8())); if (verificationSuccess) { qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded."; } else { - qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed for nonce" << actualNonce << "key" << key << "signature" << nonce; + qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed. Actual nonce:" << actualNonce << + "\nHashed actual nonce (digest):" << hashedActualNonce << "\nSent nonce (signature)" << nonce << "\nKey" << key; } return verificationSuccess; @@ -1770,24 +1772,26 @@ void EntityTree::addToNeedsParentFixupList(EntityItemPointer entity) { } void EntityTree::update(bool simulate) { - PROFILE_RANGE(simulation_physics, "ET::update"); + PROFILE_RANGE(simulation_physics, "UpdateTree"); fixupNeedsParentFixups(); if (simulate && _simulation) { withWriteLock([&] { _simulation->updateEntities(); - VectorOfEntities pendingDeletes; - _simulation->takeEntitiesToDelete(pendingDeletes); + { + PROFILE_RANGE(simulation_physics, "Deletes"); + VectorOfEntities pendingDeletes; + _simulation->takeEntitiesToDelete(pendingDeletes); + if (pendingDeletes.size() > 0) { + // translate into list of ID's + QSet idsToDelete; - if (pendingDeletes.size() > 0) { - // translate into list of ID's - QSet idsToDelete; + for (auto entity : pendingDeletes) { + idsToDelete.insert(entity->getEntityItemID()); + } - for (auto entity : pendingDeletes) { - idsToDelete.insert(entity->getEntityItemID()); + // delete these things the roundabout way + deleteEntities(idsToDelete, true); } - - // delete these things the roundabout way - deleteEntities(idsToDelete, true); } }); } diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 7e2958583d..926975f735 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -667,7 +667,7 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con glm::mat4 entityToWorldMatrix = translation * rotation; glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); - glm::vec3 dimensions = entity->getDimensions(); + glm::vec3 dimensions = entity->getScaledDimensions(); glm::vec3 registrationPoint = entity->getRegistrationPoint(); glm::vec3 corner = -(dimensions * registrationPoint); @@ -763,7 +763,7 @@ void EntityTreeElement::getEntities(const glm::vec3& searchPosition, float searc glm::vec3 penetration; if (!success || entityBox.findSpherePenetration(searchPosition, searchRadius, penetration)) { - glm::vec3 dimensions = entity->getDimensions(); + glm::vec3 dimensions = entity->getScaledDimensions(); // FIXME - consider allowing the entity to determine penetration so that // entities could presumably dull actuall hull testing if they wanted to diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp index f4944603f1..45f36a1744 100644 --- a/libraries/entities/src/LightEntityItem.cpp +++ b/libraries/entities/src/LightEntityItem.cpp @@ -41,16 +41,16 @@ LightEntityItem::LightEntityItem(const EntityItemID& entityItemID) : EntityItem( _color[RED_INDEX] = _color[GREEN_INDEX] = _color[BLUE_INDEX] = 0; } -void LightEntityItem::setDimensions(const glm::vec3& value) { +void LightEntityItem::setUnscaledDimensions(const glm::vec3& value) { if (_isSpotlight) { // If we are a spotlight, treat the z value as our radius or length, and // recalculate the x/y dimensions to properly encapsulate the spotlight. const float length = value.z; const float width = length * glm::sin(glm::radians(_cutoff)); - EntityItem::setDimensions(glm::vec3(width, width, length)); + EntityItem::setUnscaledDimensions(glm::vec3(width, width, length)); } else { float maxDimension = glm::compMax(value); - EntityItem::setDimensions(glm::vec3(maxDimension, maxDimension, maxDimension)); + EntityItem::setUnscaledDimensions(glm::vec3(maxDimension, maxDimension, maxDimension)); } } @@ -98,7 +98,7 @@ void LightEntityItem::setIsSpotlight(bool value) { return; } - glm::vec3 dimensions = getDimensions(); + glm::vec3 dimensions = getScaledDimensions(); glm::vec3 newDimensions; if (value) { const float length = dimensions.z; @@ -112,7 +112,7 @@ void LightEntityItem::setIsSpotlight(bool value) { _isSpotlight = value; _lightPropertiesChanged = true; }); - setDimensions(newDimensions); + setScaledDimensions(newDimensions); } void LightEntityItem::setCutoff(float value) { @@ -128,9 +128,9 @@ void LightEntityItem::setCutoff(float value) { if (getIsSpotlight()) { // If we are a spotlight, adjusting the cutoff will affect the area we encapsulate, // so update the dimensions to reflect this. - const float length = getDimensions().z; + const float length = getScaledDimensions().z; const float width = length * glm::sin(glm::radians(_cutoff)); - setDimensions(glm::vec3(width, width, length)); + setScaledDimensions(glm::vec3(width, width, length)); } withWriteLock([&] { diff --git a/libraries/entities/src/LightEntityItem.h b/libraries/entities/src/LightEntityItem.h index edc7376079..870b283c26 100644 --- a/libraries/entities/src/LightEntityItem.h +++ b/libraries/entities/src/LightEntityItem.h @@ -29,7 +29,7 @@ public: ALLOW_INSTANTIATION // This class can be instantiated /// set dimensions in domain scale units (0.0 - 1.0) this will also reset radius appropriately - virtual void setDimensions(const glm::vec3& value) override; + virtual void setUnscaledDimensions(const glm::vec3& value) override; virtual bool setProperties(const EntityItemProperties& properties) override; virtual bool setSubClassProperties(const EntityItemProperties& properties) override; diff --git a/libraries/entities/src/LineEntityItem.cpp b/libraries/entities/src/LineEntityItem.cpp index 02d16f2537..00196d7b23 100644 --- a/libraries/entities/src/LineEntityItem.cpp +++ b/libraries/entities/src/LineEntityItem.cpp @@ -80,7 +80,7 @@ bool LineEntityItem::appendPoint(const glm::vec3& point) { qCDebug(entities) << "MAX POINTS REACHED!"; return false; } - glm::vec3 halfBox = getDimensions() * 0.5f; + glm::vec3 halfBox = getScaledDimensions() * 0.5f; if ( (point.x < - halfBox.x || point.x > halfBox.x) || (point.y < -halfBox.y || point.y > halfBox.y) || (point.z < - halfBox.z || point.z > halfBox.z) ) { qCDebug(entities) << "Point is outside entity's bounding box"; return false; @@ -96,7 +96,7 @@ bool LineEntityItem::setLinePoints(const QVector& points) { if (points.size() > MAX_POINTS_PER_LINE) { return false; } - glm::vec3 halfBox = getDimensions() * 0.5f; + glm::vec3 halfBox = getScaledDimensions() * 0.5f; for (int i = 0; i < points.size(); i++) { glm::vec3 point = points.at(i); if ( (point.x < - halfBox.x || point.x > halfBox.x) || (point.y < -halfBox.y || point.y > halfBox.y) || (point.z < - halfBox.z || point.z > halfBox.z) ) { @@ -157,7 +157,7 @@ void LineEntityItem::debugDump() const { qCDebug(entities) << " LINE EntityItem id:" << getEntityItemID() << "---------------------------------------------"; qCDebug(entities) << " color:" << _color[0] << "," << _color[1] << "," << _color[2]; qCDebug(entities) << " position:" << debugTreeVector(getWorldPosition()); - qCDebug(entities) << " dimensions:" << debugTreeVector(getDimensions()); + qCDebug(entities) << " dimensions:" << debugTreeVector(getScaledDimensions()); qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); } diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 323584c7ee..3e6d19f430 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -245,7 +245,7 @@ void ModelEntityItem::updateFrameCount() { if (_currentFrame < 0.0f) { return; } - + if (!_lastAnimated) { _lastAnimated = usecTimestampNow(); return; @@ -263,7 +263,7 @@ void ModelEntityItem::updateFrameCount() { } int updatedFrameCount = getAnimationLastFrame() - getAnimationFirstFrame() + 1; - + if (!getAnimationHold() && getAnimationIsPlaying()) { float deltaTime = (float)interval / (float)USECS_PER_SECOND; _currentFrame += (deltaTime * getAnimationFPS()); @@ -283,15 +283,13 @@ void ModelEntityItem::updateFrameCount() { // qCDebug(entities) << "in update frame " << _currentFrame; setAnimationCurrentFrame(_currentFrame); } - - } void ModelEntityItem::debugDump() const { qCDebug(entities) << "ModelEntityItem id:" << getEntityItemID(); qCDebug(entities) << " edited ago:" << getEditedAgo(); qCDebug(entities) << " position:" << getWorldPosition(); - qCDebug(entities) << " dimensions:" << getDimensions(); + qCDebug(entities) << " dimensions:" << getScaledDimensions(); qCDebug(entities) << " model URL:" << getModelURL(); qCDebug(entities) << " compound shape URL:" << getCompoundShapeURL(); } diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index bc0c093518..c20572a491 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -353,7 +353,7 @@ void ParticleEffectEntityItem::computeAndUpdateDimensions() { float maxDistanceValue = glm::compMax(maxDistance); //times 2 because dimensions are diameters not radii glm::vec3 dims(2.0f * maxDistanceValue); - EntityItem::setDimensions(dims); + EntityItem::setScaledDimensions(dims); } @@ -593,7 +593,7 @@ void ParticleEffectEntityItem::debugDump() const { _particleProperties.color.gradient.target.green << "," << _particleProperties.color.gradient.target.blue; qCDebug(entities) << " position:" << debugTreeVector(getWorldPosition()); - qCDebug(entities) << " dimensions:" << debugTreeVector(getDimensions()); + qCDebug(entities) << " dimensions:" << debugTreeVector(getScaledDimensions()); qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); } diff --git a/libraries/entities/src/PolyLineEntityItem.cpp b/libraries/entities/src/PolyLineEntityItem.cpp index e69fb2e100..498d13058e 100644 --- a/libraries/entities/src/PolyLineEntityItem.cpp +++ b/libraries/entities/src/PolyLineEntityItem.cpp @@ -191,7 +191,7 @@ void PolyLineEntityItem::calculateScaleAndRegistrationPoint() { } // if Polyline has only one or fewer points, use default dimension settings - setDimensions(newScale); + setScaledDimensions(newScale); EntityItem::setRegistrationPoint(newRegistrationPoint); } @@ -257,7 +257,7 @@ void PolyLineEntityItem::debugDump() const { qCDebug(entities) << " QUAD EntityItem id:" << getEntityItemID() << "---------------------------------------------"; qCDebug(entities) << " color:" << _color[0] << "," << _color[1] << "," << _color[2]; qCDebug(entities) << " position:" << debugTreeVector(getWorldPosition()); - qCDebug(entities) << " dimensions:" << debugTreeVector(getDimensions()); + qCDebug(entities) << " dimensions:" << debugTreeVector(getScaledDimensions()); qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); } diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp index b08d94ca7e..84ce83d3a1 100644 --- a/libraries/entities/src/PolyVoxEntityItem.cpp +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -229,7 +229,7 @@ void PolyVoxEntityItem::debugDump() const { quint64 now = usecTimestampNow(); qCDebug(entities) << " POLYVOX EntityItem id:" << getEntityItemID() << "---------------------------------------------"; qCDebug(entities) << " position:" << debugTreeVector(getWorldPosition()); - qCDebug(entities) << " dimensions:" << debugTreeVector(getDimensions()); + qCDebug(entities) << " dimensions:" << debugTreeVector(getScaledDimensions()); qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); } @@ -377,7 +377,7 @@ EntityItemID PolyVoxEntityItem::getZPNeighborID() const { glm::vec3 PolyVoxEntityItem::getSurfacePositionAdjustment() const { glm::vec3 result; withReadLock([&] { - glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units + glm::vec3 scale = getScaledDimensions() / _voxelVolumeSize; // meters / voxel-units if (isEdged()) { result = scale / -2.0f; } @@ -392,7 +392,7 @@ glm::mat4 PolyVoxEntityItem::voxelToLocalMatrix() const { voxelVolumeSize = _voxelVolumeSize; }); - glm::vec3 dimensions = getDimensions(); + glm::vec3 dimensions = getScaledDimensions(); glm::vec3 scale = dimensions / voxelVolumeSize; // meters / voxel-units bool success; // TODO -- Does this actually have to happen in world space? glm::vec3 center = getCenterPosition(success); // this handles registrationPoint changes diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 15704ebc17..3750bc3b57 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -106,11 +106,11 @@ void ShapeEntityItem::setShape(const entity::Shape& shape) { break; case entity::Shape::Circle: // Circle is implicitly flat so we enforce flat dimensions - setDimensions(getDimensions()); + setUnscaledDimensions(getUnscaledDimensions()); break; case entity::Shape::Quad: // Quad is implicitly flat so we enforce flat dimensions - setDimensions(getDimensions()); + setUnscaledDimensions(getUnscaledDimensions()); break; default: _type = EntityTypes::Shape; @@ -204,15 +204,15 @@ void ShapeEntityItem::setColor(const QColor& value) { setAlpha(value.alpha()); } -void ShapeEntityItem::setDimensions(const glm::vec3& value) { +void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) { const float MAX_FLAT_DIMENSION = 0.0001f; - if ((_shape == entity::Shape::Circle || _shape == entity::Shape::Quad) && value.y > MAX_FLAT_DIMENSION) { + if ((_shape == entity::Shape::Circle || _shape == entity::Shape::Quad) && value.y > MAX_FLAT_DIMENSION) { // enforce flatness in Y glm::vec3 newDimensions = value; newDimensions.y = MAX_FLAT_DIMENSION; - EntityItem::setDimensions(newDimensions); - } else { - EntityItem::setDimensions(value); + EntityItem::setUnscaledDimensions(newDimensions); + } else { + EntityItem::setUnscaledDimensions(value); } } @@ -256,7 +256,7 @@ void ShapeEntityItem::debugDump() const { qCDebug(entities) << " collisionShapeType:" << ShapeInfo::getNameForShapeType(getShapeType()); qCDebug(entities) << " color:" << _color[0] << "," << _color[1] << "," << _color[2]; qCDebug(entities) << " position:" << debugTreeVector(getWorldPosition()); - qCDebug(entities) << " dimensions:" << debugTreeVector(getDimensions()); + qCDebug(entities) << " dimensions:" << debugTreeVector(getScaledDimensions()); qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); qCDebug(entities) << "SHAPE EntityItem Ptr:" << this; } @@ -266,7 +266,7 @@ void ShapeEntityItem::computeShapeInfo(ShapeInfo& info) { // This will be called whenever DIRTY_SHAPE flag (set by dimension change, etc) // is set. - const glm::vec3 entityDimensions = getDimensions(); + const glm::vec3 entityDimensions = getScaledDimensions(); switch (_shape){ case entity::Shape::Quad: diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index 20e36c88e6..c5df17db54 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -80,7 +80,7 @@ public: const rgbColor& getColor() const { return _color; } void setColor(const rgbColor& value); - void setDimensions(const glm::vec3& value) override; + void setUnscaledDimensions(const glm::vec3& value) override; xColor getXColor() const; void setColor(const xColor& value); diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 67e83ab3fd..639da69a17 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -41,9 +41,9 @@ TextEntityItem::TextEntityItem(const EntityItemID& entityItemID) : EntityItem(en const float TEXT_ENTITY_ITEM_FIXED_DEPTH = 0.01f; -void TextEntityItem::setDimensions(const glm::vec3& value) { +void TextEntityItem::setUnscaledDimensions(const glm::vec3& value) { // NOTE: Text Entities always have a "depth" of 1cm. - EntityItem::setDimensions(glm::vec3(value.x, value.y, TEXT_ENTITY_ITEM_FIXED_DEPTH)); + EntityItem::setUnscaledDimensions(glm::vec3(value.x, value.y, TEXT_ENTITY_ITEM_FIXED_DEPTH)); } EntityItemProperties TextEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { @@ -132,7 +132,7 @@ bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, void** intersectedObject, bool precisionPicking) const { - glm::vec3 dimensions = getDimensions(); + glm::vec3 dimensions = getScaledDimensions(); glm::vec2 xyDimensions(dimensions.x, dimensions.y); glm::quat rotation = getWorldOrientation(); glm::vec3 position = getWorldPosition() + rotation * diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index 8db929fa47..520a935e55 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -23,7 +23,7 @@ public: ALLOW_INSTANTIATION // This class can be instantiated /// set dimensions in domain scale units (0.0 - 1.0) this will also reset radius appropriately - virtual void setDimensions(const glm::vec3& value) override; + virtual void setUnscaledDimensions(const glm::vec3& value) override; virtual ShapeType getShapeType() const override { return SHAPE_TYPE_BOX; } // methods for getting/setting all properties of an entity diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index 5ee630d8ed..54db6e7c3b 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -36,9 +36,9 @@ WebEntityItem::WebEntityItem(const EntityItemID& entityItemID) : EntityItem(enti const float WEB_ENTITY_ITEM_FIXED_DEPTH = 0.01f; -void WebEntityItem::setDimensions(const glm::vec3& value) { +void WebEntityItem::setUnscaledDimensions(const glm::vec3& value) { // NOTE: Web Entities always have a "depth" of 1cm. - EntityItem::setDimensions(glm::vec3(value.x, value.y, WEB_ENTITY_ITEM_FIXED_DEPTH)); + EntityItem::setUnscaledDimensions(glm::vec3(value.x, value.y, WEB_ENTITY_ITEM_FIXED_DEPTH)); } EntityItemProperties WebEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { @@ -109,7 +109,7 @@ bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const g bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, void** intersectedObject, bool precisionPicking) const { - glm::vec3 dimensions = getDimensions(); + glm::vec3 dimensions = getScaledDimensions(); glm::vec2 xyDimensions(dimensions.x, dimensions.y); glm::quat rotation = getWorldOrientation(); glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index 9e84a3a776..f5066beebc 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -22,7 +22,7 @@ public: ALLOW_INSTANTIATION // This class can be instantiated /// set dimensions in domain scale units (0.0 - 1.0) this will also reset radius appropriately - virtual void setDimensions(const glm::vec3& value) override; + virtual void setUnscaledDimensions(const glm::vec3& value) override; virtual ShapeType getShapeType() const override { return SHAPE_TYPE_BOX; } // methods for getting/setting all properties of an entity diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 0ed523202b..fbfbc71a85 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -242,7 +242,7 @@ void ZoneEntityItem::debugDump() const { quint64 now = usecTimestampNow(); qCDebug(entities) << " ZoneEntityItem id:" << getEntityItemID() << "---------------------------------------------"; qCDebug(entities) << " position:" << debugTreeVector(getWorldPosition()); - qCDebug(entities) << " dimensions:" << debugTreeVector(getDimensions()); + qCDebug(entities) << " dimensions:" << debugTreeVector(getScaledDimensions()); qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); qCDebug(entities) << " _backgroundMode:" << EntityItemProperties::getBackgroundModeString(_backgroundMode); qCDebug(entities) << " _hazeMode:" << EntityItemProperties::getHazeModeString(_hazeMode); diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 315c6a86d2..7fb916efde 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -457,13 +457,33 @@ bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mappi // vertex-index/texture-index // vertex-index/texture-index/surface-normal-index QByteArray token = tokenizer.getDatum(); - if (!isdigit(token[0])) { // Tokenizer treats line endings as whitespace. Non-digit indicates done; + auto firstChar = token[0]; + // Tokenizer treats line endings as whitespace. Non-digit and non-negative sign indicates done; + if (!isdigit(firstChar) && firstChar != '-') { tokenizer.pushBackToken(OBJTokenizer::DATUM_TOKEN); break; } QList parts = token.split('/'); assert(parts.count() >= 1); assert(parts.count() <= 3); + // If indices are negative relative indices then adjust them to absolute indices based on current vector sizes + // Also add 1 to each index as 1 will be subtracted later on from each index in OBJFace::add + for (int i = 0; i < parts.count(); ++i) { + int part = parts[i].toInt(); + if (part < 0) { + switch (i) { + case 0: + parts[i].setNum(vertices.size() + part + 1); + break; + case 1: + parts[i].setNum(textureUVs.size() + part + 1); + break; + case 2: + parts[i].setNum(normals.size() + part + 1); + break; + } + } + } const QByteArray noData {}; face.add(parts[0], (parts.count() > 1) ? parts[1] : noData, (parts.count() > 2) ? parts[2] : noData, vertices, vertexColors); diff --git a/libraries/gl/src/gl/Config.h b/libraries/gl/src/gl/Config.h index b1bafe1ba6..cdf86675c6 100644 --- a/libraries/gl/src/gl/Config.h +++ b/libraries/gl/src/gl/Config.h @@ -15,6 +15,11 @@ #include #if defined(Q_OS_ANDROID) +#define HIFI_GLES +#define HIFI_EGL +#endif + +#if defined(HIFI_GLES) // Minimum GL ES version required is 3.2 #define GL_MIN_VERSION_MAJOR 0x03 #define GL_MIN_VERSION_MINOR 0x02 @@ -30,9 +35,11 @@ #define MINIMUM_GL_VERSION ((GL_MIN_VERSION_MAJOR << 8) | GL_MIN_VERSION_MINOR) -#if defined(Q_OS_ANDROID) - +#if defined(HIFI_GLES) #include +#endif + +#if defined(HIFI_GLES) #include #define GL_DEPTH_COMPONENT32_OES 0x81A7 @@ -55,7 +62,7 @@ extern "C" { extern PFNGLFRAMEBUFFERTEXTUREEXTPROC glFramebufferTextureEXT; } -#else // !defined(Q_OS_ANDROID) +#else // !defined(HIFI_GLES) #define GL_GLEXT_PROTOTYPES 1 #include diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index 0e3321d99d..722253c26f 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -28,7 +28,7 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { static QSurfaceFormat format; static std::once_flag once; std::call_once(once, [] { -#if defined(Q_OS_ANDROID) +#if defined(HIFI_GLES) format.setRenderableType(QSurfaceFormat::OpenGLES); format.setRedBufferSize(8); format.setGreenBufferSize(8); diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index d5d104098f..67eb3520c9 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -52,16 +52,15 @@ bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) { _offscreenSurface->setFormat(_context->format()); _offscreenSurface->create(); + + // Due to a https://bugreports.qt.io/browse/QTBUG-65125 we can't rely on `isValid` + // to determine if the offscreen surface was successfully created, so we use + // makeCurrent as a proxy test. Bug is fixed in Qt 5.9.4 #if defined(Q_OS_ANDROID) if (!_context->makeCurrent(_offscreenSurface)) { qFatal("Unable to make offscreen surface current"); } #else - // For some reason, the offscreen surface is considered invalid on android - // possibly because of a bad format? Would need to add some logging to the - // eglpbuffer implementation used from the android platform plugin. - // Alternatively investigate the use of an invisible surface view to do - // a 'native' offscreen surface if (!_offscreenSurface->isValid()) { qFatal("Offscreen surface is invalid"); } diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp index 6fb0d7b152..2192f5f45b 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp @@ -605,6 +605,10 @@ void GLBackend::do_glColor4f(const Batch& batch, size_t paramOffset) { if (_input._colorAttribute != newColor) { _input._colorAttribute = newColor; glVertexAttrib4fv(gpu::Stream::COLOR, &_input._colorAttribute.r); + // Color has been changed and is not white. To prevent colors from bleeding + // between different objects, we need to set the _hadColorAttribute flag + // as if a previous render call had potential colors + _input._hadColorAttribute = (newColor != glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); } (void)CHECK_GL_ERROR(); } @@ -772,7 +776,7 @@ void GLBackend::recycle() const { GLVariableAllocationSupport::manageMemory(); GLVariableAllocationSupport::_frameTexturesCreated = 0; - + Texture::KtxStorage::releaseOpenKtxFiles(); } void GLBackend::setCameraCorrection(const Mat4& correction) { diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.h b/libraries/gpu-gl/src/gpu/gl/GLBackend.h index 1908db614d..5558d3ada1 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.h @@ -253,6 +253,7 @@ protected: struct InputStageState { bool _invalidFormat { true }; + bool _hadColorAttribute{ true }; Stream::FormatPointer _format; std::string _formatKey; diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp index e8ebcbe05c..42bd56e6e4 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp @@ -62,6 +62,8 @@ void GL41Backend::updateInput() { // now we need to bind the buffers and assign the attrib pointers if (_input._format) { + bool hasColorAttribute{ false }; + const Buffers& buffers = _input._buffers; const Offsets& offsets = _input._bufferOffsets; const Offsets& strides = _input._bufferStrides; @@ -98,6 +100,8 @@ void GL41Backend::updateInput() { uintptr_t pointer = (uintptr_t)(attrib._offset + offsets[bufferNum]); GLboolean isNormalized = attrib._element.isNormalized(); + hasColorAttribute = hasColorAttribute || (slot == Stream::COLOR); + for (size_t locNum = 0; locNum < locationCount; ++locNum) { if (attrib._element.isInteger()) { glVertexAttribIPointer(slot + (GLuint)locNum, count, type, stride, @@ -117,6 +121,15 @@ void GL41Backend::updateInput() { } } } + + if (_input._hadColorAttribute && !hasColorAttribute) { + // The previous input stage had a color attribute but this one doesn't so reset + // color to pure white. + const auto white = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); + glVertexAttrib4fv(Stream::COLOR, &white.r); + _input._colorAttribute = white; + } + _input._hadColorAttribute = hasColorAttribute; } // everything format related should be in sync now _input._invalidFormat = false; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp index ece62e15f1..4a43fc988c 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp @@ -32,6 +32,8 @@ void GL45Backend::updateInput() { // Assign the vertex format required if (_input._format) { + bool hasColorAttribute{ false }; + _input._attribBindingBuffers.reset(); const Stream::Format::AttributeMap& attributes = _input._format->getAttributes(); @@ -54,6 +56,9 @@ void GL45Backend::updateInput() { GLboolean isNormalized = attrib._element.isNormalized(); GLenum perLocationSize = attrib._element.getLocationSize(); + + hasColorAttribute = hasColorAttribute || (slot == Stream::COLOR); + for (GLuint locNum = 0; locNum < locationCount; ++locNum) { GLuint attriNum = (GLuint)(slot + locNum); newActivation.set(attriNum); @@ -84,6 +89,15 @@ void GL45Backend::updateInput() { glVertexBindingDivisor(bufferChannelNum, frequency); #endif } + + if (_input._hadColorAttribute && !hasColorAttribute) { + // The previous input stage had a color attribute but this one doesn't so reset + // color to pure white. + const auto white = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); + glVertexAttrib4fv(Stream::COLOR, &white.r); + _input._colorAttribute = white; + } + _input._hadColorAttribute = hasColorAttribute; } // Manage Activation what was and what is expected now diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 1877b494cf..06208179e0 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -321,13 +321,18 @@ public: void reset() override { } + // Don't keep files open forever. We close them at the beginning of each frame (GLBackend::recycle) + static void releaseOpenKtxFiles(); + protected: std::shared_ptr maybeOpenFile() const; - mutable std::mutex _cacheFileCreateMutex; - mutable std::mutex _cacheFileWriteMutex; + mutable std::shared_ptr _cacheFileMutex { std::make_shared() }; mutable std::weak_ptr _cacheFile; + static std::vector, std::shared_ptr>> _cachedKtxFiles; + static std::mutex _cachedKtxFilesMutex; + std::string _filename; cache::FilePointer _cacheEntry; std::atomic _minMipLevelAvailable; diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 08fc4ec101..883d9abf15 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -23,6 +23,9 @@ using namespace gpu; using PixelsPointer = Texture::PixelsPointer; using KtxStorage = Texture::KtxStorage; +std::vector, std::shared_ptr>> KtxStorage::_cachedKtxFiles; +std::mutex KtxStorage::_cachedKtxFilesMutex; + struct GPUKTXPayload { using Version = uint8; @@ -187,36 +190,44 @@ KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) { } } +// maybeOpenFile should be called with _cacheFileMutex already held to avoid modifying the file from multiple threads std::shared_ptr KtxStorage::maybeOpenFile() const { - // 1. Try to get the shared ptr - // 2. If it doesn't exist, grab the mutex around its creation - // 3. If it was created before we got the mutex, return it - // 4. Otherwise, create it - + // Try to get the shared_ptr std::shared_ptr file = _cacheFile.lock(); if (file) { return file; } + // If the file isn't open, create it and save a weak_ptr to it + file = std::make_shared(_filename.c_str()); + _cacheFile = file; + { - std::lock_guard lock{ _cacheFileCreateMutex }; - - file = _cacheFile.lock(); - if (file) { - return file; - } - - file = std::make_shared(_filename.c_str()); - _cacheFile = file; + // Add the shared_ptr to the global list of open KTX files, to be released at the beginning of the next present thread frame + std::lock_guard lock(_cachedKtxFilesMutex); + _cachedKtxFiles.emplace_back(file, _cacheFileMutex); } return file; } +void KtxStorage::releaseOpenKtxFiles() { + std::vector, std::shared_ptr>> localKtxFiles; + { + std::lock_guard lock(_cachedKtxFilesMutex); + localKtxFiles.swap(_cachedKtxFiles); + } + for (auto& cacheFileAndMutex : localKtxFiles) { + std::lock_guard lock(*(cacheFileAndMutex.second)); + cacheFileAndMutex.first.reset(); + } +} + PixelsPointer KtxStorage::getMipFace(uint16 level, uint8 face) const { auto faceOffset = _ktxDescriptor->getMipFaceTexelsOffset(level, face); auto faceSize = _ktxDescriptor->getMipFaceTexelsSize(level, face); if (faceSize != 0 && faceOffset != 0) { + std::lock_guard lock(*_cacheFileMutex); auto file = maybeOpenFile(); if (file) { auto storageView = file->createView(faceSize, faceOffset); @@ -262,6 +273,7 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor return; } + std::lock_guard lock(*_cacheFileMutex); auto file = maybeOpenFile(); if (!file) { qWarning() << "Failed to open file to assign mip data " << QString::fromStdString(_filename); @@ -279,8 +291,6 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor imageData += ktx::IMAGE_SIZE_WIDTH; { - std::lock_guard lock { _cacheFileWriteMutex }; - if (level != _minMipLevelAvailable - 1) { qWarning() << "Invalid level to be stored"; return; diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index 9e4ab2e43f..f78ed1a583 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -981,7 +981,7 @@ public: static QImage extractEquirectangularFace(const QImage& source, gpu::Texture::CubeFace face, int faceWidth) { QImage image(faceWidth, faceWidth, source.format()); - glm::vec2 dstInvSize(1.0f / (float)source.width(), 1.0f / (float)source.height()); + glm::vec2 dstInvSize(1.0f / faceWidth); struct CubeToXYZ { gpu::Texture::CubeFace _face; diff --git a/libraries/midi/src/Midi.cpp b/libraries/midi/src/Midi.cpp index ad650cf067..6c2471f680 100644 --- a/libraries/midi/src/Midi.cpp +++ b/libraries/midi/src/Midi.cpp @@ -177,9 +177,10 @@ void Midi::MidiCleanup() { #endif void Midi::noteReceived(int status, int note, int velocity) { - if (((status & MIDI_STATUS_MASK) != MIDI_NOTE_OFF) && - ((status & MIDI_STATUS_MASK) != MIDI_NOTE_ON)) { - return; // NOTE: only sending note-on and note-off to Javascript + if (((status & MIDI_STATUS_MASK) != MIDI_NOTE_OFF) && + ((status & MIDI_STATUS_MASK) != MIDI_NOTE_ON) && + ((status & MIDI_STATUS_MASK) != MIDI_CONTROL_CHANGE)) { + return; // NOTE: only sending note-on, note-off, and control-change to Javascript } QVariantMap eventData; diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index b8f81dc7a4..8153dba3a5 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -217,8 +217,6 @@ gpu::TexturePointer TextureCache::cacheTextureByHash(const std::string& hash, co if (!result) { _texturesByHashes[hash] = texture; result = texture; - } else { - qCWarning(modelnetworking) << "QQQ Swapping out texture with previous live texture in hash " << hash.c_str(); } } return result; diff --git a/libraries/model/src/model/Haze.h b/libraries/model/src/model/Haze.h index a0cc7c3bc7..2a575eb151 100644 --- a/libraries/model/src/model/Haze.h +++ b/libraries/model/src/model/Haze.h @@ -128,7 +128,7 @@ namespace model { Parameters() {} }; - UniformBufferView _hazeParametersBuffer; + UniformBufferView _hazeParametersBuffer { nullptr }; }; using HazePointer = std::shared_ptr; diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index b884dcba17..21815e065a 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -596,7 +596,7 @@ bool AddressManager::handleDomainID(const QString& host) { void AddressManager::handlePath(const QString& path, LookupTrigger trigger, bool wasPathOnly) { if (!handleViewpoint(path, false, trigger, wasPathOnly)) { qCDebug(networking) << "User entered path could not be handled as a viewpoint - " << path << - "- wll attempt to ask domain-server to resolve."; + "- will attempt to ask domain-server to resolve."; if (!wasPathOnly) { // if we received a path with a host then we need to remember what it was here so we can not diff --git a/libraries/networking/src/Assignment.cpp b/libraries/networking/src/Assignment.cpp index 27d4a31ccf..58a4446aa6 100644 --- a/libraries/networking/src/Assignment.cpp +++ b/libraries/networking/src/Assignment.cpp @@ -127,7 +127,11 @@ void Assignment::swap(Assignment& otherAssignment) { } const char* Assignment::getTypeName() const { - switch (_type) { + return typeToString(_type); +} + +const char* Assignment::typeToString(Assignment::Type type) { + switch (type) { case Assignment::AudioMixerType: return "audio-mixer"; case Assignment::AvatarMixerType: diff --git a/libraries/networking/src/Assignment.h b/libraries/networking/src/Assignment.h index bbec40682f..e958c84d87 100644 --- a/libraries/networking/src/Assignment.h +++ b/libraries/networking/src/Assignment.h @@ -28,6 +28,7 @@ class Assignment : public QObject { public: enum Type : uint8_t { + FirstType = 0, AudioMixerType = 0, AvatarMixerType = 1, AgentType = 2, @@ -89,6 +90,7 @@ public: const QString& getNodeVersion() const { return _nodeVersion; } const char* getTypeName() const; + static const char* typeToString(Assignment::Type type); friend QDebug operator<<(QDebug debug, const Assignment& assignment); friend QDataStream& operator<<(QDataStream &out, const Assignment& assignment); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 94bc4eeff6..1b80c3b3af 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -42,7 +42,10 @@ static Setting::Handle LIMITED_NODELIST_LOCAL_PORT("LimitedNodeList.Loc const std::set SOLO_NODE_TYPES = { NodeType::AvatarMixer, NodeType::AudioMixer, - NodeType::AssetServer + NodeType::AssetServer, + NodeType::EntityServer, + NodeType::MessagesMixer, + NodeType::EntityScriptServer }; LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) : diff --git a/libraries/networking/src/SentPacketHistory.cpp b/libraries/networking/src/SentPacketHistory.cpp index fbb7eff41a..3bd6d4cf03 100644 --- a/libraries/networking/src/SentPacketHistory.cpp +++ b/libraries/networking/src/SentPacketHistory.cpp @@ -24,8 +24,7 @@ SentPacketHistory::SentPacketHistory(int size) } -void SentPacketHistory::packetSent(uint16_t sequenceNumber, const NLPacket& packet) { - +void SentPacketHistory::untrackedPacketSent(uint16_t sequenceNumber) { // check if given seq number has the expected value. if not, something's wrong with // the code calling this function uint16_t expectedSequenceNumber = _newestSequenceNumber + (uint16_t)1; @@ -34,6 +33,10 @@ void SentPacketHistory::packetSent(uint16_t sequenceNumber, const NLPacket& pack << "Expected:" << expectedSequenceNumber << "Actual:" << sequenceNumber; } _newestSequenceNumber = sequenceNumber; +} + +void SentPacketHistory::packetSent(uint16_t sequenceNumber, const NLPacket& packet) { + untrackedPacketSent(sequenceNumber); QWriteLocker locker(&_packetsLock); _sentPackets.insert(NLPacket::createCopy(packet)); diff --git a/libraries/networking/src/SentPacketHistory.h b/libraries/networking/src/SentPacketHistory.h index 72150658e3..dc92d38b25 100644 --- a/libraries/networking/src/SentPacketHistory.h +++ b/libraries/networking/src/SentPacketHistory.h @@ -27,6 +27,8 @@ class SentPacketHistory { public: SentPacketHistory(int size = MAX_REASONABLE_SEQUENCE_GAP); + void untrackedPacketSent(uint16_t sequenceNumber); + void packetSent(uint16_t sequenceNumber, const NLPacket& packet); const NLPacket* getPacket(uint16_t sequenceNumber) const; diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp index 0965c9834f..c63170de75 100644 --- a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp @@ -89,17 +89,19 @@ void UserActivityLoggerScriptingInterface::doLogAction(QString action, QJsonObje Q_ARG(QJsonObject, details)); } -void UserActivityLoggerScriptingInterface::commercePurchaseSuccess(QString marketplaceID, int cost, bool firstPurchaseOfThisItem) { +void UserActivityLoggerScriptingInterface::commercePurchaseSuccess(QString marketplaceID, QString contentCreator, int cost, bool firstPurchaseOfThisItem) { QJsonObject payload; payload["marketplaceID"] = marketplaceID; + payload["contentCreator"] = contentCreator; payload["cost"] = cost; payload["firstPurchaseOfThisItem"] = firstPurchaseOfThisItem; doLogAction("commercePurchaseSuccess", payload); } -void UserActivityLoggerScriptingInterface::commercePurchaseFailure(QString marketplaceID, int cost, bool firstPurchaseOfThisItem, QString errorDetails) { +void UserActivityLoggerScriptingInterface::commercePurchaseFailure(QString marketplaceID, QString contentCreator, int cost, bool firstPurchaseOfThisItem, QString errorDetails) { QJsonObject payload; payload["marketplaceID"] = marketplaceID; + payload["contentCreator"] = contentCreator; payload["cost"] = cost; payload["firstPurchaseOfThisItem"] = firstPurchaseOfThisItem; payload["errorDetails"] = errorDetails; @@ -141,3 +143,16 @@ void UserActivityLoggerScriptingInterface::commerceWalletSetupFinished(int times payload["secondsToComplete"] = secondsToComplete; doLogAction("commerceWalletSetupFinished", payload); } + +void UserActivityLoggerScriptingInterface::commercePassphraseEntry(QString source) { + QJsonObject payload; + payload["source"] = source; + doLogAction("commercePassphraseEntry", payload); +} + +void UserActivityLoggerScriptingInterface::commercePassphraseAuthenticationStatus(QString status) { + QJsonObject payload; + payload["status"] = status; + doLogAction("commercePassphraseAuthenticationStatus", payload); + +} diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.h b/libraries/networking/src/UserActivityLoggerScriptingInterface.h index e71723f03c..71d411056d 100644 --- a/libraries/networking/src/UserActivityLoggerScriptingInterface.h +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.h @@ -33,12 +33,14 @@ public: Q_INVOKABLE void bubbleToggled(bool newValue); Q_INVOKABLE void bubbleActivated(); Q_INVOKABLE void logAction(QString action, QVariantMap details = QVariantMap{}); - Q_INVOKABLE void commercePurchaseSuccess(QString marketplaceID, int cost, bool firstPurchaseOfThisItem); - Q_INVOKABLE void commercePurchaseFailure(QString marketplaceID, int cost, bool firstPurchaseOfThisItem, QString errorDetails); + 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); Q_INVOKABLE void commerceEntityRezzed(QString marketplaceID, QString source, QString type); Q_INVOKABLE void commerceWalletSetupStarted(int timestamp, QString setupAttemptID, int setupFlowVersion, QString referrer, QString currentDomain); Q_INVOKABLE void commerceWalletSetupProgress(int timestamp, QString setupAttemptID, int secondsElapsed, int currentStepNumber, QString currentStepName); Q_INVOKABLE void commerceWalletSetupFinished(int timestamp, QString setupAttemptID, int secondsToComplete); + Q_INVOKABLE void commercePassphraseEntry(QString source); + Q_INVOKABLE void commercePassphraseAuthenticationStatus(QString status); private: void doLogAction(QString action, QJsonObject details = {}); }; diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index f42049f107..77ed589e0b 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -11,6 +11,8 @@ #include "Connection.h" +#include + #include #include @@ -60,6 +62,15 @@ Connection::Connection(Socket* parentSocket, HifiSockAddr destination, std::uniq _ack2Packet = ControlPacket::create(ControlPacket::ACK2, ACK2_PAYLOAD_BYTES); _lossReport = ControlPacket::create(ControlPacket::NAK, NAK_PACKET_PAYLOAD_BYTES); _handshakeACK = ControlPacket::create(ControlPacket::HandshakeACK, HANDSHAKE_ACK_PAYLOAD_BYTES); + + + // setup psuedo-random number generation shared by all connections + static std::random_device rd; + static std::mt19937 generator(rd()); + static std::uniform_int_distribution<> distribution(0, SequenceNumber::MAX); + + // randomize the intial sequence number + _initialSequenceNumber = SequenceNumber(distribution(generator)); } Connection::~Connection() { @@ -79,11 +90,11 @@ void Connection::stopSendQueue() { // tell the send queue to stop and be deleted sendQueue->stop(); + + _lastMessageNumber = sendQueue->getCurrentMessageNumber(); + sendQueue->deleteLater(); - // since we're stopping the send queue we should consider our handshake ACK not receieved - _hasReceivedHandshakeACK = false; - // wait on the send queue thread so we know the send queue is gone sendQueueThread->quit(); sendQueueThread->wait(); @@ -101,13 +112,19 @@ void Connection::setMaxBandwidth(int maxBandwidth) { SendQueue& Connection::getSendQueue() { if (!_sendQueue) { - // we may have a sequence number from the previous inactive queue - re-use that so that the // receiver is getting the sequence numbers it expects (given that the connection must still be active) // Lasily create send queue - _sendQueue = SendQueue::create(_parentSocket, _destination); - _lastReceivedACK = _sendQueue->getCurrentSequenceNumber(); + + if (!_hasReceivedHandshakeACK) { + // First time creating a send queue for this connection + _sendQueue = SendQueue::create(_parentSocket, _destination, _initialSequenceNumber - 1, _lastMessageNumber, _hasReceivedHandshakeACK); + _lastReceivedACK = _sendQueue->getCurrentSequenceNumber(); + } else { + // Connection already has a handshake from a previous send queue + _sendQueue = SendQueue::create(_parentSocket, _destination, _lastReceivedACK, _lastMessageNumber, _hasReceivedHandshakeACK); + } #ifdef UDT_CONNECTION_DEBUG qCDebug(networking) << "Created SendQueue for connection to" << _destination; @@ -142,14 +159,6 @@ void Connection::queueInactive() { #ifdef UDT_CONNECTION_DEBUG qCDebug(networking) << "Connection to" << _destination << "has stopped its SendQueue."; #endif - - if (!_hasReceivedHandshake || !_isReceivingData) { -#ifdef UDT_CONNECTION_DEBUG - qCDebug(networking) << "Connection SendQueue to" << _destination << "stopped and no data is being received - stopping connection."; -#endif - - deactivate(); - } } void Connection::queueTimeout() { @@ -182,12 +191,23 @@ void Connection::queueReceivedMessagePacket(std::unique_ptr packet) { pendingMessage.enqueuePacket(std::move(packet)); + bool processedLastOrOnly = false; + while (pendingMessage.hasAvailablePackets()) { auto packet = pendingMessage.removeNextPacket(); + + auto packetPosition = packet->getPacketPosition(); + _parentSocket->messageReceived(std::move(packet)); + + // if this was the last or only packet, then we can remove the pending message from our hash + if (packetPosition == Packet::PacketPosition::LAST || + packetPosition == Packet::PacketPosition::ONLY) { + processedLastOrOnly = true; + } } - if (pendingMessage.isComplete()) { + if (processedLastOrOnly) { _pendingReceivedMessages.erase(messageNumber); } } @@ -208,19 +228,6 @@ void Connection::sync() { && duration_cast(sincePacketReceive).count() >= MIN_SECONDS_BEFORE_EXPIRY ) { // the receive side of this connection is expired _isReceivingData = false; - - // if we don't have a send queue that means the whole connection has expired and we can emit our signal - // otherwise we'll wait for it to also timeout before cleaning up - if (!_sendQueue) { - -#ifdef UDT_CONNECTION_DEBUG - qCDebug(networking) << "Connection to" << _destination << "no longer receiving any data and there is currently no send queue - stopping connection."; -#endif - - deactivate(); - - return; - } } // reset the number of light ACKs or non SYN ACKs during this sync interval @@ -242,26 +249,6 @@ void Connection::sync() { sendTimeoutNAK(); } } - } else if (!_sendQueue) { - // we haven't received a packet and we're not sending - // this most likely means we were started erroneously - // check the start time for this connection and auto expire it after 5 seconds of not receiving or sending any data - static const int CONNECTION_NOT_USED_EXPIRY_SECONDS = 5; - auto secondsSinceStart = duration_cast(p_high_resolution_clock::now() - _connectionStart).count(); - - if (secondsSinceStart >= CONNECTION_NOT_USED_EXPIRY_SECONDS) { - // it's been CONNECTION_NOT_USED_EXPIRY_SECONDS and nothing has actually happened with this connection - // consider it inactive and emit our inactivity signal - -#ifdef UDT_CONNECTION_DEBUG - qCDebug(networking) << "Connection to" << _destination << "did not receive or send any data in last" - << CONNECTION_NOT_USED_EXPIRY_SECONDS << "seconds - stopping connection."; -#endif - - deactivate(); - - return; - } } } @@ -444,7 +431,6 @@ void Connection::sendHandshakeRequest() { } bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, int packetSize, int payloadSize) { - if (!_hasReceivedHandshake) { // Refuse to process any packets until we've received the handshake // Send handshake request to re-request a handshake @@ -536,7 +522,7 @@ bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, in } else { _stats.recordReceivedPackets(payloadSize, packetSize); } - + return !wasDuplicate; } @@ -827,11 +813,13 @@ void Connection::processHandshakeACK(ControlPacketPointer controlPacket) { SequenceNumber initialSequenceNumber; controlPacket->readPrimitive(&initialSequenceNumber); - // hand off this handshake ACK to the send queue so it knows it can start sending - getSendQueue().handshakeACK(initialSequenceNumber); - - // indicate that handshake ACK was received - _hasReceivedHandshakeACK = true; + if (initialSequenceNumber == _initialSequenceNumber) { + // hand off this handshake ACK to the send queue so it knows it can start sending + getSendQueue().handshakeACK(); + + // indicate that handshake ACK was received + _hasReceivedHandshakeACK = true; + } } } diff --git a/libraries/networking/src/udt/Connection.h b/libraries/networking/src/udt/Connection.h index c134081dde..0017eb204a 100644 --- a/libraries/networking/src/udt/Connection.h +++ b/libraries/networking/src/udt/Connection.h @@ -37,7 +37,6 @@ class Socket; class PendingReceivedMessage { public: void enqueuePacket(std::unique_ptr packet); - bool isComplete() const { return _hasLastPacket && _numPackets == _packets.size(); } bool hasAvailablePackets() const; std::unique_ptr removeNextPacket(); @@ -72,8 +71,6 @@ public: void queueReceivedMessagePacket(std::unique_ptr packet); ConnectionStats::Stats sampleStats() { return _stats.sample(); } - - bool isActive() const { return _isActive; } HifiSockAddr getDestination() const { return _destination; } @@ -83,7 +80,6 @@ public: signals: void packetSent(); - void connectionInactive(const HifiSockAddr& sockAddr); void receiverHandshakeRequestComplete(const HifiSockAddr& sockAddr); private slots: @@ -112,8 +108,6 @@ private: void resetReceiveState(); void resetRTT(); - void deactivate() { _isActive = false; emit connectionInactive(_destination); } - SendQueue& getSendQueue(); SequenceNumber nextACK() const; void updateRTT(int rtt); @@ -138,9 +132,11 @@ private: p_high_resolution_clock::time_point _lastReceiveTime; // holds the last time we received anything from sender bool _isReceivingData { false }; // flag used for expiry of receipt portion of connection - bool _isActive { true }; // flag used for inactivity of connection - SequenceNumber _initialReceiveSequenceNumber; // Randomized by peer SendQueue on creation, identifies connection during re-connect requests + SequenceNumber _initialSequenceNumber; // Randomized on Connection creation, identifies connection during re-connect requests + SequenceNumber _initialReceiveSequenceNumber; // Randomized by peer Connection on creation, identifies connection during re-connect requests + + MessageNumber _lastMessageNumber { 0 }; LossList _lossList; // List of all missing packets SequenceNumber _lastReceivedSequenceNumber; // The largest sequence number received from the peer diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 207ddf6bbb..44f86baf40 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -30,10 +30,10 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return static_cast(EntityVersion::HazeEffect); + return static_cast(EntityVersion::OwnershipChallengeFix); case PacketType::EntityQuery: - return static_cast(EntityQueryPacketVersion::ConnectionIdentifier); + return static_cast(EntityQueryPacketVersion::RemovedJurisdictions); case PacketType::AvatarIdentity: case PacketType::AvatarData: case PacketType::BulkAvatarData: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index e5cb87c379..b2f22e2a1f 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -56,8 +56,8 @@ public: ICEServerPeerInformation, ICEServerQuery, OctreeStats, - Jurisdiction, - JurisdictionRequest, + UNUSED_PACKET_TYPE_1, + UNUSED_PACKET_TYPE_2, AssignmentClientStatus, NoisyMute, AvatarIdentity, @@ -197,9 +197,11 @@ uint qHash(const PacketType& key, uint seed); QDebug operator<<(QDebug debug, const PacketType& type); enum class EntityVersion : PacketVersion { - StrokeColorProperty = 77, + StrokeColorProperty = 0, HasDynamicOwnershipTests, - HazeEffect + HazeEffect, + StaticCertJsonVersionOne, + OwnershipChallengeFix, }; enum class EntityScriptCallMethodVersion : PacketVersion { @@ -210,7 +212,8 @@ enum class EntityScriptCallMethodVersion : PacketVersion { enum class EntityQueryPacketVersion: PacketVersion { JSONFilter = 18, JSONFilterWithFamilyTree = 19, - ConnectionIdentifier = 20 + ConnectionIdentifier = 20, + RemovedJurisdictions = 21 }; enum class AssetServerPacketVersion: PacketVersion { diff --git a/libraries/networking/src/udt/PacketQueue.cpp b/libraries/networking/src/udt/PacketQueue.cpp index 9560f2f187..0560855ecb 100644 --- a/libraries/networking/src/udt/PacketQueue.cpp +++ b/libraries/networking/src/udt/PacketQueue.cpp @@ -15,7 +15,7 @@ using namespace udt; -PacketQueue::PacketQueue() { +PacketQueue::PacketQueue(MessageNumber messageNumber) : _currentMessageNumber(messageNumber) { _channels.emplace_back(new std::list()); } diff --git a/libraries/networking/src/udt/PacketQueue.h b/libraries/networking/src/udt/PacketQueue.h index 2b3d3a4b5b..bc4c5e3432 100644 --- a/libraries/networking/src/udt/PacketQueue.h +++ b/libraries/networking/src/udt/PacketQueue.h @@ -34,7 +34,7 @@ class PacketQueue { using Channels = std::vector; public: - PacketQueue(); + PacketQueue(MessageNumber messageNumber = 0); void queuePacket(PacketPointer packet); void queuePacketList(PacketListPointer packetList); @@ -42,6 +42,8 @@ public: PacketPointer takePacket(); Mutex& getLock() { return _packetsLock; } + + MessageNumber getCurrentMessageNumber() const { return _currentMessageNumber; } private: MessageNumber getNextMessageNumber(); diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index 0c029751aa..b62624aab9 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -12,7 +12,6 @@ #include "SendQueue.h" #include -#include #include #include @@ -62,10 +61,12 @@ private: Mutex2& _mutex2; }; -std::unique_ptr SendQueue::create(Socket* socket, HifiSockAddr destination) { +std::unique_ptr SendQueue::create(Socket* socket, HifiSockAddr destination, SequenceNumber currentSequenceNumber, + MessageNumber currentMessageNumber, bool hasReceivedHandshakeACK) { Q_ASSERT_X(socket, "SendQueue::create", "Must be called with a valid Socket*"); - auto queue = std::unique_ptr(new SendQueue(socket, destination)); + auto queue = std::unique_ptr(new SendQueue(socket, destination, currentSequenceNumber, + currentMessageNumber, hasReceivedHandshakeACK)); // Setup queue private thread QThread* thread = new QThread; @@ -84,25 +85,18 @@ std::unique_ptr SendQueue::create(Socket* socket, HifiSockAddr destin return queue; } -SendQueue::SendQueue(Socket* socket, HifiSockAddr dest) : +SendQueue::SendQueue(Socket* socket, HifiSockAddr dest, SequenceNumber currentSequenceNumber, + MessageNumber currentMessageNumber, bool hasReceivedHandshakeACK) : + _packets(currentMessageNumber), _socket(socket), _destination(dest) { - // setup psuedo-random number generation for all instances of SendQueue - static std::random_device rd; - static std::mt19937 generator(rd()); - static std::uniform_int_distribution<> distribution(0, SequenceNumber::MAX); - - // randomize the intial sequence number - _initialSequenceNumber = SequenceNumber(distribution(generator)); - - // set our member variables from randomized initial number - _currentSequenceNumber = _initialSequenceNumber - 1; + // set our member variables from current sequence number + _currentSequenceNumber = currentSequenceNumber; _atomicCurrentSequenceNumber = uint32_t(_currentSequenceNumber); - _lastACKSequenceNumber = uint32_t(_currentSequenceNumber) - 1; + _lastACKSequenceNumber = uint32_t(_currentSequenceNumber); - // default the last receiver response to the current time - _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch(); + _hasReceivedHandshakeACK = hasReceivedHandshakeACK; } SendQueue::~SendQueue() { @@ -114,8 +108,8 @@ void SendQueue::queuePacket(std::unique_ptr packet) { // call notify_one on the condition_variable_any in case the send thread is sleeping waiting for packets _emptyCondition.notify_one(); - if (!this->thread()->isRunning() && _state == State::NotStarted) { - this->thread()->start(); + if (!thread()->isRunning() && _state == State::NotStarted) { + thread()->start(); } } @@ -125,8 +119,8 @@ void SendQueue::queuePacketList(std::unique_ptr packetList) { // call notify_one on the condition_variable_any in case the send thread is sleeping waiting for packets _emptyCondition.notify_one(); - if (!this->thread()->isRunning() && _state == State::NotStarted) { - this->thread()->start(); + if (!thread()->isRunning() && _state == State::NotStarted) { + thread()->start(); } } @@ -144,9 +138,6 @@ int SendQueue::sendPacket(const Packet& packet) { } void SendQueue::ack(SequenceNumber ack) { - // this is a response from the client, re-set our timeout expiry and our last response time - _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch(); - if (_lastACKSequenceNumber == (uint32_t) ack) { return; } @@ -173,10 +164,7 @@ void SendQueue::ack(SequenceNumber ack) { _emptyCondition.notify_one(); } -void SendQueue::nak(SequenceNumber start, SequenceNumber end) { - // this is a response from the client, re-set our timeout expiry - _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch(); - +void SendQueue::nak(SequenceNumber start, SequenceNumber end) { { std::lock_guard nakLocker(_naksLock); _naks.insert(start, end); @@ -197,9 +185,6 @@ void SendQueue::fastRetransmit(udt::SequenceNumber ack) { } void SendQueue::overrideNAKListFromPacket(ControlPacket& packet) { - // this is a response from the client, re-set our timeout expiry - _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch(); - { std::lock_guard nakLocker(_naksLock); _naks.clear(); @@ -225,8 +210,11 @@ void SendQueue::sendHandshake() { std::unique_lock handshakeLock { _handshakeMutex }; if (!_hasReceivedHandshakeACK) { // we haven't received a handshake ACK from the client, send another now + // if the handshake hasn't been completed, then the initial sequence number + // should be the current sequence number + 1 + SequenceNumber initialSequenceNumber = _currentSequenceNumber + 1; auto handshakePacket = ControlPacket::create(ControlPacket::Handshake, sizeof(SequenceNumber)); - handshakePacket->writePrimitive(_initialSequenceNumber); + handshakePacket->writePrimitive(initialSequenceNumber); _socket->writeBasePacket(*handshakePacket, _destination); // we wait for the ACK or the re-send interval to expire @@ -235,18 +223,14 @@ void SendQueue::sendHandshake() { } } -void SendQueue::handshakeACK(SequenceNumber initialSequenceNumber) { - if (initialSequenceNumber == _initialSequenceNumber) { - { - std::lock_guard locker { _handshakeMutex }; - _hasReceivedHandshakeACK = true; - } - - _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch(); - - // Notify on the handshake ACK condition - _handshakeACKCondition.notify_one(); +void SendQueue::handshakeACK() { + { + std::lock_guard locker { _handshakeMutex }; + _hasReceivedHandshakeACK = true; } + + // Notify on the handshake ACK condition + _handshakeACKCondition.notify_one(); } SequenceNumber SendQueue::getNextSequenceNumber() { @@ -540,28 +524,6 @@ bool SendQueue::maybeResendPacket() { bool SendQueue::isInactive(bool attemptedToSendPacket) { // check for connection timeout first - // that will be the case if we have had 16 timeouts since hearing back from the client, and it has been - // at least 5 seconds - static const int NUM_TIMEOUTS_BEFORE_INACTIVE = 16; - static const int MIN_MS_BEFORE_INACTIVE = 5 * 1000; - - auto sinceLastResponse = (QDateTime::currentMSecsSinceEpoch() - _lastReceiverResponse); - - if (sinceLastResponse > 0 && - sinceLastResponse >= int64_t(NUM_TIMEOUTS_BEFORE_INACTIVE * (_estimatedTimeout / USECS_PER_MSEC)) && - sinceLastResponse > MIN_MS_BEFORE_INACTIVE) { - // If the flow window has been full for over CONSIDER_INACTIVE_AFTER, - // then signal the queue is inactive and return so it can be cleaned up - -#ifdef UDT_CONNECTION_DEBUG - qCDebug(networking) << "SendQueue to" << _destination << "reached" << NUM_TIMEOUTS_BEFORE_INACTIVE << "timeouts" - << "and" << MIN_MS_BEFORE_INACTIVE << "milliseconds before receiving any ACK/NAK and is now inactive. Stopping."; -#endif - - deactivate(); - return true; - } - if (!attemptedToSendPacket) { // During our processing above we didn't send any packets diff --git a/libraries/networking/src/udt/SendQueue.h b/libraries/networking/src/udt/SendQueue.h index 484afcb88e..a11aacdb91 100644 --- a/libraries/networking/src/udt/SendQueue.h +++ b/libraries/networking/src/udt/SendQueue.h @@ -50,7 +50,9 @@ public: Stopped }; - static std::unique_ptr create(Socket* socket, HifiSockAddr destination); + static std::unique_ptr create(Socket* socket, HifiSockAddr destination, + SequenceNumber currentSequenceNumber, MessageNumber currentMessageNumber, + bool hasReceivedHandshakeACK); virtual ~SendQueue(); @@ -58,6 +60,7 @@ public: void queuePacketList(std::unique_ptr packetList); SequenceNumber getCurrentSequenceNumber() const { return SequenceNumber(_atomicCurrentSequenceNumber); } + MessageNumber getCurrentMessageNumber() const { return _packets.getCurrentMessageNumber(); } void setFlowWindowSize(int flowWindowSize) { _flowWindowSize = flowWindowSize; } @@ -76,7 +79,7 @@ public slots: void nak(SequenceNumber start, SequenceNumber end); void fastRetransmit(SequenceNumber ack); void overrideNAKListFromPacket(ControlPacket& packet); - void handshakeACK(SequenceNumber initialSequenceNumber); + void handshakeACK(); signals: void packetSent(int wireSize, int payloadSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint); @@ -91,7 +94,8 @@ private slots: void run(); private: - SendQueue(Socket* socket, HifiSockAddr dest); + SendQueue(Socket* socket, HifiSockAddr dest, SequenceNumber currentSequenceNumber, + MessageNumber currentMessageNumber, bool hasReceivedHandshakeACK); SendQueue(SendQueue& other) = delete; SendQueue(SendQueue&& other) = delete; @@ -115,8 +119,6 @@ private: Socket* _socket { nullptr }; // Socket to send packet on HifiSockAddr _destination; // Destination addr - - SequenceNumber _initialSequenceNumber; // Randomized on SendQueue creation, identifies connection during re-connect requests std::atomic _lastACKSequenceNumber { 0 }; // Last ACKed sequence number @@ -128,7 +130,6 @@ private: std::atomic _estimatedTimeout { 0 }; // Estimated timeout, set from CC std::atomic _syncInterval { udt::DEFAULT_SYN_INTERVAL_USECS }; // Sync interval, set from CC - std::atomic _lastReceiverResponse { 0 }; // Timestamp for the last time we got new data from the receiver (ACK/NAK) std::atomic _flowWindowSize { 0 }; // Flow control window size (number of packets that can be on wire) - set from CC diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index a3374a0f47..55643985c8 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -257,9 +257,6 @@ Connection* Socket::findOrCreateConnection(const HifiSockAddr& sockAddr) { congestionControl->setMaxBandwidth(_maxBandwidth); auto connection = std::unique_ptr(new Connection(this, sockAddr, std::move(congestionControl))); - // we queue the connection to cleanup connection in case it asks for it during its own rate control sync - QObject::connect(connection.get(), &Connection::connectionInactive, this, &Socket::cleanupConnection); - // allow higher-level classes to find out when connections have completed a handshake QObject::connect(connection.get(), &Connection::receiverHandshakeRequestComplete, this, &Socket::clientHandshakeRequestComplete); diff --git a/libraries/octree/src/JurisdictionListener.cpp b/libraries/octree/src/JurisdictionListener.cpp deleted file mode 100644 index 76c5069006..0000000000 --- a/libraries/octree/src/JurisdictionListener.cpp +++ /dev/null @@ -1,85 +0,0 @@ -// -// JurisdictionListener.cpp -// libraries/octree/src -// -// Created by Brad Hefta-Gaub on 8/12/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include - -#include -#include -#include -#include "JurisdictionListener.h" - -JurisdictionListener::JurisdictionListener(NodeType_t type) : - _nodeType(type), - _packetSender(JurisdictionListener::DEFAULT_PACKETS_PER_SECOND) -{ - setObjectName("Jurisdiction Listener"); - - connect(DependencyManager::get().data(), &NodeList::nodeKilled, this, &JurisdictionListener::nodeKilled); - - // tell our NodeList we want to hear about nodes with our node type - DependencyManager::get()->addNodeTypeToInterestSet(type); -} - -void JurisdictionListener::nodeKilled(SharedNodePointer node) { - if (_jurisdictions.find(node->getUUID()) != _jurisdictions.end()) { - _jurisdictions.erase(_jurisdictions.find(node->getUUID())); - } -} - -bool JurisdictionListener::queueJurisdictionRequest() { - auto nodeList = DependencyManager::get(); - - int nodeCount = 0; - - nodeList->eachNode([&](const SharedNodePointer& node) { - if (node->getType() == getNodeType() && node->getActiveSocket()) { - auto packet = NLPacket::create(PacketType::JurisdictionRequest, 0); - _packetSender.queuePacketForSending(node, std::move(packet)); - nodeCount++; - } - }); - - if (nodeCount > 0){ - _packetSender.setPacketsPerSecond(nodeCount); - } else { - _packetSender.setPacketsPerSecond(NO_SERVER_CHECK_RATE); - } - - // keep going if still running - return isStillRunning(); -} - -void JurisdictionListener::processPacket(QSharedPointer message, SharedNodePointer sendingNode) { - if (message->getType() == PacketType::Jurisdiction) { - JurisdictionMap map; - map.unpackFromPacket(*message); - _jurisdictions[message->getSourceID()] = map; - } -} - -bool JurisdictionListener::process() { - bool continueProcessing = isStillRunning(); - - // If we're still running, and we don't have any requests waiting to be sent, then queue our jurisdiction requests - if (continueProcessing && !_packetSender.hasPacketsToSend()) { - queueJurisdictionRequest(); - } - - if (continueProcessing) { - continueProcessing = _packetSender.process(); - } - if (continueProcessing) { - // NOTE: This will sleep if there are no pending packets to process - continueProcessing = ReceivedPacketProcessor::process(); - } - - return continueProcessing; -} diff --git a/libraries/octree/src/JurisdictionListener.h b/libraries/octree/src/JurisdictionListener.h deleted file mode 100644 index 7aee1659ff..0000000000 --- a/libraries/octree/src/JurisdictionListener.h +++ /dev/null @@ -1,60 +0,0 @@ -// -// JurisdictionListener.h -// libraries/octree/src -// -// Created by Brad Hefta-Gaub on 8/12/13. -// Copyright 2013 High Fidelity, Inc. -// -// Voxel Packet Sender -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_JurisdictionListener_h -#define hifi_JurisdictionListener_h - -#include -#include -#include - -#include "JurisdictionMap.h" - -/// Sends out PacketType::_JURISDICTION_REQUEST packets to all voxel servers and then listens for and processes -/// the PacketType::_JURISDICTION packets it receives in order to maintain an accurate state of all jurisidictions -/// within the domain. As with other ReceivedPacketProcessor classes the user is responsible for reading inbound packets -/// and adding them to the processing queue by calling queueReceivedPacket() -class JurisdictionListener : public ReceivedPacketProcessor { - Q_OBJECT -public: - static const int DEFAULT_PACKETS_PER_SECOND = 1; - static const int NO_SERVER_CHECK_RATE = 60; // if no servers yet detected, keep checking at 60fps - - JurisdictionListener(NodeType_t type = NodeType::EntityServer); - - virtual bool process() override; - - NodeToJurisdictionMap* getJurisdictions() { return &_jurisdictions; } - - - NodeType_t getNodeType() const { return _nodeType; } - void setNodeType(NodeType_t type) { _nodeType = type; } - -public slots: - /// Called by NodeList to inform us that a node has been killed. - void nodeKilled(SharedNodePointer node); - -protected: - /// Callback for processing of received packets. Will process any queued PacketType::_JURISDICTION and update the - /// jurisdiction map member variable - virtual void processPacket(QSharedPointer messsage, SharedNodePointer sendingNode) override; - -private: - NodeToJurisdictionMap _jurisdictions; - NodeType_t _nodeType; - - bool queueJurisdictionRequest(); - - PacketSender _packetSender; -}; -#endif // hifi_JurisdictionListener_h diff --git a/libraries/octree/src/JurisdictionMap.cpp b/libraries/octree/src/JurisdictionMap.cpp deleted file mode 100644 index fcbc085339..0000000000 --- a/libraries/octree/src/JurisdictionMap.cpp +++ /dev/null @@ -1,325 +0,0 @@ -// -// JurisdictionMap.cpp -// libraries/octree/src -// -// Created by Brad Hefta-Gaub on 8/1/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include -#include -#include -#include - -#include -#include -#include - -#include "OctreeLogging.h" -#include "JurisdictionMap.h" - -void myDebugOutputBits(unsigned char byte, bool withNewLine) { - if (isalnum(byte)) { - printf("[ %d (%c): ", byte, byte); - } else { - printf("[ %d (0x%x): ", byte, byte); - } - - for (int i = 0; i < 8; i++) { - printf("%d", byte >> (7 - i) & 1); - } - printf(" ] "); - - if (withNewLine) { - printf("\n"); - } -} - -void myDebugPrintOctalCode(const unsigned char* octalCode, bool withNewLine) { - if (!octalCode) { - printf("nullptr"); - } else { - for (size_t i = 0; i < bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(octalCode)); i++) { - myDebugOutputBits(octalCode[i], false); - } - } - if (withNewLine) { - printf("\n"); - } -} - -// standard assignment -// copy assignment -JurisdictionMap& JurisdictionMap::operator=(const JurisdictionMap& other) { - copyContents(other); - return *this; -} - -// Copy constructor -JurisdictionMap::JurisdictionMap(const JurisdictionMap& other) : _rootOctalCode(nullptr) { - copyContents(other); -} - -void JurisdictionMap::copyContents(const OctalCodePtr& rootCodeIn, const OctalCodePtrList& endNodesIn) { - OctalCodePtr rootCode = rootCodeIn; - if (!rootCode) { - rootCode = createOctalCodePtr(1); - *rootCode = 0; - } - - OctalCodePtrList emptyEndNodes; - init(rootCode, endNodesIn); -} - -void JurisdictionMap::copyContents(const JurisdictionMap& other) { - _nodeType = other._nodeType; - - OctalCodePtr rootOctalCode; - OctalCodePtrList endNodes; - - std::tie(rootOctalCode, endNodes) = other.getRootAndEndNodeOctalCodes(); - - init(rootOctalCode, endNodes); -} - -JurisdictionMap::~JurisdictionMap() { -} - -JurisdictionMap::JurisdictionMap(NodeType_t type) : _rootOctalCode(nullptr) { - _nodeType = type; - OctalCodePtr rootCode = createOctalCodePtr(1); - *rootCode = 0; - - OctalCodePtrList emptyEndNodes; - init(rootCode, emptyEndNodes); -} - -JurisdictionMap::JurisdictionMap(const char* filename) : _rootOctalCode(nullptr) { - readFromFile(filename); -} - -JurisdictionMap::JurisdictionMap(const char* rootHexCode, const char* endNodesHexCodes) { - - qCDebug(octree, "JurisdictionMap::JurisdictionMap(const char* rootHexCode=[%p] %s, const char* endNodesHexCodes=[%p] %s)", - rootHexCode, rootHexCode, endNodesHexCodes, endNodesHexCodes); - - _rootOctalCode = hexStringToOctalCode(QString(rootHexCode)); - - qCDebug(octree, "JurisdictionMap::JurisdictionMap() _rootOctalCode=%p octalCode=", _rootOctalCode.get()); - myDebugPrintOctalCode(_rootOctalCode.get(), true); - - QString endNodesHexStrings(endNodesHexCodes); - QString delimiterPattern(","); - QStringList endNodeList = endNodesHexStrings.split(delimiterPattern); - - for (int i = 0; i < endNodeList.size(); i++) { - QString endNodeHexString = endNodeList.at(i); - - auto endNodeOctcode = hexStringToOctalCode(endNodeHexString); - - qCDebug(octree, "JurisdictionMap::JurisdictionMap() endNodeList(%d)=%s", - i, endNodeHexString.toLocal8Bit().constData()); - - //printOctalCode(endNodeOctcode); - _endNodes.push_back(endNodeOctcode); - - qCDebug(octree, "JurisdictionMap::JurisdictionMap() endNodeOctcode=%p octalCode=", endNodeOctcode.get()); - myDebugPrintOctalCode(endNodeOctcode.get(), true); - - } -} - -std::tuple JurisdictionMap::getRootAndEndNodeOctalCodes() const { - std::lock_guard lock(_octalCodeMutex); - return std::tuple(_rootOctalCode, _endNodes); -} - -OctalCodePtr JurisdictionMap::getRootOctalCode() const { - std::lock_guard lock(_octalCodeMutex); - return _rootOctalCode; -} - -OctalCodePtrList JurisdictionMap::getEndNodeOctalCodes() const { - std::lock_guard lock(_octalCodeMutex); - return _endNodes; -} - -void JurisdictionMap::init(OctalCodePtr rootOctalCode, const OctalCodePtrList& endNodes) { - std::lock_guard lock(_octalCodeMutex); - _rootOctalCode = rootOctalCode; - _endNodes = endNodes; -} - -JurisdictionMap::Area JurisdictionMap::isMyJurisdiction(const unsigned char* nodeOctalCode, int childIndex) const { - // to be in our jurisdiction, we must be under the root... - - std::lock_guard lock(_octalCodeMutex); - - // if the node is an ancestor of my root, then we return ABOVE - if (isAncestorOf(nodeOctalCode, _rootOctalCode.get())) { - return ABOVE; - } - - // otherwise... - bool isInJurisdiction = isAncestorOf(_rootOctalCode.get(), nodeOctalCode, childIndex); - // if we're under the root, then we can't be under any of the endpoints - if (isInJurisdiction) { - for (size_t i = 0; i < _endNodes.size(); i++) { - bool isUnderEndNode = isAncestorOf(_endNodes[i].get(), nodeOctalCode); - if (isUnderEndNode) { - isInJurisdiction = false; - break; - } - } - } - return isInJurisdiction ? WITHIN : BELOW; -} - - -bool JurisdictionMap::readFromFile(const char* filename) { - QString settingsFile(filename); - QSettings settings(settingsFile, QSettings::IniFormat); - QString rootCode = settings.value("root","00").toString(); - qCDebug(octree) << "rootCode=" << rootCode; - - std::lock_guard lock(_octalCodeMutex); - _rootOctalCode = hexStringToOctalCode(rootCode); - printOctalCode(_rootOctalCode.get()); - - settings.beginGroup("endNodes"); - const QStringList childKeys = settings.childKeys(); - QHash values; - foreach (const QString &childKey, childKeys) { - QString childValue = settings.value(childKey).toString(); - values.insert(childKey, childValue); - qCDebug(octree) << childKey << "=" << childValue; - - auto octcode = hexStringToOctalCode(childValue); - printOctalCode(octcode.get()); - - _endNodes.push_back(octcode); - } - settings.endGroup(); - return true; -} - -void JurisdictionMap::displayDebugDetails() const { - std::lock_guard lock(_octalCodeMutex); - - QString rootNodeValue = octalCodeToHexString(_rootOctalCode.get()); - - qCDebug(octree) << "root:" << rootNodeValue; - - for (size_t i = 0; i < _endNodes.size(); i++) { - QString value = octalCodeToHexString(_endNodes[i].get()); - qCDebug(octree) << "End node[" << i << "]: " << rootNodeValue; - } -} - - -bool JurisdictionMap::writeToFile(const char* filename) { - QString settingsFile(filename); - QSettings settings(settingsFile, QSettings::IniFormat); - - std::lock_guard lock(_octalCodeMutex); - - QString rootNodeValue = octalCodeToHexString(_rootOctalCode.get()); - - settings.setValue("root", rootNodeValue); - - settings.beginGroup("endNodes"); - for (size_t i = 0; i < _endNodes.size(); i++) { - QString key = QString("endnode%1").arg(i); - QString value = octalCodeToHexString(_endNodes[i].get()); - settings.setValue(key, value); - } - settings.endGroup(); - return true; -} - -std::unique_ptr JurisdictionMap::packEmptyJurisdictionIntoMessage(NodeType_t type) { - int bytes = 0; - auto packet = NLPacket::create(PacketType::Jurisdiction, sizeof(type) + sizeof(bytes)); - - // Pack the Node Type in first byte - packet->writePrimitive(type); - // No root or end node details to pack! - packet->writePrimitive(bytes); - - return packet; // includes header! -} - -std::unique_ptr JurisdictionMap::packIntoPacket() { - auto packet = NLPacket::create(PacketType::Jurisdiction); - - // Pack the Node Type in first byte - NodeType_t type = getNodeType(); - packet->writePrimitive(type); - - // add the root jurisdiction - std::lock_guard lock(_octalCodeMutex); - if (_rootOctalCode) { - size_t bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(_rootOctalCode.get())); - // No root or end node details to pack! - packet->writePrimitive(bytes); - packet->write(reinterpret_cast(_rootOctalCode.get()), bytes); - - // if and only if there's a root jurisdiction, also include the end nodes - int endNodeCount = (int)_endNodes.size(); - packet->writePrimitive(endNodeCount); - - for (int i=0; i < endNodeCount; i++) { - auto endNodeCode = _endNodes[i].get(); - size_t bytes = 0; - if (endNodeCode) { - bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode)); - } - packet->writePrimitive(bytes); - packet->write(reinterpret_cast(endNodeCode), bytes); - } - } else { - int bytes = 0; - packet->writePrimitive(bytes); - } - - return packet; -} - -int JurisdictionMap::unpackFromPacket(ReceivedMessage& message) { - // read the root jurisdiction - int bytes = 0; - message.readPrimitive(&bytes); - - std::lock_guard lock(_octalCodeMutex); - _rootOctalCode = nullptr; - _endNodes.clear(); - - if (bytes > 0 && bytes <= message.getBytesLeftToRead()) { - _rootOctalCode = createOctalCodePtr(bytes); - message.read(reinterpret_cast(_rootOctalCode.get()), bytes); - - // if and only if there's a root jurisdiction, also include the end nodes - int endNodeCount = 0; - message.readPrimitive(&endNodeCount); - - for (int i = 0; i < endNodeCount; i++) { - int bytes = 0; - message.readPrimitive(&bytes); - - if (bytes <= message.getBytesLeftToRead()) { - auto endNodeCode = createOctalCodePtr(bytes); - message.read(reinterpret_cast(endNodeCode.get()), bytes); - - // if the endNodeCode was 0 length then don't add it - if (bytes > 0) { - _endNodes.push_back(endNodeCode); - } - } - } - } - - return message.getPosition(); // excludes header -} diff --git a/libraries/octree/src/JurisdictionMap.h b/libraries/octree/src/JurisdictionMap.h deleted file mode 100644 index b5a311c3d9..0000000000 --- a/libraries/octree/src/JurisdictionMap.h +++ /dev/null @@ -1,88 +0,0 @@ -// -// JurisdictionMap.h -// libraries/octree/src -// -// Created by Brad Hefta-Gaub on 8/1/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_JurisdictionMap_h -#define hifi_JurisdictionMap_h - -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include - -class JurisdictionMap { -public: - enum Area { - ABOVE, - WITHIN, - BELOW - }; - - // standard constructors - JurisdictionMap(NodeType_t type = NodeType::EntityServer); // default constructor - JurisdictionMap(const JurisdictionMap& other); // copy constructor - - // standard assignment - JurisdictionMap& operator=(const JurisdictionMap& other); // copy assignment - - // application constructors - JurisdictionMap(const char* filename); - JurisdictionMap(const char* rootHextString, const char* endNodesHextString); - - ~JurisdictionMap(); - - Area isMyJurisdiction(const unsigned char* nodeOctalCode, int childIndex) const; - - bool writeToFile(const char* filename); - bool readFromFile(const char* filename); - - // Provide an atomic way to get both the rootOctalCode and endNodeOctalCodes. - std::tuple getRootAndEndNodeOctalCodes() const; - OctalCodePtr getRootOctalCode() const; - OctalCodePtrList getEndNodeOctalCodes() const; - - void copyContents(const OctalCodePtr& rootCodeIn, const OctalCodePtrList& endNodesIn); - - int unpackFromPacket(ReceivedMessage& message); - std::unique_ptr packIntoPacket(); - - /// Available to pack an empty or unknown jurisdiction into a network packet, used when no JurisdictionMap is available - static std::unique_ptr packEmptyJurisdictionIntoMessage(NodeType_t type); - - void displayDebugDetails() const; - - NodeType_t getNodeType() const { return _nodeType; } - void setNodeType(NodeType_t type) { _nodeType = type; } - -private: - void copyContents(const JurisdictionMap& other); // use assignment instead - void init(OctalCodePtr rootOctalCode, const OctalCodePtrList& endNodes); - - mutable std::mutex _octalCodeMutex; - OctalCodePtr _rootOctalCode { nullptr }; - OctalCodePtrList _endNodes; - NodeType_t _nodeType; -}; - -/// Map between node IDs and their reported JurisdictionMap. Typically used by classes that need to know which nodes are -/// managing which jurisdictions. -class NodeToJurisdictionMap : public QMap, public ReadWriteLockable {}; -typedef QMap::iterator NodeToJurisdictionMapIterator; - - -#endif // hifi_JurisdictionMap_h diff --git a/libraries/octree/src/JurisdictionSender.cpp b/libraries/octree/src/JurisdictionSender.cpp deleted file mode 100644 index dfe1a6d872..0000000000 --- a/libraries/octree/src/JurisdictionSender.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// -// JurisdictionSender.cpp -// libraries/octree/src -// -// Created by Brad Hefta-Gaub on 8/12/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include - -#include -#include -#include -#include "JurisdictionSender.h" - - -JurisdictionSender::JurisdictionSender(JurisdictionMap* map, NodeType_t type) : - ReceivedPacketProcessor(), - _jurisdictionMap(map), - _nodeType(type), - _packetSender(JurisdictionSender::DEFAULT_PACKETS_PER_SECOND) -{ -} - -JurisdictionSender::~JurisdictionSender() { -} - -void JurisdictionSender::processPacket(QSharedPointer message, SharedNodePointer sendingNode) { - if (message->getType() == PacketType::JurisdictionRequest) { - lockRequestingNodes(); - _nodesRequestingJurisdictions.push(sendingNode->getUUID()); - unlockRequestingNodes(); - } -} - -bool JurisdictionSender::process() { - bool continueProcessing = isStillRunning(); - - // call our ReceivedPacketProcessor base class process so we'll get any pending packets - if (continueProcessing && (continueProcessing = ReceivedPacketProcessor::process())) { - int nodeCount = 0; - - lockRequestingNodes(); - while (!_nodesRequestingJurisdictions.empty()) { - - QUuid nodeUUID = _nodesRequestingJurisdictions.front(); - _nodesRequestingJurisdictions.pop(); - SharedNodePointer node = DependencyManager::get()->nodeWithUUID(nodeUUID); - - if (node && node->getActiveSocket()) { - auto packet = (_jurisdictionMap) ? _jurisdictionMap->packIntoPacket() - : JurisdictionMap::packEmptyJurisdictionIntoMessage(getNodeType()); - _packetSender.queuePacketForSending(node, std::move(packet)); - nodeCount++; - } - } - unlockRequestingNodes(); - - // set our packets per second to be the number of nodes - _packetSender.setPacketsPerSecond(nodeCount); - - continueProcessing = _packetSender.process(); - } - return continueProcessing; -} diff --git a/libraries/octree/src/JurisdictionSender.h b/libraries/octree/src/JurisdictionSender.h deleted file mode 100644 index b2d738cd36..0000000000 --- a/libraries/octree/src/JurisdictionSender.h +++ /dev/null @@ -1,58 +0,0 @@ -// -// JurisdictionSender.h -// libraries/octree/src -// -// Created by Brad Hefta-Gaub on 8/12/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_JurisdictionSender_h -#define hifi_JurisdictionSender_h - -#include -#include - -#include -#include -#include "JurisdictionMap.h" - -/// Will process PacketType::_JURISDICTION_REQUEST packets and send out PacketType::_JURISDICTION packets -/// to requesting parties. As with other ReceivedPacketProcessor classes the user is responsible for reading inbound packets -/// and adding them to the processing queue by calling queueReceivedPacket() -class JurisdictionSender : public ReceivedPacketProcessor { - Q_OBJECT -public: - static const int DEFAULT_PACKETS_PER_SECOND = 1; - - JurisdictionSender(JurisdictionMap* map, NodeType_t type = NodeType::EntityServer); - ~JurisdictionSender(); - - void setJurisdiction(JurisdictionMap* map) { _jurisdictionMap = map; } - - virtual bool process() override; - - NodeType_t getNodeType() const { return _nodeType; } - void setNodeType(NodeType_t type) { _nodeType = type; } - -protected: - virtual void processPacket(QSharedPointer message, SharedNodePointer sendingNode) override; - - /// Locks all the resources of the thread. - void lockRequestingNodes() { _requestingNodeMutex.lock(); } - - /// Unlocks all the resources of the thread. - void unlockRequestingNodes() { _requestingNodeMutex.unlock(); } - - -private: - QMutex _requestingNodeMutex; - JurisdictionMap* _jurisdictionMap; - std::queue _nodesRequestingJurisdictions; - NodeType_t _nodeType; - - PacketSender _packetSender; -}; -#endif // hifi_JurisdictionSender_h diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 7563122290..c63ff2f560 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -9,10 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifdef _WIN32 -#define _USE_MATH_DEFINES -#endif - #include #include #include @@ -1024,16 +1020,6 @@ int Octree::encodeTreeBitstreamRecursion(const OctreeElementPointer& element, return bytesAtThisLevel; } - // If we've been provided a jurisdiction map, then we need to honor it. - if (params.jurisdictionMap) { - // here's how it works... if we're currently above our root jurisdiction, then we proceed normally. - // but once we're in our own jurisdiction, then we need to make sure we're not below it. - if (JurisdictionMap::BELOW == params.jurisdictionMap->isMyJurisdiction(element->getOctalCode(), CHECK_NODE_ONLY)) { - params.stopReason = EncodeBitstreamParams::OUT_OF_JURISDICTION; - return bytesAtThisLevel; - } - } - ViewFrustum::intersection nodeLocationThisView = ViewFrustum::INSIDE; // assume we're inside if (octreeQueryNode->getUsesFrustum() && !params.recurseEverything) { float boundaryDistance = boundaryDistanceForRenderLevel(element->getLevel() + params.boundaryLevelAdjust, @@ -1156,18 +1142,9 @@ int Octree::encodeTreeBitstreamRecursion(const OctreeElementPointer& element, for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { OctreeElementPointer childElement = element->getChildAtIndex(i); - // if the caller wants to include childExistsBits, then include them even if not in view, if however, - // we're in a portion of the tree that's not our responsibility, then we assume the child nodes exist - // even if they don't in our local tree - bool notMyJurisdiction = false; - if (params.jurisdictionMap) { - notMyJurisdiction = JurisdictionMap::WITHIN != params.jurisdictionMap->isMyJurisdiction(element->getOctalCode(), i); - } - if (params.includeExistsBits) { - // If the child is known to exist, OR, it's not my jurisdiction, then we mark the bit as existing - if (childElement || notMyJurisdiction) { - childrenExistInTreeBits += (1 << (7 - i)); - } + // if the caller wants to include childExistsBits, then include them + if (params.includeExistsBits && childElement) { + childrenExistInTreeBits += (1 << (7 - i)); } sortedChildren[i] = childElement; diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index ec6a0e810d..1648cb0f47 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -24,7 +24,6 @@ #include #include -#include "JurisdictionMap.h" #include "OctreeElement.h" #include "OctreeElementBag.h" #include "OctreePacketData.h" @@ -62,7 +61,6 @@ const int NO_BOUNDARY_ADJUST = 0; const int LOW_RES_MOVING_ADJUST = 1; #define IGNORE_COVERAGE_MAP NULL -#define IGNORE_JURISDICTION_MAP NULL class EncodeBitstreamParams { public: @@ -77,7 +75,6 @@ public: int boundaryLevelAdjust; float octreeElementSizeScale; bool forceSendScene; - JurisdictionMap* jurisdictionMap; NodeData* nodeData; // output hints from the encode process @@ -87,7 +84,6 @@ public: NULL_NODE, NULL_NODE_DATA, TOO_DEEP, - OUT_OF_JURISDICTION, LOD_SKIP, OUT_OF_VIEW, WAS_IN_VIEW, @@ -105,7 +101,6 @@ public: int boundaryLevelAdjust = NO_BOUNDARY_ADJUST, float octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE, bool forceSendScene = true, - JurisdictionMap* jurisdictionMap = IGNORE_JURISDICTION_MAP, NodeData* nodeData = nullptr) : maxEncodeLevel(maxEncodeLevel), maxLevelReached(0), @@ -115,7 +110,6 @@ public: boundaryLevelAdjust(boundaryLevelAdjust), octreeElementSizeScale(octreeElementSizeScale), forceSendScene(forceSendScene), - jurisdictionMap(jurisdictionMap), nodeData(nodeData), stopReason(UNKNOWN) { @@ -131,7 +125,6 @@ public: case DIDNT_FIT: qDebug("DIDNT_FIT"); break; case NULL_NODE: qDebug("NULL_NODE"); break; case TOO_DEEP: qDebug("TOO_DEEP"); break; - case OUT_OF_JURISDICTION: qDebug("OUT_OF_JURISDICTION"); break; case LOD_SKIP: qDebug("LOD_SKIP"); break; case OUT_OF_VIEW: qDebug("OUT_OF_VIEW"); break; case WAS_IN_VIEW: qDebug("WAS_IN_VIEW"); break; @@ -148,7 +141,6 @@ public: case DIDNT_FIT: return QString("DIDNT_FIT"); break; case NULL_NODE: return QString("NULL_NODE"); break; case TOO_DEEP: return QString("TOO_DEEP"); break; - case OUT_OF_JURISDICTION: return QString("OUT_OF_JURISDICTION"); break; case LOD_SKIP: return QString("LOD_SKIP"); break; case OUT_OF_VIEW: return QString("OUT_OF_VIEW"); break; case WAS_IN_VIEW: return QString("WAS_IN_VIEW"); break; diff --git a/libraries/octree/src/OctreeEditPacketSender.cpp b/libraries/octree/src/OctreeEditPacketSender.cpp index 9cb383df41..4f10c9bf79 100644 --- a/libraries/octree/src/OctreeEditPacketSender.cpp +++ b/libraries/octree/src/OctreeEditPacketSender.cpp @@ -22,13 +22,10 @@ const int OctreeEditPacketSender::DEFAULT_MAX_PENDING_MESSAGES = PacketSender::D OctreeEditPacketSender::OctreeEditPacketSender() : - PacketSender(), _shouldSend(true), _maxPendingMessages(DEFAULT_MAX_PENDING_MESSAGES), - _releaseQueuedMessagesPending(false), - _serverJurisdictions(NULL) + _releaseQueuedMessagesPending(false) { - } OctreeEditPacketSender::~OctreeEditPacketSender() { @@ -40,34 +37,8 @@ OctreeEditPacketSender::~OctreeEditPacketSender() { bool OctreeEditPacketSender::serversExist() const { - bool hasServers = false; - bool atLeastOneJurisdictionMissing = false; // assume the best - - DependencyManager::get()->eachNodeBreakable([&](const SharedNodePointer& node){ - if (node->getType() == getMyNodeType() && node->getActiveSocket()) { - - QUuid nodeUUID = node->getUUID(); - // If we've got Jurisdictions set, then check to see if we know the jurisdiction for this server - if (_serverJurisdictions) { - // lookup our nodeUUID in the jurisdiction map, if it's missing then we're - // missing at least one jurisdiction - _serverJurisdictions->withReadLock([&] { - if ((*_serverJurisdictions).find(nodeUUID) == (*_serverJurisdictions).end()) { - atLeastOneJurisdictionMissing = true; - } - }); - } - hasServers = true; - } - - if (atLeastOneJurisdictionMissing) { - return false; // no point in looking further - return false from anonymous function - } else { - return true; - } - }); - - return (hasServers && !atLeastOneJurisdictionMissing); + auto node = DependencyManager::get()->soloNodeOfType(getMyNodeType()); + return node && node->getActiveSocket(); } // This method is called when the edit packet layer has determined that it has a fully formed packet destined for @@ -132,7 +103,7 @@ void OctreeEditPacketSender::queuePacketListToNode(const QUuid& nodeUUID, std::u } void OctreeEditPacketSender::processPreServerExistsPackets() { - assert(serversExist()); // we should only be here if we have jurisdictions + assert(serversExist()); // we should only be here if we have servers // First send out all the single message packets... _pendingPacketsLock.lock(); @@ -150,7 +121,7 @@ void OctreeEditPacketSender::processPreServerExistsPackets() { _pendingPacketsLock.unlock(); - // if while waiting for the jurisdictions the caller called releaseQueuedMessages() + // if while waiting for the servers the caller called releaseQueuedMessages() // then we want to honor that request now. if (_releaseQueuedMessagesPending) { releaseQueuedMessages(); @@ -178,34 +149,12 @@ void OctreeEditPacketSender::queuePacketToNodes(std::unique_ptr packet return; // bail early } - assert(serversExist()); // we must have jurisdictions to be here!! + assert(serversExist()); // we must have servers to be here!! - const unsigned char* octCode = reinterpret_cast(packet->getPayload()) + sizeof(short) + sizeof(quint64); - - // We want to filter out edit messages for servers based on the server's Jurisdiction - // But we can't really do that with a packed message, since each edit message could be destined - // for a different server... So we need to actually manage multiple queued packets... one - // for each server - - DependencyManager::get()->eachNode([&](const SharedNodePointer& node){ - // only send to the NodeTypes that are getMyNodeType() - if (node->getActiveSocket() && node->getType() == getMyNodeType()) { - QUuid nodeUUID = node->getUUID(); - bool isMyJurisdiction = true; - // we need to get the jurisdiction for this - // here we need to get the "pending packet" for this server - _serverJurisdictions->withReadLock([&] { - const JurisdictionMap& map = (*_serverJurisdictions)[nodeUUID]; - isMyJurisdiction = (map.isMyJurisdiction(octCode, CHECK_NODE_ONLY) == JurisdictionMap::WITHIN); - }); - - if (isMyJurisdiction) { - // make a copy of this packet for this node and queue - auto packetCopy = NLPacket::createCopy(*packet); - queuePacketToNode(nodeUUID, std::move(packetCopy)); - } - } - }); + auto node = DependencyManager::get()->soloNodeOfType(getMyNodeType()); + if (node && node->getActiveSocket()) { + queuePacketToNode(node->getUUID(), std::move(packet)); + } } @@ -216,8 +165,8 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, QByteArray& return; // bail early } - // If we don't have jurisdictions, then we will simply queue up all of these packets and wait till we have - // jurisdictions for processing + // If we don't have servers, then we will simply queue up all of these packets and wait till we have + // servers for processing if (!serversExist()) { if (_maxPendingMessages > 0) { EditMessagePair messagePair { type, QByteArray(editMessage) }; @@ -235,104 +184,80 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, QByteArray& return; // bail early } - // We want to filter out edit messages for servers based on the server's Jurisdiction - // But we can't really do that with a packed message, since each edit message could be destined - // for a different server... So we need to actually manage multiple queued packets... one - // for each server _packetsQueueLock.lock(); - DependencyManager::get()->eachNode([&](const SharedNodePointer& node){ - // only send to the NodeTypes that are getMyNodeType() - if (node->getActiveSocket() && node->getType() == getMyNodeType()) { - QUuid nodeUUID = node->getUUID(); - bool isMyJurisdiction = true; + auto node = DependencyManager::get()->soloNodeOfType(getMyNodeType()); + if (node && node->getActiveSocket()) { + QUuid nodeUUID = node->getUUID(); - if (type == PacketType::EntityErase) { - isMyJurisdiction = true; // send erase messages to all servers - } else if (_serverJurisdictions) { - // we need to get the jurisdiction for this - // here we need to get the "pending packet" for this server - _serverJurisdictions->withReadLock([&] { - if ((*_serverJurisdictions).find(nodeUUID) != (*_serverJurisdictions).end()) { - const JurisdictionMap& map = (*_serverJurisdictions)[nodeUUID]; - isMyJurisdiction = (map.isMyJurisdiction(reinterpret_cast(editMessage.data()), - CHECK_NODE_ONLY) == JurisdictionMap::WITHIN); - } else { - isMyJurisdiction = false; - } - }); + // for edit messages, we will attempt to combine multiple edit commands where possible, we + // don't do this for add because we send those reliably + if (type == PacketType::EntityAdd) { + auto newPacket = NLPacketList::create(type, QByteArray(), true, true); + auto nodeClockSkew = node->getClockSkewUsec(); + + // pack sequence number + quint16 sequence = _outgoingSequenceNumbers[nodeUUID]++; + newPacket->writePrimitive(sequence); + + // pack in timestamp + quint64 now = usecTimestampNow() + nodeClockSkew; + newPacket->writePrimitive(now); + + + // We call this virtual function that allows our specific type of EditPacketSender to + // fixup the buffer for any clock skew + if (nodeClockSkew != 0) { + adjustEditPacketForClockSkew(type, editMessage, nodeClockSkew); } - if (isMyJurisdiction) { - // for edit messages, we will attempt to combine multiple edit commands where possible, we - // don't do this for add because we send those reliably - if (type == PacketType::EntityAdd) { + newPacket->write(editMessage); - auto newPacket = NLPacketList::create(type, QByteArray(), true, true); - auto nodeClockSkew = node->getClockSkewUsec(); + // release the new packet + releaseQueuedPacketList(nodeUUID, std::move(newPacket)); - // pack sequence number - quint16 sequence = _outgoingSequenceNumbers[nodeUUID]++; - newPacket->writePrimitive(sequence); + // tell the sent packet history that we used a sequence number for an untracked packet + auto& sentPacketHistory = _sentPacketHistories[nodeUUID]; + sentPacketHistory.untrackedPacketSent(sequence); + } else { + // only a NLPacket for now + std::unique_ptr& bufferedPacket = _pendingEditPackets[nodeUUID].first; - // pack in timestamp - quint64 now = usecTimestampNow() + nodeClockSkew; - newPacket->writePrimitive(now); + if (!bufferedPacket) { + bufferedPacket = initializePacket(type, node->getClockSkewUsec()); + } else { + // If we're switching type, then we send the last one and start over + if ((type != bufferedPacket->getType() && bufferedPacket->getPayloadSize() > 0) || + (editMessage.size() >= bufferedPacket->bytesAvailableForWrite())) { + // create the new packet and swap it with the packet in _pendingEditPackets + auto packetToRelease = initializePacket(type, node->getClockSkewUsec()); + bufferedPacket.swap(packetToRelease); - // We call this virtual function that allows our specific type of EditPacketSender to - // fixup the buffer for any clock skew - if (nodeClockSkew != 0) { - adjustEditPacketForClockSkew(type, editMessage, nodeClockSkew); - } - - newPacket->write(editMessage); - - // release the new packet - releaseQueuedPacketList(nodeUUID, std::move(newPacket)); - - } else { - - std::unique_ptr& bufferedPacket = _pendingEditPackets[nodeUUID].first; //only a NLPacket for now - - if (!bufferedPacket) { - bufferedPacket = initializePacket(type, node->getClockSkewUsec()); - } else { - // If we're switching type, then we send the last one and start over - if ((type != bufferedPacket->getType() && bufferedPacket->getPayloadSize() > 0) || - (editMessage.size() >= bufferedPacket->bytesAvailableForWrite())) { - - // create the new packet and swap it with the packet in _pendingEditPackets - auto packetToRelease = initializePacket(type, node->getClockSkewUsec()); - bufferedPacket.swap(packetToRelease); - - // release the previously buffered packet - releaseQueuedPacket(nodeUUID, std::move(packetToRelease)); - } - } - - // This is really the first time we know which server/node this particular edit message - // is going to, so we couldn't adjust for clock skew till now. But here's our chance. - // We call this virtual function that allows our specific type of EditPacketSender to - // fixup the buffer for any clock skew - if (node->getClockSkewUsec() != 0) { - adjustEditPacketForClockSkew(type, editMessage, node->getClockSkewUsec()); - } - - bufferedPacket->write(editMessage); - + // release the previously buffered packet + releaseQueuedPacket(nodeUUID, std::move(packetToRelease)); } } + + // This is really the first time we know which server/node this particular edit message + // is going to, so we couldn't adjust for clock skew till now. But here's our chance. + // We call this virtual function that allows our specific type of EditPacketSender to + // fixup the buffer for any clock skew + if (node->getClockSkewUsec() != 0) { + adjustEditPacketForClockSkew(type, editMessage, node->getClockSkewUsec()); + } + + bufferedPacket->write(editMessage); } - }); + } _packetsQueueLock.unlock(); } void OctreeEditPacketSender::releaseQueuedMessages() { - // if we don't yet have jurisdictions then we can't actually release messages yet because we don't - // know where to send them to. Instead, just remember this request and when we eventually get jurisdictions + // if we don't yet have servers then we can't actually release messages yet because we don't + // know where to send them to. Instead, just remember this request and when we eventually get servers // call release again at that time. if (!serversExist()) { _releaseQueuedMessagesPending = true; @@ -394,8 +319,8 @@ std::unique_ptr OctreeEditPacketSender::initializePacket(PacketType ty } bool OctreeEditPacketSender::process() { - // if we have server jurisdiction details, and we have pending pre-jurisdiction packets, then process those - // before doing our normal process step. This processPreJurisdictionPackets() + // if we have servers, and we have pending pre-servers exist packets, then process those + // before doing our normal process step. This processPreServerExistPackets() if (serversExist() && (!_preServerEdits.empty() || !_preServerSingleMessagePackets.empty() )) { processPreServerExistsPackets(); } diff --git a/libraries/octree/src/OctreeEditPacketSender.h b/libraries/octree/src/OctreeEditPacketSender.h index 79c363bec5..8a33eb8b73 100644 --- a/libraries/octree/src/OctreeEditPacketSender.h +++ b/libraries/octree/src/OctreeEditPacketSender.h @@ -17,7 +17,6 @@ #include #include -#include "JurisdictionMap.h" #include "SentPacketHistory.h" /// Utility for processing, packing, queueing and sending of outbound edit messages. @@ -49,14 +48,6 @@ public: /// in an application like interface when all octree features are disabled. void setShouldSend(bool shouldSend) { _shouldSend = shouldSend; } - /// call this to inform the OctreeEditPacketSender of the server jurisdictions. This is required for normal operation. - /// The internal contents of the jurisdiction map may change throughout the lifetime of the OctreeEditPacketSender. This map - /// can be set prior to servers being present, so long as the contents of the map accurately reflect the current - /// known jurisdictions. - void setServerJurisdictions(NodeToJurisdictionMap* serverJurisdictions) { - _serverJurisdictions = serverJurisdictions; - } - /// if you're running in non-threaded mode, you must call this method regularly virtual bool process() override; @@ -108,8 +99,6 @@ protected: std::list _preServerEdits; // these will get packed into other larger packets std::list> _preServerSingleMessagePackets; // these will go out as is - NodeToJurisdictionMap* _serverJurisdictions; - QMutex _releaseQueuedPacketMutex; // TODO: add locks for this and _pendingEditPackets diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp index 09b2d6ddf5..054603440d 100644 --- a/libraries/octree/src/OctreeSceneStats.cpp +++ b/libraries/octree/src/OctreeSceneStats.cpp @@ -37,15 +37,13 @@ OctreeSceneStats::OctreeSceneStats() : _incomingBytes(0), _incomingWastedBytes(0), _incomingOctreeSequenceNumberStats(), - _incomingFlightTimeAverage(samples), - _jurisdictionRoot(NULL) + _incomingFlightTimeAverage(samples) { reset(); } // copy constructor -OctreeSceneStats::OctreeSceneStats(const OctreeSceneStats& other) : -_jurisdictionRoot(NULL) { +OctreeSceneStats::OctreeSceneStats(const OctreeSceneStats& other) { copyFromOther(other); } @@ -109,26 +107,6 @@ void OctreeSceneStats::copyFromOther(const OctreeSceneStats& other) { _existsInPacketBitsWritten = other._existsInPacketBitsWritten; _treesRemoved = other._treesRemoved; - // before copying the jurisdictions, delete any current values... - _jurisdictionRoot = nullptr; - _jurisdictionEndNodes.clear(); - - // Now copy the values from the other - if (other._jurisdictionRoot) { - auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(other._jurisdictionRoot.get())); - _jurisdictionRoot = createOctalCodePtr(bytes); - memcpy(_jurisdictionRoot.get(), other._jurisdictionRoot.get(), bytes); - } - for (size_t i = 0; i < other._jurisdictionEndNodes.size(); i++) { - auto& endNodeCode = other._jurisdictionEndNodes[i]; - if (endNodeCode) { - auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode.get())); - auto endNodeCodeCopy = createOctalCodePtr(bytes); - memcpy(endNodeCodeCopy.get(), endNodeCode.get(), bytes); - _jurisdictionEndNodes.push_back(endNodeCodeCopy); - } - } - _incomingPacket = other._incomingPacket; _incomingBytes = other._incomingBytes; _incomingWastedBytes = other._incomingWastedBytes; @@ -141,8 +119,7 @@ OctreeSceneStats::~OctreeSceneStats() { reset(); } -void OctreeSceneStats::sceneStarted(bool isFullScene, bool isMoving, const OctreeElementPointer& root, - JurisdictionMap* jurisdictionMap) { +void OctreeSceneStats::sceneStarted(bool isFullScene, bool isMoving, const OctreeElementPointer& root) { reset(); // resets packet and octree stats _isStarted = true; _start = usecTimestampNow(); @@ -153,14 +130,6 @@ void OctreeSceneStats::sceneStarted(bool isFullScene, bool isMoving, const Octre _isFullScene = isFullScene; _isMoving = isMoving; - - // setup jurisdictions - if (jurisdictionMap) { - std::tie(_jurisdictionRoot, _jurisdictionEndNodes) = jurisdictionMap->getRootAndEndNodeOctalCodes(); - } else { - _jurisdictionRoot = nullptr; - _jurisdictionEndNodes.clear(); - } } void OctreeSceneStats::sceneCompleted() { @@ -236,9 +205,6 @@ void OctreeSceneStats::reset() { _existsBitsWritten = 0; _existsInPacketBitsWritten = 0; _treesRemoved = 0; - - _jurisdictionRoot = nullptr; - _jurisdictionEndNodes.clear(); } void OctreeSceneStats::packetSent(int bytes) { @@ -374,29 +340,6 @@ int OctreeSceneStats::packIntoPacket() { _statsPacket->writePrimitive(_existsInPacketBitsWritten); _statsPacket->writePrimitive(_treesRemoved); - // add the root jurisdiction - if (_jurisdictionRoot) { - // copy the - int bytes = (int)bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(_jurisdictionRoot.get())); - _statsPacket->writePrimitive(bytes); - _statsPacket->write(reinterpret_cast(_jurisdictionRoot.get()), bytes); - - // if and only if there's a root jurisdiction, also include the end elements - int endNodeCount = (int)_jurisdictionEndNodes.size(); - - _statsPacket->writePrimitive(endNodeCount); - - for (int i=0; i < endNodeCount; i++) { - auto& endNodeCode = _jurisdictionEndNodes[i]; - auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode.get())); - _statsPacket->writePrimitive(bytes); - _statsPacket->write(reinterpret_cast(endNodeCode.get()), bytes); - } - } else { - int bytes = 0; - _statsPacket->writePrimitive(bytes); - } - return _statsPacket->getPayloadSize(); } @@ -458,38 +401,6 @@ int OctreeSceneStats::unpackFromPacket(ReceivedMessage& packet) { packet.readPrimitive(&_existsBitsWritten); packet.readPrimitive(&_existsInPacketBitsWritten); packet.readPrimitive(&_treesRemoved); - // before allocating new juridiction, clean up existing ones - _jurisdictionRoot = nullptr; - _jurisdictionEndNodes.clear(); - - // read the root jurisdiction - int bytes = 0; - packet.readPrimitive(&bytes); - - if (bytes == 0) { - _jurisdictionRoot = nullptr; - _jurisdictionEndNodes.clear(); - } else { - _jurisdictionRoot = createOctalCodePtr(bytes); - packet.read(reinterpret_cast(_jurisdictionRoot.get()), bytes); - - // if and only if there's a root jurisdiction, also include the end elements - _jurisdictionEndNodes.clear(); - - int endNodeCount = 0; - packet.readPrimitive(&endNodeCount); - - for (int i=0; i < endNodeCount; i++) { - int bytes = 0; - - packet.readPrimitive(&bytes); - - auto endNodeCode = createOctalCodePtr(bytes); - packet.read(reinterpret_cast(endNodeCode.get()), bytes); - - _jurisdictionEndNodes.push_back(endNodeCode); - } - } // running averages _elapsedAverage.updateAverage((float)_elapsed); diff --git a/libraries/octree/src/OctreeSceneStats.h b/libraries/octree/src/OctreeSceneStats.h index 3774d4287d..78b4dfd26f 100644 --- a/libraries/octree/src/OctreeSceneStats.h +++ b/libraries/octree/src/OctreeSceneStats.h @@ -17,7 +17,6 @@ #include #include -#include "JurisdictionMap.h" #include "OctreePacketData.h" #include "SequenceNumberStats.h" #include "OctalCode.h" @@ -39,7 +38,7 @@ public: OctreeSceneStats& operator= (const OctreeSceneStats& other); // copy assignment /// Call when beginning the computation of a scene. Initializes internal structures - void sceneStarted(bool fullScene, bool moving, const OctreeElementPointer& root, JurisdictionMap* jurisdictionMap); + void sceneStarted(bool fullScene, bool moving, const OctreeElementPointer& root); bool getIsSceneStarted() const { return _isStarted; } /// Call when the computation of a scene is completed. Finalizes internal structures @@ -143,12 +142,6 @@ public: /// \param Item item The item from the stats you're interested in. const char* getItemValue(Item item); - /// Returns OctCode for root element of the jurisdiction of this particular octree server - OctalCodePtr getJurisdictionRoot() const { return _jurisdictionRoot; } - - /// Returns list of OctCodes for end elements of the jurisdiction of this particular octree server - const OctalCodePtrList& getJurisdictionEndNodes() const { return _jurisdictionEndNodes; } - bool isMoving() const { return _isMoving; } bool isFullScene() const { return _isFullScene; } quint64 getTotalElements() const { return _totalElements; } @@ -277,9 +270,6 @@ private: static ItemInfo _ITEMS[]; static const int MAX_ITEM_VALUE_LENGTH = 128; char _itemValueBuffer[MAX_ITEM_VALUE_LENGTH]; - - OctalCodePtr _jurisdictionRoot; - std::vector _jurisdictionEndNodes; }; /// Map between element IDs and their reported OctreeSceneStats. Typically used by classes that need to know which elements sent diff --git a/libraries/octree/src/OctreeScriptingInterface.cpp b/libraries/octree/src/OctreeScriptingInterface.cpp index 8913e88cf5..618e8ac469 100644 --- a/libraries/octree/src/OctreeScriptingInterface.cpp +++ b/libraries/octree/src/OctreeScriptingInterface.cpp @@ -13,12 +13,9 @@ #include "OctreeScriptingInterface.h" -OctreeScriptingInterface::OctreeScriptingInterface(OctreeEditPacketSender* packetSender, - JurisdictionListener* jurisdictionListener) : +OctreeScriptingInterface::OctreeScriptingInterface(OctreeEditPacketSender* packetSender) : _packetSender(packetSender), - _jurisdictionListener(jurisdictionListener), - _managedPacketSender(false), - _managedJurisdictionListener(false), + _managedPacketSender(false), _initialized(false) { } @@ -28,12 +25,6 @@ OctreeScriptingInterface::~OctreeScriptingInterface() { } void OctreeScriptingInterface::cleanupManagedObjects() { - if (_managedJurisdictionListener) { - _jurisdictionListener->terminate(); - _jurisdictionListener->deleteLater(); - _managedJurisdictionListener = false; - _jurisdictionListener = NULL; - } if (_managedPacketSender) { _packetSender->terminate(); _packetSender->deleteLater(); @@ -46,29 +37,16 @@ void OctreeScriptingInterface::setPacketSender(OctreeEditPacketSender* packetSen _packetSender = packetSender; } -void OctreeScriptingInterface::setJurisdictionListener(JurisdictionListener* jurisdictionListener) { - _jurisdictionListener = jurisdictionListener; -} - void OctreeScriptingInterface::init() { if (_initialized) { return; } - if (_jurisdictionListener) { - _managedJurisdictionListener = false; - } else { - _managedJurisdictionListener = true; - _jurisdictionListener = new JurisdictionListener(getServerNodeType()); - _jurisdictionListener->initialize(true); - } - if (_packetSender) { _managedPacketSender = false; } else { _managedPacketSender = true; _packetSender = createPacketSender(); - _packetSender->setServerJurisdictions(_jurisdictionListener->getJurisdictions()); } if (QCoreApplication::instance()) { diff --git a/libraries/octree/src/OctreeScriptingInterface.h b/libraries/octree/src/OctreeScriptingInterface.h index 86b9730393..c31da94532 100644 --- a/libraries/octree/src/OctreeScriptingInterface.h +++ b/libraries/octree/src/OctreeScriptingInterface.h @@ -14,21 +14,18 @@ #include -#include "JurisdictionListener.h" #include "OctreeEditPacketSender.h" /// handles scripting of Particle commands from JS passed to assigned clients class OctreeScriptingInterface : public QObject { Q_OBJECT public: - OctreeScriptingInterface(OctreeEditPacketSender* packetSender = NULL, JurisdictionListener* jurisdictionListener = NULL); + OctreeScriptingInterface(OctreeEditPacketSender* packetSender = nullptr); ~OctreeScriptingInterface(); OctreeEditPacketSender* getPacketSender() const { return _packetSender; } - JurisdictionListener* getJurisdictionListener() const { return _jurisdictionListener; } void setPacketSender(OctreeEditPacketSender* packetSender); - void setJurisdictionListener(JurisdictionListener* jurisdictionListener); void init(); virtual NodeType_t getServerNodeType() const = 0; @@ -86,9 +83,7 @@ public slots: protected: /// attached OctreeEditPacketSender that handles queuing and sending of packets to VS OctreeEditPacketSender* _packetSender = nullptr; - JurisdictionListener* _jurisdictionListener = nullptr; bool _managedPacketSender; - bool _managedJurisdictionListener; bool _initialized; }; diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 32e764bd10..d39930ab76 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -391,6 +391,10 @@ void CharacterController::setState(State desiredState) { } } +void CharacterController::recomputeFlying() { + _pendingFlags |= PENDING_FLAG_RECOMPUTE_FLYING; +} + void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const glm::vec3& scale) { float x = scale.x; float z = scale.z; @@ -657,6 +661,13 @@ void CharacterController::updateState() { if (!_dynamicsWorld) { return; } + if (_pendingFlags & PENDING_FLAG_RECOMPUTE_FLYING) { + SET_STATE(CharacterController::State::Hover, "recomputeFlying"); + _hasSupport = false; + _stepHeight = _minStepHeight; // clears memory of last step obstacle + _pendingFlags &= ~PENDING_FLAG_RECOMPUTE_FLYING; + } + const btScalar FLY_TO_GROUND_THRESHOLD = 0.1f * _radius; const btScalar GROUND_TO_FLY_THRESHOLD = 0.8f * _radius + _halfHeight; const quint64 TAKE_OFF_TO_IN_AIR_PERIOD = 250 * MSECS_PER_SECOND; diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index 0f97cc7c16..96e479dcad 100644 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -31,6 +31,7 @@ const uint32_t PENDING_FLAG_REMOVE_FROM_SIMULATION = 1U << 1; const uint32_t PENDING_FLAG_UPDATE_SHAPE = 1U << 2; const uint32_t PENDING_FLAG_JUMP = 1U << 3; const uint32_t PENDING_FLAG_UPDATE_COLLISION_GROUP = 1U << 4; +const uint32_t PENDING_FLAG_RECOMPUTE_FLYING = 1U << 5; const float DEFAULT_MIN_FLOOR_NORMAL_DOT_UP = cosf(PI / 3.0f); class btRigidBody; @@ -54,6 +55,7 @@ public: void setGravity(float gravity); float getGravity(); + void recomputeFlying(); virtual void updateShapeIfNecessary() = 0; diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 7e8b431ceb..33ac887f4f 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -14,8 +14,9 @@ #include #include #include -#include #include +#include +#include #include "BulletUtil.h" #include "EntityMotionState.h" @@ -154,7 +155,7 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) { // (1) we own it but may need to change the priority OR... // (2) we don't own it but should bid (because a local script has been changing physics properties) uint8_t newPriority = isLocallyOwned() ? _entity->getSimulationOwner().getPriority() : _entity->getSimulationOwner().getPendingPriority(); - _outgoingPriority = glm::max(_outgoingPriority, newPriority); + upgradeOutgoingPriority(newPriority); // reset bid expiry so that we bid ASAP _nextOwnershipBid = 0; @@ -325,6 +326,7 @@ bool EntityMotionState::isCandidateForOwnership() const { } bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { + DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync"); // NOTE: we only get here if we think we own the simulation assert(_body); @@ -401,7 +403,8 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { } if (_entity->dynamicDataNeedsTransmit()) { - _outgoingPriority = _entity->hasActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : SCRIPT_POKE_SIMULATION_PRIORITY; + uint8_t priority = _entity->hasActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : SCRIPT_POKE_SIMULATION_PRIORITY; + upgradeOutgoingPriority(priority); return true; } @@ -476,6 +479,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { } bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { + DETAILED_PROFILE_RANGE(simulation_physics, "ShouldSend"); // NOTE: we expect _entity and _body to be valid in this context, since shouldSendUpdate() is only called // after doesNotNeedToSendUpdate() returns false and that call should return 'true' if _entity or _body are NULL. assert(_entity); @@ -499,23 +503,28 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { // we don't own the simulation // NOTE: we do not volunteer to own kinematic or static objects - uint8_t insufficientPriority = _body->isStaticOrKinematicObject() ? VOLUNTEER_SIMULATION_PRIORITY : 0; + uint8_t volunteerPriority = _body->isStaticOrKinematicObject() ? VOLUNTEER_SIMULATION_PRIORITY : 0; - bool shouldBid = _outgoingPriority > insufficientPriority && // but we would like to own it AND + bool shouldBid = _outgoingPriority > volunteerPriority && // but we would like to own it AND usecTimestampNow() > _nextOwnershipBid; // it is time to bid again if (shouldBid && _outgoingPriority < _entity->getSimulationPriority()) { - // we are insufficiently interested so clear our interest + // we are insufficiently interested so clear _outgoingPriority // and reset the bid expiry _outgoingPriority = 0; _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; } return shouldBid; + } else { + // When we own the simulation: make sure _outgoingPriority is not less than current owned priority + // because: an _outgoingPriority of zero indicates that we should drop ownership when we have it. + upgradeOutgoingPriority(_entity->getSimulationPriority()); } return remoteSimulationOutOfSync(simulationStep); } void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { + DETAILED_PROFILE_RANGE(simulation_physics, "Send"); assert(_entity); assert(entityTreeIsLocked()); @@ -614,8 +623,10 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ _entity->setPendingOwnershipPriority(_outgoingPriority, now); // don't forget to remember that we have made a bid _entity->rememberHasSimulationOwnershipBid(); - // ...then reset _outgoingPriority in preparation for the next frame + // ...then reset _outgoingPriority _outgoingPriority = 0; + // _outgoingPrioriuty will be re-computed before next bid, + // or will be set to agree with ownership priority should we win the bid } else if (_outgoingPriority != _entity->getSimulationPriority()) { // we own the simulation but our desired priority has changed if (_outgoingPriority == 0) { @@ -731,6 +742,7 @@ void EntityMotionState::resetMeasuredBodyAcceleration() { } void EntityMotionState::measureBodyAcceleration() { + DETAILED_PROFILE_RANGE(simulation_physics, "MeasureAccel"); // try to manually measure the true acceleration of the object uint32_t thisStep = ObjectMotionState::getWorldSimulationStep(); uint32_t numSubsteps = thisStep - _lastMeasureStep; diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 3e87b9437d..e4ba47e205 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -10,12 +10,14 @@ // +#include "PhysicalEntitySimulation.h" + +#include #include "PhysicsHelpers.h" #include "PhysicsLogging.h" #include "ShapeManager.h" -#include "PhysicalEntitySimulation.h" PhysicalEntitySimulation::PhysicalEntitySimulation() { } @@ -274,20 +276,24 @@ void PhysicalEntitySimulation::handleDeactivatedMotionStates(const VectorOfMotio } void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionStates& motionStates) { + PROFILE_RANGE_EX(simulation_physics, "ChangedEntities", 0x00000000, (uint64_t)motionStates.size()); QMutexLocker lock(&_mutex); // walk the motionStates looking for those that correspond to entities - for (auto stateItr : motionStates) { - ObjectMotionState* state = &(*stateItr); - assert(state); - if (state->getType() == MOTIONSTATE_TYPE_ENTITY) { - EntityMotionState* entityState = static_cast(state); - EntityItemPointer entity = entityState->getEntity(); - assert(entity.get()); - if (entityState->isCandidateForOwnership()) { - _outgoingChanges.insert(entityState); + { + PROFILE_RANGE_EX(simulation_physics, "Filter", 0x00000000, (uint64_t)motionStates.size()); + for (auto stateItr : motionStates) { + ObjectMotionState* state = &(*stateItr); + assert(state); + if (state->getType() == MOTIONSTATE_TYPE_ENTITY) { + EntityMotionState* entityState = static_cast(state); + EntityItemPointer entity = entityState->getEntity(); + assert(entity.get()); + if (entityState->isCandidateForOwnership()) { + _outgoingChanges.insert(entityState); + } + _entitiesToSort.insert(entity); } - _entitiesToSort.insert(entity); } } @@ -302,6 +308,7 @@ void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionSta } // look for entities to prune or update + PROFILE_RANGE_EX(simulation_physics, "Prune/Send", 0x00000000, (uint64_t)_outgoingChanges.size()); QSet::iterator stateItr = _outgoingChanges.begin(); while (stateItr != _outgoingChanges.end()) { EntityMotionState* state = *stateItr; diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index a64796308e..2d84f0cef1 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -11,7 +11,12 @@ #include +#include + +#include + #include +#include #include "CharacterController.h" #include "ObjectMotionState.h" @@ -290,6 +295,7 @@ void PhysicsEngine::stepSimulation() { float timeStep = btMin(dt, MAX_TIMESTEP); if (_myAvatarController) { + DETAILED_PROFILE_RANGE(simulation_physics, "avatarController"); BT_PROFILE("avatarController"); // TODO: move this stuff outside and in front of stepSimulation, because // the updateShapeIfNecessary() call needs info from MyAvatar and should @@ -311,6 +317,7 @@ void PhysicsEngine::stepSimulation() { auto onSubStep = [this]() { this->updateContactMap(); + this->doOwnershipInfectionForConstraints(); }; int numSubsteps = _dynamicsWorld->stepSimulationWithSubstepCallback(timeStep, PHYSICS_ENGINE_MAX_NUM_SUBSTEPS, @@ -328,45 +335,107 @@ void PhysicsEngine::stepSimulation() { } } +class CProfileOperator { +public: + CProfileOperator() {} + void recurse(CProfileIterator* itr, QString context) { + // The context string will get too long if we accumulate it properly + //QString newContext = context + QString("/") + itr->Get_Current_Parent_Name(); + // so we use this four-character indentation + QString newContext = context + QString(".../"); + process(itr, newContext); + + // count the children + int32_t numChildren = 0; + itr->First(); + while (!itr->Is_Done()) { + itr->Next(); + ++numChildren; + } + + // recurse the children + if (numChildren > 0) { + // recurse the children + for (int32_t i = 0; i < numChildren; ++i) { + itr->Enter_Child(i); + recurse(itr, newContext); + } + } + // retreat back to parent + itr->Enter_Parent(); + } + virtual void process(CProfileIterator*, QString context) = 0; +}; + +class StatsHarvester : public CProfileOperator { +public: + StatsHarvester() {} + void process(CProfileIterator* itr, QString context) override { + QString name = context + itr->Get_Current_Parent_Name(); + uint64_t time = (uint64_t)((btScalar)MSECS_PER_SECOND * itr->Get_Current_Parent_Total_Time()); + PerformanceTimer::addTimerRecord(name, time); + }; +}; + +class StatsWriter : public CProfileOperator { +public: + StatsWriter(QString filename) : _file(filename) { + _file.open(QFile::WriteOnly); + if (_file.error() != QFileDevice::NoError) { + qCDebug(physics) << "unable to open file " << _file.fileName() << " to save stepSimulation() stats"; + } + } + ~StatsWriter() { + _file.close(); + } + void process(CProfileIterator* itr, QString context) override { + QString name = context + itr->Get_Current_Parent_Name(); + float time = (btScalar)MSECS_PER_SECOND * itr->Get_Current_Parent_Total_Time(); + if (_file.error() == QFileDevice::NoError) { + QTextStream s(&_file); + s << name << " = " << time << "\n"; + } + } +protected: + QFile _file; +}; + void PhysicsEngine::harvestPerformanceStats() { // unfortunately the full context names get too long for our stats presentation format //QString contextName = PerformanceTimer::getContextName(); // TODO: how to show full context name? QString contextName("..."); - CProfileIterator* profileIterator = CProfileManager::Get_Iterator(); - if (profileIterator) { + CProfileIterator* itr = CProfileManager::Get_Iterator(); + if (itr) { // hunt for stepSimulation context - profileIterator->First(); - for (int32_t childIndex = 0; !profileIterator->Is_Done(); ++childIndex) { - if (QString(profileIterator->Get_Current_Name()) == "stepSimulation") { - profileIterator->Enter_Child(childIndex); - recursivelyHarvestPerformanceStats(profileIterator, contextName); + itr->First(); + for (int32_t childIndex = 0; !itr->Is_Done(); ++childIndex) { + if (QString(itr->Get_Current_Name()) == "stepSimulation") { + itr->Enter_Child(childIndex); + StatsHarvester harvester; + harvester.recurse(itr, "step/"); break; } - profileIterator->Next(); + itr->Next(); } } } -void PhysicsEngine::recursivelyHarvestPerformanceStats(CProfileIterator* profileIterator, QString contextName) { - QString parentContextName = contextName + QString("/") + QString(profileIterator->Get_Current_Parent_Name()); - // get the stats for the children - int32_t numChildren = 0; - profileIterator->First(); - while (!profileIterator->Is_Done()) { - QString childContextName = parentContextName + QString("/") + QString(profileIterator->Get_Current_Name()); - uint64_t time = (uint64_t)((btScalar)MSECS_PER_SECOND * profileIterator->Get_Current_Total_Time()); - PerformanceTimer::addTimerRecord(childContextName, time); - profileIterator->Next(); - ++numChildren; +void PhysicsEngine::printPerformanceStatsToFile(const QString& filename) { + CProfileIterator* itr = CProfileManager::Get_Iterator(); + if (itr) { + // hunt for stepSimulation context + itr->First(); + for (int32_t childIndex = 0; !itr->Is_Done(); ++childIndex) { + if (QString(itr->Get_Current_Name()) == "stepSimulation") { + itr->Enter_Child(childIndex); + StatsWriter writer(filename); + writer.recurse(itr, ""); + break; + } + itr->Next(); + } } - // recurse the children - for (int32_t i = 0; i < numChildren; ++i) { - profileIterator->Enter_Child(i); - recursivelyHarvestPerformanceStats(profileIterator, contextName); - } - // retreat back to parent - profileIterator->Enter_Parent(); } void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const btCollisionObject* objectB) { @@ -383,7 +452,7 @@ void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const // NOTE: we might own the simulation of a kinematic object (A) // but we don't claim ownership of kinematic objects (B) based on collisions here. if (!objectB->isStaticOrKinematicObject() && motionStateB->getSimulatorID() != Physics::getSessionUUID()) { - quint8 priorityA = motionStateA ? motionStateA->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY; + uint8_t priorityA = motionStateA ? motionStateA->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY; motionStateB->bump(priorityA); } } else if (motionStateA && @@ -392,13 +461,14 @@ void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const // SIMILARLY: we might own the simulation of a kinematic object (B) // but we don't claim ownership of kinematic objects (A) based on collisions here. if (!objectA->isStaticOrKinematicObject() && motionStateA->getSimulatorID() != Physics::getSessionUUID()) { - quint8 priorityB = motionStateB ? motionStateB->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY; + uint8_t priorityB = motionStateB ? motionStateB->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY; motionStateA->bump(priorityB); } } } void PhysicsEngine::updateContactMap() { + DETAILED_PROFILE_RANGE(simulation_physics, "updateContactMap"); BT_PROFILE("updateContactMap"); ++_numContactFrames; @@ -432,6 +502,54 @@ void PhysicsEngine::updateContactMap() { } } +void PhysicsEngine::doOwnershipInfectionForConstraints() { + BT_PROFILE("ownershipInfectionForConstraints"); + const btCollisionObject* characterObject = _myAvatarController ? _myAvatarController->getCollisionObject() : nullptr; + foreach(const auto& dynamic, _objectDynamics) { + if (!dynamic) { + continue; + } + QList bodies = std::static_pointer_cast(dynamic)->getRigidBodies(); + if (bodies.size() > 1) { + int32_t numOwned = 0; + int32_t numStatic = 0; + uint8_t priority = VOLUNTEER_SIMULATION_PRIORITY; + foreach(btRigidBody* body, bodies) { + ObjectMotionState* motionState = static_cast(body->getUserPointer()); + if (body->isStaticObject()) { + ++numStatic; + } else if (motionState->getType() == MOTIONSTATE_TYPE_AVATAR) { + // we can never take ownership of this constraint + numOwned = 0; + break; + } else { + if (motionState && motionState->getSimulatorID() == Physics::getSessionUUID()) { + priority = glm::max(priority, motionState->getSimulationPriority()); + } else if (body == characterObject) { + priority = glm::max(priority, PERSONAL_SIMULATION_PRIORITY); + } + numOwned++; + } + } + + if (numOwned > 0) { + if (numOwned + numStatic != bodies.size()) { + // we have partial ownership but it isn't complete so we walk each object + // and bump the simulation priority to the highest priority we encountered earlier + foreach(btRigidBody* body, bodies) { + ObjectMotionState* motionState = static_cast(body->getUserPointer()); + if (motionState) { + // NOTE: we submit priority+1 because the default behavior of bump() is to actually use priority - 1 + // and we want all priorities of the objects to be at the SAME level + motionState->bump(priority + 1); + } + } + } + } + } + } +} + const CollisionEvents& PhysicsEngine::getCollisionEvents() { _collisionEvents.clear(); @@ -515,10 +633,21 @@ const VectorOfMotionStates& PhysicsEngine::getChangedMotionStates() { void PhysicsEngine::dumpStatsIfNecessary() { if (_dumpNextStats) { _dumpNextStats = false; + CProfileManager::Increment_Frame_Counter(); + if (_saveNextStats) { + _saveNextStats = false; + printPerformanceStatsToFile(_statsFilename); + } CProfileManager::dumpAll(); } } +void PhysicsEngine::saveNextPhysicsStats(QString filename) { + _saveNextStats = true; + _dumpNextStats = true; + _statsFilename = filename; +} + // Bullet collision flags are as follows: // CF_STATIC_OBJECT= 1, // CF_KINEMATIC_OBJECT= 2, diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 3063a4a89a..92d2e6885a 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -62,7 +62,9 @@ public: void stepSimulation(); void harvestPerformanceStats(); + void printPerformanceStatsToFile(const QString& filename); void updateContactMap(); + void doOwnershipInfectionForConstraints(); bool hasOutgoingChanges() const { return _hasOutgoingChanges; } @@ -76,6 +78,9 @@ public: /// \brief prints timings for last frame if stats have been requested. void dumpStatsIfNecessary(); + /// \brief saves timings for last frame in filename + void saveNextPhysicsStats(QString filename); + /// \param offset position of simulation origin in domain-frame void setOriginOffset(const glm::vec3& offset) { _originOffset = offset; } @@ -94,7 +99,6 @@ public: private: QList removeDynamicsForBody(btRigidBody* body); void addObjectToDynamicsWorld(ObjectMotionState* motionState); - void recursivelyHarvestPerformanceStats(CProfileIterator* profileIterator, QString contextName); /// \brief bump any objects that touch this one, then remove contact info void bumpAndPruneContacts(ObjectMotionState* motionState); @@ -116,6 +120,7 @@ private: QHash _objectDynamics; QHash> _objectDynamicsByBody; std::set _activeStaticBodies; + QString _statsFilename; glm::vec3 _originOffset; @@ -124,8 +129,9 @@ private: uint32_t _numContactFrames = 0; uint32_t _numSubsteps; - bool _dumpNextStats = false; - bool _hasOutgoingChanges = false; + bool _dumpNextStats { false }; + bool _saveNextStats { false }; + bool _hasOutgoingChanges { false }; }; diff --git a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp index 24cfbc2609..5b8c0d5843 100644 --- a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp +++ b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp @@ -18,6 +18,7 @@ #include #include "ThreadSafeDynamicsWorld.h" +#include "Profile.h" ThreadSafeDynamicsWorld::ThreadSafeDynamicsWorld( btDispatcher* dispatcher, @@ -29,6 +30,7 @@ ThreadSafeDynamicsWorld::ThreadSafeDynamicsWorld( int ThreadSafeDynamicsWorld::stepSimulationWithSubstepCallback(btScalar timeStep, int maxSubSteps, btScalar fixedTimeStep, SubStepCallback onSubStep) { + DETAILED_PROFILE_RANGE(simulation_physics, "stepWithCB"); BT_PROFILE("stepSimulationWithSubstepCallback"); int subSteps = 0; if (maxSubSteps) { @@ -68,11 +70,13 @@ int ThreadSafeDynamicsWorld::stepSimulationWithSubstepCallback(btScalar timeStep saveKinematicState(fixedTimeStep*clampedSimulationSteps); { + DETAILED_PROFILE_RANGE(simulation_physics, "applyGravity"); BT_PROFILE("applyGravity"); applyGravity(); } for (int i=0;i #include +#include #include #include +#include #include #include "Plugin.h" @@ -203,6 +205,7 @@ public: virtual void cycleDebugOutput() {} void waitForPresent(); + float getAveragePresentTime() { return _movingAveragePresent.average / (float)USECS_PER_MSEC; } // in msec std::function getHUDOperator(); @@ -220,6 +223,8 @@ protected: std::function _hudOperator { std::function() }; + MovingAverage _movingAveragePresent; + private: QMutex _presentMutex; QWaitCondition _presentCondition; diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp index fcebb2a23f..5307e17355 100644 --- a/libraries/pointers/src/Pointer.cpp +++ b/libraries/pointers/src/Pointer.cpp @@ -11,6 +11,12 @@ #include "PickManager.h" #include "PointerManager.h" +#include "NumericalConstants.h" + +const float Pointer::POINTER_MOVE_DELAY = 0.33f * USECS_PER_SECOND; +const float TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0481f; +const float Pointer::TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED = TOUCH_PRESS_TO_MOVE_DEADSPOT * TOUCH_PRESS_TO_MOVE_DEADSPOT; + Pointer::~Pointer() { DependencyManager::get()->removePick(_pickUID); } @@ -77,7 +83,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin Buttons newButtons; Buttons sameButtons; if (_enabled && shouldTrigger(pickResult)) { - buttons = getPressedButtons(); + buttons = getPressedButtons(pickResult); for (const std::string& button : buttons) { if (_prevButtons.find(button) == _prevButtons.end()) { newButtons.insert(button); @@ -175,17 +181,6 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin } } - // send hoverEnd events if we disable the pointer or disable hovering - if (_hover && ((!_enabled && _prevEnabled) || (!doHover && _prevDoHover))) { - if (_prevHoveredObject.type == ENTITY) { - emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent); - } else if (_prevHoveredObject.type == OVERLAY) { - emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, hoveredEvent); - } else if (_prevHoveredObject.type == HUD) { - emit pointerManager->hoverEndHUD(hoveredEvent); - } - } - // Trigger begin const std::string SHOULD_FOCUS_BUTTON = "Focus"; for (const std::string& button : newButtons) { @@ -204,7 +199,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin // Trigger continue for (const std::string& button : sameButtons) { - PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult, false); + PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult, button, false); triggeredEvent.setID(pointerID); triggeredEvent.setType(PointerEvent::Move); triggeredEvent.setButton(chooseButton(button)); @@ -219,7 +214,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin // Trigger end for (const std::string& button : _prevButtons) { - PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult, false); + PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult, button, false); triggeredEvent.setID(pointerID); triggeredEvent.setType(PointerEvent::Release); triggeredEvent.setButton(chooseButton(button)); @@ -233,6 +228,17 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin _triggeredObjects.erase(button); } + // if we disable the pointer or disable hovering, send hoverEnd events after triggerEnd + if (_hover && ((!_enabled && _prevEnabled) || (!doHover && _prevDoHover))) { + if (_prevHoveredObject.type == ENTITY) { + emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent); + } else if (_prevHoveredObject.type == OVERLAY) { + emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, hoveredEvent); + } else if (_prevHoveredObject.type == HUD) { + emit pointerManager->hoverEndHUD(hoveredEvent); + } + } + _prevHoveredObject = hoveredObject; _prevButtons = buttons; _prevEnabled = _enabled; diff --git a/libraries/pointers/src/Pointer.h b/libraries/pointers/src/Pointer.h index 5ee55e7aeb..3197c80cad 100644 --- a/libraries/pointers/src/Pointer.h +++ b/libraries/pointers/src/Pointer.h @@ -82,14 +82,17 @@ protected: bool _enabled; bool _hover; - virtual PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover = true) const = 0; + virtual PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) = 0; virtual PickedObject getHoveredObject(const PickResultPointer& pickResult) = 0; - virtual Buttons getPressedButtons() = 0; + virtual Buttons getPressedButtons(const PickResultPointer& pickResult) = 0; virtual bool shouldHover(const PickResultPointer& pickResult) { return true; } virtual bool shouldTrigger(const PickResultPointer& pickResult) { return true; } + static const float POINTER_MOVE_DELAY; + static const float TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED; + private: PickedObject _prevHoveredObject; Buttons _prevButtons; diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp index 3bb2aa2ef9..8a5b0d6bc3 100644 --- a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp +++ b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp @@ -39,18 +39,7 @@ void CauterizedMeshPartPayload::updateTransformForCauterizedMesh(const Transform } void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const { - // Still relying on the raw data from the model - bool useCauterizedMesh = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE && renderMode != RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE); - if (useCauterizedMesh) { - ModelPointer model = _model.lock(); - if (model) { - CauterizedModel* skeleton = static_cast(model.get()); - useCauterizedMesh = useCauterizedMesh && skeleton->getEnableCauterization(); - } else { - useCauterizedMesh = false; - } - } - + bool useCauterizedMesh = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE && renderMode != RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE) && _enableCauterization; if (useCauterizedMesh) { if (_cauterizedClusterBuffer) { batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, _cauterizedClusterBuffer); diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.h b/libraries/render-utils/src/CauterizedMeshPartPayload.h index 1c98f5abf3..44eddc6e31 100644 --- a/libraries/render-utils/src/CauterizedMeshPartPayload.h +++ b/libraries/render-utils/src/CauterizedMeshPartPayload.h @@ -21,9 +21,12 @@ public: void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const override; + void setEnableCauterization(bool enableCauterization) { _enableCauterization = enableCauterization; } + private: gpu::BufferPointer _cauterizedClusterBuffer; Transform _cauterizedTransform; + bool _enableCauterization { false }; }; #endif // hifi_CauterizedMeshPartPayload_h diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index c437a8c556..dbb82ab638 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -178,6 +178,12 @@ void CauterizedModel::updateRenderItems() { modelTransform.setTranslation(self->getTranslation()); modelTransform.setRotation(self->getRotation()); + bool isWireframe = self->isWireframe(); + bool isVisible = self->isVisible(); + bool isLayeredInFront = self->isLayeredInFront(); + bool isLayeredInHUD = self->isLayeredInHUD(); + bool enableCauterization = self->getEnableCauterization(); + render::Transaction transaction; for (int i = 0; i < (int)self->_modelMeshRenderItemIDs.size(); i++) { @@ -186,7 +192,10 @@ void CauterizedModel::updateRenderItems() { auto clusterMatrices(self->getMeshState(meshIndex).clusterMatrices); auto clusterMatricesCauterized(self->getCauterizeMeshState(meshIndex).clusterMatrices); - transaction.updateItem(itemID, [modelTransform, clusterMatrices, clusterMatricesCauterized](CauterizedMeshPartPayload& data) { + bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex); + + transaction.updateItem(itemID, [modelTransform, clusterMatrices, clusterMatricesCauterized, invalidatePayloadShapeKey, + isWireframe, isVisible, isLayeredInFront, isLayeredInHUD, enableCauterization](CauterizedMeshPartPayload& data) { data.updateClusterBuffer(clusterMatrices, clusterMatricesCauterized); Transform renderTransform = modelTransform; @@ -200,6 +209,11 @@ void CauterizedModel::updateRenderItems() { renderTransform = modelTransform.worldTransform(Transform(clusterMatricesCauterized[0])); } data.updateTransformForCauterizedMesh(renderTransform); + + data.setEnableCauterization(enableCauterization); + data.setKey(isVisible, isLayeredInFront || isLayeredInHUD); + data.setLayer(isLayeredInFront, isLayeredInHUD); + data.setShapeKey(invalidatePayloadShapeKey, isWireframe); }); } diff --git a/libraries/render-utils/src/DeferredGlobalLight.slh b/libraries/render-utils/src/DeferredGlobalLight.slh index f70daf1e77..de2d41be6b 100644 --- a/libraries/render-utils/src/DeferredGlobalLight.slh +++ b/libraries/render-utils/src/DeferredGlobalLight.slh @@ -91,6 +91,7 @@ vec3 albedo, vec3 fresnel, float metallic, float roughness <@endfunc@> +<@include Haze.slh@> <@func declareEvalSkyboxGlobalColor(supportScattering)@> @@ -101,8 +102,6 @@ vec3 albedo, vec3 fresnel, float metallic, float roughness <$declareDeferredCurvature()$> <@endif@> -<@include Haze.slh@> - vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, vec3 fresnel, float metallic, float roughness <@if supportScattering@> @@ -122,7 +121,6 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu color += ambientDiffuse; color += ambientSpecular; - // Directional vec3 directionalDiffuse; vec3 directionalSpecular; evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, fragEyeDir, fragNormal, roughness, metallic, fresnel, albedo, shadowAttenuation @@ -135,52 +133,7 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu // Attenuate the light if haze effect selected if ((hazeParams.hazeMode & HAZE_MODE_IS_KEYLIGHT_ATTENUATED) == HAZE_MODE_IS_KEYLIGHT_ATTENUATED) { - // Directional light attenuation is simulated by assuming the light source is at a fixed height above the - // fragment. This height is where the haze density is reduced by 95% from the haze at the fragment's height - // - // The distance is computed from the height and the directional light orientation - // The distance is limited to height * 1,000, which gives an angle of ~0.057 degrees - - // Get directional light - Light light = getLight(); - vec3 lightDirection = getLightDirection(light); - - // Height at which haze density is reduced by 95% (default set to 2000.0 for safety ,this should never happen) - float height_95p = 2000.0; - if (hazeParams.hazeKeyLightAltitudeFactor > 0.0f) { - height_95p = -log(0.05) / hazeParams.hazeKeyLightAltitudeFactor; - } - - // Note that the sine will always be positive - float sin_pitch = sqrt(1.0 - lightDirection.y * lightDirection.y); - - float distance; - const float minimumSinPitch = 0.001; - if (sin_pitch < minimumSinPitch) { - distance = height_95p / minimumSinPitch; - } else { - distance = height_95p / sin_pitch; - } - - // Position of fragment in world coordinates - vec4 worldFragPos = invViewMat * vec4(position, 0.0); - - // Integration is from the fragment towards the light source - // Note that the haze base reference affects only the haze density as function of altitude - float hazeDensityDistribution = - hazeParams.hazeKeyLightRangeFactor * - exp(-hazeParams.hazeKeyLightAltitudeFactor * (worldFragPos.y - hazeParams.hazeBaseReference)); - - float hazeIntegral = hazeDensityDistribution * distance; - - // Note that t is constant and equal to -log(0.05) - // float t = hazeParams.hazeAltitudeFactor * height_95p; - // hazeIntegral *= (1.0 - exp (-t)) / t; - hazeIntegral *= 0.3171178; - - float hazeAmount = 1.0 - exp(-hazeIntegral); - - color = mix(color, vec3(0.0, 0.0, 0.0), hazeAmount); + color = computeHazeColorKeyLightAttenuation(color, lightDirection, position); } return color; @@ -213,9 +166,6 @@ vec3 evalLightmappedColor(mat4 invViewMat, float shadowAttenuation, float obscur } <@endfunc@> - - - <@func declareEvalGlobalLightingAlphaBlended()@> <$declareLightingAmbient(1, 1, 1)$> @@ -233,7 +183,6 @@ vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, fl color += ambientDiffuse; color += ambientSpecular / opacity; - // Directional vec3 directionalDiffuse; vec3 directionalSpecular; @@ -244,6 +193,43 @@ vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, fl return color; } +vec3 evalGlobalLightingAlphaBlendedWithHaze( + mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, + vec3 albedo, vec3 fresnel, float metallic, vec3 emissive, float roughness, float opacity) +{ + <$prepareGlobalLight()$> + + color += emissive * isEmissiveEnabled(); + + // Ambient + vec3 ambientDiffuse; + vec3 ambientSpecular; + evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, fragEyeDir, fragNormal, roughness, metallic, fresnel, albedo, obscurance); + color += ambientDiffuse; + color += ambientSpecular / opacity; + + // Directional + vec3 directionalDiffuse; + vec3 directionalSpecular; + evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, fragEyeDir, fragNormal, roughness, metallic, fresnel, albedo, shadowAttenuation); + color += directionalDiffuse; + color += directionalSpecular / opacity; + + // Haze + if ((hazeParams.hazeMode & HAZE_MODE_IS_ACTIVE) == HAZE_MODE_IS_ACTIVE) { + vec4 colorV4 = computeHazeColor( + vec4(color, 1.0), // fragment original color + position, // fragment position in eye coordinates + fragEyeVector, // fragment position in world coordinates + invViewMat[3].y, // eye height in world coordinates + lightDirection // keylight direction vector + ); + + color = colorV4.rgb; + } + + return color; +} <@endfunc@> diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 3286531643..151fb7990d 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -71,12 +71,12 @@ enum DeferredShader_BufferSlot { SCATTERING_PARAMETERS_BUFFER_SLOT, LIGHTING_MODEL_BUFFER_SLOT = render::ShapePipeline::Slot::LIGHTING_MODEL, LIGHT_GPU_SLOT = render::ShapePipeline::Slot::LIGHT, - LIGHT_AMBIENT_SLOT, + LIGHT_AMBIENT_SLOT = render::ShapePipeline::Slot::LIGHT_AMBIENT_BUFFER, + HAZE_MODEL_BUFFER_SLOT = render::ShapePipeline::Slot::HAZE_MODEL, LIGHT_INDEX_GPU_SLOT, LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT, LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, - HAZE_MODEL_BUFFER_SLOT }; static void loadLightProgram(const char* vertSource, const char* fragSource, bool lightVolume, gpu::PipelinePointer& program, LightLocationsPtr& locations); diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 76c354bdf8..2616d08600 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -760,6 +760,20 @@ void GeometryCache::renderWireShape(gpu::Batch& batch, Shape shape) { _shapes[shape].drawWire(batch); } +void GeometryCache::renderShape(gpu::Batch& batch, Shape shape, const glm::vec4& color) { + batch.setInputFormat(getSolidStreamFormat()); + // Color must be set after input format + batch._glColor4f(color.r, color.g, color.b, color.a); + _shapes[shape].draw(batch); +} + +void GeometryCache::renderWireShape(gpu::Batch& batch, Shape shape, const glm::vec4& color) { + batch.setInputFormat(getSolidStreamFormat()); + // Color must be set after input format + batch._glColor4f(color.r, color.g, color.b, color.a); + _shapes[shape].drawWire(batch); +} + void setupBatchInstance(gpu::Batch& batch, gpu::BufferPointer colorBuffer) { gpu::BufferView colorView(colorBuffer, COLOR_ELEMENT); batch.setInputBuffer(gpu::Stream::COLOR, colorView); @@ -811,6 +825,14 @@ void GeometryCache::renderWireCube(gpu::Batch& batch) { renderWireShape(batch, Cube); } +void GeometryCache::renderCube(gpu::Batch& batch, const glm::vec4& color) { + renderShape(batch, Cube, color); +} + +void GeometryCache::renderWireCube(gpu::Batch& batch, const glm::vec4& color) { + renderWireShape(batch, Cube, color); +} + void GeometryCache::renderSphere(gpu::Batch& batch) { renderShape(batch, Sphere); } @@ -819,6 +841,14 @@ void GeometryCache::renderWireSphere(gpu::Batch& batch) { renderWireShape(batch, Sphere); } +void GeometryCache::renderSphere(gpu::Batch& batch, const glm::vec4& color) { + renderShape(batch, Sphere, color); +} + +void GeometryCache::renderWireSphere(gpu::Batch& batch, const glm::vec4& color) { + renderWireShape(batch, Sphere, color); +} + void GeometryCache::renderGrid(gpu::Batch& batch, const glm::vec2& minCorner, const glm::vec2& maxCorner, int majorRows, int majorCols, float majorEdge, int minorRows, int minorCols, float minorEdge, diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index cd8c43f1df..0585cc9e55 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -251,14 +251,20 @@ public: // Dynamic geometry void renderShape(gpu::Batch& batch, Shape shape); void renderWireShape(gpu::Batch& batch, Shape shape); + void renderShape(gpu::Batch& batch, Shape shape, const glm::vec4& color); + void renderWireShape(gpu::Batch& batch, Shape shape, const glm::vec4& color); size_t getShapeTriangleCount(Shape shape); void renderCube(gpu::Batch& batch); void renderWireCube(gpu::Batch& batch); + void renderCube(gpu::Batch& batch, const glm::vec4& color); + void renderWireCube(gpu::Batch& batch, const glm::vec4& color); size_t getCubeTriangleCount(); void renderSphere(gpu::Batch& batch); void renderWireSphere(gpu::Batch& batch); + void renderSphere(gpu::Batch& batch, const glm::vec4& color); + void renderWireSphere(gpu::Batch& batch, const glm::vec4& color); size_t getSphereTriangleCount(); void renderGrid(gpu::Batch& batch, const glm::vec2& minCorner, const glm::vec2& maxCorner, diff --git a/libraries/render-utils/src/Haze.slf b/libraries/render-utils/src/Haze.slf index b366e6d639..0270aa58f0 100644 --- a/libraries/render-utils/src/Haze.slf +++ b/libraries/render-utils/src/Haze.slf @@ -23,6 +23,7 @@ <@include Haze.slh@> uniform sampler2D colorMap; +uniform sampler2D linearDepthMap; vec4 unpackPositionFromZeye(vec2 texcoord) { float Zeye = -texture(linearDepthMap, texcoord).x; @@ -45,104 +46,15 @@ void main(void) { discard; } - // Distance to fragment - vec4 eyeFragPos = unpackPositionFromZeye(varTexCoord0); - float distance = length(eyeFragPos.xyz); - vec4 fragColor = texture(colorMap, varTexCoord0); - vec4 hazeColor = vec4(hazeParams.hazeColor, 1.0); + vec4 eyeFragPos = unpackPositionFromZeye(varTexCoord0); - // Directional light component is a function of the angle from the eye, between the fragment and the sun - DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); - vec4 worldFragPos = getViewInverse() * eyeFragPos; - vec3 eyeFragDir = normalize(worldFragPos.xyz); + mat4 viewInverse = getViewInverse(); + vec4 worldFragPos = viewInverse * eyeFragPos; + vec4 worldEyePos = viewInverse[3]; Light light = getLight(); vec3 lightDirection = getLightDirection(light); - float glareComponent = max(0.0, dot(eyeFragDir, -lightDirection)); - float power = min(1.0, pow(glareComponent, hazeParams.hazeGlareBlend)); - - vec4 glareColor = vec4(hazeParams.hazeGlareColor, 1.0); - - // Use the haze colour for the glare colour, if blend is not enabled - vec4 blendedHazeColor; - if ((hazeParams.hazeMode & HAZE_MODE_IS_ENABLE_LIGHT_BLEND) == HAZE_MODE_IS_ENABLE_LIGHT_BLEND) { - blendedHazeColor = mix(hazeColor, glareColor, power); - } else { - blendedHazeColor = hazeColor; - } - - vec4 potentialFragColor; - - if ((hazeParams.hazeMode & HAZE_MODE_IS_MODULATE_COLOR) == HAZE_MODE_IS_MODULATE_COLOR) { - // Compute separately for each colour - // Haze is based on both range and altitude - // Taken from www.crytek.com/download/GDC2007_RealtimeAtmoFxInGamesRev.ppt - - // The eyepoint position is in the last column of the matrix - vec3 worldEyePos = getViewInverse()[3].xyz; - - // Note that the haze base reference affects only the haze density as function of altitude - vec3 hazeDensityDistribution = - hazeParams.colorModulationFactor * - exp(-hazeParams.hazeHeightFactor * (worldEyePos.y - hazeParams.hazeBaseReference)); - - vec3 hazeIntegral = hazeDensityDistribution * distance; - - const float slopeThreshold = 0.01; - float deltaHeight = worldFragPos.y - worldEyePos.y; - if (abs(deltaHeight) > slopeThreshold) { - float t = hazeParams.hazeHeightFactor * deltaHeight; - hazeIntegral *= (1.0 - exp (-t)) / t; - } - - vec3 hazeAmount = 1.0 - exp(-hazeIntegral); - - // Compute color after haze effect - potentialFragColor = mix(fragColor, vec4(1.0, 1.0, 1.0, 1.0), vec4(hazeAmount, 1.0)); - } else if ((hazeParams.hazeMode & HAZE_MODE_IS_ALTITUDE_BASED) != HAZE_MODE_IS_ALTITUDE_BASED) { - // Haze is based only on range - float hazeAmount = 1.0 - exp(-distance * hazeParams.hazeRangeFactor); - - // Compute color after haze effect - potentialFragColor = mix(fragColor, blendedHazeColor, hazeAmount); - } else { - // Haze is based on both range and altitude - // Taken from www.crytek.com/download/GDC2007_RealtimeAtmoFxInGamesRev.ppt - - // The eyepoint position is in the last column of the matrix - vec3 worldEyePos = getViewInverse()[3].xyz; - - // Note that the haze base reference affects only the haze density as function of altitude - float hazeDensityDistribution = - hazeParams.hazeRangeFactor * - exp(-hazeParams.hazeHeightFactor * (worldEyePos.y - hazeParams.hazeBaseReference)); - - float hazeIntegral = hazeDensityDistribution * distance; - - const float slopeThreshold = 0.01; - float deltaHeight = worldFragPos.y - worldEyePos.y; - if (abs(deltaHeight) > slopeThreshold) { - float t = hazeParams.hazeHeightFactor * deltaHeight; - // Protect from wild values - if (abs(t) > 0.0000001) { - hazeIntegral *= (1.0 - exp (-t)) / t; - } - } - - float hazeAmount = 1.0 - exp(-hazeIntegral); - - // Compute color after haze effect - potentialFragColor = mix(fragColor, blendedHazeColor, hazeAmount); - } - - // Mix with background at far range - const float BLEND_DISTANCE = 27000.0; - if (distance > BLEND_DISTANCE) { - outFragColor = mix(potentialFragColor, fragColor, hazeParams.backgroundBlend); - } else { - outFragColor = potentialFragColor; - } + outFragColor = computeHazeColor(fragColor, eyeFragPos.xyz, worldFragPos.xyz, worldEyePos.y, lightDirection); } - diff --git a/libraries/render-utils/src/Haze.slh b/libraries/render-utils/src/Haze.slh index de7f0ac246..15e484e055 100644 --- a/libraries/render-utils/src/Haze.slh +++ b/libraries/render-utils/src/Haze.slh @@ -40,7 +40,160 @@ layout(std140) uniform hazeBuffer { HazeParams hazeParams; }; -uniform sampler2D linearDepthMap; + +// Input: +// color - fragment original color +// lightDirection - parameters of the keylight +// worldFragPos - fragment position in world coordinates +// Output: +// fragment colour after haze effect +// +// General algorithm taken from http://www.iquilezles.org/www/articles/fog/fog.htm, with permission +// +vec3 computeHazeColorKeyLightAttenuation(vec3 color, vec3 lightDirection, vec3 worldFragPos) { + // Directional light attenuation is simulated by assuming the light source is at a fixed height above the + // fragment. This height is where the haze density is reduced by 95% from the haze at the fragment's height + // + // The distance is computed from the height and the directional light orientation + // The distance is limited to height * 1,000, which gives an angle of ~0.057 degrees + + // Height at which haze density is reduced by 95% (default set to 2000.0 for safety ,this should never happen) + float height_95p = 2000.0; + const float log_p_005 = log(0.05); + if (hazeParams.hazeKeyLightAltitudeFactor > 0.0f) { + height_95p = -log_p_005 / hazeParams.hazeKeyLightAltitudeFactor; + } + + // Note that we need the sine to be positive + float sin_pitch = abs(lightDirection.y); + + float distance; + const float minimumSinPitch = 0.001; + if (sin_pitch < minimumSinPitch) { + distance = height_95p / minimumSinPitch; + } else { + distance = height_95p / sin_pitch; + } + + // Integration is from the fragment towards the light source + // Note that the haze base reference affects only the haze density as function of altitude + float hazeDensityDistribution = + hazeParams.hazeKeyLightRangeFactor * + exp(-hazeParams.hazeKeyLightAltitudeFactor * (worldFragPos.y - hazeParams.hazeBaseReference)); + + float hazeIntegral = hazeDensityDistribution * distance; + + // Note that t is constant and equal to -log(0.05) + // float t = hazeParams.hazeAltitudeFactor * height_95p; + // hazeIntegral *= (1.0 - exp (-t)) / t; + hazeIntegral *= 0.3171178; + + return color * exp(-hazeIntegral); +} + +// Input: +// fragColor - fragment original color +// eyeFragPos - fragment position in eye coordinates +// worldFragPos - fragment position in world coordinates +// worldEyeHeight - eye height in world coordinates +// Output: +// fragment colour after haze effect +// +// General algorithm taken from http://www.iquilezles.org/www/articles/fog/fog.htm, with permission +// +vec4 computeHazeColor(vec4 fragColor, vec3 eyeFragPos, vec3 worldFragPos, float worldEyeHeight, vec3 lightDirection) { + // Distance to fragment + float distance = length(eyeFragPos); + + // Convert haze colour from uniform into a vec4 + vec4 hazeColor = vec4(hazeParams.hazeColor, 1.0); + + // Directional light component is a function of the angle from the eye, between the fragment and the sun + vec3 eyeFragDir = normalize(worldFragPos); + + float glareComponent = max(0.0, dot(eyeFragDir, -lightDirection)); + float power = min(1.0, pow(glareComponent, hazeParams.hazeGlareBlend)); + + vec4 glareColor = vec4(hazeParams.hazeGlareColor, 1.0); + + // Use the haze colour for the glare colour, if blend is not enabled + vec4 blendedHazeColor; + if ((hazeParams.hazeMode & HAZE_MODE_IS_ENABLE_LIGHT_BLEND) == HAZE_MODE_IS_ENABLE_LIGHT_BLEND) { + blendedHazeColor = mix(hazeColor, glareColor, power); + } else { + blendedHazeColor = hazeColor; + } + + vec4 potentialFragColor; + + if ((hazeParams.hazeMode & HAZE_MODE_IS_MODULATE_COLOR) == HAZE_MODE_IS_MODULATE_COLOR) { + // Compute separately for each colour + // Haze is based on both range and altitude + // Taken from www.crytek.com/download/GDC2007_RealtimeAtmoFxInGamesRev.ppt + + // Note that the haze base reference affects only the haze density as function of altitude + vec3 hazeDensityDistribution = + hazeParams.colorModulationFactor * + exp(-hazeParams.hazeHeightFactor * (worldEyeHeight - hazeParams.hazeBaseReference)); + + vec3 hazeIntegral = hazeDensityDistribution * distance; + + const float slopeThreshold = 0.01; + float deltaHeight = worldFragPos.y - worldEyeHeight; + if (abs(deltaHeight) > slopeThreshold) { + float t = hazeParams.hazeHeightFactor * deltaHeight; + hazeIntegral *= (1.0 - exp (-t)) / t; + } + + vec3 hazeAmount = 1.0 - exp(-hazeIntegral); + + // Compute color after haze effect + potentialFragColor = mix(fragColor, vec4(1.0, 1.0, 1.0, 1.0), vec4(hazeAmount, 1.0)); + } else if ((hazeParams.hazeMode & HAZE_MODE_IS_ALTITUDE_BASED) != HAZE_MODE_IS_ALTITUDE_BASED) { + // Haze is based only on range + float hazeAmount = 1.0 - exp(-distance * hazeParams.hazeRangeFactor); + + // Compute color after haze effect + potentialFragColor = mix(fragColor, blendedHazeColor, hazeAmount); + } else { + // Haze is based on both range and altitude + // Taken from www.crytek.com/download/GDC2007_RealtimeAtmoFxInGamesRev.ppt + + // Note that the haze base reference affects only the haze density as function of altitude + float hazeDensityDistribution = + hazeParams.hazeRangeFactor * + exp(-hazeParams.hazeHeightFactor * (worldEyeHeight - hazeParams.hazeBaseReference)); + + float hazeIntegral = hazeDensityDistribution * distance; + + const float slopeThreshold = 0.01; + float deltaHeight = worldFragPos.y - worldEyeHeight; + if (abs(deltaHeight) > slopeThreshold) { + float t = hazeParams.hazeHeightFactor * deltaHeight; + // Protect from wild values + const float EPSILON = 0.0000001f; + if (abs(t) > EPSILON) { + hazeIntegral *= (1.0 - exp (-t)) / t; + } + } + + float hazeAmount = 1.0 - exp(-hazeIntegral); + + // Compute color after haze effect + potentialFragColor = mix(fragColor, blendedHazeColor, hazeAmount); + } + + // Mix with background at far range + const float BLEND_DISTANCE = 27000.0f; + vec4 outFragColor; + if (distance > BLEND_DISTANCE) { + outFragColor = mix(potentialFragColor, fragColor, hazeParams.backgroundBlend); + } else { + outFragColor = potentialFragColor; + } + + return outFragColor; +} <@endif@> diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 1ea3e1a705..9d63bd78cb 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -122,11 +122,6 @@ void MeshPartPayload::bindMesh(gpu::Batch& batch) { batch.setInputFormat((_drawMesh->getVertexFormat())); batch.setInputStream(0, _drawMesh->getVertexStream()); - - // TODO: Get rid of that extra call - if (!_hasColorAttrib) { - batch._glColor4f(1.0f, 1.0f, 1.0f, 1.0f); - } } void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::LocationsPointer locations, bool enableTextures) const { @@ -325,7 +320,7 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in _shapeID(shapeIndex) { assert(model && model->isLoaded()); - _model = model; + _blendedVertexBuffer = model->_blendedVertexBuffers[_meshIndex]; auto& modelMesh = model->getGeometry()->getMeshes().at(_meshIndex); const Model::MeshState& state = model->getMeshState(_meshIndex); @@ -339,13 +334,10 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in } updateTransformForSkinnedMesh(renderTransform, transform); - initCache(); + initCache(model); } -void ModelMeshPartPayload::initCache() { - ModelPointer model = _model.lock(); - assert(model && model->isLoaded()); - +void ModelMeshPartPayload::initCache(const ModelPointer& model) { if (_drawMesh) { auto vertexFormat = _drawMesh->getVertexFormat(); _hasColorAttrib = vertexFormat->hasAttribute(gpu::Stream::COLOR); @@ -355,6 +347,7 @@ void ModelMeshPartPayload::initCache() { const FBXMesh& mesh = geometry.meshes.at(_meshIndex); _isBlendShaped = !mesh.blendshapes.isEmpty(); + _hasTangents = !mesh.tangents.isEmpty(); } auto networkMaterial = model->getGeometry()->getShapeMaterial(_shapeID); @@ -388,94 +381,70 @@ void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& render _worldBound.transform(boundTransform); } -ItemKey ModelMeshPartPayload::getKey() const { +void ModelMeshPartPayload::setKey(bool isVisible, bool isLayered) { ItemKey::Builder builder; builder.withTypeShape(); - ModelPointer model = _model.lock(); - if (model) { - if (!model->isVisible()) { - builder.withInvisible(); - } + if (!isVisible) { + builder.withInvisible(); + } - if (model->isLayeredInFront() || model->isLayeredInHUD()) { - builder.withLayered(); - } + if (isLayered) { + builder.withLayered(); + } - if (_isBlendShaped || _isSkinned) { - builder.withDeformed(); - } + if (_isBlendShaped || _isSkinned) { + builder.withDeformed(); + } - if (_drawMaterial) { - auto matKey = _drawMaterial->getKey(); - if (matKey.isTranslucent()) { - builder.withTransparent(); - } + if (_drawMaterial) { + auto matKey = _drawMaterial->getKey(); + if (matKey.isTranslucent()) { + builder.withTransparent(); } } - return builder.build(); + + _itemKey = builder.build(); +} + +ItemKey ModelMeshPartPayload::getKey() const { + return _itemKey; +} + +void ModelMeshPartPayload::setLayer(bool isLayeredInFront, bool isLayeredInHUD) { + if (isLayeredInFront) { + _layer = Item::LAYER_3D_FRONT; + } else if (isLayeredInHUD) { + _layer = Item::LAYER_3D_HUD; + } else { + _layer = Item::LAYER_3D; + } } int ModelMeshPartPayload::getLayer() const { - ModelPointer model = _model.lock(); - if (model) { - if (model->isLayeredInFront()) { - return Item::LAYER_3D_FRONT; - } else if (model->isLayeredInHUD()) { - return Item::LAYER_3D_HUD; - } - } - return Item::LAYER_3D; + return _layer; } -ShapeKey ModelMeshPartPayload::getShapeKey() const { - // guard against partially loaded meshes - ModelPointer model = _model.lock(); - if (!model || !model->isLoaded() || !model->getGeometry()) { - return ShapeKey::Builder::invalid(); +void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, bool isWireframe) { + if (invalidateShapeKey) { + _shapeKey = ShapeKey::Builder::invalid(); + return; } - const FBXGeometry& geometry = model->getFBXGeometry(); - const auto& networkMeshes = model->getGeometry()->getMeshes(); - - // guard against partially loaded meshes - if (_meshIndex >= (int)networkMeshes.size() || _meshIndex >= (int)geometry.meshes.size() || _meshIndex >= (int)model->_meshStates.size()) { - return ShapeKey::Builder::invalid(); - } - - const FBXMesh& mesh = geometry.meshes.at(_meshIndex); - - // if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown - // to false to rebuild out mesh groups. - if (_meshIndex < 0 || _meshIndex >= (int)networkMeshes.size() || _meshIndex > geometry.meshes.size()) { - model->_needsFixupInScene = true; // trigger remove/add cycle - model->invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid - return ShapeKey::Builder::invalid(); - } - - - int vertexCount = mesh.vertices.size(); - if (vertexCount == 0) { - // sanity check - return ShapeKey::Builder::invalid(); // FIXME - } - - model::MaterialKey drawMaterialKey; if (_drawMaterial) { drawMaterialKey = _drawMaterial->getKey(); } bool isTranslucent = drawMaterialKey.isTranslucent(); - bool hasTangents = drawMaterialKey.isNormalMap() && !mesh.tangents.isEmpty(); + bool hasTangents = drawMaterialKey.isNormalMap() && _hasTangents; bool hasSpecular = drawMaterialKey.isMetallicMap(); bool hasLightmap = drawMaterialKey.isLightmapMap(); bool isUnlit = drawMaterialKey.isUnlit(); bool isSkinned = _isSkinned; - bool wireframe = model->isWireframe(); - if (wireframe) { + if (isWireframe) { isTranslucent = hasTangents = hasSpecular = hasLightmap = isSkinned = false; } @@ -500,10 +469,14 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const { if (isSkinned) { builder.withSkinned(); } - if (wireframe) { + if (isWireframe) { builder.withWireframe(); } - return builder.build(); + _shapeKey = builder.build(); +} + +ShapeKey ModelMeshPartPayload::getShapeKey() const { + return _shapeKey; } void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) { @@ -515,10 +488,9 @@ void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) { batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0); batch.setInputFormat((_drawMesh->getVertexFormat())); - ModelPointer model = _model.lock(); - if (model) { - batch.setInputBuffer(0, model->_blendedVertexBuffers[_meshIndex], 0, sizeof(glm::vec3)); - batch.setInputBuffer(1, model->_blendedVertexBuffers[_meshIndex], _drawMesh->getNumVertices() * sizeof(glm::vec3), sizeof(glm::vec3)); + if (_blendedVertexBuffer) { + batch.setInputBuffer(0, _blendedVertexBuffer, 0, sizeof(glm::vec3)); + batch.setInputBuffer(1, _blendedVertexBuffer, _drawMesh->getNumVertices() * sizeof(glm::vec3), sizeof(glm::vec3)); batch.setInputStream(2, _drawMesh->getVertexStream().makeRangedStream(2)); } else { batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0); @@ -526,15 +498,9 @@ void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) { batch.setInputStream(0, _drawMesh->getVertexStream()); } } - - // TODO: Get rid of that extra call - if (!_hasColorAttrib) { - batch._glColor4f(1.0f, 1.0f, 1.0f, 1.0f); - } } void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const { - // Still relying on the raw data from the model if (_clusterBuffer) { batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, _clusterBuffer); } @@ -544,31 +510,9 @@ void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline: void ModelMeshPartPayload::render(RenderArgs* args) { PerformanceTimer perfTimer("ModelMeshPartPayload::render"); - ModelPointer model = _model.lock(); - if (!model || !model->isAddedToScene() || !model->isVisible()) { - return; // bail asap - } - - if (_state == WAITING_TO_START) { - if (model->isLoaded()) { - _state = STARTED; - model->setRenderItemsNeedUpdate(); - } else { - return; - } - } - - if (_materialNeedsUpdate && model->getGeometry()->areTexturesLoaded()) { - model->setRenderItemsNeedUpdate(); - _materialNeedsUpdate = false; - } - if (!args) { return; } - if (!getShapeKey().isValid()) { - return; - } gpu::Batch& batch = *(args->_batch); auto locations = args->_shapePipeline->locations; diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 971c6fe90b..fb55883101 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -96,32 +96,32 @@ public: render::ShapeKey getShapeKey() const override; // shape interface void render(RenderArgs* args) override; + void setKey(bool isVisible, bool isLayered); + void setLayer(bool isLayeredInFront, bool isLayeredInHUD); + void setShapeKey(bool invalidateShapeKey, bool isWireframe); + // ModelMeshPartPayload functions to perform render void bindMesh(gpu::Batch& batch) override; void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const override; - void initCache(); - void computeAdjustedLocalBound(const std::vector& clusterMatrices); gpu::BufferPointer _clusterBuffer; - ModelWeakPointer _model; int _meshIndex; int _shapeID; bool _isSkinned{ false }; bool _isBlendShaped { false }; - bool _materialNeedsUpdate { true }; + bool _hasTangents { false }; private: + void initCache(const ModelPointer& model); - enum State : uint8_t { - WAITING_TO_START = 0, - STARTED = 1, - }; - - mutable State _state { WAITING_TO_START } ; + gpu::BufferPointer _blendedVertexBuffer; + render::ItemKey _itemKey { render::ItemKey::Builder::opaqueShape().build() }; + render::ShapeKey _shapeKey { render::ShapeKey::Builder::invalid() }; + int _layer { render::Item::LAYER_3D }; }; namespace render { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index c4bc435691..536e76e44e 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -163,7 +163,7 @@ void Model::setScale(const glm::vec3& scale) { _scaledToFit = false; } -const float SCALE_CHANGE_EPSILON = 0.001f; +const float SCALE_CHANGE_EPSILON = 0.0000001f; void Model::setScaleInternal(const glm::vec3& scale) { if (glm::distance(_scale, scale) > SCALE_CHANGE_EPSILON) { @@ -210,6 +210,24 @@ int Model::getRenderInfoTextureCount() { return _renderInfoTextureCount; } +bool Model::shouldInvalidatePayloadShapeKey(int meshIndex) { + if (!getGeometry()) { + return true; + } + + const FBXGeometry& geometry = getFBXGeometry(); + const auto& networkMeshes = getGeometry()->getMeshes(); + // if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown + // to false to rebuild out mesh groups. + if (meshIndex < 0 || meshIndex >= (int)networkMeshes.size() || meshIndex >= (int)geometry.meshes.size() || meshIndex >= (int)_meshStates.size()) { + _needsFixupInScene = true; // trigger remove/add cycle + invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid + return true; + } + + return false; +} + void Model::updateRenderItems() { if (!_addedToScene) { return; @@ -237,6 +255,11 @@ void Model::updateRenderItems() { Transform modelTransform = self->getTransform(); modelTransform.setScale(glm::vec3(1.0f)); + bool isWireframe = self->isWireframe(); + bool isVisible = self->isVisible(); + bool isLayeredInFront = self->isLayeredInFront(); + bool isLayeredInHUD = self->isLayeredInHUD(); + render::Transaction transaction; for (int i = 0; i < (int) self->_modelMeshRenderItemIDs.size(); i++) { @@ -244,13 +267,20 @@ void Model::updateRenderItems() { auto meshIndex = self->_modelMeshRenderItemShapes[i].meshIndex; auto clusterMatrices(self->getMeshState(meshIndex).clusterMatrices); - transaction.updateItem(itemID, [modelTransform, clusterMatrices](ModelMeshPartPayload& data) { + bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex); + + transaction.updateItem(itemID, [modelTransform, clusterMatrices, invalidatePayloadShapeKey, + isWireframe, isVisible, isLayeredInFront, isLayeredInHUD](ModelMeshPartPayload& data) { data.updateClusterBuffer(clusterMatrices); Transform renderTransform = modelTransform; if (clusterMatrices.size() == 1) { renderTransform = modelTransform.worldTransform(Transform(clusterMatrices[0])); } data.updateTransformForSkinnedMesh(renderTransform, modelTransform); + + data.setKey(isVisible, isLayeredInFront || isLayeredInHUD); + data.setLayer(isLayeredInFront, isLayeredInHUD); + data.setShapeKey(invalidatePayloadShapeKey, isWireframe); }); } @@ -272,16 +302,6 @@ void Model::setRenderItemsNeedUpdate() { emit requestRenderUpdate(); } -void Model::initJointTransforms() { - if (isLoaded()) { - glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset); - _rig.setModelOffset(modelOffset); - } -} - -void Model::init() { -} - void Model::reset() { if (isLoaded()) { const FBXGeometry& geometry = getFBXGeometry(); @@ -574,47 +594,72 @@ void Model::calculateTriangleSets() { } } -void Model::setVisibleInScene(bool newValue, const render::ScenePointer& scene) { - if (_isVisible != newValue) { - _isVisible = newValue; +void Model::setVisibleInScene(bool isVisible, const render::ScenePointer& scene) { + if (_isVisible != isVisible) { + _isVisible = isVisible; + + bool isLayeredInFront = _isLayeredInFront; + bool isLayeredInHUD = _isLayeredInHUD; render::Transaction transaction; foreach (auto item, _modelMeshRenderItemsMap.keys()) { - transaction.resetItem(item, _modelMeshRenderItemsMap[item]); + transaction.updateItem(item, [isVisible, isLayeredInFront, isLayeredInHUD](ModelMeshPartPayload& data) { + data.setKey(isVisible, isLayeredInFront || isLayeredInHUD); + }); } foreach(auto item, _collisionRenderItemsMap.keys()) { - transaction.resetItem(item, _collisionRenderItemsMap[item]); + transaction.updateItem(item, [isVisible, isLayeredInFront, isLayeredInHUD](ModelMeshPartPayload& data) { + data.setKey(isVisible, isLayeredInFront || isLayeredInHUD); + }); } scene->enqueueTransaction(transaction); } } -void Model::setLayeredInFront(bool layered, const render::ScenePointer& scene) { - if (_isLayeredInFront != layered) { - _isLayeredInFront = layered; +void Model::setLayeredInFront(bool isLayeredInFront, const render::ScenePointer& scene) { + if (_isLayeredInFront != isLayeredInFront) { + _isLayeredInFront = isLayeredInFront; + + bool isVisible = _isVisible; + bool isLayeredInHUD = _isLayeredInHUD; render::Transaction transaction; foreach(auto item, _modelMeshRenderItemsMap.keys()) { - transaction.resetItem(item, _modelMeshRenderItemsMap[item]); + transaction.updateItem(item, [isVisible, isLayeredInFront, isLayeredInHUD](ModelMeshPartPayload& data) { + data.setKey(isVisible, isLayeredInFront || isLayeredInHUD); + data.setLayer(isLayeredInFront, isLayeredInHUD); + }); } foreach(auto item, _collisionRenderItemsMap.keys()) { - transaction.resetItem(item, _collisionRenderItemsMap[item]); + transaction.updateItem(item, [isVisible, isLayeredInFront, isLayeredInHUD](ModelMeshPartPayload& data) { + data.setKey(isVisible, isLayeredInFront || isLayeredInHUD); + data.setLayer(isLayeredInFront, isLayeredInHUD); + }); } scene->enqueueTransaction(transaction); } } -void Model::setLayeredInHUD(bool layered, const render::ScenePointer& scene) { - if (_isLayeredInHUD != layered) { - _isLayeredInHUD = layered; +void Model::setLayeredInHUD(bool isLayeredInHUD, const render::ScenePointer& scene) { + if (_isLayeredInHUD != isLayeredInHUD) { + _isLayeredInHUD = isLayeredInHUD; + + bool isVisible = _isVisible; + bool isLayeredInFront = _isLayeredInFront; render::Transaction transaction; foreach(auto item, _modelMeshRenderItemsMap.keys()) { - transaction.resetItem(item, _modelMeshRenderItemsMap[item]); + transaction.updateItem(item, [isVisible, isLayeredInFront, isLayeredInHUD](ModelMeshPartPayload& data) { + data.setKey(isVisible, isLayeredInFront || isLayeredInHUD); + data.setLayer(isLayeredInFront, isLayeredInHUD); + }); } foreach(auto item, _collisionRenderItemsMap.keys()) { - transaction.resetItem(item, _collisionRenderItemsMap[item]); + transaction.updateItem(item, [isVisible, isLayeredInFront, isLayeredInHUD](ModelMeshPartPayload& data) { + data.setKey(isVisible, isLayeredInFront || isLayeredInHUD); + data.setLayer(isLayeredInFront, isLayeredInHUD); + }); } scene->enqueueTransaction(transaction); } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 7568a17342..4df7faac84 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -82,9 +82,9 @@ public: const QUrl& getURL() const { return _url; } // new Scene/Engine rendering support - void setVisibleInScene(bool newValue, const render::ScenePointer& scene); - void setLayeredInFront(bool layered, const render::ScenePointer& scene); - void setLayeredInHUD(bool layered, const render::ScenePointer& scene); + void setVisibleInScene(bool isVisible, const render::ScenePointer& scene); + void setLayeredInFront(bool isLayeredInFront, const render::ScenePointer& scene); + void setLayeredInHUD(bool isLayeredInHUD, const render::ScenePointer& scene); bool needsFixupInScene() const; bool needsReload() const { return _needsReload; } @@ -122,7 +122,6 @@ public: void setIsWireframe(bool isWireframe) { _isWireframe = isWireframe; } bool isWireframe() const { return _isWireframe; } - void init(); void reset(); void setSnapModelToRegistrationPoint(bool snapModelToRegistrationPoint, const glm::vec3& registrationPoint); @@ -346,11 +345,7 @@ protected: // hook for derived classes to be notified when setUrl invalidates the current model. virtual void onInvalidate() {}; - -protected: - virtual void deleteGeometry(); - void initJointTransforms(); QVector _blendshapeCoefficients; @@ -419,6 +414,8 @@ protected: bool _isLayeredInFront { false }; bool _isLayeredInHUD { false }; + bool shouldInvalidatePayloadShapeKey(int meshIndex); + private: float _loadingPriority { 0.0f }; diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 5c9abbabed..f860c0494e 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -163,6 +163,9 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren // Similar to light stage, background stage has been filled by several potential render items and resolved for the frame in this job task.addJob("DrawBackgroundDeferred", lightingModel); + const auto drawHazeInputs = render::Varying(DrawHaze::Inputs(hazeModel, lightingFramebuffer, linearDepthTarget, deferredFrameTransform, lightingFramebuffer)); + task.addJob("DrawHazeDeferred", drawHazeInputs); + // Render transparent objects forward in LightingBuffer const auto transparentsInputs = DrawDeferred::Inputs(transparents, lightingModel).asVarying(); task.addJob("DrawTransparentDeferred", transparentsInputs, shapePlumber); @@ -173,9 +176,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DebugLightClusters", debugLightClustersInputs); } - const auto drawHazeInputs = render::Varying(DrawHaze::Inputs(hazeModel, lightingFramebuffer, linearDepthTarget, deferredFrameTransform, lightingFramebuffer)); - task.addJob("DrawHaze", drawHazeInputs); - const auto toneAndPostRangeTimer = task.addJob("BeginToneAndPostRangeTimer", "PostToneOverlaysAntialiasing"); // Add bloom @@ -335,6 +335,13 @@ void DrawDeferred::run(const RenderContextPointer& renderContext, const Inputs& // Setup lighting model for all items; batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer()); + // Setup haze iff curretn zone has haze + auto hazeStage = args->_scene->getStage(); + if (hazeStage && hazeStage->_currentFrame._hazes.size() > 0) { + model::HazePointer hazePointer = hazeStage->getHaze(hazeStage->_currentFrame._hazes.front()); + batch.setUniformBuffer(render::ShapePipeline::Slot::HAZE_MODEL, hazePointer->getHazeParametersBuffer()); + } + // From the lighting model define a global shapKey ORED with individiual keys ShapeKey::Builder keyBuilder; if (lightingModel->isWireframeEnabled()) { diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index f51201d77d..e41b7edc77 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -16,7 +16,6 @@ #include #include "LightingModel.h" - class BeginGPURangeTimer { public: using JobModel = render::Job::ModelO; diff --git a/libraries/render-utils/src/ZoneRenderer.cpp b/libraries/render-utils/src/ZoneRenderer.cpp index 3d1fe33267..8494688f92 100644 --- a/libraries/render-utils/src/ZoneRenderer.cpp +++ b/libraries/render-utils/src/ZoneRenderer.cpp @@ -70,7 +70,7 @@ void SetupZones::run(const RenderContextPointer& context, const Inputs& inputs) lightStage->_currentFrame.pushSunLight(0); lightStage->_currentFrame.pushAmbientLight(0); - + hazeStage->_currentFrame.pushHaze(0); backgroundStage->_currentFrame.pushBackground(0); } diff --git a/libraries/render-utils/src/forward_model_translucent.slf b/libraries/render-utils/src/forward_model_translucent.slf index 52e8ce50e7..906393db1f 100644 --- a/libraries/render-utils/src/forward_model_translucent.slf +++ b/libraries/render-utils/src/forward_model_translucent.slf @@ -66,7 +66,7 @@ void main(void) { TransformCamera cam = getTransformCamera(); - _fragColor = vec4(evalGlobalLightingAlphaBlended( + _fragColor = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, occlusionTex, diff --git a/libraries/render-utils/src/model_translucent.slf b/libraries/render-utils/src/model_translucent.slf index 6cf99a68ef..38f162fdc3 100644 --- a/libraries/render-utils/src/model_translucent.slf +++ b/libraries/render-utils/src/model_translucent.slf @@ -66,7 +66,7 @@ void main(void) { TransformCamera cam = getTransformCamera(); - _fragColor = vec4(evalGlobalLightingAlphaBlended( + _fragColor = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, occlusionTex, diff --git a/libraries/render-utils/src/model_translucent_fade.slf b/libraries/render-utils/src/model_translucent_fade.slf index c46b396ebc..9d5477304c 100644 --- a/libraries/render-utils/src/model_translucent_fade.slf +++ b/libraries/render-utils/src/model_translucent_fade.slf @@ -76,7 +76,7 @@ void main(void) { TransformCamera cam = getTransformCamera(); - _fragColor = vec4(evalGlobalLightingAlphaBlended( + _fragColor = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, occlusionTex, diff --git a/libraries/render-utils/src/overlay3D_model_translucent.slf b/libraries/render-utils/src/overlay3D_model_translucent.slf index 748eea329c..b26e70f465 100644 --- a/libraries/render-utils/src/overlay3D_model_translucent.slf +++ b/libraries/render-utils/src/overlay3D_model_translucent.slf @@ -65,7 +65,7 @@ void main(void) { vec3 fragNormal; <$transformEyeToWorldDir(cam, _normal, fragNormal)$> - vec4 color = vec4(evalGlobalLightingAlphaBlended( + vec4 color = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, occlusionTex, diff --git a/libraries/render-utils/src/simple_transparent_textured.slf b/libraries/render-utils/src/simple_transparent_textured.slf index b9eb921e9d..b16b19c8b4 100644 --- a/libraries/render-utils/src/simple_transparent_textured.slf +++ b/libraries/render-utils/src/simple_transparent_textured.slf @@ -45,7 +45,7 @@ void main(void) { TransformCamera cam = getTransformCamera(); - _fragColor0 = vec4(evalGlobalLightingAlphaBlended( + _fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, 1.0, diff --git a/libraries/render-utils/src/simple_transparent_textured_fade.slf b/libraries/render-utils/src/simple_transparent_textured_fade.slf index 20c7907bbe..ad260210a7 100644 --- a/libraries/render-utils/src/simple_transparent_textured_fade.slf +++ b/libraries/render-utils/src/simple_transparent_textured_fade.slf @@ -57,7 +57,7 @@ void main(void) { TransformCamera cam = getTransformCamera(); - _fragColor0 = vec4(evalGlobalLightingAlphaBlended( + _fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, 1.0, diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp index 762b7712d7..4254280fa1 100644 --- a/libraries/render/src/render/ShapePipeline.cpp +++ b/libraries/render/src/render/ShapePipeline.cpp @@ -87,6 +87,7 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), Slot::MAP::LIGHT_AMBIENT)); slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), Slot::MAP::FADE_MASK)); slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), Slot::BUFFER::FADE_PARAMETERS)); + slotBindings.insert(gpu::Shader::Binding(std::string("hazeParametersBuffer"), Slot::BUFFER::HAZE_MODEL)); gpu::Shader::makeProgram(*program, slotBindings); @@ -107,6 +108,7 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p locations->lightAmbientMapUnit = program->getTextures().findLocation("skyboxMap"); locations->fadeMaskTextureUnit = program->getTextures().findLocation("fadeMaskMap"); locations->fadeParameterBufferUnit = program->getUniformBuffers().findLocation("fadeParametersBuffer"); + locations->hazeParameterBufferUnit = program->getUniformBuffers().findLocation("hazeParametersBuffer"); ShapeKey key{filter._flags}; auto gpuPipeline = gpu::Pipeline::create(program, state); diff --git a/libraries/render/src/render/ShapePipeline.h b/libraries/render/src/render/ShapePipeline.h index f0749504eb..be77e2f95e 100644 --- a/libraries/render/src/render/ShapePipeline.h +++ b/libraries/render/src/render/ShapePipeline.h @@ -237,6 +237,7 @@ public: LIGHTING_MODEL, LIGHT, LIGHT_AMBIENT_BUFFER, + HAZE_MODEL, FADE_PARAMETERS, }; @@ -270,6 +271,7 @@ public: int lightAmbientMapUnit; int fadeMaskTextureUnit; int fadeParameterBufferUnit; + int hazeParameterBufferUnit; }; using LocationsPointer = std::shared_ptr; diff --git a/libraries/script-engine/src/FileScriptingInterface.cpp b/libraries/script-engine/src/FileScriptingInterface.cpp index e475a2e445..1472e53045 100644 --- a/libraries/script-engine/src/FileScriptingInterface.cpp +++ b/libraries/script-engine/src/FileScriptingInterface.cpp @@ -23,6 +23,7 @@ #include #include +// FIXME quazip hasn't been built on the android toolchain #if !defined(Q_OS_ANDROID) #include #include @@ -73,6 +74,7 @@ void FileScriptingInterface::runUnzip(QString path, QUrl url, bool autoAdd, bool QStringList FileScriptingInterface::unzipFile(QString path, QString tempDir) { #if defined(Q_OS_ANDROID) + // FIXME quazip hasn't been built on the android toolchain return QStringList(); #else QDir dir(path); @@ -137,6 +139,7 @@ void FileScriptingInterface::recursiveFileScan(QFileInfo file, QString* dirName) return; }*/ QFileInfoList files; + // FIXME quazip hasn't been built on the android toolchain #if !defined(Q_OS_ANDROID) if (file.fileName().contains(".zip")) { qCDebug(scriptengine) << "Extracting archive: " + file.fileName(); diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index 6f2e7d8b19..b33b330fc0 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -30,9 +30,30 @@ QString TEMP_DIR_FORMAT { "%1-%2-%3" }; const QString& PathUtils::resourcesPath() { #ifdef Q_OS_MAC - static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/../Resources/"; + static const QString staticResourcePath = QCoreApplication::applicationDirPath() + "/../Resources/"; #else - static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/resources/"; + static const QString staticResourcePath = QCoreApplication::applicationDirPath() + "/resources/"; +#endif + return staticResourcePath; +} + +#ifdef DEV_BUILD +const QString& PathUtils::projectRootPath() { + static QString sourceFolder; + static std::once_flag once; + std::call_once(once, [&] { + QDir thisDir = QFileInfo(__FILE__).absoluteDir(); + sourceFolder = QDir::cleanPath(thisDir.absoluteFilePath("../../../")); + }); + return sourceFolder; +} +#endif + +const QString& PathUtils::qmlBasePath() { +#ifdef DEV_BUILD + static const QString staticResourcePath = QUrl::fromLocalFile(projectRootPath() + "/interface/resources/qml/").toString(); +#else + static const QString staticResourcePath = "qrc:///qml/"; #endif return staticResourcePath; diff --git a/libraries/shared/src/PathUtils.h b/libraries/shared/src/PathUtils.h index bc058e7820..2b4fe35d97 100644 --- a/libraries/shared/src/PathUtils.h +++ b/libraries/shared/src/PathUtils.h @@ -34,6 +34,10 @@ class PathUtils : public QObject, public Dependency { Q_PROPERTY(QUrl defaultScripts READ defaultScriptsLocation CONSTANT) public: static const QString& resourcesPath(); + static const QString& qmlBasePath(); +#ifdef DEV_BUILD + static const QString& projectRootPath(); +#endif static QString getAppDataPath(); static QString getAppLocalDataPath(); diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index dc6a877bb9..279fa42ea4 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -28,7 +28,7 @@ glm::vec3 getPosition() const override { return _thing->getPosition(); } float getRadius() const override { return 0.5f * _thing->getBoundingRadius(); } uint64_t getTimestamp() const override { return _thing->getLastTime(); } - const Thing& getThing() const { return _thing; } + Thing getThing() const { return _thing; } private: Thing _thing; }; @@ -43,6 +43,13 @@ (3) Loop over your priority queue and do timeboxed work: + NOTE: Be careful using references to members of instances of T from std::priority_queue. + Under the hood std::priority_queue may re-use instances of T. + For example, after a pop() or a push() the top T may have the same memory address + as the top T before the pop() or push() (but point to a swapped instance of T). + This causes a reference to member variable of T to point to a different value + when operations taken on std::priority_queue shuffle around the instances of T. + uint64_t cutoffTime = usecTimestampNow() + TIME_BUDGET; while (!sortedThings.empty()) { const Thing& thing = sortedThings.top(); diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 2114cbf944..20a5a76b73 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -19,7 +19,7 @@ #include "SharedLogging.h" const float defaultAACubeSize = 1.0f; -const int maxParentingChain = 30; +const int MAX_PARENTING_CHAIN_SIZE = 30; SpatiallyNestable::SpatiallyNestable(NestableType nestableType, QUuid id) : _nestableType(nestableType), @@ -28,6 +28,7 @@ SpatiallyNestable::SpatiallyNestable(NestableType nestableType, QUuid id) : // set flags in _transform _transform.setTranslation(glm::vec3(0.0f)); _transform.setRotation(glm::quat()); + _transform.setScale(1.0f); _scaleChanged = usecTimestampNow(); _translationChanged = usecTimestampNow(); _rotationChanged = usecTimestampNow(); @@ -85,6 +86,9 @@ Transform SpatiallyNestable::getParentTransform(bool& success, int depth) const } if (parent) { result = parent->getTransform(_parentJointIndex, success, depth + 1); + if (getScalesWithParent()) { + result.setScale(parent->scaleForChildren()); + } } return result; } @@ -165,7 +169,7 @@ void SpatiallyNestable::setParentJointIndex(quint16 parentJointIndex) { glm::vec3 SpatiallyNestable::worldToLocal(const glm::vec3& position, const QUuid& parentID, int parentJointIndex, - bool& success) { + bool scalesWithParent, bool& success) { QSharedPointer parentFinder = DependencyManager::get(); if (!parentFinder) { success = false; @@ -189,6 +193,9 @@ glm::vec3 SpatiallyNestable::worldToLocal(const glm::vec3& position, if (!success) { return glm::vec3(0.0f); } + if (scalesWithParent) { + parentTransform.setScale(parent->scaleForChildren()); + } } success = true; @@ -199,7 +206,7 @@ glm::vec3 SpatiallyNestable::worldToLocal(const glm::vec3& position, glm::quat SpatiallyNestable::worldToLocal(const glm::quat& orientation, const QUuid& parentID, int parentJointIndex, - bool& success) { + bool scalesWithParent, bool& success) { QSharedPointer parentFinder = DependencyManager::get(); if (!parentFinder) { success = false; @@ -223,6 +230,9 @@ glm::quat SpatiallyNestable::worldToLocal(const glm::quat& orientation, if (!success) { return glm::quat(); } + if (scalesWithParent) { + parentTransform.setScale(parent->scaleForChildren()); + } } success = true; @@ -231,7 +241,7 @@ glm::quat SpatiallyNestable::worldToLocal(const glm::quat& orientation, } glm::vec3 SpatiallyNestable::worldToLocalVelocity(const glm::vec3& velocity, const QUuid& parentID, - int parentJointIndex, bool& success) { + int parentJointIndex, bool scalesWithParent, bool& success) { SpatiallyNestablePointer parent = SpatiallyNestable::findByID(parentID, success); if (!success || !parent) { return velocity; @@ -240,6 +250,9 @@ glm::vec3 SpatiallyNestable::worldToLocalVelocity(const glm::vec3& velocity, con if (!success) { return velocity; } + if (scalesWithParent) { + parentTransform.setScale(parent->scaleForChildren()); + } glm::vec3 parentVelocity = parent->getWorldVelocity(success); if (!success) { return velocity; @@ -249,7 +262,7 @@ glm::vec3 SpatiallyNestable::worldToLocalVelocity(const glm::vec3& velocity, con } glm::vec3 SpatiallyNestable::worldToLocalAngularVelocity(const glm::vec3& angularVelocity, const QUuid& parentID, - int parentJointIndex, bool& success) { + int parentJointIndex, bool scalesWithParent, bool& success) { SpatiallyNestablePointer parent = SpatiallyNestable::findByID(parentID, success); if (!success || !parent) { return angularVelocity; @@ -258,12 +271,50 @@ glm::vec3 SpatiallyNestable::worldToLocalAngularVelocity(const glm::vec3& angula if (!success) { return angularVelocity; } + if (scalesWithParent) { + parentTransform.setScale(parent->scaleForChildren()); + } return glm::inverse(parentTransform.getRotation()) * angularVelocity; } + +glm::vec3 SpatiallyNestable::worldToLocalDimensions(const glm::vec3& dimensions, + const QUuid& parentID, int parentJointIndex, + bool scalesWithParent, bool& success) { + if (!scalesWithParent) { + success = true; + return dimensions; + } + + QSharedPointer parentFinder = DependencyManager::get(); + if (!parentFinder) { + success = false; + return dimensions; + } + + Transform parentTransform; + auto parentWP = parentFinder->find(parentID, success); + if (!success) { + return dimensions; + } + + auto parent = parentWP.lock(); + if (!parentID.isNull() && !parent) { + success = false; + return dimensions; + } + + success = true; + if (parent) { + return dimensions / parent->scaleForChildren(); + } + return dimensions; +} + glm::vec3 SpatiallyNestable::localToWorld(const glm::vec3& position, const QUuid& parentID, int parentJointIndex, + bool scalesWithParent, bool& success) { Transform result; QSharedPointer parentFinder = DependencyManager::get(); @@ -289,6 +340,9 @@ glm::vec3 SpatiallyNestable::localToWorld(const glm::vec3& position, if (!success) { return glm::vec3(0.0f); } + if (scalesWithParent) { + parentTransform.setScale(parent->scaleForChildren()); + } } success = true; @@ -300,6 +354,7 @@ glm::vec3 SpatiallyNestable::localToWorld(const glm::vec3& position, glm::quat SpatiallyNestable::localToWorld(const glm::quat& orientation, const QUuid& parentID, int parentJointIndex, + bool scalesWithParent, bool& success) { Transform result; QSharedPointer parentFinder = DependencyManager::get(); @@ -325,7 +380,9 @@ glm::quat SpatiallyNestable::localToWorld(const glm::quat& orientation, if (!success) { return glm::quat(); } - parentTransform.setScale(1.0f); + if (scalesWithParent) { + parentTransform.setScale(parent->scaleForChildren()); + } } success = true; @@ -336,7 +393,7 @@ glm::quat SpatiallyNestable::localToWorld(const glm::quat& orientation, } glm::vec3 SpatiallyNestable::localToWorldVelocity(const glm::vec3& velocity, const QUuid& parentID, - int parentJointIndex, bool& success) { + int parentJointIndex, bool scalesWithParent, bool& success) { SpatiallyNestablePointer parent = SpatiallyNestable::findByID(parentID, success); if (!success || !parent) { return velocity; @@ -345,6 +402,9 @@ glm::vec3 SpatiallyNestable::localToWorldVelocity(const glm::vec3& velocity, con if (!success) { return velocity; } + if (scalesWithParent) { + parentTransform.setScale(parent->scaleForChildren()); + } glm::vec3 parentVelocity = parent->getWorldVelocity(success); if (!success) { return velocity; @@ -354,7 +414,7 @@ glm::vec3 SpatiallyNestable::localToWorldVelocity(const glm::vec3& velocity, con } glm::vec3 SpatiallyNestable::localToWorldAngularVelocity(const glm::vec3& angularVelocity, const QUuid& parentID, - int parentJointIndex, bool& success) { + int parentJointIndex, bool scalesWithParent, bool& success) { SpatiallyNestablePointer parent = SpatiallyNestable::findByID(parentID, success); if (!success || !parent) { return angularVelocity; @@ -363,10 +423,47 @@ glm::vec3 SpatiallyNestable::localToWorldAngularVelocity(const glm::vec3& angula if (!success) { return angularVelocity; } - + if (scalesWithParent) { + parentTransform.setScale(parent->scaleForChildren()); + } return parentTransform.getRotation() * angularVelocity; } + +glm::vec3 SpatiallyNestable::localToWorldDimensions(const glm::vec3& dimensions, + const QUuid& parentID, int parentJointIndex, bool scalesWithParent, + bool& success) { + if (!scalesWithParent) { + success = true; + return dimensions; + } + + Transform result; + QSharedPointer parentFinder = DependencyManager::get(); + if (!parentFinder) { + success = false; + return dimensions; + } + + Transform parentTransform; + auto parentWP = parentFinder->find(parentID, success); + if (!success) { + return dimensions; + } + + auto parent = parentWP.lock(); + if (!parentID.isNull() && !parent) { + success = false; + return dimensions; + } + + success = true; + if (parent) { + return dimensions * parent->scaleForChildren(); + } + return dimensions; +} + glm::vec3 SpatiallyNestable::getWorldPosition(bool& success) const { return getTransform(success).getTranslation(); } @@ -615,10 +712,10 @@ const Transform SpatiallyNestable::getTransform(int jointIndex, bool& success, i // cause this object's parent to query its parent, etc) and multiplies this object's local transform onto it. Transform jointInWorldFrame; - if (depth > maxParentingChain) { + if (depth > MAX_PARENTING_CHAIN_SIZE) { success = false; // someone created a loop. break it... - qCDebug(shared) << "Parenting loop detected."; + qCDebug(shared) << "Parenting loop detected: " << getID(); SpatiallyNestablePointer _this = getThisPointer(); _this->setParentID(QUuid()); bool setPositionSuccess; diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 9cd38f9ce9..2a315e9230 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -50,19 +50,28 @@ public: virtual quint16 getParentJointIndex() const { return _parentJointIndex; } virtual void setParentJointIndex(quint16 parentJointIndex); - static glm::vec3 worldToLocal(const glm::vec3& position, const QUuid& parentID, int parentJointIndex, bool& success); - static glm::quat worldToLocal(const glm::quat& orientation, const QUuid& parentID, int parentJointIndex, bool& success); + static glm::vec3 worldToLocal(const glm::vec3& position, const QUuid& parentID, int parentJointIndex, + bool scalesWithParent, bool& success); + static glm::quat worldToLocal(const glm::quat& orientation, const QUuid& parentID, int parentJointIndex, + bool scalesWithParent, bool& success); static glm::vec3 worldToLocalVelocity(const glm::vec3& velocity, const QUuid& parentID, - int parentJointIndex, bool& success); + int parentJointIndex, bool scalesWithParent, bool& success); static glm::vec3 worldToLocalAngularVelocity(const glm::vec3& angularVelocity, const QUuid& parentID, - int parentJointIndex, bool& success); + int parentJointIndex, bool scalesWithParent, bool& success); + static glm::vec3 worldToLocalDimensions(const glm::vec3& dimensions, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent, bool& success); - static glm::vec3 localToWorld(const glm::vec3& position, const QUuid& parentID, int parentJointIndex, bool& success); - static glm::quat localToWorld(const glm::quat& orientation, const QUuid& parentID, int parentJointIndex, bool& success); + static glm::vec3 localToWorld(const glm::vec3& position, const QUuid& parentID, int parentJointIndex, + bool scalesWithParent, bool& success); + static glm::quat localToWorld(const glm::quat& orientation, const QUuid& parentID, int parentJointIndex, + bool scalesWithParent, bool& success); static glm::vec3 localToWorldVelocity(const glm::vec3& velocity, - const QUuid& parentID, int parentJointIndex, bool& success); + const QUuid& parentID, int parentJointIndex, bool scalesWithParent, bool& success); static glm::vec3 localToWorldAngularVelocity(const glm::vec3& angularVelocity, - const QUuid& parentID, int parentJointIndex, bool& success); + const QUuid& parentID, int parentJointIndex, + bool scalesWithParent, bool& success); + static glm::vec3 localToWorldDimensions(const glm::vec3& dimensions, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent, bool& success); static QString nestableTypeToString(NestableType nestableType); @@ -140,6 +149,9 @@ public: virtual glm::vec3 getLocalSNScale() const; virtual void setLocalSNScale(const glm::vec3& scale); + virtual bool getScalesWithParent() const { return false; } + virtual glm::vec3 scaleForChildren() const { return glm::vec3(1.0f); } + QList getChildren() const; bool hasChildren() const; diff --git a/libraries/shared/src/Trace.cpp b/libraries/shared/src/Trace.cpp index d7feb65ff3..3f6a2dd643 100644 --- a/libraries/shared/src/Trace.cpp +++ b/libraries/shared/src/Trace.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include @@ -31,6 +30,8 @@ #include "Gzip.h" #include "PortableHighResolutionClock.h" +#include "SharedLogging.h" +#include "shared/FileUtils.h" #include "shared/GlobalAppProperties.h" using namespace tracing; @@ -104,30 +105,13 @@ void TraceEvent::writeJson(QTextStream& out) const { #endif } -void Tracer::serialize(const QString& originalPath) { - - QString path = originalPath; - - // Filter for specific tokens potentially present in the path: - auto now = QDateTime::currentDateTime(); - - path = path.replace("{DATE}", now.date().toString("yyyyMMdd")); - path = path.replace("{TIME}", now.time().toString("HHmm")); - - // If the filename is relative, turn it into an absolute path relative to the document directory. - QFileInfo originalFileInfo(path); - if (originalFileInfo.isRelative()) { - QString docsLocation = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); - path = docsLocation + "/" + path; - QFileInfo info(path); - if (!info.absoluteDir().exists()) { - QString originalRelativePath = originalFileInfo.path(); - QDir(docsLocation).mkpath(originalRelativePath); - } +void Tracer::serialize(const QString& filename) { + QString fullPath = FileUtils::replaceDateTimeTokens(filename); + fullPath = FileUtils::computeDocumentPath(fullPath); + if (!FileUtils::canCreateFile(fullPath)) { + return; } - - std::list currentEvents; { std::lock_guard guard(_eventsMutex); @@ -137,11 +121,6 @@ void Tracer::serialize(const QString& originalPath) { } } - // If the file exists and we can't remove it, fail early - if (QFileInfo(path).exists() && !QFile::remove(path)) { - return; - } - // If we can't open a temp file for writing, fail early QByteArray data; { @@ -159,15 +138,16 @@ void Tracer::serialize(const QString& originalPath) { out << "\n]"; } - if (path.endsWith(".gz")) { + if (fullPath.endsWith(".gz")) { QByteArray compressed; gzip(data, compressed); data = compressed; - } - + } + { - QFile file(path); + QFile file(fullPath); if (!file.open(QIODevice::WriteOnly)) { + qDebug(shared) << "failed to open file '" << fullPath << "'"; return; } file.write(data); @@ -191,7 +171,6 @@ void Tracer::serialize(const QString& originalPath) { } } } }; - data = document.toJson(QJsonDocument::Compact); } #endif diff --git a/libraries/shared/src/Transform.cpp b/libraries/shared/src/Transform.cpp index 3e29c38add..67da2f6911 100644 --- a/libraries/shared/src/Transform.cpp +++ b/libraries/shared/src/Transform.cpp @@ -49,7 +49,7 @@ void Transform::evalRotationScale(Quat& rotation, Vec3& scale, const Mat3& rotat // extract scale of the matrix as the length of each axis Mat3 scaleMat = glm::inverse(rotationMat) * rotationScaleMatrix; - scale = glm::max(Vec3(ACCURACY_THREASHOLD), Vec3(scaleMat[0][0], scaleMat[1][1], scaleMat[2][2])); + scale = Vec3(scaleMat[0][0], scaleMat[1][1], scaleMat[2][2]); // Let's work on a local matrix containing rotation only Mat3 matRot( diff --git a/libraries/shared/src/shared/AbstractLoggerInterface.h b/libraries/shared/src/shared/AbstractLoggerInterface.h index 6a3c47a324..c202496103 100644 --- a/libraries/shared/src/shared/AbstractLoggerInterface.h +++ b/libraries/shared/src/shared/AbstractLoggerInterface.h @@ -24,7 +24,21 @@ public: AbstractLoggerInterface(QObject* parent = NULL); ~AbstractLoggerInterface(); inline bool extraDebugging() { return _extraDebugging; } - inline void setExtraDebugging(bool debugging) { _extraDebugging = debugging; } + inline bool debugPrint() { return _debugPrint; } + inline bool infoPrint() { return _infoPrint; } + inline bool criticalPrint() { return _criticalPrint; } + inline bool warningPrint() { return _warningPrint; } + inline bool suppressPrint() { return _suppressPrint; } + inline bool fatalPrint() { return _fatalPrint; } + inline bool unknownPrint() { return _unknownPrint; } + inline void setExtraDebugging(bool extraDebugging) { _extraDebugging = extraDebugging; } + inline void setDebugPrint(bool debugPrint) { _debugPrint = debugPrint; } + inline void setInfoPrint(bool infoPrint) { _infoPrint = infoPrint; } + inline void setCriticalPrint(bool criticalPrint) { _criticalPrint = criticalPrint; } + inline void setWarningPrint(bool warningPrint) { _warningPrint = warningPrint; } + inline void setSuppressPrint(bool suppressPrint) { _suppressPrint = suppressPrint; } + inline void setFatalPrint(bool fatalPrint) { _fatalPrint = fatalPrint; } + inline void setUnknownPrint(bool unknownPrint) { _unknownPrint = unknownPrint; } virtual void addMessage(const QString&) = 0; virtual QString getLogData() = 0; @@ -36,6 +50,13 @@ signals: private: bool _extraDebugging{ false }; + bool _debugPrint{ false }; + bool _infoPrint{ false }; + bool _criticalPrint{ true }; + bool _warningPrint{ true }; + bool _suppressPrint{ true }; + bool _fatalPrint{ true }; + bool _unknownPrint{ true }; }; #endif // hifi_AbstractLoggerInterface_h diff --git a/libraries/shared/src/shared/FileUtils.cpp b/libraries/shared/src/shared/FileUtils.cpp index 8c962dfd6d..dba0af7b16 100644 --- a/libraries/shared/src/shared/FileUtils.cpp +++ b/libraries/shared/src/shared/FileUtils.cpp @@ -12,6 +12,7 @@ #include "FileUtils.h" +#include #include #include #include @@ -20,6 +21,8 @@ #include #include +#include "../SharedLogging.h" + QString FileUtils::readFile(const QString& filename) { QFile file(filename); @@ -82,20 +85,54 @@ QString FileUtils::standardPath(QString subfolder) { // standard path // Mac: ~/Library/Application Support/Interface QString path = QStandardPaths::writableLocation(QStandardPaths::DataLocation); - if (!subfolder.startsWith("/")) { subfolder.prepend("/"); } - if (!subfolder.endsWith("/")) { subfolder.append("/"); } - path.append(subfolder); QDir logDir(path); if (!logDir.exists(path)) { logDir.mkpath(path); } - return path; } + +QString FileUtils::replaceDateTimeTokens(const QString& originalPath) { + // Filter for specific tokens potentially present in the path: + auto now = QDateTime::currentDateTime(); + QString path = originalPath; + path.replace("{DATE}", now.date().toString("yyyyMMdd")); + path.replace("{TIME}", now.time().toString("HHmm")); + return path; +} + + +QString FileUtils::computeDocumentPath(const QString& originalPath) { + // If the filename is relative, turn it into an absolute path relative to the document directory. + QString path = originalPath; + QFileInfo originalFileInfo(originalPath); + if (originalFileInfo.isRelative()) { + QString docsLocation = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + path = docsLocation + "/" + originalPath; + } + return path; +} + +bool FileUtils::canCreateFile(const QString& fullPath) { + // If the file exists and we can't remove it, fail early + QFileInfo fileInfo(fullPath); + if (fileInfo.exists() && !QFile::remove(fullPath)) { + qDebug(shared) << "unable to overwrite file '" << fullPath << "'"; + return false; + } + QDir dir(fileInfo.absolutePath()); + if (!dir.exists()) { + if (!dir.mkpath(fullPath)) { + qDebug(shared) << "unable to create dir '" << dir.absolutePath() << "'"; + return false; + } + } + return true; +} diff --git a/libraries/shared/src/shared/FileUtils.h b/libraries/shared/src/shared/FileUtils.h index 4f2c1b7af5..d68fcd8a44 100644 --- a/libraries/shared/src/shared/FileUtils.h +++ b/libraries/shared/src/shared/FileUtils.h @@ -21,6 +21,9 @@ public: static QString standardPath(QString subfolder); static QString readFile(const QString& filename); static QStringList readLines(const QString& filename, QString::SplitBehavior splitBehavior = QString::KeepEmptyParts); + static QString replaceDateTimeTokens(const QString& path); + static QString computeDocumentPath(const QString& path); + static bool canCreateFile(const QString& fullPath); }; #endif // hifi_FileUtils_h diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 16a62cea4b..905a224ef4 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -134,9 +134,6 @@ void OffscreenUi::create() { myContext->setContextProperty("OffscreenUi", this); myContext->setContextProperty("offscreenFlags", offscreenFlags = new OffscreenFlags()); myContext->setContextProperty("fileDialogHelper", new FileDialogHelper()); - auto tabletScriptingInterface = DependencyManager::get(); - TabletProxy* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"); - myContext->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership); } void OffscreenUi::show(const QUrl& url, const QString& name, std::function f) { diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index 14d8ec8985..90b91c5ec2 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -61,8 +61,9 @@ QVariantMap QmlWindowClass::parseArguments(QScriptContext* context) { } QUrl url { properties[SOURCE_PROPERTY].toString() }; + // If the passed URL doesn't correspond to a known scheme, assume it's a local file path if (url.scheme() != "http" && url.scheme() != "https" && url.scheme() != "file" && url.scheme() != "about" && - url.scheme() != "atp") { + url.scheme() != "atp" && url.scheme() != "qrc") { properties[SOURCE_PROPERTY] = QUrl::fromLocalFile(url.toString()).toString(); } diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 8f696275ba..902f91f9b8 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -52,6 +52,8 @@ #include "types/HFWebEngineProfile.h" #include "types/SoundEffect.h" +#include "TabletScriptingInterface.h" +#include "ToolbarScriptingInterface.h" #include "Logging.h" Q_LOGGING_CATEGORY(trace_render_qml, "trace.render.qml") @@ -66,7 +68,10 @@ public: void addWhitelistContextHandler(const std::initializer_list& urls, const QmlContextCallback& callback) { withWriteLock([&] { - for (const auto& url : urls) { + for (auto url : urls) { + if (url.isRelative()) { + url = QUrl(PathUtils::qmlBasePath() + url.toString()); + } _callbacks[url].push_back(callback); } }); @@ -103,7 +108,7 @@ void OffscreenQmlSurface::addWhitelistContextHandler(const std::initializer_list } -QmlContextCallback OffscreenQmlSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*, QObject*) {}; +QmlContextObjectCallback OffscreenQmlSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*, QQuickItem*) {}; struct TextureSet { // The number of surfaces with this size @@ -450,6 +455,15 @@ void initializeQmlEngine(QQmlEngine* engine, QQuickWindow* window) { rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext)); #endif rootContext->setContextProperty("Paths", DependencyManager::get().data()); + static std::once_flag once; + std::call_once(once, [&] { + qRegisterMetaType(); + qRegisterMetaType(); + }); + rootContext->setContextProperty("Tablet", DependencyManager::get().data()); + rootContext->setContextProperty("Toolbars", DependencyManager::get().data()); + TabletProxy* tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); + engine->setObjectOwnership(tablet, QQmlEngine::CppOwnership); } QQmlEngine* acquireEngine(QQuickWindow* window) { @@ -668,10 +682,11 @@ void OffscreenQmlSurface::create() { auto qmlEngine = acquireEngine(_quickWindow); _qmlContext = new QQmlContext(qmlEngine->rootContext()); - + _qmlContext->setBaseUrl(QUrl{ PathUtils::qmlBasePath() }); _qmlContext->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow())); _qmlContext->setContextProperty("eventBridge", this); _qmlContext->setContextProperty("webEntity", this); + _qmlContext->setContextProperty("QmlSurface", this); // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper // Find a way to flag older scripts using this mechanism and wanr that this is deprecated @@ -818,55 +833,70 @@ QQuickItem* OffscreenQmlSurface::getRootItem() { return _rootItem; } -void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) { - _qmlContext->setBaseUrl(baseUrl); +QQmlContext* OffscreenQmlSurface::contextForUrl(const QUrl& qmlSource, QQuickItem* parent, bool forceNewContext) { + // Get any whitelist functionality + QList callbacks = getQmlWhitelist()->getCallbacksForUrl(qmlSource); + // If we have whitelisted content, we must load a new context + forceNewContext |= !callbacks.empty(); + + QQmlContext* targetContext = parent ? QQmlEngine::contextForObject(parent) : _qmlContext; + if (!targetContext) { + targetContext = _qmlContext; + } + if (_rootItem && forceNewContext) { + targetContext = new QQmlContext(targetContext); + } + + for (const auto& callback : callbacks) { + callback(targetContext); + } + + return targetContext; } -void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, const QmlContextCallback& onQmlLoadedCallback) { +void OffscreenQmlSurface::load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback) { + loadInternal(qmlSource, false, parent, [callback](QQmlContext* context, QQuickItem* newItem) { + QJSValue(callback).call(QJSValueList() << context->engine()->newQObject(newItem)); + }); +} + +void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& onQmlLoadedCallback) { + loadInternal(qmlSource, createNewContext, nullptr, onQmlLoadedCallback); +} + +void OffscreenQmlSurface::loadInternal(const QUrl& qmlSource, bool createNewContext, QQuickItem* parent, const QmlContextObjectCallback& onQmlLoadedCallback) { if (QThread::currentThread() != thread()) { qCWarning(uiLogging) << "Called load on a non-surface thread"; } // Synchronous loading may take a while; restart the deadlock timer QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection); - // Get any whitelist functionality - QList callbacks = getQmlWhitelist()->getCallbacksForUrl(qmlSource); - // If we have whitelisted content, we must load a new context - createNewContext |= !callbacks.empty(); - callbacks.push_back(onQmlLoadedCallback); - - QQmlContext* targetContext = _qmlContext; - if (_rootItem && createNewContext) { - targetContext = new QQmlContext(targetContext); - } - - - // FIXME eliminate loading of relative file paths for QML QUrl finalQmlSource = qmlSource; if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) { finalQmlSource = _qmlContext->resolvedUrl(qmlSource); } + auto targetContext = contextForUrl(finalQmlSource, parent, createNewContext); auto qmlComponent = new QQmlComponent(_qmlContext->engine(), finalQmlSource, QQmlComponent::PreferSynchronous); if (qmlComponent->isLoading()) { connect(qmlComponent, &QQmlComponent::statusChanged, this, [=](QQmlComponent::Status) { - finishQmlLoad(qmlComponent, targetContext, callbacks); + finishQmlLoad(qmlComponent, targetContext, parent, onQmlLoadedCallback); }); return; } - finishQmlLoad(qmlComponent, targetContext, callbacks); + finishQmlLoad(qmlComponent, targetContext, parent, onQmlLoadedCallback); } -void OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback) { +void OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback) { load(qmlSource, true, onQmlLoadedCallback); } -void OffscreenQmlSurface::load(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback) { +void OffscreenQmlSurface::load(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback) { load(qmlSource, false, onQmlLoadedCallback); } -void OffscreenQmlSurface::load(const QString& qmlSourceFile, const QmlContextCallback& onQmlLoadedCallback) { +void OffscreenQmlSurface::load(const QString& qmlSourceFile, const QmlContextObjectCallback& onQmlLoadedCallback) { return load(QUrl(qmlSourceFile), onQmlLoadedCallback); } @@ -874,7 +904,8 @@ void OffscreenQmlSurface::clearCache() { _qmlContext->engine()->clearComponentCache(); } -void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, const QList& callbacks) { + +void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, QQuickItem* parent, const QmlContextObjectCallback& callback) { disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0); if (qmlComponent->isError()) { for (const auto& error : qmlComponent->errors()) { @@ -896,6 +927,22 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext return; } + if (!newObject) { + if (!_rootItem) { + qFatal("Could not load object as root item"); + return; + } + qCWarning(uiLogging) << "Unable to load QML item"; + return; + } + + QObject* eventBridge = qmlContext->contextProperty("eventBridge").value(); + if (qmlContext != _qmlContext && eventBridge && eventBridge != this) { + // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper + // Find a way to flag older scripts using this mechanism and wanr that this is deprecated + qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(eventBridge, qmlContext)); + } + qmlContext->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); // All quick items should be focusable @@ -906,41 +953,30 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext newItem->setFlag(QQuickItem::ItemIsFocusScope, true); } + // Make sure we will call callback for this codepath // Call this before qmlComponent->completeCreate() otherwise ghost window appears - if (newItem && _rootItem) { - for (const auto& callback : callbacks) { - callback(qmlContext, newObject); - } - } + // If we already have a root, just set a couple of flags and the ancestry + if (_rootItem) { + callback(qmlContext, newItem); - QObject* eventBridge = qmlContext->contextProperty("eventBridge").value(); - if (qmlContext != _qmlContext && eventBridge && eventBridge != this) { - // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper - // Find a way to flag older scripts using this mechanism and wanr that this is deprecated - qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(eventBridge, qmlContext)); + if (!parent) { + parent = _rootItem; + } + // Allow child windows to be destroyed from JS + QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership); + newObject->setParent(parent); + newItem->setParentItem(parent); } qmlComponent->completeCreate(); qmlComponent->deleteLater(); - // If we already have a root, just set a couple of flags and the ancestry - if (newItem && _rootItem) { - // Allow child windows to be destroyed from JS - QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership); - newObject->setParent(_rootItem); - if (newItem) { - newItem->setParentItem(_rootItem); - } + if (_rootItem) { QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection); return; } - if (!newItem) { - qFatal("Could not load object as root item"); - return; - } - connect(newItem, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(QVariant))); // The root item is ready. Associate it with the window. @@ -948,11 +984,16 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext _rootItem->setParentItem(_quickWindow->contentItem()); _rootItem->setSize(_quickWindow->renderTargetSize()); - // Call this callback after rootitem is set, otherwise VrMenu wont work - for (const auto& callback : callbacks) { - callback(qmlContext, newObject); + if (_rootItem->objectName() == "tabletRoot") { + _qmlContext->setContextProperty("tabletRoot", QVariant::fromValue(_rootItem)); + auto tabletScriptingInterface = DependencyManager::get(); + tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", this); + QObject* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"); + _qmlContext->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership); } QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection); + // Call this callback after rootitem is set, otherwise VrMenu wont work + callback(qmlContext, newItem); } void OffscreenQmlSurface::updateQuick() { diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h index 03f7475f61..4251bb6a9d 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.h +++ b/libraries/ui/src/ui/OffscreenQmlSurface.h @@ -33,12 +33,14 @@ class QQmlContext; class QQmlComponent; class QQuickWindow; class QQuickItem; +class QJSValue; // GPU resources are typically buffered for one copy being used by the renderer, // one copy in flight, and one copy being used by the receiver #define GPU_RESOURCE_BUFFER_SIZE 3 -using QmlContextCallback = std::function; +using QmlContextCallback = std::function; +using QmlContextObjectCallback = std::function; class OffscreenQmlSurface : public QObject, public QEnableSharedFromThis { Q_OBJECT @@ -46,7 +48,7 @@ class OffscreenQmlSurface : public QObject, public QEnableSharedFromThis& urls, const QmlContextCallback& callback); static void addWhitelistContextHandler(const QUrl& url, const QmlContextCallback& callback) { addWhitelistContextHandler({ { url } }, callback); }; @@ -59,10 +61,15 @@ public: void resize(const QSize& size, bool forceResize = false); QSize size() const; - Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); - Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); - Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); - Q_INVOKABLE void load(const QString& qmlSourceFile, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); + // Usable from QML code as QmlSurface.load(url, parent, function(newItem){ ... }) + Q_INVOKABLE void load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback); + + // For C++ use + Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); + Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); + Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); + Q_INVOKABLE void load(const QString& qmlSourceFile, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); + void clearCache(); void setMaxFps(uint8_t maxFps) { _maxFps = maxFps; } // Optional values for event handling @@ -77,7 +84,6 @@ public: bool isPaused() const; bool getCleaned() { return _isCleaned; } - void setBaseUrl(const QUrl& baseUrl); QQuickItem* getRootItem(); QQuickWindow* getWindow(); QObject* getEventHandler(); @@ -142,13 +148,13 @@ protected: private: static QOpenGLContext* getSharedContext(); - void finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, const QList& callbacks); + QQmlContext* contextForUrl(const QUrl& url, QQuickItem* parent, bool forceNewContext = false); + void loadInternal(const QUrl& qmlSource, bool createNewContext, QQuickItem* parent, const QmlContextObjectCallback& onQmlLoadedCallback); + void finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, QQuickItem* parent, const QmlContextObjectCallback& onQmlLoadedCallback); QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject); - void setupFbo(); bool allowNewFrame(uint8_t fps); void render(); void cleanup(); - QJsonObject getGLContextData(); void disconnectAudioOutputTimer(); private slots: diff --git a/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp b/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp index 2a0ca4a2e9..9b6b031dd9 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp @@ -45,7 +45,6 @@ void OffscreenQmlSurfaceCache::release(const QString& rootSource, const QSharedP QSharedPointer OffscreenQmlSurfaceCache::buildSurface(const QString& rootSource) { auto surface = QSharedPointer(new OffscreenQmlSurface()); surface->create(); - surface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); surface->load(rootSource); surface->resize(QSize(100, 100)); return surface; diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index dd26dd7be7..c69ec1ce84 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -32,6 +32,14 @@ const QString SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system"; const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system"; const QString TabletScriptingInterface::QML = "hifi/tablet/TabletRoot.qml"; +static QString getUsername() { + QString username = "Unknown user"; + auto accountManager = DependencyManager::get(); + if (accountManager->isLoggedIn()) { + username = accountManager->getAccountInfo().getUsername(); + } + return username; +} static Setting::Handle tabletSoundsButtonClick("TabletSounds", QStringList { "/sounds/Button06.wav", "/sounds/Button04.wav", @@ -39,8 +47,93 @@ static Setting::Handle tabletSoundsButtonClick("TabletSounds", QStr "/sounds/Tab01.wav", "/sounds/Tab02.wav" }); +TabletButtonListModel::TabletButtonListModel() { + +} + +TabletButtonListModel::~TabletButtonListModel() { + +} + +enum ButtonDeviceRole { + ButtonProxyRole = Qt::UserRole, +}; + +QHash TabletButtonListModel::_roles{ + { ButtonProxyRole, "buttonProxy" }, +}; + +Qt::ItemFlags TabletButtonListModel::_flags{ Qt::ItemIsSelectable | Qt::ItemIsEnabled }; + +QVariant TabletButtonListModel::data(const QModelIndex& index, int role) const { + if (!index.isValid() || index.row() >= rowCount() || role != ButtonProxyRole) { + return QVariant(); + } + + return QVariant::fromValue(_buttons.at(index.row()).data()); +} + +TabletButtonProxy* TabletButtonListModel::addButton(const QVariant& properties) { + auto tabletButtonProxy = QSharedPointer(new TabletButtonProxy(properties.toMap())); + beginResetModel(); + _buttons.push_back(tabletButtonProxy); + endResetModel(); + return tabletButtonProxy.data(); +} + +void TabletButtonListModel::removeButton(TabletButtonProxy* button) { + auto itr = std::find(_buttons.begin(), _buttons.end(), button); + if (itr == _buttons.end()) { + qCWarning(uiLogging) << "TabletProxy::removeButton() could not find button " << button; + return; + } + beginResetModel(); + _buttons.erase(itr); + endResetModel(); +} + +TabletButtonsProxyModel::TabletButtonsProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) { +} + +int TabletButtonsProxyModel::pageIndex() const { + return _pageIndex; +} + +int TabletButtonsProxyModel::buttonIndex(const QString &uuid) { + if (!sourceModel() || _pageIndex < 0) { + return -1; + } + TabletButtonListModel* model = static_cast(sourceModel()); + for (int i = 0; i < model->rowCount(); i++) { + TabletButtonProxy* bproxy = model->data(model->index(i), ButtonProxyRole).value(); + if (bproxy && bproxy->getUuid().toString().contains(uuid)) { + return i - (_pageIndex*TabletScriptingInterface::ButtonsOnPage); + } + } + return -1; +} + +void TabletButtonsProxyModel::setPageIndex(int pageIndex) +{ + if (_pageIndex == pageIndex) + return; + + _pageIndex = pageIndex; + invalidateFilter(); + emit pageIndexChanged(_pageIndex); +} + +bool TabletButtonsProxyModel::filterAcceptsRow(int sourceRow, + const QModelIndex &sourceParent) const { + Q_UNUSED(sourceParent); + return (sourceRow >= _pageIndex*TabletScriptingInterface::ButtonsOnPage + && sourceRow < (_pageIndex + 1)*TabletScriptingInterface::ButtonsOnPage); +} + TabletScriptingInterface::TabletScriptingInterface() { qmlRegisterType("TabletScriptingInterface", 1, 0, "TabletEnums"); + qmlRegisterType("TabletScriptingInterface", 1, 0, "TabletButtonsProxyModel"); } TabletScriptingInterface::~TabletScriptingInterface() { @@ -210,9 +303,9 @@ QObject* TabletScriptingInterface::getFlags() { // TabletProxy // -static const char* TABLET_SOURCE_URL = "Tablet.qml"; -static const char* WEB_VIEW_SOURCE_URL = "TabletWebView.qml"; -static const char* VRMENU_SOURCE_URL = "TabletMenu.qml"; +static const char* TABLET_HOME_SOURCE_URL = "hifi/tablet/TabletHome.qml"; +static const char* WEB_VIEW_SOURCE_URL = "hifi/tablet/TabletWebView.qml"; +static const char* VRMENU_SOURCE_URL = "hifi/tablet/TabletMenu.qml"; class TabletRootWindow : public QmlWindowClass { virtual QString qmlSource() const override { return "hifi/tablet/WindowRoot.qml"; } @@ -247,9 +340,6 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { auto offscreenUi = DependencyManager::get(); if (toolbarMode) { - removeButtonsFromHomeScreen(); - addButtonsToToolbar(); - // create new desktop window auto tabletRootWindow = new TabletRootWindow(); tabletRootWindow->initQml(QVariantMap()); @@ -264,11 +354,7 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { // forward qml surface events to interface js connect(tabletRootWindow, &QmlWindowClass::fromQml, this, &TabletProxy::fromQml); } else { - removeButtonsFromToolbar(); - - if (_currentPathLoaded == TABLET_SOURCE_URL) { - addButtonsToHomeScreen(); - } else { + if (_currentPathLoaded != TABLET_HOME_SOURCE_URL) { loadHomeScreen(true); } //check if running scripts window opened and save it for reopen in Tablet @@ -282,40 +368,8 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { _desktopWindow = nullptr; } } -} -static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) { - Q_ASSERT(QThread::currentThread() == qApp->thread()); - if (buttonProxy == NULL){ - qCCritical(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet buttonProxy is NULL"; - return; - } - - QVariant resultVar; - bool hasResult = QMetaObject::invokeMethod(qmlTablet, "addButtonProxy", Qt::DirectConnection, - Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, buttonProxy->getProperties())); - if (!hasResult) { - qCWarning(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet has no result"; - return; - } - - QObject* qmlButton = qvariant_cast(resultVar); - if (!qmlButton) { - qCWarning(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet result not a QObject"; - return; - } - QObject::connect(qmlButton, SIGNAL(clicked()), buttonProxy, SLOT(clickedSlot())); - buttonProxy->setQmlButton(qobject_cast(qmlButton)); -} - -static QString getUsername() { - QString username = "Unknown user"; - auto accountManager = DependencyManager::get(); - if (accountManager->isLoggedIn()) { - return accountManager->getAccountInfo().getUsername(); - } else { - return "Unknown user"; - } + emit toolbarModeChanged(); } void TabletProxy::initialScreen(const QVariant& url) { @@ -363,7 +417,7 @@ void TabletProxy::onTabletShown() { static_cast(parent())->playSound(TabletScriptingInterface::TabletOpen); if (_showRunningScripts) { _showRunningScripts = false; - pushOntoStack("../../hifi/dialogs/TabletRunningScripts.qml"); + pushOntoStack("hifi/dialogs/TabletRunningScripts.qml"); } } } @@ -397,10 +451,7 @@ void TabletProxy::setQmlTabletRoot(OffscreenQmlSurface* qmlOffscreenSurface) { }); if (_toolbarMode) { - // if someone creates the tablet in toolbar mode, make sure to display the home screen on the tablet. - auto loader = _qmlTabletRoot->findChild("loader"); - QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen())); - QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL))); + QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_HOME_SOURCE_URL))); } // force to the tablet to go to the homescreen @@ -436,7 +487,6 @@ void TabletProxy::setQmlTabletRoot(OffscreenQmlSurface* qmlOffscreenSurface) { QMetaObject::invokeMethod(_qmlTabletRoot, "setShown", Q_ARG(const QVariant&, QVariant(true))); } } else { - removeButtonsFromHomeScreen(); _state = State::Uninitialized; emit screenChanged(QVariant("Closed"), QVariant("")); _currentPathLoaded = ""; @@ -465,7 +515,6 @@ void TabletProxy::gotoMenuScreen(const QString& submenu) { } if (root) { - removeButtonsFromHomeScreen(); auto offscreenUi = DependencyManager::get(); QObject* menu = offscreenUi->getRootMenu(); QMetaObject::invokeMethod(root, "setMenuProperties", Q_ARG(QVariant, QVariant::fromValue(menu)), Q_ARG(const QVariant&, QVariant(submenu))); @@ -539,7 +588,6 @@ void TabletProxy::loadQMLSource(const QVariant& path, bool resizable) { } if (root) { - removeButtonsFromHomeScreen(); //works only in Tablet QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, path)); _state = State::QML; if (path != _currentPathLoaded) { @@ -621,9 +669,7 @@ void TabletProxy::loadHomeScreen(bool forceOntoHomeScreen) { if ((_state != State::Home && _state != State::Uninitialized) || forceOntoHomeScreen) { if (!_toolbarMode && _qmlTabletRoot) { - auto loader = _qmlTabletRoot->findChild("loader"); - QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen())); - QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL))); + QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_HOME_SOURCE_URL))); QMetaObject::invokeMethod(_qmlTabletRoot, "playButtonClickSound"); } else if (_toolbarMode && _desktopWindow) { // close desktop window @@ -632,8 +678,8 @@ void TabletProxy::loadHomeScreen(bool forceOntoHomeScreen) { } } _state = State::Home; - emit screenChanged(QVariant("Home"), QVariant(TABLET_SOURCE_URL)); - _currentPathLoaded = TABLET_SOURCE_URL; + emit screenChanged(QVariant("Home"), QVariant(TABLET_HOME_SOURCE_URL)); + _currentPathLoaded = TABLET_HOME_SOURCE_URL; } } @@ -683,17 +729,18 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS } if (root) { - removeButtonsFromHomeScreen(); if (loadOtherBase) { - QMetaObject::invokeMethod(root, "loadTabletWebBase"); + QMetaObject::invokeMethod(root, "loadTabletWebBase", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl))); } else { - QMetaObject::invokeMethod(root, "loadWebBase"); + QMetaObject::invokeMethod(root, "loadWebBase", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl))); } QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true))); if (_toolbarMode && _desktopWindow) { QMetaObject::invokeMethod(root, "setResizable", Q_ARG(const QVariant&, QVariant(false))); } - QMetaObject::invokeMethod(root, "loadWebUrl", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl))); + _state = State::Web; + emit screenChanged(QVariant("Web"), QVariant(url)); + _currentPathLoaded = QVariant(url); } else { // tablet is not initialized yet, save information and load when // the tablet root is set @@ -702,10 +749,8 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS _initialWebPathParams.first = injectedJavaScriptUrl; _initialWebPathParams.second = loadOtherBase; _initialScreen = true; + } - _state = State::Web; - emit screenChanged(QVariant("Web"), QVariant(url)); - _currentPathLoaded = QVariant(url); } TabletButtonProxy* TabletProxy::addButton(const QVariant& properties) { @@ -715,24 +760,7 @@ TabletButtonProxy* TabletProxy::addButton(const QVariant& properties) { return result; } - auto tabletButtonProxy = QSharedPointer(new TabletButtonProxy(properties.toMap())); - _tabletButtonProxies.push_back(tabletButtonProxy); - if (!_toolbarMode && _qmlTabletRoot) { - auto tablet = getQmlTablet(); - if (tablet) { - addButtonProxyToQmlTablet(tablet, tabletButtonProxy.data()); - } else { - qCCritical(uiLogging) << "Could not find tablet in TabletRoot.qml"; - } - } else if (_toolbarMode) { - auto toolbarProxy = DependencyManager::get()->getSystemToolbarProxy(); - if (toolbarProxy) { - // copy properties from tablet button proxy to toolbar button proxy. - auto toolbarButtonProxy = toolbarProxy->addButton(tabletButtonProxy->getProperties()); - tabletButtonProxy->setToolbarButtonProxy(toolbarButtonProxy); - } - } - return tabletButtonProxy.data(); + return _buttons.addButton(properties); } bool TabletProxy::onHomeScreen() { @@ -751,35 +779,7 @@ void TabletProxy::removeButton(TabletButtonProxy* tabletButtonProxy) { return; } - auto tablet = getQmlTablet(); - if (!tablet) { - qCCritical(uiLogging) << "Could not find tablet in TabletRoot.qml"; - } - - QSharedPointer buttonProxy; - { - auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy); - if (iter == _tabletButtonProxies.end()) { - qCWarning(uiLogging) << "TabletProxy::removeButton() could not find button " << tabletButtonProxy; - return; - } - buttonProxy = *iter; - _tabletButtonProxies.erase(iter); - } - - if (!_toolbarMode && _qmlTabletRoot) { - buttonProxy->setQmlButton(nullptr); - if (tablet) { - QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getProperties())); - } - } else if (_toolbarMode) { - auto toolbarProxy = DependencyManager::get()->getSystemToolbarProxy(); - // remove button from toolbarProxy - if (toolbarProxy) { - toolbarProxy->removeButton(buttonProxy->getUuid().toString()); - buttonProxy->setToolbarButtonProxy(nullptr); - } - } + _buttons.removeButton(tabletButtonProxy); } void TabletProxy::emitScriptEvent(const QVariant& msg) { @@ -808,57 +808,17 @@ void TabletProxy::sendToQml(const QVariant& msg) { } } -void TabletProxy::addButtonsToHomeScreen() { - auto tablet = getQmlTablet(); - if (!tablet || _toolbarMode) { - return; - } - for (auto& buttonProxy : _tabletButtonProxies) { - addButtonProxyToQmlTablet(tablet, buttonProxy.data()); - } - auto loader = _qmlTabletRoot->findChild("loader"); - QObject::disconnect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen())); -} + OffscreenQmlSurface* TabletProxy::getTabletSurface() { return _qmlOffscreenSurface; } -void TabletProxy::removeButtonsFromHomeScreen() { - auto tablet = getQmlTablet(); - for (auto& buttonProxy : _tabletButtonProxies) { - if (tablet) { - QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getProperties())); - } - buttonProxy->setQmlButton(nullptr); - } -} void TabletProxy::desktopWindowClosed() { gotoHomeScreen(); } -void TabletProxy::addButtonsToToolbar() { - Q_ASSERT(QThread::currentThread() == thread()); - ToolbarProxy* toolbarProxy = DependencyManager::get()->getSystemToolbarProxy(); - for (auto& buttonProxy : _tabletButtonProxies) { - // copy properties from tablet button proxy to toolbar button proxy. - buttonProxy->setToolbarButtonProxy(toolbarProxy->addButton(buttonProxy->getProperties())); - } - - // make the toolbar visible - toolbarProxy->writeProperty("visible", QVariant(true)); -} - -void TabletProxy::removeButtonsFromToolbar() { - Q_ASSERT(QThread::currentThread() == thread()); - ToolbarProxy* toolbarProxy = DependencyManager::get()->getSystemToolbarProxy(); - for (auto& buttonProxy : _tabletButtonProxies) { - // remove button from toolbarProxy - toolbarProxy->removeButton(buttonProxy->getUuid().toString()); - buttonProxy->setToolbarButtonProxy(nullptr); - } -} QQuickItem* TabletProxy::getQmlTablet() const { if (!_qmlTabletRoot) { @@ -928,25 +888,6 @@ TabletButtonProxy::~TabletButtonProxy() { } } -void TabletButtonProxy::setQmlButton(QQuickItem* qmlButton) { - Q_ASSERT(QThread::currentThread() == qApp->thread()); - if (_qmlButton) { - QObject::disconnect(_qmlButton, &QQuickItem::destroyed, this, nullptr); - } - _qmlButton = qmlButton; - if (_qmlButton) { - QObject::connect(_qmlButton, &QQuickItem::destroyed, this, [this] { _qmlButton = nullptr; }); - } -} - -void TabletButtonProxy::setToolbarButtonProxy(QObject* toolbarButtonProxy) { - Q_ASSERT(QThread::currentThread() == thread()); - _toolbarButtonProxy = toolbarButtonProxy; - if (_toolbarButtonProxy) { - QObject::connect(_toolbarButtonProxy, SIGNAL(clicked()), this, SLOT(clickedSlot())); - } -} - QVariantMap TabletButtonProxy::getProperties() { if (QThread::currentThread() != thread()) { QVariantMap result; @@ -963,20 +904,19 @@ void TabletButtonProxy::editProperties(const QVariantMap& properties) { return; } + bool changed = false; QVariantMap::const_iterator iter = properties.constBegin(); while (iter != properties.constEnd()) { const auto& key = iter.key(); const auto& value = iter.value(); if (!_properties.contains(key) || _properties[key] != value) { - _properties[iter.key()] = iter.value(); - if (_qmlButton) { - QMetaObject::invokeMethod(_qmlButton, "changeProperty", Qt::AutoConnection, Q_ARG(QVariant, QVariant(iter.key())), Q_ARG(QVariant, iter.value())); - } + _properties[key] = value; + changed = true; } ++iter; } - if (_toolbarButtonProxy) { - QMetaObject::invokeMethod(_toolbarButtonProxy, "editProperties", Qt::AutoConnection, Q_ARG(QVariantMap, properties)); + if (changed) { + emit propertiesChanged(); } } diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index ad6a7c8001..56e3ae257b 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -12,13 +12,17 @@ #include #include -#include -#include +#include +#include +#include +#include +#include + #include -#include -#include -#include -#include +#include +#include + +#include #include #include @@ -43,6 +47,10 @@ public: enum TabletAudioEvents { ButtonClick, ButtonHover, TabletOpen, TabletHandsIn, TabletHandsOut, Last}; Q_ENUM(TabletAudioEvents) + //Different useful constants + enum TabletConstants { ButtonsColumnsOnPage = 3, ButtonsRowsOnPage = 4, ButtonsOnPage = 12 }; + Q_ENUM(TabletConstants) + TabletScriptingInterface(); virtual ~TabletScriptingInterface(); static const QString QML; @@ -90,6 +98,56 @@ protected: bool _toolbarMode { false }; }; +class TabletButtonListModel : public QAbstractListModel { + Q_OBJECT + +public: + TabletButtonListModel(); + ~TabletButtonListModel(); + + int rowCount(const QModelIndex& parent = QModelIndex()) const override { Q_UNUSED(parent); return (int)_buttons.size(); } + QHash roleNames() const override { return _roles; } + Qt::ItemFlags flags(const QModelIndex& index) const override { return _flags; } + QVariant data(const QModelIndex& index, int role) const override; + + +protected: + friend class TabletProxy; + TabletButtonProxy* addButton(const QVariant& properties); + void removeButton(TabletButtonProxy* button); + using List = std::list>; + static QHash _roles; + static Qt::ItemFlags _flags; + std::vector> _buttons; +}; + +Q_DECLARE_METATYPE(TabletButtonListModel*); + +class TabletButtonsProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + + Q_PROPERTY(int pageIndex READ pageIndex WRITE setPageIndex NOTIFY pageIndexChanged) +public: + TabletButtonsProxyModel(QObject* parent = 0); + int pageIndex() const; + Q_INVOKABLE int buttonIndex(const QString& uuid); + +public slots: + void setPageIndex(int pageIndex); + +signals: + void pageIndexChanged(int pageIndex); + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + +private: + int _pageIndex { -1 }; +}; + +Q_DECLARE_METATYPE(TabletButtonsProxyModel*); + /**jsdoc * @class TabletProxy * @property name {string} READ_ONLY: name of this tablet @@ -99,9 +157,10 @@ protected: class TabletProxy : public QObject { Q_OBJECT Q_PROPERTY(QString name READ getName) - Q_PROPERTY(bool toolbarMode READ getToolbarMode WRITE setToolbarMode) + Q_PROPERTY(bool toolbarMode READ getToolbarMode WRITE setToolbarMode NOTIFY toolbarModeChanged) Q_PROPERTY(bool landscape READ getLandscape WRITE setLandscape) Q_PROPERTY(bool tabletShown MEMBER _tabletShown NOTIFY tabletShownChanged) + Q_PROPERTY(TabletButtonListModel* buttons READ getButtons CONSTANT) public: TabletProxy(QObject* parent, const QString& name); ~TabletProxy(); @@ -204,6 +263,8 @@ public: QQuickItem* getQmlMenu() const; + TabletButtonListModel* getButtons() { return &_buttons; } + signals: /**jsdoc * Signaled when this tablet receives an event from the html/js embedded in the tablet @@ -236,21 +297,19 @@ signals: */ void tabletShownChanged(); + void toolbarModeChanged(); + protected slots: - void addButtonsToHomeScreen(); void desktopWindowClosed(); void emitWebEvent(const QVariant& msg); void onTabletShown(); + protected: - void removeButtonsFromHomeScreen(); void loadHomeScreen(bool forceOntoHomeScreen); - void addButtonsToToolbar(); - void removeButtonsFromToolbar(); bool _initialScreen { false }; QVariant _currentPathLoaded { "" }; QString _name; - std::vector> _tabletButtonProxies; QQuickItem* _qmlTabletRoot { nullptr }; OffscreenQmlSurface* _qmlOffscreenSurface { nullptr }; QmlWindowClass* _desktopWindow { nullptr }; @@ -263,6 +322,8 @@ protected: std::pair _initialWebPathParams; bool _landscape { false }; bool _showRunningScripts { false }; + + TabletButtonListModel _buttons; }; Q_DECLARE_METATYPE(TabletProxy*); @@ -274,13 +335,11 @@ Q_DECLARE_METATYPE(TabletProxy*); class TabletButtonProxy : public QObject { Q_OBJECT Q_PROPERTY(QUuid uuid READ getUuid) + Q_PROPERTY(QVariantMap properties READ getProperties NOTIFY propertiesChanged) public: TabletButtonProxy(const QVariantMap& properties); ~TabletButtonProxy(); - void setQmlButton(QQuickItem* qmlButton); - void setToolbarButtonProxy(QObject* toolbarButtonProxy); - QUuid getUuid() const { return _uuid; } /**jsdoc @@ -297,9 +356,6 @@ public: */ Q_INVOKABLE void editProperties(const QVariantMap& properties); -public slots: - void clickedSlot() { emit clicked(); } - signals: /**jsdoc * Signaled when this button has been clicked on by the user. @@ -307,12 +363,11 @@ signals: * @returns {Signal} */ void clicked(); + void propertiesChanged(); protected: QUuid _uuid; int _stableOrder; - QQuickItem* _qmlButton { nullptr }; - QObject* _toolbarButtonProxy { nullptr }; QVariantMap _properties; }; diff --git a/scripts/developer/EZrecord.js b/scripts/developer/EZrecord.js index 7fdebada79..049ccbc03d 100644 --- a/scripts/developer/EZrecord.js +++ b/scripts/developer/EZrecord.js @@ -234,7 +234,7 @@ Recorder.setUp(); - // Tablet/toolbar button. + // tablet/toolbar button. button = tablet.addButton({ icon: APP_ICON_INACTIVE, activeIcon: APP_ICON_ACTIVE, diff --git a/scripts/developer/inputRecording.js b/scripts/developer/inputRecording.js index 85bda623b3..6fb8e471cd 100644 --- a/scripts/developer/inputRecording.js +++ b/scripts/developer/inputRecording.js @@ -19,7 +19,7 @@ tablet.gotoHomeScreen(); onRecordingScreen = false; } else { - tablet.loadQMLSource("InputRecorder.qml"); + tablet.loadQMLSource("hifi/tablet/InputRecorder.qml"); onRecordingScreen = true; } } diff --git a/scripts/developer/tests/qmlTest.js b/scripts/developer/tests/qmlTest.js index c891b6a1b7..0eaabac6d1 100644 --- a/scripts/developer/tests/qmlTest.js +++ b/scripts/developer/tests/qmlTest.js @@ -1,7 +1,7 @@ print("Launching web window"); qmlWindow = new OverlayWindow({ title: 'Test Qml', - source: "https://s3.amazonaws.com/DreamingContent/qml/content.qml", + source: "qrc:///qml/OverlayWindowTest.qml", height: 240, width: 320, toolWindow: false, diff --git a/scripts/system/audio.js b/scripts/system/audio.js index 0a3471fa81..a93177ca38 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -15,7 +15,7 @@ var TABLET_BUTTON_NAME = "AUDIO"; var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; -var AUDIO_QML_SOURCE = "../audio/Audio.qml"; +var AUDIO_QML_SOURCE = "hifi/audio/Audio.qml"; var MUTE_ICONS = { icon: "icons/tablet-icons/mic-mute-i.svg", diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index a4cdc5b097..b3e3134380 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -26,8 +26,8 @@ // Relevant Variables: // -WALLET_QML_SOURCE: The path to the Wallet QML // -onWalletScreen: true/false depending on whether we're looking at the app. - var WALLET_QML_SOURCE = Script.resourcesPath() + "qml/hifi/commerce/wallet/Wallet.qml"; - var MARKETPLACE_PURCHASES_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/purchases/Purchases.qml"; + var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml"; + var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; var onWalletScreen = false; function onButtonClicked() { if (!tablet) { @@ -61,10 +61,26 @@ function fromQml(message) { switch (message.method) { case 'passphrasePopup_cancelClicked': - case 'walletSetup_cancelClicked': case 'needsLogIn_cancelClicked': tablet.gotoHomeScreen(); break; + case 'walletSetup_cancelClicked': + switch (message.referrer) { + case '': // User clicked "Wallet" app + case undefined: + case null: + tablet.gotoHomeScreen(); + break; + case 'purchases': + case 'marketplace cta': + case 'mainPage': + tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + default: // User needs to return to an individual marketplace item URL + tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.referrer, MARKETPLACES_INJECT_SCRIPT_URL); + break; + } + break; case 'needsLogIn_loginClicked': openLoginWindow(); break; @@ -87,6 +103,7 @@ case 'transactionHistory_linkClicked': tablet.gotoWebScreen(message.marketplaceLink, MARKETPLACES_INJECT_SCRIPT_URL); break; + case 'goToPurchases_fromWalletHome': case 'goToPurchases': tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); break; diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 51f927f224..16f1d086b7 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -145,11 +145,11 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); return deltaTime; }; - this.setIgnoreTablet = function() { + this.setIgnorePointerItems = function() { if (HMD.tabletID !== this.tabletID) { this.tabletID = HMD.tabletID; - Pointers.setIgnoreItems(_this.leftPointer, _this.blacklist.concat([HMD.tabletID])); - Pointers.setIgnoreItems(_this.rightPointer, _this.blacklist.concat([HMD.tabletID])); + Pointers.setIgnoreItems(_this.leftPointer, _this.blacklist); + Pointers.setIgnoreItems(_this.rightPointer, _this.blacklist); } }; @@ -168,7 +168,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } var sensorScaleFactor = MyAvatar.sensorToWorldScale; var deltaTime = _this.updateTimings(); - _this.setIgnoreTablet(); + _this.setIgnorePointerItems(); if (controllerDispatcherPluginsNeedSort) { _this.orderedPluginNames = []; @@ -182,16 +182,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); controllerDispatcherPlugins[b].parameters.priority; }); - var output = "controllerDispatcher -- new plugin order: "; - for (var k = 0; k < _this.orderedPluginNames.length; k++) { - var dbgPluginName = _this.orderedPluginNames[k]; - var priority = controllerDispatcherPlugins[dbgPluginName].parameters.priority; - output += dbgPluginName + ":" + priority; - if (k + 1 < _this.orderedPluginNames.length) { - output += ", "; - } - } - controllerDispatcherPluginsNeedSort = false; } @@ -388,8 +378,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); }; this.setBlacklist = function() { - RayPick.setIgnoreItems(_this.leftControllerRayPick, this.blacklist.concat(HMD.tabletID)); - RayPick.setIgnoreItems(_this.rightControllerRayPick, this.blacklist.concat(HMD.tabletID)); + RayPick.setIgnoreItems(_this.leftControllerRayPick, this.blacklist); + RayPick.setIgnoreItems(_this.rightControllerRayPick, this.blacklist); }; var MAPPING_NAME = "com.highfidelity.controllerDispatcher"; diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 0d421bbaec..0e04f3b985 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -14,7 +14,7 @@ PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, ensureDynamic, getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Reticle, Overlays, isPointingAtUI - Picks, makeLaserLockInfo Xform + Picks, makeLaserLockInfo Xform, makeLaserParams */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -98,6 +98,7 @@ Script.include("/~/system/libraries/Xform.js"); this.targetObject = null; this.actionID = null; // action this script created... this.entityToLockOnto = null; + this.potentialEntityWithContextOverlay = false; this.entityWithContextOverlay = false; this.contextOverlayTimer = false; this.previousCollisionStatus = false; @@ -119,7 +120,7 @@ Script.include("/~/system/libraries/Xform.js"); this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100, - this.hand); + makeLaserParams(this.hand, false)); this.handToController = function() { @@ -364,6 +365,7 @@ Script.include("/~/system/libraries/Xform.js"); if (this.entityWithContextOverlay) { ContextOverlay.destroyContextOverlay(this.entityWithContextOverlay); this.entityWithContextOverlay = false; + this.potentialEntityWithContextOverlay = false; } }; @@ -444,9 +446,13 @@ Script.include("/~/system/libraries/Xform.js"); this.targetObject = new TargetObject(entityID, targetProps); this.targetObject.parentProps = getEntityParents(targetProps); + + Script.clearTimeout(this.contextOverlayTimer); + this.contextOverlayTimer = false; if (entityID !== this.entityWithContextOverlay) { this.destroyContextOverlay(); } + var targetEntity = this.targetObject.getTargetEntity(); entityID = targetEntity.id; targetProps = targetEntity.props; @@ -470,26 +476,39 @@ Script.include("/~/system/libraries/Xform.js"); this.startFarGrabAction(controllerData, targetProps); } } - } else if (!this.entityWithContextOverlay && !this.contextOverlayTimer) { + } else if (!this.entityWithContextOverlay) { var _this = this; - _this.contextOverlayTimer = Script.setTimeout(function () { - if (!_this.entityWithContextOverlay && _this.contextOverlayTimer) { - var props = Entities.getEntityProperties(rayPickInfo.objectID); - var pointerEvent = { - type: "Move", - id: this.hand + 1, // 0 is reserved for hardware mouse - pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID, rayPickInfo.intersection, props), - pos3D: rayPickInfo.intersection, - normal: rayPickInfo.surfaceNormal, - direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal), - button: "Secondary" - }; - if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.objectID, pointerEvent)) { - _this.entityWithContextOverlay = rayPickInfo.objectID; - } + + if (_this.potentialEntityWithContextOverlay !== rayPickInfo.objectID) { + if (_this.contextOverlayTimer) { + Script.clearTimeout(_this.contextOverlayTimer); } _this.contextOverlayTimer = false; - }, 500); + _this.potentialEntityWithContextOverlay = rayPickInfo.objectID; + } + + if (!_this.contextOverlayTimer) { + _this.contextOverlayTimer = Script.setTimeout(function () { + if (!_this.entityWithContextOverlay && + _this.contextOverlayTimer && + _this.potentialEntityWithContextOverlay === rayPickInfo.objectID) { + var props = Entities.getEntityProperties(rayPickInfo.objectID); + var pointerEvent = { + type: "Move", + id: _this.hand + 1, // 0 is reserved for hardware mouse + pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID, rayPickInfo.intersection, props), + pos3D: rayPickInfo.intersection, + normal: rayPickInfo.surfaceNormal, + direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal), + button: "Secondary" + }; + if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.objectID, pointerEvent)) { + _this.entityWithContextOverlay = rayPickInfo.objectID; + } + } + _this.contextOverlayTimer = false; + }, 500); + } } } else if (this.distanceRotating) { this.distanceRotate(otherFarGrabModule); diff --git a/scripts/system/controllers/controllerModules/farTrigger.js b/scripts/system/controllers/controllerModules/farTrigger.js index 24f336d581..70e9ceff16 100644 --- a/scripts/system/controllers/controllerModules/farTrigger.js +++ b/scripts/system/controllers/controllerModules/farTrigger.js @@ -9,7 +9,7 @@ /* global Script, Controller, LaserPointers, RayPick, RIGHT_HAND, LEFT_HAND, MyAvatar, getGrabPointSphereOffset, makeRunningValues, Entities, enableDispatcherModule, disableDispatcherModule, makeDispatcherModuleParameters, PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, - DEFAULT_SEARCH_SPHERE_DISTANCE, getGrabbableData + DEFAULT_SEARCH_SPHERE_DISTANCE, getGrabbableData, makeLaserParams */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -34,7 +34,7 @@ Script.include("/~/system/libraries/controllers.js"); this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100, - this.hand); + makeLaserParams(this.hand, false)); this.getTargetProps = function (controllerData) { // nearbyEntityProperties is already sorted by length from controller diff --git a/scripts/system/controllers/controllerModules/hudOverlayPointer.js b/scripts/system/controllers/controllerModules/hudOverlayPointer.js index a1faacbdd6..a7a186b07a 100644 --- a/scripts/system/controllers/controllerModules/hudOverlayPointer.js +++ b/scripts/system/controllers/controllerModules/hudOverlayPointer.js @@ -16,7 +16,8 @@ makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, ensureDynamic, - getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Reticle, Overlays, isPointingAtUI + getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Reticle, Overlays, isPointingAtUI, + makeLaserParams */ (function() { @@ -36,7 +37,7 @@ this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100, - (this.hand + HUD_LASER_OFFSET)); + makeLaserParams((this.hand + HUD_LASER_OFFSET), false)); this.getOtherHandController = function() { return (this.hand === RIGHT_HAND) ? Controller.Standard.LeftHand : Controller.Standard.RightHand; diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js index f79b2db1e2..763258573d 100644 --- a/scripts/system/controllers/controllerModules/inEditMode.js +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -10,7 +10,7 @@ /* global Script, Controller, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule, makeRunningValues, Messages, makeDispatcherModuleParameters, HMD, getGrabPointSphereOffset, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_ON_VALUE, - getEnabledModuleByName, PICK_MAX_DISTANCE, isInEditMode, LaserPointers, RayPick, Picks + getEnabledModuleByName, PICK_MAX_DISTANCE, isInEditMode, LaserPointers, RayPick, Picks, makeLaserParams */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -28,7 +28,7 @@ Script.include("/~/system/libraries/utils.js"); this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"], [], 100, - this.hand); + makeLaserParams(this.hand, false)); this.nearTablet = function(overlays) { for (var i = 0; i < overlays.length; i++) { @@ -44,10 +44,7 @@ Script.include("/~/system/libraries/utils.js"); }; this.pointingAtTablet = function(objectID) { - if (objectID === HMD.tabletScreenID || objectID === HMD.homeButtonID) { - return true; - } - return false; + return objectID === HMD.tabletScreenID || objectID === HMD.homeButtonID; }; this.sendPickData = function(controllerData) { @@ -99,8 +96,8 @@ Script.include("/~/system/libraries/utils.js"); var overlayLaser = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightWebSurfaceLaserInput" : "LeftWebSurfaceLaserInput"); if (overlayLaser) { var overlayLaserReady = overlayLaser.isReady(controllerData); - - if (overlayLaserReady.active && this.pointingAtTablet(overlayLaser.target)) { + var target = controllerData.rayPicks[this.hand].objectID; + if (overlayLaserReady.active && this.pointingAtTablet(target)) { return this.exitModule(); } } diff --git a/scripts/system/controllers/controllerModules/inVREditMode.js b/scripts/system/controllers/controllerModules/inVREditMode.js index e3035b26f2..38eca65dd3 100644 --- a/scripts/system/controllers/controllerModules/inVREditMode.js +++ b/scripts/system/controllers/controllerModules/inVREditMode.js @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html /* global Script, MyAvatar, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule, - makeDispatcherModuleParameters, makeRunningValues, getEnabledModuleByName + makeDispatcherModuleParameters, makeRunningValues, getEnabledModuleByName, makeLaserParams */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -19,15 +19,21 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); function InVREditMode(hand) { this.hand = hand; this.disableModules = false; + var NO_HAND_LASER = -1; // Invalid hand parameter so that default laser is not displayed. this.parameters = makeDispatcherModuleParameters( 200, // Not too high otherwise the tablet laser doesn't work. this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"], [], - 100 + 100, + makeLaserParams(NO_HAND_LASER, false) ); + this.pointingAtTablet = function (objectID) { + return objectID === HMD.tabletScreenID || objectID === HMD.homeButtonID; + }; + this.isReady = function (controllerData) { if (this.disableModules) { return makeRunningValues(true, [], []); @@ -42,7 +48,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } // Tablet stylus. - // Includes the tablet laser. var tabletStylusInput = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightTabletStylusInput" : "LeftTabletStylusInput"); @@ -53,6 +58,18 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } } + // Tablet surface. + var overlayLaser = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightWebSurfaceLaserInput" + : "LeftWebSurfaceLaserInput"); + if (overlayLaser) { + var overlayLaserReady = overlayLaser.isReady(controllerData); + var target = controllerData.rayPicks[this.hand].objectID; + if (overlayLaserReady.active && this.pointingAtTablet(target)) { + return makeRunningValues(false, [], []); + } + } + // Tablet grabbing. var nearOverlay = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" diff --git a/scripts/system/controllers/controllerModules/stylusInput.js b/scripts/system/controllers/controllerModules/stylusInput.js index d9fa0f76a9..aa65135289 100644 --- a/scripts/system/controllers/controllerModules/stylusInput.js +++ b/scripts/system/controllers/controllerModules/stylusInput.js @@ -142,7 +142,10 @@ Script.include("/~/system/libraries/controllers.js"); }; this.isReady = function (controllerData) { - if (this.processStylus(controllerData)) { + var PREFER_STYLUS_OVER_LASER = "preferStylusOverLaser"; + var isUsingStylus = Settings.getValue(PREFER_STYLUS_OVER_LASER, false); + + if (isUsingStylus && this.processStylus(controllerData)) { Pointers.enablePointer(this.pointer); return makeRunningValues(true, [], []); } else { diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index bf90124cab..3d9d7979d5 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -9,7 +9,7 @@ makeRunningValues, Messages, Quat, Vec3, makeDispatcherModuleParameters, Overlays, ZERO_VEC, HMD, INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, getGrabPointSphereOffset, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_ON_VALUE, - TRIGGER_OFF_VALUE, getEnabledModuleByName, PICK_MAX_DISTANCE, LaserPointers, RayPick, ContextOverlay, Picks + TRIGGER_OFF_VALUE, getEnabledModuleByName, PICK_MAX_DISTANCE, LaserPointers, RayPick, ContextOverlay, Picks, makeLaserParams */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -18,6 +18,7 @@ Script.include("/~/system/libraries/controllers.js"); (function() { function WebSurfaceLaserInput(hand) { this.hand = hand; + this.otherHand = this.hand === RIGHT_HAND ? LEFT_HAND : RIGHT_HAND; this.running = false; this.parameters = makeDispatcherModuleParameters( @@ -25,7 +26,7 @@ Script.include("/~/system/libraries/controllers.js"); this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100, - this.hand); + makeLaserParams(hand, true)); this.grabModuleWantsNearbyOverlay = function(controllerData) { if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { @@ -65,7 +66,8 @@ Script.include("/~/system/libraries/controllers.js"); }; this.deleteContextOverlay = function() { - var farGrabModule = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightFarActionGrabEntity" : "LeftFarActionGrabEntity"); + var farGrabModule = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightFarActionGrabEntity" : "LeftFarActionGrabEntity"); if (farGrabModule) { var entityWithContextOverlay = farGrabModule.entityWithContextOverlay; @@ -76,25 +78,50 @@ Script.include("/~/system/libraries/controllers.js"); } }; - this.isReady = function (controllerData) { + this.updateAllwaysOn = function() { + var PREFER_STYLUS_OVER_LASER = "preferStylusOverLaser"; + this.parameters.handLaser.allwaysOn = !Settings.getValue(PREFER_STYLUS_OVER_LASER, false); + }; + + this.getDominantHand = function() { + return MyAvatar.getDominantHand() === "right" ? 1 : 0; + }; + + this.dominantHandOverride = false; + + this.isReady = function(controllerData) { var otherModuleRunning = this.getOtherModule().running; - if ((this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData)) && - !otherModuleRunning) { - if (controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE) { + otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand. + var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE + && controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; + if ((!otherModuleRunning || isTriggerPressed) + && (this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData))) { + this.updateAllwaysOn(); + if (isTriggerPressed) { + this.dominantHandOverride = true; // Override dominant hand. + this.getOtherModule().dominantHandOverride = false; + } + if (this.parameters.handLaser.allwaysOn || isTriggerPressed) { return makeRunningValues(true, [], []); } } return makeRunningValues(false, [], []); }; - this.run = function (controllerData, deltaTime) { + this.run = function(controllerData, deltaTime) { + var otherModuleRunning = this.getOtherModule().running; + otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand. + otherModuleRunning = otherModuleRunning || this.getOtherModule().dominantHandOverride; // Override dominant hand. var grabModuleNeedsToRun = this.grabModuleWantsNearbyOverlay(controllerData); - if (controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE && !grabModuleNeedsToRun) { + if (!otherModuleRunning && !grabModuleNeedsToRun && (controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE + || this.parameters.handLaser.allwaysOn + && (this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData)))) { this.running = true; return makeRunningValues(true, [], []); } this.deleteContextOverlay(); this.running = false; + this.dominantHandOverride = false; return makeRunningValues(false, [], []); }; } diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 8a7e36465d..e28f877d85 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -427,7 +427,7 @@ var toolBar = (function () { }); createButton = activeButton; tablet.screenChanged.connect(function (type, url) { - if (isActive && (type !== "QML" || url !== "Edit.qml")) { + if (isActive && (type !== "QML" || url !== "hifi/tablet/Edit.qml")) { that.setActive(false) } }); @@ -656,7 +656,7 @@ var toolBar = (function () { selectionDisplay.triggerMapping.disable(); tablet.landscape = false; } else { - tablet.loadQMLSource("Edit.qml", true); + tablet.loadQMLSource("hifi/tablet/Edit.qml", true); UserActivityLogger.enabledEdit(); entityListTool.setVisible(true); gridTool.setVisible(true); diff --git a/scripts/system/generalSettings.js b/scripts/system/generalSettings.js index 7d97f13757..082528ffc5 100644 --- a/scripts/system/generalSettings.js +++ b/scripts/system/generalSettings.js @@ -18,7 +18,7 @@ var buttonName = "Settings"; var toolBar = null; var tablet = null; - var settings = "TabletGeneralPreferences.qml" + var settings = "hifi/tablet/TabletGeneralPreferences.qml" function onClicked(){ if (tablet) { tablet.loadQMLSource(settings); diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index b361f1120b..fb49de1050 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -29,7 +29,7 @@ var commerceMode = false; var userIsLoggedIn = false; var walletNeedsSetup = false; - var metaverseServerURL = "https://metaverse.highfidelity.com"; + var marketplaceBaseURL = "https://highfidelity.com"; function injectCommonCode(isDirectoryPage) { @@ -58,7 +58,7 @@ ); // Footer. - var isInitialHiFiPage = location.href === metaverseServerURL + "/marketplace?"; + var isInitialHiFiPage = location.href === marketplaceBaseURL + "/marketplace?"; $("body").append( '
' + (!isInitialHiFiPage ? '' : '') + @@ -70,7 +70,7 @@ // Footer actions. $("#back-button").on("click", function () { - (document.referrer !== "") ? window.history.back() : window.location = (metaverseServerURL + "/marketplace?"); + (document.referrer !== "") ? window.history.back() : window.location = (marketplaceBaseURL + "/marketplace?"); }); $("#all-markets").on("click", function () { EventBridge.emitWebEvent(GOTO_DIRECTORY); @@ -89,7 +89,7 @@ window.location = "https://clara.io/library?gameCheck=true&public=true"; }); $('#exploreHifiMarketplace').on('click', function () { - window.location = "http://www.highfidelity.com/marketplace"; + window.location = marketplaceBaseURL + "/marketplace"; }); } @@ -199,7 +199,7 @@ var purchasesElement = document.createElement('a'); var dropDownElement = document.getElementById('user-dropdown'); - $('#user-dropdown').find('.username')[0].style = "max-width:80px;white-space:nowrap;overflow:hidden;" + + $('#user-dropdown').find('.username')[0].style = "max-width:80px;white-space:nowrap;overflow:hidden;" + "text-overflow:ellipsis;display:inline-block;position:relative;top:4px;"; $('#user-dropdown').find('.caret')[0].style = "position:relative;top:-3px;"; @@ -243,13 +243,15 @@ }); } - function buyButtonClicked(id, name, author, price, href) { + function buyButtonClicked(id, name, author, price, href, referrer) { EventBridge.emitWebEvent(JSON.stringify({ type: "CHECKOUT", itemId: id, itemName: name, itemPrice: price ? parseInt(price, 10) : 0, - itemHref: href + itemHref: href, + referrer: referrer, + itemAuthor: author })); } @@ -316,7 +318,8 @@ $(this).closest('.grid-item').find('.item-title').text(), $(this).closest('.grid-item').find('.creator').find('.value').text(), $(this).closest('.grid-item').find('.item-cost').text(), - $(this).attr('data-href')); + $(this).attr('data-href'), + "mainPage"); }); } @@ -389,27 +392,41 @@ var href = purchaseButton.attr('href'); purchaseButton.attr('href', '#'); - purchaseButton.css({ - "background": "linear-gradient(#00b4ef, #0093C5)", - "color": "#FFF", - "font-weight": "600", - "padding-bottom": "10px" - }); + var availability = $.trim($('.item-availability').text()); + if (availability === 'available') { + purchaseButton.css({ + "background": "linear-gradient(#00b4ef, #0093C5)", + "color": "#FFF", + "font-weight": "600", + "padding-bottom": "10px" + }); + } else { + purchaseButton.css({ + "background": "linear-gradient(#a2a2a2, #fefefe)", + "color": "#000", + "font-weight": "600", + "padding-bottom": "10px" + }); + } var cost = $('.item-cost').text(); - - if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { + if (availability !== 'available') { + purchaseButton.html('UNAVAILABLE (' + availability + ')'); + } else if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { purchaseButton.html('PURCHASE ' + cost); } purchaseButton.on('click', function () { - buyButtonClicked(window.location.pathname.split("/")[3], - $('#top-center').find('h1').text(), - $('#creator').find('.value').text(), - cost, - href); - }); + if ('available' === availability) { + buyButtonClicked(window.location.pathname.split("/")[3], + $('#top-center').find('h1').text(), + $('#creator').find('.value').text(), + cost, + href, + "itemPage"); + } + }); maybeAddPurchasesButton(); } } @@ -641,9 +658,9 @@ var HIFI_ITEM_PAGE = 3; var pageType = DIRECTORY; - if (location.href.indexOf("highfidelity.com/") !== -1) { pageType = HIFI; } + if (location.href.indexOf(marketplaceBaseURL + "/") !== -1) { pageType = HIFI; } if (location.href.indexOf("clara.io/") !== -1) { pageType = CLARA; } - if (location.href.indexOf("highfidelity.com/marketplace/items/") !== -1) { pageType = HIFI_ITEM_PAGE; } + if (location.href.indexOf(marketplaceBaseURL + "/marketplace/items/") !== -1) { pageType = HIFI_ITEM_PAGE; } injectCommonCode(pageType === DIRECTORY); switch (pageType) { @@ -677,7 +694,10 @@ commerceMode = !!parsedJsonMessage.data.commerceMode; userIsLoggedIn = !!parsedJsonMessage.data.userIsLoggedIn; walletNeedsSetup = !!parsedJsonMessage.data.walletNeedsSetup; - metaverseServerURL = parsedJsonMessage.data.metaverseServerURL; + marketplaceBaseURL = parsedJsonMessage.data.metaverseServerURL; + if (marketplaceBaseURL.indexOf('metaverse.') !== -1) { + marketplaceBaseURL = marketplaceBaseURL.replace('metaverse.', ''); + } injectCode(); } } diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index 4217ec503e..05b4963280 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -118,7 +118,8 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { Overlays.deleteOverlay(this.webOverlayID); } - var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) / sensorScaleFactor; + var RAYPICK_OFFSET = 0.0001; // Sufficient for raypick to reliably intersect tablet screen before tablet model. + var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) / sensorScaleFactor + RAYPICK_OFFSET; var WEB_ENTITY_Y_OFFSET = 0.004; var screenWidth = 0.82 * tabletWidth; var screenHeight = 0.81 * tabletHeight; diff --git a/scripts/system/libraries/accountUtils.js b/scripts/system/libraries/accountUtils.js index 6df0aa3a87..69092b30fc 100644 --- a/scripts/system/libraries/accountUtils.js +++ b/scripts/system/libraries/accountUtils.js @@ -10,7 +10,7 @@ openLoginWindow = function openLoginWindow() { || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { Menu.triggerOption("Login / Sign Up"); } else { - tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); + tablet.loadQMLOnTop("dialogs/TabletLoginDialog.qml"); HMD.openTablet(); } }; diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index e0971201e6..915cfc05fb 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -45,6 +45,7 @@ BUMPER_ON_VALUE:true, getEntityParents:true, findHandChildEntities:true, + makeLaserParams:true, TEAR_AWAY_DISTANCE:true, TEAR_AWAY_COUNT:true, TEAR_AWAY_CHECK_TIME:true, @@ -134,6 +135,17 @@ makeLaserLockInfo = function(targetID, isOverlay, hand, offset) { }; }; +makeLaserParams = function(hand, allwaysOn) { + if (allwaysOn === undefined) { + allwaysOn = false; + } + + return { + hand: hand, + allwaysOn: allwaysOn + }; +}; + makeRunningValues = function (active, targets, requiredDataForRun, laserLockInfo) { return { active: active, diff --git a/scripts/system/libraries/pointersUtils.js b/scripts/system/libraries/pointersUtils.js index 896ef094b8..2af563f8d4 100644 --- a/scripts/system/libraries/pointersUtils.js +++ b/scripts/system/libraries/pointersUtils.js @@ -95,6 +95,7 @@ Pointer = function(hudLayer, pickType, pointerData) { this.pointerID = null; this.visible = false; this.locked = false; + this.allwaysOn = false; this.hand = pointerData.hand; delete pointerData.hand; @@ -150,7 +151,7 @@ Pointer = function(hudLayer, pickType, pointerData) { mode = "hold"; } else if (triggerClicks[this.hand]) { mode = "full"; - } else if (triggerValues[this.hand] > TRIGGER_ON_VALUE) { + } else if (triggerValues[this.hand] > TRIGGER_ON_VALUE || this.allwaysOn) { mode = "half"; } } @@ -172,19 +173,23 @@ PointerManager = function() { return pointer.pointerID; }; - this.makePointerVisible = function(index) { + this.makePointerVisible = function(laserParams) { + var index = laserParams.hand; if (index < this.pointers.length && index >= 0) { this.pointers[index].makeVisible(); + this.pointers[index].allwaysOn = laserParams.allwaysOn; } }; - this.makePointerInvisible = function(index) { + this.makePointerInvisible = function(laserParams) { + var index = laserParams.hand; if (index < this.pointers.length && index >= 0) { this.pointers[index].makeInvisible(); } }; - this.lockPointerEnd = function(index, lockData) { + this.lockPointerEnd = function(laserParams, lockData) { + var index = laserParams.hand; if (index < this.pointers.length && index >= 0) { this.pointers[index].lockEnd(lockData); } diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index 4a1fcdf301..a6e2751a83 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -400,7 +400,8 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) }); // update webOverlay - var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) * sensorScaleOffsetOverride; + var RAYPICK_OFFSET = 0.0001; // Sufficient for raypick to reliably intersect tablet screen before tablet model. + var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) * sensorScaleOffsetOverride + RAYPICK_OFFSET; var WEB_ENTITY_Y_OFFSET = 0.004 * sensorScaleFactor * sensorScaleOffsetOverride; var screenWidth = 0.82 * tabletWidth; var screenHeight = 0.81 * tabletHeight; @@ -417,11 +418,13 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) var homeButtonDim = 4.0 * tabletScaleFactor / 3.0; Overlays.editOverlay(HMD.homeButtonID, { localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, + localRotation: Quat.angleAxis(180, Vec3.UNIT_Y), dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } }); Overlays.editOverlay(HMD.homeButtonHighlightID, { localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, + localRotation: Quat.angleAxis(180, Vec3.UNIT_Y), dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } }); }; diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index fedadbb2b4..d9003ffeaa 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -898,8 +898,7 @@ } } function keyReleaseEvent(event) { - if ((event.text.toUpperCase() === "X") && !event.isAutoRepeat && !event.isShifted && !event.isMeta && !event.isControl - && !event.isAlt) { + if (event.text.toUpperCase() === "X" && !event.isAutoRepeat) { updateTriggers(0.0, true, Controller.Standard.RightHand); } } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 004375bff7..a5360974f6 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -8,7 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global Tablet, Script, HMD, UserActivityLogger, Entities */ +/* global Tablet, Script, HMD, UserActivityLogger, Entities, Account, Wallet, ContextOverlay, Settings, Camera, Vec3, + Quat, MyAvatar, Clipboard, Menu, Grid, Uuid, GlobalServices, openLoginWindow */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ var selectionDisplay = null; // for gridTool.js to ignore @@ -23,10 +24,9 @@ var selectionDisplay = null; // for gridTool.js to ignore var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page. var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html"); var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); - var MARKETPLACE_CHECKOUT_QML_PATH_BASE = "qml/hifi/commerce/checkout/Checkout.qml"; - var MARKETPLACE_CHECKOUT_QML_PATH = Script.resourcesPath() + MARKETPLACE_CHECKOUT_QML_PATH_BASE; - var MARKETPLACE_PURCHASES_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/purchases/Purchases.qml"; - var MARKETPLACE_WALLET_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/wallet/Wallet.qml"; + var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml"; + var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; + var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "commerce/inspectionCertificate/InspectionCertificate.qml"; var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; @@ -114,7 +114,7 @@ var selectionDisplay = null; // for gridTool.js to ignore function onScreenChanged(type, url) { onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1; onWalletScreen = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1; - onCommerceScreen = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH_BASE) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH + onCommerceScreen = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH || url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1); wireEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen); @@ -220,6 +220,41 @@ var selectionDisplay = null; // for gridTool.js to ignore function rezEntity(itemHref, isWearable) { var success = Clipboard.importEntities(itemHref); + var wearableLocalPosition = null; + var wearableLocalRotation = null; + var wearableLocalDimensions = null; + var wearableDimensions = null; + + if (isWearable) { + var wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.transforms"); + if (!wearableTransforms) { + // TODO delete this clause + wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.tranforms"); + } + var certPos = itemHref.search("certificate_id="); // TODO how do I parse a URL from here? + if (certPos >= 0) { + certPos += 15; // length of "certificate_id=" + var certURLEncoded = itemHref.substring(certPos); + var certB64Encoded = decodeURIComponent(certURLEncoded); + for (var key in wearableTransforms) { + if (wearableTransforms.hasOwnProperty(key)) { + var certificateTransforms = wearableTransforms[key].certificateTransforms; + if (certificateTransforms) { + for (var certID in certificateTransforms) { + if (certificateTransforms.hasOwnProperty(certID) && + certID == certB64Encoded) { + var certificateTransform = certificateTransforms[certID]; + wearableLocalPosition = certificateTransform.localPosition; + wearableLocalRotation = certificateTransform.localRotation; + wearableLocalDimensions = certificateTransform.localDimensions; + wearableDimensions = certificateTransform.dimensions; + } + } + } + } + } + } + } if (success) { var VERY_LARGE = 10000; @@ -288,6 +323,24 @@ var selectionDisplay = null; // for gridTool.js to ignore } } } + + if (isWearable) { + // apply the relative offsets saved during checkout + var offsets = {}; + if (wearableLocalPosition) { + offsets.localPosition = wearableLocalPosition; + } + if (wearableLocalRotation) { + offsets.localRotation = wearableLocalRotation; + } + if (wearableLocalDimensions) { + offsets.localDimensions = wearableLocalDimensions; + } else if (wearableDimensions) { + offsets.dimensions = wearableDimensions; + } + // we currently assume a wearable is a single entity + Entities.editEntity(pastedEntityIDs[0], offsets); + } } else { Window.notifyEditError("Can't import entities: entities would be out of bounds."); } @@ -437,7 +490,7 @@ var selectionDisplay = null; // for gridTool.js to ignore wireEventBridge(true); tablet.sendToQml({ method: 'updateWalletReferrer', - referrer: message.itemId + referrer: message.referrer === "itemPage" ? message.itemId : message.referrer }); openWallet(); break; @@ -492,7 +545,7 @@ var selectionDisplay = null; // for gridTool.js to ignore Menu.setIsOptionChecked("Disable Preview", isHmdPreviewDisabled); break; case 'purchases_openGoTo': - tablet.loadQMLSource("TabletAddressDialog.qml"); + tablet.loadQMLSource("hifi/tablet/TabletAddressDialog.qml"); break; case 'purchases_itemCertificateClicked': setCertificateInfo("", message.itemCertificateId); diff --git a/scripts/system/pal.js b/scripts/system/pal.js index b5551cf596..ed7059f9f3 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -40,7 +40,7 @@ var HOVER_TEXTURES = { var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6}; var SELECTED_COLOR = {red: 0xF3, green: 0x91, blue: 0x29}; var HOVER_COLOR = {red: 0xD0, green: 0xD0, blue: 0xD0}; // almost white for now -var PAL_QML_SOURCE = "../Pal.qml"; +var PAL_QML_SOURCE = "hifi/Pal.qml"; var conserveResources = true; Script.include("/~/system/libraries/controllers.js"); diff --git a/scripts/system/particle_explorer/hifi-entity-ui.js b/scripts/system/particle_explorer/hifi-entity-ui.js index abf9e3cce6..720aaee4d9 100644 --- a/scripts/system/particle_explorer/hifi-entity-ui.js +++ b/scripts/system/particle_explorer/hifi-entity-ui.js @@ -261,15 +261,18 @@ HifiEntityUI.prototype = { addSection: function (parent, section, properties, index) { var self = this; - var sectionDivHeader = document.createElement("div"); - var title = document.createElement("label"); + var sectionDivHeader = document.createElement("fieldset"); + var title = document.createElement("legend"); var dropDown = document.createElement("span"); dropDown.className = "arrow"; - sectionDivHeader.className = "section-header"; + sectionDivHeader.className = "major"; + title.className = "section-header"; + title.id = section + "-section"; title.innerHTML = section; + title.appendChild(dropDown); sectionDivHeader.appendChild(title); - sectionDivHeader.appendChild(dropDown); + var collapsed = index !== 0; dropDown.innerHTML = collapsed ? "L" : "M"; @@ -292,7 +295,7 @@ HifiEntityUI.prototype = { } } sectionDivBody.appendChild(animationWrapper); - parent.appendChild(sectionDivBody); + sectionDivHeader.appendChild(sectionDivBody); _.defer(function () { var height = (animationWrapper.clientHeight) + "px"; if (collapsed) { @@ -303,7 +306,7 @@ HifiEntityUI.prototype = { sectionDivBody.style.maxHeight = height; } - sectionDivHeader.onclick = function () { + title.onclick = function () { collapsed = !collapsed; if (collapsed) { sectionDivBody.classList.remove("visible"); @@ -314,7 +317,7 @@ HifiEntityUI.prototype = { } // sectionDivBody.style.display = collapsed ? "none": "block"; dropDown.innerHTML = collapsed ? "L" : "M"; - sectionDivHeader.setAttribute("collapsed", collapsed); + title.setAttribute("collapsed", collapsed); }; }); }, diff --git a/scripts/system/particle_explorer/particle-style.css b/scripts/system/particle_explorer/particle-style.css index 1e2801c19f..cde325f6c6 100644 --- a/scripts/system/particle_explorer/particle-style.css +++ b/scripts/system/particle_explorer/particle-style.css @@ -126,3 +126,15 @@ hr.splitter:last-of-type{ .texture-image.no-preview { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAA8sSURBVHhe7Z3rbxXFG8d7B9SWthRabLmIYlHkIEXKJdXYBEXxHtEXprwxxsR3/jG+8PLCaDDGeAkmKsTEoCUVKoVCA6WNtLS2UEUKBSy0tKW/D+eZM9nu7tmz55z+mC2Zz4tl9tk5c2bnO/PMM2dnS+6nn36aYzFH7vvvv6+SFhMoAY4fPy7nljvG448/zjFPTiymsAIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjmLhQgPz+/pKRk3rx56jzaRHFf0ObNmxctWkTi7Nmzp0+fFqNm+/btRUVFP/30kzp3UFtbu27duqVLl+bl3e5Y169f7+rqam1tvXnzpmSIFNHdF1RTU7M6TkNDQ0FBgbImWLVqFZfUSQKyvfzyy88991x1dfXU1NSFCxdGRkbuueeeurq6pqam0tJSlS96RNcFSQvSo9V5IC+88MIDDzwwOjr6448/fvTRR19++eVnn322Z8+ev//+u7i4+M0331ywYIHKGjGiK8Aff/zBMRaL5ebmiiUZjz322MqVK/Ez33333ZkzZxgBYh8eHt67d++lS5do/W3btokxakRXANxIf38/3mPNmjXKlARxpkeOHKGtxaIZHx9vaWkhwfTg9WZRILoCgIQG0r7JKC8vlxm7s7NTLC6YyW/cuFFYWIiPUqYoEWkB+vr6cOJLlizBwyiTB2l9vA0xj1hcTE9PDw4OkiA6EkukiLQAcOzYMY4bN26UUy8LFy7k+O+//8qpL1euXOF43333yWmkiLoATKqEQwSmlZWVyjQTIiWOwZG+npYjSNQFwIG0tbWRqK+vF4sL1r0qlZzJyUmOYXLeeaIuAHR3d+PfmQbE27hgguUY3LgS/0RzHMwBAei/R48ezcvL8x0EOCiOxEJy6osoJ1JFjTkgAHR0dExMTBDLexe0EvsTKQUMgsWLF3OUWChqzA0BGARoQBN7wyHWa6Ojo1x6+OGHlWkmaEOoeuvWrXPnzilTlJgbAgBeiEEQi8W8Pf3kyZMct27d6v0JGsf15JNPkmA5lmyhYJY5IwAenNmYBW1RUZEyJSBMYiYoLi7etWtXWVmZsubkkHPHjh2EsCjX3NysrBFjzggANDSeRJ04wEF9//33rLYqKip27979yiuvNDY2Pvvss2+//TZ+ieBn//79V69eVbkjRv6WLVv4hxW/nEcB+iyuo6ura3x8XJnicIqToV8zGpgSlDXO2NhYZ2cnV+WnIVZtTLxEn+fPn9+3b180p9+qqiqOd9ub8ihH67M8xuPT65mf1YXocXe+KY+PGhoa6unp4Rjl1tfcbQLMOawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGyfy3oNdff72mpkadJLh27Vpvb29LS8vExIRYdu7c6dpLOz09ffPmTXLypadOnVLWnJzGxsZYLKZOPHR0dDQ3N7/33nv5+fkff/yx7/PFBQsWvPPOO5T/4YcfLly4sKmpaXBw8Ntvv5Wr7777bsAOUbINDw+Th5IpX1kTyGcPHz7c2tqqTHG4NW7wzz//9N2tHczs/BY0NjZ2PQFVLy4uXr9+/UsvvaQuJxgfH1eZ4tkKCwsrKiq2b9/u3XbozOkEzaamps6ePUueZHvcsOfl5ZFHtkH4oorzQOFU7MqVKzS0S6fy8nKxeDvckiVLOGbza2u22yW/+eYbOo46ie9Te/XVV5ctW7Z8+fK//vpLWXNyfvjhB2ctaaaGhoYNGzZs3bq1q6tLWeP88ssvdCh14oFLDz30EA3tuxFRhBGRkvHJJ5+olB8XLlxg6NCs/f39ypRo93/++Wfp0qWMP+fuCnna7N2TGp5ZngMQ48iRIyQefPBBsfhy69atgwcPjo6OlpSU+G42SQaicv80tPfBJBbslBwsQDBDQ0McpVk1CMBAx2HyFa79jUhFfeRTmTH7k7DsEky5DxBPffHiRRKytS0kNMTAwAAN4d0tigX7+fPnfaeHkEjlxbFoEIAvlTFRXV0tRhBnNTIy4hwT6TL7Asgz2zBvBUlO/K+chkQc1IoVK+RUI5YzZ87IaWZIX3buMpIJAP+Jroxv5zQgOmW52WL2BZDtyv/995+cJkMeHHJX6T42wcPgZ5gJ1HkCsWTjf4C+TCuXlpZqFyctLl6etpZpIH5F6eScAjNglgVg+n3iiSdIuHoiI/f2S19xamtrN23a9NprrzEVt7W1uSKWtWvXPu2HuhzfHkF/pFfef//9ypSTQxoLPi3lw3dV3Ez4UnU5/nicJpZuBAigvTzfyyU9DWQfAkG2UdCLL76oPeC99947f/58Et3d3cQMYhTk0b8TejGhfXt7uzpPgCfxuhf49ddfVSonp6enhyhr1apVeHyxkOYYxv8QJauUA9yaXpEQCKEH8zAJThGA1pd7lLamM0mCPNhl73vGZDsCGK10FgGffvnyZZYqP//8s7qcgCY7EUemMvz+F198ceDAAaZiyaA5duwYixov6nIcaWhpdEHSfIucBqCKm4m8hSDIBhHp3URoMgHEr9wefHoaYChw71qbjMlWgK+//pp1o/DBBx98/vnnLBfp3epyAmI4ujDs3bv3t99+I/J5/vnnfd++4/7pj17U5TjohzsuKysTL8yRNM5HwqpgVHEzce7KoYlpUynZO83qaYAOxzGbFYCQrQAsXOkXgrc7+4IYuA5WwgHvvaSEVuMoKy859vb23r6QNbQ+zof2Je2cAAQ9DYhCWU4AMPtRUBhko2B9fX1aiwAnEu3IakCOYfxPSFgN4HnwP7h7xHA6GT0NyFScZQgEZgRgimYyKCwsrKurU6Y0weHIbwO0FEfGX5bxuBPp8kR0jAPX22d8EY2Oa6qqqiJt3gVlzKFDhzjGYjFaUCzpgs/BGzQ2NnJkWg7pAMMg8Y/8Wul1Mn19fUiONtl3fzAmAP0XN8IgcM0EGzZs2JkElSOBTAMsLDiGnwBUWR74XpUjvuxiJS/TgK8AdBpUz34CAGMCgPy27hoEdC5Zr3lRORIQ8krYMzExMTAwIMaUqLI8iE/XyCCgj+NnxKLRoWf2/gcyfyBDGDNv3jw6csCP70C0QPvSUq6tzgKelK5EUxJZElazlFMX/PB6efkIJXsD0IKCgsrKSuclmpi1t6S9uBy6lJzMy1My5ae892DExdn/R8wYd+fu6DmHFcAwVgDDWAEMYwUwjBXAMFYAw1gBDGMFMIwVwDBp/xSxZs2aqqqqsbGxw4cPK1PiD2W0t7cne0K9ePHitWvXXr9+Xf4aKFRWVj7yyCMkKIfSxKgpLS1lpT4yMqIrxinGU6dOBf95OGH16tXV1dWuSmrkmbs6iTM5OXnjxo2enh7560Oap+O7MZz7AVzIF6kTPwI+m+FPEbT1+vXrN2/eXFJSokzxfXAYH330UXXuYd26dWRw/uoZi8WwgPPZukYKdO5vJI0FDdR5IL6V1KxYseL2FzvYuHFjQ0NDU1OTa7uRXFUnftTU1EieZKh8yUlPALott3T58mXSiC9GkJ/mA/aDyo1JNsjPz6fdr169OjU15SxnVqioqCgrK/NW0oXefrF///4DBw5QN2r1zDPPFBcXqxyhOXnypBTlReVITnoCyP20tLS4Gq6/v58hvGjRIudfi9HIrqnR0VG9jWfZsmXz58/nnoeGhiQt9llBVxIXFCCA3n7R3d3d0dFBY3EXRUVF4hjTAq8oRXlROZKTtgATExN9fX0DAwMyGsQ+PT0te3V8b1iMztqIpbe3l6JkNIh9VtCVpEGdlUyJPOjnI3J6Z0hDALkZbozuL63pbG6vReMSQFqcEcOACPhUZoj/kUrKPonwhcvTlTDbimeRNASQt1mkp9N5uUPn+y2Dg4M4Ge7f1eOQTR4taf+zcuVKfI6UI5sbli9f7pyfs0GaWwpnmLoqGYxswwr/dHNWSEMA7o37kfdecK+4b+luchUv5NudnS0iiEU/Rmfg5+XlBb/QEZ7gSjoh0CpPwOy1adMmQrVz58653tgJAz1MFTQT79+w8xJWACZSvobeoWN2r9MXAWSfmkb8u8v/UIjuaOk6igCkrYMrqXnqqad2JyAA3bZtG8N037593n2VKamvr1cFzaS2tlblSE5YAeQenLvPpJc57w0ng0thYaL3u0mLcGN6Bwf+p7CwkOmRfiqWixcv4rsIqLP3QmEqqRkeHqZWQK8njMH1U+233nor5FLDCcs3KcpFypckIOz2dLkHhiqrG7EAlZYmlqAb6Oksaoj65W+6iWOhG+pdU1IOGjjLQSGGF5nlD1BmTMhKCq2trXpcAkOT5RuV37Fjx1dffaWs4Whvb3f9DbvwhBoBdE8aiASr5y0O5B0j519MlVvSDt21/iooKBCPxFEVEYcGwhhmwAYgrUwiZSV9YUQeOnQI31VVVZXWe4NZEkoAqT3tyIrRibwQ6Ww4Qho6mvgTmoNG4ZZ0/EO70/cZ7+rzDojc+VTGe3VBur+3kvq/MInnCgINqD+JDLxQxqQWIDc3VzoyHYSB5uT333/HfUtDS2agCYhqWN8CpxKwyiVpI/XhmUhQJBkyQz7rrWRbWxvu3lXJZMhw0RW+A6QWQLoz9+DyoYI3hmFlzxHN+CAJp/+RAMk5SWqyjIXE/ySrJOsyjikLp+OzaiEKohxl+v+TWgCpt2+rgTfOu3TpEoENrQ/OcBP/w0RHyMGUKxYnrAbod84IyheCa/K4YH4KrqSvAK6i6urq3njjDcbu6dOnXTVUOWZCf1KX48opqweZOwNIEQVp/6PXTS7w77SyDHC9C5NeT0RBorOz0+V/5PcWL5OTk0hFkEq2EydOKKsHJlWVcoCjl8KTVVJUd1XStyjmp4MHD6qTBLt27VIpB3v27NEDZUMcSbugbrhBdeJHij9dTDyAvFQrWaMQXyLS+Pj4tWvX9PAn/kV5hgJhJXYxMgLIQDm+u3SBeZgOKJM2/YuhwJSoN+SWlJTQiJTphTZlzRlQSXBWkjUwsan6cBy+iLD9+PHjzc3Nzv22RLQqhwfEphBukx6mTH6wEEn2kOru/NPFc4gMn4hZZhcrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYdS2FIsp7AgwSk7O/wCqCi/+JioQYgAAAABJRU5ErkJggg=='); } + +#properties-list > fieldset { + margin-top: 0px; +} + +#main-header { + margin-bottom: 21px; +} + +.section-wrap { + padding: 21px 0px; +} \ No newline at end of file diff --git a/scripts/system/particle_explorer/particleExplorer.html b/scripts/system/particle_explorer/particleExplorer.html index 0f014e9fa8..48cd4afa06 100644 --- a/scripts/system/particle_explorer/particleExplorer.html +++ b/scripts/system/particle_explorer/particleExplorer.html @@ -30,8 +30,8 @@ -
-
+
+
diff --git a/scripts/system/particle_explorer/particleExplorer.js b/scripts/system/particle_explorer/particleExplorer.js index 057bd1dd85..971798828f 100644 --- a/scripts/system/particle_explorer/particleExplorer.js +++ b/scripts/system/particle_explorer/particleExplorer.js @@ -5,6 +5,7 @@ // Copyright 2017 High Fidelity, Inc. // // Reworked by Menithal on 20/5/2017 +// Reworked by Daniela Fontes and Artur Gomes (Mimicry) on 12/18/2017 // // Web app side of the App - contains GUI. // This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities. @@ -17,7 +18,7 @@ (function () { - var root = document.getElementById("particle-explorer"); + var root = document.getElementById("properties-list"); window.onload = function () { var ui = new HifiEntityUI(root); @@ -51,6 +52,7 @@ } }; textarea.oninput = textarea.onchange; + document.getElementById("show-properties-button").value = "Hide Properties"; } else { textarea.onchange = function () {}; textarea.oninput = textarea.onchange; @@ -60,6 +62,7 @@ insertZone.parentNode.clientHeight + "px"; document.getElementById("export-properties-button").setAttribute("disabled", true); document.getElementById("import-properties-button").setAttribute("disabled", true); + document.getElementById("show-properties-button").value = "Show Properties"; } } }, diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 929c6e0e5c..2a0e827932 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -24,7 +24,7 @@ print('tablet-goto.js:', [].map.call(arguments, JSON.stringify)); } - var gotoQmlSource = "TabletAddressDialog.qml"; + var gotoQmlSource = "hifi/tablet/TabletAddressDialog.qml"; var buttonName = "GOTO"; var onGotoScreen = false; var shouldActivateButton = false; diff --git a/tests/gpu-test/src/TestShapes.cpp b/tests/gpu-test/src/TestShapes.cpp index 253d89cf61..67a348c002 100644 --- a/tests/gpu-test/src/TestShapes.cpp +++ b/tests/gpu-test/src/TestShapes.cpp @@ -29,19 +29,18 @@ void TestShapes::renderTest(size_t testId, RenderArgs* args) { float seconds = secTimestampNow() - startSecs; seconds /= 4.0f; batch.setModelTransform(Transform()); - batch._glColor4f(0.8f, 0.25f, 0.25f, 1.0f); + const auto color = glm::vec4(0.8f, 0.25f, 0.25f, 1.0f); bool wire = (seconds - floorf(seconds) > 0.5f); int shapeIndex = ((int)seconds) % TYPE_COUNT; if (wire) { - geometryCache->renderWireShape(batch, SHAPE[shapeIndex]); + geometryCache->renderWireShape(batch, SHAPE[shapeIndex], color); } else { - geometryCache->renderShape(batch, SHAPE[shapeIndex]); + geometryCache->renderShape(batch, SHAPE[shapeIndex], color); } batch.setModelTransform(Transform().setScale(1.01f)); - batch._glColor4f(1, 1, 1, 1); - geometryCache->renderWireCube(batch); + geometryCache->renderWireCube(batch, glm::vec4(1,1,1,1)); } diff --git a/tests/shared/src/PathUtilsTests.cpp b/tests/shared/src/PathUtilsTests.cpp new file mode 100644 index 0000000000..1c445908f7 --- /dev/null +++ b/tests/shared/src/PathUtilsTests.cpp @@ -0,0 +1,27 @@ +// +// Created by Bradley Austin Davis on 2017/11/08 +// Copyright 2013-2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#include "PathUtilsTests.h" + +#include + +#include + +QTEST_MAIN(PathUtilsTests) + +void PathUtilsTests::testPathUtils() { + QString result = PathUtils::qmlBasePath(); +#if DEV_BUILD + QVERIFY(result.startsWith("file:///")); +#else + QVERIFY(result.startsWith("qrc:///")); +#endif + QVERIFY(result.endsWith("/")); +} + diff --git a/tests/shared/src/PathUtilsTests.h b/tests/shared/src/PathUtilsTests.h new file mode 100644 index 0000000000..8ae410d6cb --- /dev/null +++ b/tests/shared/src/PathUtilsTests.h @@ -0,0 +1,20 @@ +// +// Created by Bradley Austin Davis on 2017/11/08 +// Copyright 2013-2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_PathUtilsTests_h +#define hifi_PathUtilsTests_h + +#include + +class PathUtilsTests : public QObject { + Q_OBJECT +private slots: + void testPathUtils(); +}; + +#endif // hifi_PathUtilsTests_h diff --git a/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js b/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js index e7a135ec9e..7bc65722cd 100644 --- a/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js +++ b/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js @@ -24,7 +24,7 @@ if (onSkyboxChangerScreen) { tablet.gotoHomeScreen(); } else { - tablet.loadQMLSource("../SkyboxChanger.qml"); + tablet.loadQMLSource("hifi/SkyboxChanger.qml"); } } diff --git a/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js b/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js index 9eb543e768..730a5808b8 100644 --- a/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js +++ b/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js @@ -393,7 +393,7 @@ // Relevant Variables: // -SPECTATOR_CAMERA_QML_SOURCE: The path to the SpectatorCamera QML // -onSpectatorCameraScreen: true/false depending on whether we're looking at the spectator camera app. - var SPECTATOR_CAMERA_QML_SOURCE = Script.resourcesPath() + "qml/hifi/SpectatorCamera.qml"; + var SPECTATOR_CAMERA_QML_SOURCE = "hifi/SpectatorCamera.qml"; var onSpectatorCameraScreen = false; function onTabletButtonClicked() { if (!tablet) {