diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 95bd8f25d5..d744acc42b 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -43,8 +44,8 @@ static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10; -Agent::Agent(NLPacket& packet) : - ThreadedAssignment(packet), +Agent::Agent(ReceivedMessage& message) : + ThreadedAssignment(message), _entityEditSender(), _receivedAudioStream(AudioConstants::NETWORK_FRAME_SAMPLES_STEREO, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, InboundAudioStream::Settings(0, false, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, false, @@ -79,46 +80,46 @@ Agent::Agent(NLPacket& packet) : packetReceiver.registerListener(PacketType::Jurisdiction, this, "handleJurisdictionPacket"); } -void Agent::handleOctreePacket(QSharedPointer packet, SharedNodePointer senderNode) { - auto packetType = packet->getType(); +void Agent::handleOctreePacket(QSharedPointer message, SharedNodePointer senderNode) { + auto packetType = message->getType(); if (packetType == PacketType::OctreeStats) { - int statsMessageLength = OctreeHeadlessViewer::parseOctreeStats(packet, senderNode); - if (packet->getPayloadSize() > statsMessageLength) { + int statsMessageLength = OctreeHeadlessViewer::parseOctreeStats(message, senderNode); + if (message->getSize() > statsMessageLength) { // pull out the piggybacked packet and create a new QSharedPointer for it - int piggyBackedSizeWithHeader = packet->getPayloadSize() - statsMessageLength; - + int piggyBackedSizeWithHeader = message->getSize() - statsMessageLength; + auto buffer = std::unique_ptr(new char[piggyBackedSizeWithHeader]); - memcpy(buffer.get(), packet->getPayload() + statsMessageLength, piggyBackedSizeWithHeader); + memcpy(buffer.get(), message->getRawMessage() + statsMessageLength, piggyBackedSizeWithHeader); - auto newPacket = NLPacket::fromReceivedPacket(std::move(buffer), piggyBackedSizeWithHeader, packet->getSenderSockAddr()); - packet = QSharedPointer(newPacket.release()); + auto newPacket = NLPacket::fromReceivedPacket(std::move(buffer), piggyBackedSizeWithHeader, message->getSenderSockAddr()); + message = QSharedPointer::create(*newPacket); } else { return; // bail since no piggyback data } - packetType = packet->getType(); + packetType = message->getType(); } // fall through to piggyback message if (packetType == PacketType::EntityData || packetType == PacketType::EntityErase) { - _entityViewer.processDatagram(*packet, senderNode); + _entityViewer.processDatagram(*message, senderNode); } } -void Agent::handleJurisdictionPacket(QSharedPointer packet, SharedNodePointer senderNode) { +void Agent::handleJurisdictionPacket(QSharedPointer message, SharedNodePointer senderNode) { NodeType_t nodeType; - packet->peekPrimitive(&nodeType); + message->peekPrimitive(&nodeType); // PacketType_JURISDICTION, first byte is the node type... if (nodeType == NodeType::EntityServer) { DependencyManager::get()->getJurisdictionListener()-> - queueReceivedPacket(packet, senderNode); + queueReceivedPacket(message, senderNode); } } -void Agent::handleAudioPacket(QSharedPointer packet) { - _receivedAudioStream.parseData(*packet); +void Agent::handleAudioPacket(QSharedPointer message) { + _receivedAudioStream.parseData(*message); _lastReceivedAudioLoudness = _receivedAudioStream.getNextOutputFrameLoudness(); @@ -131,6 +132,7 @@ void Agent::run() { // make sure we request our script once the agent connects to the domain auto nodeList = DependencyManager::get(); + connect(&nodeList->getDomainHandler(), &DomainHandler::connectedToDomain, this, &Agent::requestScript); ThreadedAssignment::commonInit(AGENT_LOGGING_NAME, NodeType::Agent); @@ -223,6 +225,7 @@ void Agent::executeScript() { // call model URL setters with empty URLs so our avatar, if user, will have the default models scriptedAvatar->setFaceModelURL(QUrl()); scriptedAvatar->setSkeletonModelURL(QUrl()); + // give this AvatarData object to the script engine _scriptEngine->registerGlobalObject("Avatar", scriptedAvatar.data()); diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 6d3ce2b3b1..fbaec7efe6 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -39,7 +39,7 @@ class Agent : public ThreadedAssignment { Q_PROPERTY(QUuid sessionUUID READ getSessionUUID) public: - Agent(NLPacket& packet); + Agent(ReceivedMessage& message); void setIsAvatar(bool isAvatar); bool isAvatar() const { return _isAvatar; } @@ -63,9 +63,10 @@ private slots: void scriptRequestFinished(); void executeScript(); - void handleAudioPacket(QSharedPointer packet); - void handleOctreePacket(QSharedPointer packet, SharedNodePointer senderNode); - void handleJurisdictionPacket(QSharedPointer packet, SharedNodePointer senderNode); + void handleAudioPacket(QSharedPointer message); + void handleOctreePacket(QSharedPointer message, SharedNodePointer senderNode); + void handleJurisdictionPacket(QSharedPointer message, SharedNodePointer senderNode); + void processAgentAvatarAndAudio(float deltaTime); private: diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index 674b317812..fc0cfe1abb 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -225,11 +225,11 @@ void AssignmentClient::sendAssignmentRequest() { } } -void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer packet) { +void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer message) { qDebug() << "Received a PacketType::CreateAssignment - attempting to unpack."; // construct the deployed assignment from the packet data - _currentAssignment = AssignmentFactory::unpackAssignment(*packet); + _currentAssignment = AssignmentFactory::unpackAssignment(*message); if (_currentAssignment && !_isAssigned) { qDebug() << "Received an assignment -" << *_currentAssignment; @@ -239,7 +239,7 @@ void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer pac // switch our DomainHandler hostname and port to whoever sent us the assignment - nodeList->getDomainHandler().setSockAddr(packet->getSenderSockAddr(), _assignmentServerHostname); + nodeList->getDomainHandler().setSockAddr(message->getSenderSockAddr(), _assignmentServerHostname); nodeList->getDomainHandler().setAssignmentUUID(_currentAssignment->getUUID()); qDebug() << "Destination IP for assignment is" << nodeList->getDomainHandler().getIP().toString(); @@ -274,8 +274,8 @@ void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer pac } } -void AssignmentClient::handleStopNodePacket(QSharedPointer packet) { - const HifiSockAddr& senderSockAddr = packet->getSenderSockAddr(); +void AssignmentClient::handleStopNodePacket(QSharedPointer message) { + const HifiSockAddr& senderSockAddr = message->getSenderSockAddr(); if (senderSockAddr.getAddress() == QHostAddress::LocalHost || senderSockAddr.getAddress() == QHostAddress::LocalHostIPv6) { diff --git a/assignment-client/src/AssignmentClient.h b/assignment-client/src/AssignmentClient.h index 9d7591f931..5147bfca98 100644 --- a/assignment-client/src/AssignmentClient.h +++ b/assignment-client/src/AssignmentClient.h @@ -38,8 +38,8 @@ public slots: void aboutToQuit(); private slots: - void handleCreateAssignmentPacket(QSharedPointer packet); - void handleStopNodePacket(QSharedPointer packet); + void handleCreateAssignmentPacket(QSharedPointer message); + void handleStopNodePacket(QSharedPointer message); private: void setUpStatusToMonitor(); diff --git a/assignment-client/src/AssignmentClientMonitor.cpp b/assignment-client/src/AssignmentClientMonitor.cpp index 763945fa3e..2d27962071 100644 --- a/assignment-client/src/AssignmentClientMonitor.cpp +++ b/assignment-client/src/AssignmentClientMonitor.cpp @@ -207,14 +207,14 @@ void AssignmentClientMonitor::checkSpares() { } } -void AssignmentClientMonitor::handleChildStatusPacket(QSharedPointer packet) { +void AssignmentClientMonitor::handleChildStatusPacket(QSharedPointer message) { // read out the sender ID - QUuid senderID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + QUuid senderID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); auto nodeList = DependencyManager::get(); SharedNodePointer matchingNode = nodeList->nodeWithUUID(senderID); - const HifiSockAddr& senderSockAddr = packet->getSenderSockAddr(); + const HifiSockAddr& senderSockAddr = message->getSenderSockAddr(); AssignmentClientChildData* childData = nullptr; @@ -251,7 +251,7 @@ void AssignmentClientMonitor::handleChildStatusPacket(QSharedPointer p // get child's assignment type out of the packet quint8 assignmentType; - packet->readPrimitive(&assignmentType); + message->readPrimitive(&assignmentType); childData->setChildType((Assignment::Type) assignmentType); diff --git a/assignment-client/src/AssignmentClientMonitor.h b/assignment-client/src/AssignmentClientMonitor.h index 93fc9361ad..7ea7c3e274 100644 --- a/assignment-client/src/AssignmentClientMonitor.h +++ b/assignment-client/src/AssignmentClientMonitor.h @@ -36,7 +36,7 @@ public: private slots: void checkSpares(); void childProcessFinished(); - void handleChildStatusPacket(QSharedPointer packet); + void handleChildStatusPacket(QSharedPointer message); public slots: void aboutToQuit(); diff --git a/assignment-client/src/AssignmentFactory.cpp b/assignment-client/src/AssignmentFactory.cpp index c4cd6821ef..75d474f566 100644 --- a/assignment-client/src/AssignmentFactory.cpp +++ b/assignment-client/src/AssignmentFactory.cpp @@ -19,26 +19,26 @@ #include "assets/AssetServer.h" #include "messages/MessagesMixer.h" -ThreadedAssignment* AssignmentFactory::unpackAssignment(NLPacket& packet) { +ThreadedAssignment* AssignmentFactory::unpackAssignment(ReceivedMessage& message) { quint8 packedType; - packet.peekPrimitive(&packedType); + message.peekPrimitive(&packedType); Assignment::Type unpackedType = (Assignment::Type) packedType; switch (unpackedType) { case Assignment::AudioMixerType: - return new AudioMixer(packet); + return new AudioMixer(message); case Assignment::AvatarMixerType: - return new AvatarMixer(packet); + return new AvatarMixer(message); case Assignment::AgentType: - return new Agent(packet); + return new Agent(message); case Assignment::EntityServerType: - return new EntityServer(packet); + return new EntityServer(message); case Assignment::AssetServerType: - return new AssetServer(packet); + return new AssetServer(message); case Assignment::MessagesMixerType: - return new MessagesMixer(packet); + return new MessagesMixer(message); default: return NULL; } diff --git a/assignment-client/src/AssignmentFactory.h b/assignment-client/src/AssignmentFactory.h index 3c1fef99ef..58afcc7727 100644 --- a/assignment-client/src/AssignmentFactory.h +++ b/assignment-client/src/AssignmentFactory.h @@ -16,7 +16,7 @@ class AssignmentFactory { public: - static ThreadedAssignment* unpackAssignment(NLPacket& packet); + static ThreadedAssignment* unpackAssignment(ReceivedMessage& message); }; #endif // hifi_AssignmentFactory_h diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index b8cf6b2e01..2f9e7ee1e6 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -27,8 +27,8 @@ const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server"; -AssetServer::AssetServer(NLPacket& packet) : - ThreadedAssignment(packet), +AssetServer::AssetServer(ReceivedMessage& message) : + ThreadedAssignment(message), _taskPool(this) { @@ -40,7 +40,7 @@ AssetServer::AssetServer(NLPacket& packet) : auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListener(PacketType::AssetGet, this, "handleAssetGet"); packetReceiver.registerListener(PacketType::AssetGetInfo, this, "handleAssetGetInfo"); - packetReceiver.registerMessageListener(PacketType::AssetUpload, this, "handleAssetUpload"); + packetReceiver.registerListener(PacketType::AssetUpload, this, "handleAssetUpload"); } void AssetServer::run() { @@ -84,20 +84,20 @@ void AssetServer::run() { } } -void AssetServer::handleAssetGetInfo(QSharedPointer packet, SharedNodePointer senderNode) { +void AssetServer::handleAssetGetInfo(QSharedPointer message, SharedNodePointer senderNode) { QByteArray assetHash; MessageID messageID; uint8_t extensionLength; - if (packet->getPayloadSize() < qint64(SHA256_HASH_LENGTH + sizeof(messageID) + sizeof(extensionLength))) { + if (message->getSize() < qint64(SHA256_HASH_LENGTH + sizeof(messageID) + sizeof(extensionLength))) { qDebug() << "ERROR bad file request"; return; } - packet->readPrimitive(&messageID); - assetHash = packet->readWithoutCopy(SHA256_HASH_LENGTH); - packet->readPrimitive(&extensionLength); - QByteArray extension = packet->read(extensionLength); + message->readPrimitive(&messageID); + assetHash = message->readWithoutCopy(SHA256_HASH_LENGTH); + message->readPrimitive(&extensionLength); + QByteArray extension = message->read(extensionLength); auto replyPacket = NLPacket::create(PacketType::AssetGetInfoReply); @@ -122,26 +122,26 @@ void AssetServer::handleAssetGetInfo(QSharedPointer packet, SharedNode nodeList->sendPacket(std::move(replyPacket), *senderNode); } -void AssetServer::handleAssetGet(QSharedPointer packet, SharedNodePointer senderNode) { +void AssetServer::handleAssetGet(QSharedPointer message, SharedNodePointer senderNode) { auto minSize = qint64(sizeof(MessageID) + SHA256_HASH_LENGTH + sizeof(uint8_t) + sizeof(DataOffset) + sizeof(DataOffset)); - if (packet->getPayloadSize() < minSize) { + if (message->getSize() < minSize) { qDebug() << "ERROR bad file request"; return; } // Queue task - auto task = new SendAssetTask(packet, senderNode, _resourcesDirectory); + auto task = new SendAssetTask(message, senderNode, _resourcesDirectory); _taskPool.start(task); } -void AssetServer::handleAssetUpload(QSharedPointer packetList, SharedNodePointer senderNode) { +void AssetServer::handleAssetUpload(QSharedPointer message, SharedNodePointer senderNode) { if (senderNode->getCanRez()) { qDebug() << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(senderNode->getUUID()); - auto task = new UploadAssetTask(packetList, senderNode, _resourcesDirectory); + auto task = new UploadAssetTask(message, senderNode, _resourcesDirectory); _taskPool.start(task); } else { // this is a node the domain told us is not allowed to rez entities @@ -151,7 +151,7 @@ void AssetServer::handleAssetUpload(QSharedPointer packetList, Sha auto permissionErrorPacket = NLPacket::create(PacketType::AssetUploadReply, sizeof(MessageID) + sizeof(AssetServerError)); MessageID messageID; - packetList->readPrimitive(&messageID); + message->readPrimitive(&messageID); // write the message ID and a permission denied error permissionErrorPacket->writePrimitive(messageID); diff --git a/assignment-client/src/assets/AssetServer.h b/assignment-client/src/assets/AssetServer.h index 09144660ec..fe83ce92a6 100644 --- a/assignment-client/src/assets/AssetServer.h +++ b/assignment-client/src/assets/AssetServer.h @@ -18,19 +18,20 @@ #include #include "AssetUtils.h" +#include "ReceivedMessage.h" class AssetServer : public ThreadedAssignment { Q_OBJECT public: - AssetServer(NLPacket& packet); + AssetServer(ReceivedMessage& message); public slots: void run(); private slots: - void handleAssetGetInfo(QSharedPointer packet, SharedNodePointer senderNode); - void handleAssetGet(QSharedPointer packet, SharedNodePointer senderNode); - void handleAssetUpload(QSharedPointer packetList, SharedNodePointer senderNode); + void handleAssetGetInfo(QSharedPointer packet, SharedNodePointer senderNode); + void handleAssetGet(QSharedPointer packet, SharedNodePointer senderNode); + void handleAssetUpload(QSharedPointer packetList, SharedNodePointer senderNode); void sendStatsPacket(); diff --git a/assignment-client/src/assets/SendAssetTask.cpp b/assignment-client/src/assets/SendAssetTask.cpp index 9211aa1256..35f4ad1801 100644 --- a/assignment-client/src/assets/SendAssetTask.cpp +++ b/assignment-client/src/assets/SendAssetTask.cpp @@ -22,9 +22,9 @@ #include "AssetUtils.h" -SendAssetTask::SendAssetTask(QSharedPointer packet, const SharedNodePointer& sendToNode, const QDir& resourcesDir) : +SendAssetTask::SendAssetTask(QSharedPointer message, const SharedNodePointer& sendToNode, const QDir& resourcesDir) : QRunnable(), - _packet(packet), + _message(message), _senderNode(sendToNode), _resourcesDir(resourcesDir) { @@ -36,16 +36,16 @@ void SendAssetTask::run() { uint8_t extensionLength; DataOffset start, end; - _packet->readPrimitive(&messageID); - QByteArray assetHash = _packet->read(SHA256_HASH_LENGTH); - _packet->readPrimitive(&extensionLength); - QByteArray extension = _packet->read(extensionLength); + _message->readPrimitive(&messageID); + QByteArray assetHash = _message->read(SHA256_HASH_LENGTH); + _message->readPrimitive(&extensionLength); + QByteArray extension = _message->read(extensionLength); // `start` and `end` indicate the range of data to retrieve for the asset identified by `assetHash`. // `start` is inclusive, `end` is exclusive. Requesting `start` = 1, `end` = 10 will retrieve 9 bytes of data, // starting at index 1. - _packet->readPrimitive(&start); - _packet->readPrimitive(&end); + _message->readPrimitive(&start); + _message->readPrimitive(&end); QString hexHash = assetHash.toHex(); diff --git a/assignment-client/src/assets/SendAssetTask.h b/assignment-client/src/assets/SendAssetTask.h index 4e1a35b3fe..9fef8662d4 100644 --- a/assignment-client/src/assets/SendAssetTask.h +++ b/assignment-client/src/assets/SendAssetTask.h @@ -25,12 +25,12 @@ class NLPacket; class SendAssetTask : public QRunnable { public: - SendAssetTask(QSharedPointer packet, const SharedNodePointer& sendToNode, const QDir& resourcesDir); + SendAssetTask(QSharedPointer message, const SharedNodePointer& sendToNode, const QDir& resourcesDir); void run(); private: - QSharedPointer _packet; + QSharedPointer _message; SharedNodePointer _senderNode; QDir _resourcesDir; }; diff --git a/assignment-client/src/assets/UploadAssetTask.cpp b/assignment-client/src/assets/UploadAssetTask.cpp index 1acca295b4..114fd20320 100644 --- a/assignment-client/src/assets/UploadAssetTask.cpp +++ b/assignment-client/src/assets/UploadAssetTask.cpp @@ -19,9 +19,9 @@ #include -UploadAssetTask::UploadAssetTask(QSharedPointer packetList, SharedNodePointer senderNode, +UploadAssetTask::UploadAssetTask(QSharedPointer receivedMessage, SharedNodePointer senderNode, const QDir& resourcesDir) : - _packetList(packetList), + _receivedMessage(receivedMessage), _senderNode(senderNode), _resourcesDir(resourcesDir) { @@ -29,7 +29,7 @@ UploadAssetTask::UploadAssetTask(QSharedPointer packetList, Shared } void UploadAssetTask::run() { - auto data = _packetList->getMessage(); + auto data = _receivedMessage->getMessage(); QBuffer buffer { &data }; buffer.open(QIODevice::ReadOnly); diff --git a/assignment-client/src/assets/UploadAssetTask.h b/assignment-client/src/assets/UploadAssetTask.h index c310bfc948..aa7beccd5c 100644 --- a/assignment-client/src/assets/UploadAssetTask.h +++ b/assignment-client/src/assets/UploadAssetTask.h @@ -19,17 +19,19 @@ #include #include +#include "ReceivedMessage.h" + class NLPacketList; class Node; class UploadAssetTask : public QRunnable { public: - UploadAssetTask(QSharedPointer packetList, QSharedPointer senderNode, const QDir& resourcesDir); + UploadAssetTask(QSharedPointer message, QSharedPointer senderNode, const QDir& resourcesDir); void run(); private: - QSharedPointer _packetList; + QSharedPointer _receivedMessage; QSharedPointer _senderNode; QDir _resourcesDir; }; diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index dbf7e1e21a..8fda0ead8e 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -75,8 +75,8 @@ bool AudioMixer::shouldMute(float quietestFrame) { return (quietestFrame > _noiseMutingThreshold); } -AudioMixer::AudioMixer(NLPacket& packet) : - ThreadedAssignment(packet), +AudioMixer::AudioMixer(ReceivedMessage& message) : + ThreadedAssignment(message), _trailingSleepRatio(1.0f), _minAudibilityThreshold(LOUDNESS_TO_DISTANCE_RATIO / 2.0f), _performanceThrottlingRatio(0.0f), @@ -438,7 +438,6 @@ int AudioMixer::prepareMixForListeningNode(Node* node) { AudioMixerClientData* listenerNodeData = static_cast(node->getLinkedData()); // zero out the client mix for this node - memset(_preMixSamples, 0, sizeof(_preMixSamples)); memset(_mixSamples, 0, sizeof(_mixSamples)); // loop through all other nodes that have sufficient audio to mix @@ -459,6 +458,9 @@ int AudioMixer::prepareMixForListeningNode(Node* node) { if (otherNodeStream->getType() == PositionalAudioStream::Microphone) { streamUUID = otherNode->getUUID(); } + + // clear out the pre-mix samples before filling it up with this source + memset(_preMixSamples, 0, sizeof(_preMixSamples)); if (*otherNode != *node || otherNodeStream->shouldLoopbackForNode()) { streamsMixed += addStreamToMixForListeningNodeWithStream(listenerNodeData, streamUUID, @@ -542,17 +544,17 @@ void AudioMixer::sendAudioEnvironmentPacket(SharedNodePointer node) { } } -void AudioMixer::handleNodeAudioPacket(QSharedPointer packet, SharedNodePointer sendingNode) { - DependencyManager::get()->updateNodeWithDataFromPacket(packet, sendingNode); +void AudioMixer::handleNodeAudioPacket(QSharedPointer message, SharedNodePointer sendingNode) { + DependencyManager::get()->updateNodeWithDataFromPacket(message, sendingNode); } -void AudioMixer::handleMuteEnvironmentPacket(QSharedPointer packet, SharedNodePointer sendingNode) { +void AudioMixer::handleMuteEnvironmentPacket(QSharedPointer message, SharedNodePointer sendingNode) { auto nodeList = DependencyManager::get(); if (sendingNode->getCanAdjustLocks()) { - auto newPacket = NLPacket::create(PacketType::MuteEnvironment, packet->getPayloadSize()); + auto newPacket = NLPacket::create(PacketType::MuteEnvironment, message->getSize()); // Copy payload - newPacket->write(packet->getPayload(), packet->getPayloadSize()); + newPacket->write(message->getRawMessage(), message->getSize()); nodeList->eachNode([&](const SharedNodePointer& node){ if (node->getType() == NodeType::Agent && node->getActiveSocket() && diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index a09e67f4ba..ef963bdcc7 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -28,7 +28,7 @@ const int READ_DATAGRAMS_STATS_WINDOW_SECONDS = 30; class AudioMixer : public ThreadedAssignment { Q_OBJECT public: - AudioMixer(NLPacket& packet); + AudioMixer(ReceivedMessage& message); void deleteLater() { qDebug() << "DELETE LATER CALLED?"; QObject::deleteLater(); } public slots: @@ -41,8 +41,8 @@ public slots: private slots: void broadcastMixes(); - void handleNodeAudioPacket(QSharedPointer packet, SharedNodePointer sendingNode); - void handleMuteEnvironmentPacket(QSharedPointer packet, SharedNodePointer sendingNode); + void handleNodeAudioPacket(QSharedPointer packet, SharedNodePointer sendingNode); + void handleMuteEnvironmentPacket(QSharedPointer packet, SharedNodePointer sendingNode); private: void domainSettingsRequestComplete(); diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 4fc5a3785f..729e9fa633 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -49,18 +49,18 @@ AvatarAudioStream* AudioMixerClientData::getAvatarAudioStream() const { return NULL; } -int AudioMixerClientData::parseData(NLPacket& packet) { - PacketType packetType = packet.getType(); +int AudioMixerClientData::parseData(ReceivedMessage& message) { + PacketType packetType = message.getType(); if (packetType == PacketType::AudioStreamStats) { // skip over header, appendFlag, and num stats packed - packet.seek(sizeof(quint8) + sizeof(quint16)); + message.seek(sizeof(quint8) + sizeof(quint16)); // read the downstream audio stream stats - packet.readPrimitive(&_downstreamAudioStreamStats); + message.readPrimitive(&_downstreamAudioStreamStats); - return packet.pos(); + return message.getPosition(); } else { PositionalAudioStream* matchingStream = NULL; @@ -74,10 +74,10 @@ int AudioMixerClientData::parseData(NLPacket& packet) { // we don't have a mic stream yet, so add it // read the channel flag to see if our stream is stereo or not - packet.seek(sizeof(quint16)); + message.seek(sizeof(quint16)); quint8 channelFlag; - packet.readPrimitive(&channelFlag); + message.readPrimitive(&channelFlag); bool isStereo = channelFlag == 1; @@ -89,11 +89,11 @@ int AudioMixerClientData::parseData(NLPacket& packet) { // this is injected audio // grab the stream identifier for this injected audio - packet.seek(sizeof(quint16)); - QUuid streamIdentifier = QUuid::fromRfc4122(packet.readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + message.seek(sizeof(quint16)); + QUuid streamIdentifier = QUuid::fromRfc4122(message.readWithoutCopy(NUM_BYTES_RFC4122_UUID)); bool isStereo; - packet.readPrimitive(&isStereo); + message.readPrimitive(&isStereo); if (!_audioStreams.contains(streamIdentifier)) { // we don't have this injected stream yet, so add it @@ -105,9 +105,9 @@ int AudioMixerClientData::parseData(NLPacket& packet) { } // seek to the beginning of the packet so that the next reader is in the right spot - packet.seek(0); + message.seek(0); - return matchingStream->parseData(packet); + return matchingStream->parseData(message); } return 0; } diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index 20bcaf5627..17b2fecd12 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -42,7 +42,7 @@ public: const QHash& getAudioStreams() const { return _audioStreams; } AvatarAudioStream* getAvatarAudioStream() const; - int parseData(NLPacket& packet); + int parseData(ReceivedMessage& message); void checkBuffersBeforeFrameSend(); diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 0e29610e12..759a4956f2 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -34,8 +34,8 @@ const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer"; const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 60; const unsigned int AVATAR_DATA_SEND_INTERVAL_MSECS = (1.0f / (float) AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND) * 1000; -AvatarMixer::AvatarMixer(NLPacket& packet) : - ThreadedAssignment(packet), +AvatarMixer::AvatarMixer(ReceivedMessage& message) : + ThreadedAssignment(message), _broadcastThread(), _lastFrameTimestamp(QDateTime::currentMSecsSinceEpoch()), _trailingSleepRatio(1.0f), @@ -157,7 +157,7 @@ void AvatarMixer::broadcastAvatarData() { ++_sumListeners; AvatarData& avatar = nodeData->getAvatar(); - glm::vec3 myPosition = avatar.getPosition(); + glm::vec3 myPosition = avatar.getClientGlobalPosition(); // reset the internal state for correct random number distribution distribution.reset(); @@ -290,7 +290,7 @@ void AvatarMixer::broadcastAvatarData() { // The full rate distance is the distance at which EVERY update will be sent for this avatar // at twice the full rate distance, there will be a 50% chance of sending this avatar's update - glm::vec3 otherPosition = otherAvatar.getPosition(); + glm::vec3 otherPosition = otherAvatar.getClientGlobalPosition(); float distanceToAvatar = glm::length(myPosition - otherPosition); // potentially update the max full rate distance for this frame @@ -424,19 +424,19 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { } } -void AvatarMixer::handleAvatarDataPacket(QSharedPointer packet, SharedNodePointer senderNode) { +void AvatarMixer::handleAvatarDataPacket(QSharedPointer message, SharedNodePointer senderNode) { auto nodeList = DependencyManager::get(); - nodeList->updateNodeWithDataFromPacket(packet, senderNode); + nodeList->updateNodeWithDataFromPacket(message, senderNode); } -void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer packet, SharedNodePointer senderNode) { +void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer message, SharedNodePointer senderNode) { if (senderNode->getLinkedData()) { AvatarMixerClientData* nodeData = dynamic_cast(senderNode->getLinkedData()); if (nodeData != nullptr) { AvatarData& avatar = nodeData->getAvatar(); // parse the identity packet and update the change timestamp if appropriate - if (avatar.hasIdentityChangedAfterParsing(*packet)) { + if (avatar.hasIdentityChangedAfterParsing(message->getMessage())) { QMutexLocker nodeDataLocker(&nodeData->getMutex()); nodeData->setIdentityChangeTimestamp(QDateTime::currentMSecsSinceEpoch()); } @@ -444,13 +444,13 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer packet, Sh } } -void AvatarMixer::handleAvatarBillboardPacket(QSharedPointer packet, SharedNodePointer senderNode) { +void AvatarMixer::handleAvatarBillboardPacket(QSharedPointer message, SharedNodePointer senderNode) { AvatarMixerClientData* nodeData = dynamic_cast(senderNode->getLinkedData()); if (nodeData) { AvatarData& avatar = nodeData->getAvatar(); // parse the billboard packet and update the change timestamp if appropriate - if (avatar.hasBillboardChangedAfterParsing(*packet)) { + if (avatar.hasBillboardChangedAfterParsing(message->getMessage())) { QMutexLocker nodeDataLocker(&nodeData->getMutex()); nodeData->setBillboardChangeTimestamp(QDateTime::currentMSecsSinceEpoch()); } @@ -458,8 +458,8 @@ void AvatarMixer::handleAvatarBillboardPacket(QSharedPointer packet, S } } -void AvatarMixer::handleKillAvatarPacket(QSharedPointer packet) { - DependencyManager::get()->processKillNode(*packet); +void AvatarMixer::handleKillAvatarPacket(QSharedPointer message) { + DependencyManager::get()->processKillNode(*message); } void AvatarMixer::sendStatsPacket() { diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 6e87bd6a43..00c53e916b 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -21,7 +21,7 @@ class AvatarMixer : public ThreadedAssignment { Q_OBJECT public: - AvatarMixer(NLPacket& packet); + AvatarMixer(ReceivedMessage& message); ~AvatarMixer(); public slots: /// runs the avatar mixer @@ -32,10 +32,10 @@ public slots: void sendStatsPacket(); private slots: - void handleAvatarDataPacket(QSharedPointer packet, SharedNodePointer senderNode); - void handleAvatarIdentityPacket(QSharedPointer packet, SharedNodePointer senderNode); - void handleAvatarBillboardPacket(QSharedPointer packet, SharedNodePointer senderNode); - void handleKillAvatarPacket(QSharedPointer packet); + void handleAvatarDataPacket(QSharedPointer message, SharedNodePointer senderNode); + void handleAvatarIdentityPacket(QSharedPointer message, SharedNodePointer senderNode); + void handleAvatarBillboardPacket(QSharedPointer message, SharedNodePointer senderNode); + void handleKillAvatarPacket(QSharedPointer message); void domainSettingsRequestComplete(); private: diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 9d78d92463..4b7a696d58 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -13,12 +13,12 @@ #include "AvatarMixerClientData.h" -int AvatarMixerClientData::parseData(NLPacket& packet) { +int AvatarMixerClientData::parseData(ReceivedMessage& message) { // pull the sequence number from the data first - packet.readPrimitive(&_lastReceivedSequenceNumber); - + message.readPrimitive(&_lastReceivedSequenceNumber); + // compute the offset to the data payload - return _avatar.parseDataFromBuffer(packet.readWithoutCopy(packet.bytesLeftToRead())); + return _avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead())); } bool AvatarMixerClientData::checkAndSetHasReceivedFirstPacketsFrom(const QUuid& uuid) { @@ -40,7 +40,7 @@ uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& node } void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const { - jsonObject["display_name"] = _avatar.getDisplayName(); + jsonObject["display_name"] = _avatar->getDisplayName(); jsonObject["full_rate_distance"] = _fullRateDistance; jsonObject["max_av_distance"] = _maxAvatarDistance; jsonObject["num_avs_sent_last_frame"] = _numAvatarsSentLastFrame; @@ -49,7 +49,7 @@ void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const { jsonObject["total_num_out_of_order_sends"] = _numOutOfOrderSends; jsonObject[OUTBOUND_AVATAR_DATA_STATS_KEY] = getOutboundAvatarDataKbps(); - jsonObject[INBOUND_AVATAR_DATA_STATS_KEY] = _avatar.getAverageBytesReceivedPerSecond() / (float) BYTES_PER_KILOBIT; + jsonObject[INBOUND_AVATAR_DATA_STATS_KEY] = _avatar->getAverageBytesReceivedPerSecond() / (float) BYTES_PER_KILOBIT; - jsonObject["av_data_receive_rate"] = _avatar.getReceiveRate(); + jsonObject["av_data_receive_rate"] = _avatar->getReceiveRate(); } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 1f5e8fa77a..a310d0f16a 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -33,8 +33,8 @@ const QString INBOUND_AVATAR_DATA_STATS_KEY = "inbound_av_data_kbps"; class AvatarMixerClientData : public NodeData { Q_OBJECT public: - int parseData(NLPacket& packet); - AvatarData& getAvatar() { return _avatar; } + int parseData(ReceivedMessage& message) override; + AvatarData& getAvatar() { return *_avatar; } bool checkAndSetHasReceivedFirstPacketsFrom(const QUuid& uuid); @@ -80,7 +80,7 @@ public: void loadJSONStats(QJsonObject& jsonObject) const; private: - AvatarData _avatar; + AvatarSharedPointer _avatar { new AvatarData() }; uint16_t _lastReceivedSequenceNumber { 0 }; std::unordered_map _lastBroadcastSequenceNumbers; diff --git a/assignment-client/src/entities/AssignmentParentFinder.cpp b/assignment-client/src/entities/AssignmentParentFinder.cpp new file mode 100644 index 0000000000..5fe4742b90 --- /dev/null +++ b/assignment-client/src/entities/AssignmentParentFinder.cpp @@ -0,0 +1,19 @@ +// +// AssignmentParentFinder.cpp +// assignment-client/src/entities +// +// Created by Seth Alves on 2015-10-22 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AssignmentParentFinder.h" + +SpatiallyNestableWeakPointer AssignmentParentFinder::find(QUuid parentID) const { + SpatiallyNestableWeakPointer parent; + // search entities + parent = _tree->findEntityByEntityItemID(parentID); + return parent; +} diff --git a/assignment-client/src/entities/AssignmentParentFinder.h b/assignment-client/src/entities/AssignmentParentFinder.h new file mode 100644 index 0000000000..99fa58ffed --- /dev/null +++ b/assignment-client/src/entities/AssignmentParentFinder.h @@ -0,0 +1,34 @@ +// +// AssignmentParentFinder.h +// interface/src/entities +// +// Created by Seth Alves on 2015-10-21 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AssignmentParentFinder_h +#define hifi_AssignmentParentFinder_h + +#include +#include + +#include +#include + +// This interface is used to turn a QUuid into a pointer to a "parent" -- something that children can +// be spatially relative to. At this point, this means either an EntityItem or an Avatar. + +class AssignmentParentFinder : public SpatialParentFinder { +public: + AssignmentParentFinder(EntityTreePointer tree) : _tree(tree) { } + virtual ~AssignmentParentFinder() { } + virtual SpatiallyNestableWeakPointer find(QUuid parentID) const; + +protected: + EntityTreePointer _tree; +}; + +#endif // hifi_AssignmentParentFinder_h diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 6508f09f72..b6223497e6 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -16,13 +16,14 @@ #include "EntityServer.h" #include "EntityServerConsts.h" #include "EntityNodeData.h" +#include "AssignmentParentFinder.h" const char* MODEL_SERVER_NAME = "Entity"; const char* MODEL_SERVER_LOGGING_TARGET_NAME = "entity-server"; const char* LOCAL_MODELS_PERSIST_FILE = "resources/models.svo"; -EntityServer::EntityServer(NLPacket& packet) : - OctreeServer(packet), +EntityServer::EntityServer(ReceivedMessage& message) : + OctreeServer(message), _entitySimulation(NULL) { auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); @@ -40,9 +41,9 @@ EntityServer::~EntityServer() { tree->removeNewlyCreatedHook(this); } -void EntityServer::handleEntityPacket(QSharedPointer packet, SharedNodePointer senderNode) { +void EntityServer::handleEntityPacket(QSharedPointer message, SharedNodePointer senderNode) { if (_octreeInboundPacketProcessor) { - _octreeInboundPacketProcessor->queueReceivedPacket(packet, senderNode); + _octreeInboundPacketProcessor->queueReceivedPacket(message, senderNode); } } @@ -60,6 +61,10 @@ OctreePointer EntityServer::createTree() { tree->setSimulation(simpleSimulation); _entitySimulation = simpleSimulation; } + + DependencyManager::registerInheritance(); + DependencyManager::set(tree); + return tree; } @@ -266,3 +271,72 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio tree->setWantEditLogging(wantEditLogging); tree->setWantTerseEditLogging(wantTerseEditLogging); } + + +// FIXME - this stats tracking is somewhat temporary to debug the Whiteboard issues. It's not a bad +// set of stats to have, but we'd probably want a different data structure if we keep it very long. +// Since this version uses a single shared QMap for all senders, there could be some lock contention +// on this QWriteLocker +void EntityServer::trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& viewerNode) { + QWriteLocker locker(&_viewerSendingStatsLock); + _viewerSendingStats[viewerNode][dataID] = { usecTimestampNow(), dataLastEdited }; +} + +void EntityServer::trackViewerGone(const QUuid& viewerNode) { + QWriteLocker locker(&_viewerSendingStatsLock); + _viewerSendingStats.remove(viewerNode); +} + +QString EntityServer::serverSubclassStats() { + QLocale locale(QLocale::English); + QString statsString; + + // display memory usage stats + statsString += "Entity Server Memory Statistics\r\n"; + statsString += QString().sprintf("EntityTreeElement size... %ld bytes\r\n", sizeof(EntityTreeElement)); + statsString += QString().sprintf(" EntityItem size... %ld bytes\r\n", sizeof(EntityItem)); + statsString += "\r\n\r\n"; + + statsString += "Entity Server Sending to Viewer Statistics\r\n"; + statsString += "----- Viewer Node ID ----------------- ----- Entity ID ---------------------- " + "---------- Last Sent To ---------- ---------- Last Edited -----------\r\n"; + + int viewers = 0; + const int COLUMN_WIDTH = 24; + + { + QReadLocker locker(&_viewerSendingStatsLock); + quint64 now = usecTimestampNow(); + + for (auto viewerID : _viewerSendingStats.keys()) { + statsString += viewerID.toString() + "\r\n"; + + auto viewerData = _viewerSendingStats[viewerID]; + for (auto entityID : viewerData.keys()) { + ViewerSendingStats stats = viewerData[entityID]; + + quint64 elapsedSinceSent = now - stats.lastSent; + double sentMsecsAgo = (double)(elapsedSinceSent / USECS_PER_MSEC); + + quint64 elapsedSinceEdit = now - stats.lastEdited; + double editMsecsAgo = (double)(elapsedSinceEdit / USECS_PER_MSEC); + + statsString += " "; // the viewerID spacing + statsString += entityID.toString(); + statsString += " "; + statsString += QString("%1 msecs ago") + .arg(locale.toString((double)sentMsecsAgo).rightJustified(COLUMN_WIDTH, ' ')); + statsString += QString("%1 msecs ago") + .arg(locale.toString((double)editMsecsAgo).rightJustified(COLUMN_WIDTH, ' ')); + statsString += "\r\n"; + } + viewers++; + } + } + if (viewers < 1) { + statsString += " no viewers... \r\n"; + } + statsString += "\r\n\r\n"; + + return statsString; +} diff --git a/assignment-client/src/entities/EntityServer.h b/assignment-client/src/entities/EntityServer.h index d9795316c4..cd603f44d8 100644 --- a/assignment-client/src/entities/EntityServer.h +++ b/assignment-client/src/entities/EntityServer.h @@ -21,10 +21,16 @@ #include "EntityTree.h" /// Handles assignments of type EntityServer - sending entities to various clients. + +struct ViewerSendingStats { + quint64 lastSent; + quint64 lastEdited; +}; + class EntityServer : public OctreeServer, public NewlyCreatedEntityHook { Q_OBJECT public: - EntityServer(NLPacket& packet); + EntityServer(ReceivedMessage& message); ~EntityServer(); // Subclasses must implement these methods @@ -44,6 +50,10 @@ public: virtual void entityCreated(const EntityItem& newEntity, const SharedNodePointer& senderNode) override; virtual void readAdditionalConfiguration(const QJsonObject& settingsSectionObject) override; + virtual QString serverSubclassStats() override; + + virtual void trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& viewerNode) override; + virtual void trackViewerGone(const QUuid& viewerNode) override; public slots: void pruneDeletedEntities(); @@ -52,11 +62,14 @@ protected: virtual OctreePointer createTree() override; private slots: - void handleEntityPacket(QSharedPointer packet, SharedNodePointer senderNode); + void handleEntityPacket(QSharedPointer message, SharedNodePointer senderNode); private: EntitySimulation* _entitySimulation; QTimer* _pruneDeletedEntitiesTimer = nullptr; + + QReadWriteLock _viewerSendingStatsLock; + QMap> _viewerSendingStats; }; #endif // hifi_EntityServer_h diff --git a/assignment-client/src/messages/MessagesMixer.cpp b/assignment-client/src/messages/MessagesMixer.cpp index 8f2b8a5475..3baea67486 100644 --- a/assignment-client/src/messages/MessagesMixer.cpp +++ b/assignment-client/src/messages/MessagesMixer.cpp @@ -20,13 +20,13 @@ const QString MESSAGES_MIXER_LOGGING_NAME = "messages-mixer"; -MessagesMixer::MessagesMixer(NLPacket& packet) : ThreadedAssignment(packet) +MessagesMixer::MessagesMixer(ReceivedMessage& message) : ThreadedAssignment(message) { connect(DependencyManager::get().data(), &NodeList::nodeKilled, this, &MessagesMixer::nodeKilled); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerMessageListener(PacketType::MessagesData, this, "handleMessages"); - packetReceiver.registerMessageListener(PacketType::MessagesSubscribe, this, "handleMessagesSubscribe"); - packetReceiver.registerMessageListener(PacketType::MessagesUnsubscribe, this, "handleMessagesUnsubscribe"); + packetReceiver.registerListener(PacketType::MessagesData, this, "handleMessages"); + packetReceiver.registerListener(PacketType::MessagesSubscribe, this, "handleMessagesSubscribe"); + packetReceiver.registerListener(PacketType::MessagesUnsubscribe, this, "handleMessagesUnsubscribe"); } void MessagesMixer::nodeKilled(SharedNodePointer killedNode) { @@ -35,10 +35,10 @@ void MessagesMixer::nodeKilled(SharedNodePointer killedNode) { } } -void MessagesMixer::handleMessages(QSharedPointer packetList, SharedNodePointer senderNode) { +void MessagesMixer::handleMessages(QSharedPointer receivedMessage, SharedNodePointer senderNode) { QString channel, message; QUuid senderID; - MessagesClient::decodeMessagesPacket(packetList, channel, message, senderID); + MessagesClient::decodeMessagesPacket(receivedMessage, channel, message, senderID); auto nodeList = DependencyManager::get(); @@ -53,13 +53,13 @@ void MessagesMixer::handleMessages(QSharedPointer packetList, Shar }); } -void MessagesMixer::handleMessagesSubscribe(QSharedPointer packetList, SharedNodePointer senderNode) { - QString channel = QString::fromUtf8(packetList->getMessage()); +void MessagesMixer::handleMessagesSubscribe(QSharedPointer message, SharedNodePointer senderNode) { + QString channel = QString::fromUtf8(message->getMessage()); _channelSubscribers[channel] << senderNode->getUUID(); } -void MessagesMixer::handleMessagesUnsubscribe(QSharedPointer packetList, SharedNodePointer senderNode) { - QString channel = QString::fromUtf8(packetList->getMessage()); +void MessagesMixer::handleMessagesUnsubscribe(QSharedPointer message, SharedNodePointer senderNode) { + QString channel = QString::fromUtf8(message->getMessage()); if (_channelSubscribers.contains(channel)) { _channelSubscribers[channel].remove(senderNode->getUUID()); } diff --git a/assignment-client/src/messages/MessagesMixer.h b/assignment-client/src/messages/MessagesMixer.h index cf5fc79e17..76ae2f7195 100644 --- a/assignment-client/src/messages/MessagesMixer.h +++ b/assignment-client/src/messages/MessagesMixer.h @@ -21,7 +21,7 @@ class MessagesMixer : public ThreadedAssignment { Q_OBJECT public: - MessagesMixer(NLPacket& packet); + MessagesMixer(ReceivedMessage& message); public slots: void run(); @@ -29,9 +29,9 @@ public slots: void sendStatsPacket(); private slots: - void handleMessages(QSharedPointer packetList, SharedNodePointer senderNode); - void handleMessagesSubscribe(QSharedPointer packetList, SharedNodePointer senderNode); - void handleMessagesUnsubscribe(QSharedPointer packetList, SharedNodePointer senderNode); + void handleMessages(QSharedPointer message, SharedNodePointer senderNode); + void handleMessagesSubscribe(QSharedPointer message, SharedNodePointer senderNode); + void handleMessagesUnsubscribe(QSharedPointer message, SharedNodePointer senderNode); private: QHash> _channelSubscribers; diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp index e22f241453..6e4e822196 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp @@ -75,7 +75,7 @@ void OctreeInboundPacketProcessor::midProcess() { } } -void OctreeInboundPacketProcessor::processPacket(QSharedPointer packet, SharedNodePointer sendingNode) { +void OctreeInboundPacketProcessor::processPacket(QSharedPointer message, SharedNodePointer sendingNode) { if (_shuttingDown) { qDebug() << "OctreeInboundPacketProcessor::processPacket() while shutting down... ignoring incoming packet"; return; @@ -85,22 +85,22 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer packet if (debugProcessPacket) { qDebug("OctreeInboundPacketProcessor::processPacket() payload=%p payloadLength=%lld", - packet->getPayload(), - packet->getPayloadSize()); + message->getRawMessage(), + message->getSize()); } // Ask our tree subclass if it can handle the incoming packet... - PacketType packetType = packet->getType(); + PacketType packetType = message->getType(); if (_myServer->getOctree()->handlesEditPacketType(packetType)) { PerformanceWarning warn(debugProcessPacket, "processPacket KNOWN TYPE", debugProcessPacket); _receivedPacketCount++; unsigned short int sequence; - packet->readPrimitive(&sequence); + message->readPrimitive(&sequence); quint64 sentAt; - packet->readPrimitive(&sentAt); + message->readPrimitive(&sentAt); quint64 arrivedAt = usecTimestampNow(); if (sentAt > arrivedAt) { @@ -118,7 +118,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer packet if (debugProcessPacket || _myServer->wantsDebugReceiving()) { qDebug() << "PROCESSING THREAD: got '" << packetType << "' packet - " << _receivedPacketCount << " command from client"; - qDebug() << " receivedBytes=" << packet->getDataSize(); + qDebug() << " receivedBytes=" << message->getSize(); qDebug() << " sequence=" << sequence; qDebug() << " sentAt=" << sentAt << " usecs"; qDebug() << " arrivedAt=" << arrivedAt << " usecs"; @@ -132,29 +132,29 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer packet qDebug() << " numBytesPacketHeader=" << NLPacket::totalHeaderSize(packetType); qDebug() << " sizeof(sequence)=" << sizeof(sequence); qDebug() << " sizeof(sentAt)=" << sizeof(sentAt); - qDebug() << " atByte (in payload)=" << packet->pos(); - qDebug() << " payload size=" << packet->getPayloadSize(); + qDebug() << " atByte (in payload)=" << message->getPosition(); + qDebug() << " payload size=" << message->getSize(); - if (!packet->bytesLeftToRead()) { + if (!message->getBytesLeftToRead()) { qDebug() << " ----- UNEXPECTED ---- got a packet without any edit details!!!! --------"; } } const unsigned char* editData = nullptr; - while (packet->bytesLeftToRead() > 0) { + while (message->getBytesLeftToRead() > 0) { - editData = reinterpret_cast(packet->getPayload() + packet->pos()); + editData = reinterpret_cast(message->getRawMessage() + message->getPosition()); - int maxSize = packet->bytesLeftToRead(); + int maxSize = message->getBytesLeftToRead(); if (debugProcessPacket) { qDebug() << " --- inside while loop ---"; qDebug() << " maxSize=" << maxSize; qDebug("OctreeInboundPacketProcessor::processPacket() %hhu " "payload=%p payloadLength=%lld editData=%p payloadPosition=%lld maxSize=%d", - packetType, packet->getPayload(), packet->getPayloadSize(), editData, - packet->pos(), maxSize); + packetType, message->getRawMessage(), message->getSize(), editData, + message->getPosition(), maxSize); } quint64 startProcess, startLock = usecTimestampNow(); @@ -162,7 +162,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer packet _myServer->getOctree()->withWriteLock([&] { startProcess = usecTimestampNow(); editDataBytesRead = - _myServer->getOctree()->processEditPacketData(*packet, editData, maxSize, sendingNode); + _myServer->getOctree()->processEditPacketData(*message, editData, maxSize, sendingNode); }); quint64 endProcess = usecTimestampNow(); @@ -178,12 +178,12 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer packet lockWaitTime += thisLockWaitTime; // skip to next edit record in the packet - packet->seek(packet->pos() + editDataBytesRead); + message->seek(message->getPosition() + editDataBytesRead); if (debugProcessPacket) { qDebug() << " editDataBytesRead=" << editDataBytesRead; - qDebug() << " AFTER processEditPacketData payload position=" << packet->pos(); - qDebug() << " AFTER processEditPacketData payload size=" << packet->getPayloadSize(); + qDebug() << " AFTER processEditPacketData payload position=" << message->getPosition(); + qDebug() << " AFTER processEditPacketData payload size=" << message->getSize(); } } @@ -191,7 +191,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer packet if (debugProcessPacket) { qDebug("OctreeInboundPacketProcessor::processPacket() DONE LOOPING FOR %hhu " "payload=%p payloadLength=%lld editData=%p payloadPosition=%lld", - packetType, packet->getPayload(), packet->getPayloadSize(), editData, packet->pos()); + packetType, message->getRawMessage(), message->getSize(), editData, message->getPosition()); } // Make sure our Node and NodeList knows we've heard from this node. diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.h b/assignment-client/src/octree/OctreeInboundPacketProcessor.h index 83960abaa6..8d0245c2b1 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.h +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.h @@ -78,7 +78,7 @@ public: protected: - virtual void processPacket(QSharedPointer packet, SharedNodePointer sendingNode); + virtual void processPacket(QSharedPointer message, SharedNodePointer sendingNode); virtual unsigned long getMaxWait() const; virtual void preProcess(); diff --git a/assignment-client/src/octree/OctreeQueryNode.cpp b/assignment-client/src/octree/OctreeQueryNode.cpp index f70ff62f91..ba47a15ef3 100644 --- a/assignment-client/src/octree/OctreeQueryNode.cpp +++ b/assignment-client/src/octree/OctreeQueryNode.cpp @@ -33,8 +33,6 @@ OctreeQueryNode::OctreeQueryNode() : _lastTimeBagEmpty(0), _viewFrustumChanging(false), _viewFrustumJustStoppedChanging(true), - _currentPacketIsColor(true), - _currentPacketIsCompressed(false), _octreeSendThread(NULL), _lastClientBoundaryLevelAdjust(0), _lastClientOctreeSizeScale(DEFAULT_OCTREE_SIZE_SCALE), @@ -59,7 +57,6 @@ OctreeQueryNode::~OctreeQueryNode() { void OctreeQueryNode::nodeKilled() { _isShuttingDown = true; - elementBag.unhookNotifications(); // if our node is shutting down, then we no longer need octree element notifications if (_octreeSendThread) { // just tell our thread we want to shutdown, this is asynchronous, and fast, we don't need or want it to block // while the thread actually shuts down @@ -69,7 +66,6 @@ void OctreeQueryNode::nodeKilled() { void OctreeQueryNode::forceNodeShutdown() { _isShuttingDown = true; - elementBag.unhookNotifications(); // if our node is shutting down, then we no longer need octree element notifications if (_octreeSendThread) { // we really need to force our thread to shutdown, this is synchronous, we will block while the thread actually // shuts down because we really need it to shutdown, and it's ok if we wait for it to complete @@ -181,15 +177,9 @@ void OctreeQueryNode::resetOctreePacket() { // If we're moving, and the client asked for low res, then we force monochrome, otherwise, use // the clients requested color state. - _currentPacketIsColor = getWantColor(); - _currentPacketIsCompressed = getWantCompression(); OCTREE_PACKET_FLAGS flags = 0; - if (_currentPacketIsColor) { - setAtBit(flags, PACKET_IS_COLOR_BIT); - } - if (_currentPacketIsCompressed) { - setAtBit(flags, PACKET_IS_COMPRESSED_BIT); - } + setAtBit(flags, PACKET_IS_COLOR_BIT); // always color + setAtBit(flags, PACKET_IS_COMPRESSED_BIT); // always compressed _octreePacket->reset(); @@ -214,10 +204,9 @@ void OctreeQueryNode::writeToPacket(const unsigned char* buffer, unsigned int by // compressed packets include lead bytes which contain compressed size, this allows packing of // multiple compressed portions together - if (_currentPacketIsCompressed) { - OCTREE_PACKET_INTERNAL_SECTION_SIZE sectionSize = bytes; - _octreePacket->writePrimitive(sectionSize); - } + OCTREE_PACKET_INTERNAL_SECTION_SIZE sectionSize = bytes; + _octreePacket->writePrimitive(sectionSize); + if (bytes <= _octreePacket->bytesAvailableForWrite()) { _octreePacket->write(reinterpret_cast(buffer), bytes); _octreePacketWaiting = true; @@ -338,8 +327,7 @@ void OctreeQueryNode::dumpOutOfView() { int stillInView = 0; int outOfView = 0; OctreeElementBag tempBag; - while (!elementBag.isEmpty()) { - OctreeElementPointer elementToCheck = elementBag.extract(); + while (OctreeElementPointer elementToCheck = elementBag.extract()) { if (elementToCheck->isInView(_currentViewFrustum)) { tempBag.insert(elementToCheck); stillInView++; @@ -348,8 +336,7 @@ void OctreeQueryNode::dumpOutOfView() { } } if (stillInView > 0) { - while (!tempBag.isEmpty()) { - OctreeElementPointer elementToKeepInBag = tempBag.extract(); + while (OctreeElementPointer elementToKeepInBag = tempBag.extract()) { if (elementToKeepInBag->isInView(_currentViewFrustum)) { elementBag.insert(elementToKeepInBag); } @@ -375,11 +362,11 @@ const NLPacket* OctreeQueryNode::getNextNackedPacket() { return nullptr; } -void OctreeQueryNode::parseNackPacket(NLPacket& packet) { +void OctreeQueryNode::parseNackPacket(ReceivedMessage& message) { // read sequence numbers - while (packet.bytesLeftToRead()) { + while (message.getBytesLeftToRead()) { OCTREE_PACKET_SEQUENCE sequenceNumber; - packet.readPrimitive(&sequenceNumber); + message.readPrimitive(&sequenceNumber); _nackedSequenceNumbers.enqueue(sequenceNumber); } } diff --git a/assignment-client/src/octree/OctreeQueryNode.h b/assignment-client/src/octree/OctreeQueryNode.h index 0c691a06a2..89583492e0 100644 --- a/assignment-client/src/octree/OctreeQueryNode.h +++ b/assignment-client/src/octree/OctreeQueryNode.h @@ -14,7 +14,6 @@ #include -#include #include #include #include @@ -55,7 +54,6 @@ public: void setMaxLevelReached(int maxLevelReached) { _maxLevelReachedInLastSearch = maxLevelReached; } OctreeElementBag elementBag; - CoverageMap map; OctreeElementExtraEncodeData extraEncodeData; ViewFrustum& getCurrentViewFrustum() { return _currentViewFrustum; } @@ -77,12 +75,6 @@ public: quint64 getLastTimeBagEmpty() const { return _lastTimeBagEmpty; } void setLastTimeBagEmpty() { _lastTimeBagEmpty = _sceneSendStartTime; } - bool getCurrentPacketIsColor() const { return _currentPacketIsColor; } - bool getCurrentPacketIsCompressed() const { return _currentPacketIsCompressed; } - bool getCurrentPacketFormatMatches() { - return (getCurrentPacketIsColor() == getWantColor() && getCurrentPacketIsCompressed() == getWantCompression()); - } - bool hasLodChanged() const { return _lodChanged; } OctreeSceneStats stats; @@ -108,7 +100,7 @@ public: OCTREE_PACKET_SEQUENCE getSequenceNumber() const { return _sequenceNumber; } - void parseNackPacket(NLPacket& packet); + void parseNackPacket(ReceivedMessage& message); bool hasNextNackedPacket() const; const NLPacket* getNextNackedPacket(); @@ -135,8 +127,6 @@ private: quint64 _lastTimeBagEmpty; bool _viewFrustumChanging; bool _viewFrustumJustStoppedChanging; - bool _currentPacketIsColor; - bool _currentPacketIsCompressed; OctreeSendThread* _octreeSendThread; diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 7c8d8f0e01..9b664572c1 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -309,37 +309,29 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus int truePacketsSent = 0; int trueBytesSent = 0; int packetsSentThisInterval = 0; - bool isFullScene = ((!viewFrustumChanged || !nodeData->getWantDelta()) && nodeData->getViewFrustumJustStoppedChanging()) + bool isFullScene = ((!viewFrustumChanged) && nodeData->getViewFrustumJustStoppedChanging()) || nodeData->hasLodChanged(); bool somethingToSend = true; // assume we have something - // FOR NOW... node tells us if it wants to receive only view frustum deltas - bool wantDelta = viewFrustumChanged && nodeData->getWantDelta(); - // If our packet already has content in it, then we must use the color choice of the waiting packet. // If we're starting a fresh packet, then... // If we're moving, and the client asked for low res, then we force monochrome, otherwise, use // the clients requested color state. - bool wantColor = nodeData->getWantColor(); - bool wantCompression = nodeData->getWantCompression(); // If we have a packet waiting, and our desired want color, doesn't match the current waiting packets color // then let's just send that waiting packet. - if (!nodeData->getCurrentPacketFormatMatches()) { - if (nodeData->isPacketWaiting()) { - packetsSentThisInterval += handlePacketSend(nodeData, trueBytesSent, truePacketsSent); - } else { - nodeData->resetOctreePacket(); - } - int targetSize = MAX_OCTREE_PACKET_DATA_SIZE; - if (wantCompression) { - targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE); - } - _packetData.changeSettings(wantCompression, targetSize); + if (nodeData->isPacketWaiting()) { + packetsSentThisInterval += handlePacketSend(nodeData, trueBytesSent, truePacketsSent); + } else { + nodeData->resetOctreePacket(); } + int targetSize = MAX_OCTREE_PACKET_DATA_SIZE; + targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE); - const ViewFrustum* lastViewFrustum = wantDelta ? &nodeData->getLastKnownViewFrustum() : NULL; + _packetData.changeSettings(true, targetSize); // FIXME - eventually support only compressed packets + + const ViewFrustum* lastViewFrustum = viewFrustumChanged ? &nodeData->getLastKnownViewFrustum() : NULL; // If the current view frustum has changed OR we have nothing to send, then search against // the current view frustum for things to send. @@ -350,12 +342,6 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus if (nodeData->moveShouldDump() || nodeData->hasLodChanged()) { nodeData->dumpOutOfView(); } - nodeData->map.erase(); - } - - if (!viewFrustumChanged && !nodeData->getWantDelta()) { - // only set our last sent time if we weren't resetting due to frustum change - nodeData->setLastTimeBagEmpty(); } // track completed scenes and send out the stats packet accordingly @@ -424,8 +410,11 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus quint64 lockWaitEnd = usecTimestampNow(); lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart); quint64 encodeStart = usecTimestampNow(); - + OctreeElementPointer subTree = nodeData->elementBag.extract(); + if (!subTree) { + return; + } /* TODO: Looking for a way to prevent locking and encoding a tree that is not // going to result in any packets being sent... @@ -448,22 +437,25 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus } */ - bool wantOcclusionCulling = nodeData->getWantOcclusionCulling(); - CoverageMap* coverageMap = wantOcclusionCulling ? &nodeData->map : IGNORE_COVERAGE_MAP; - float octreeSizeScale = nodeData->getOctreeSizeScale(); int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust(); - int boundaryLevelAdjust = boundaryLevelAdjustClient + (viewFrustumChanged && nodeData->getWantLowResMoving() - ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); + int boundaryLevelAdjust = boundaryLevelAdjustClient + + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); - EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), wantColor, - WANT_EXISTS_BITS, DONT_CHOP, wantDelta, lastViewFrustum, - wantOcclusionCulling, coverageMap, boundaryLevelAdjust, octreeSizeScale, + EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), + WANT_EXISTS_BITS, DONT_CHOP, viewFrustumChanged, lastViewFrustum, + boundaryLevelAdjust, octreeSizeScale, nodeData->getLastTimeBagEmpty(), isFullScene, &nodeData->stats, _myServer->getJurisdiction(), &nodeData->extraEncodeData); + // Our trackSend() function is implemented by the server subclass, and will be called back + // during the encodeTreeBitstream() as new entities/data elements are sent + params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) { + _myServer->trackSend(dataID, dataEdited, _nodeUUID); + }; + // TODO: should this include the lock time or not? This stat is sent down to the client, // it seems like it may be a good idea to include the lock time as part of the encode time // are reported to client. Since you can encode without the lock @@ -518,8 +510,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus // if for some reason the finalized size is greater than our available size, then probably the "compressed" // form actually inflated beyond our padding, and in this case we will send the current packet, then // write to out new packet... - unsigned int writtenSize = _packetData.getFinalizedSize() - + (nodeData->getCurrentPacketIsCompressed() ? sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE) : 0); + unsigned int writtenSize = _packetData.getFinalizedSize() + sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE); if (writtenSize > nodeData->getAvailable()) { packetsSentThisInterval += handlePacketSend(nodeData, trueBytesSent, truePacketsSent); @@ -535,8 +526,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus // the packet doesn't have enough space to bother attempting to pack more... bool sendNow = true; - if (nodeData->getCurrentPacketIsCompressed() && - nodeData->getAvailable() >= MINIMUM_ATTEMPT_MORE_PACKING && + if (nodeData->getAvailable() >= MINIMUM_ATTEMPT_MORE_PACKING && extraPackingAttempts <= REASONABLE_NUMBER_OF_PACKING_ATTEMPTS) { sendNow = false; // try to pack more } @@ -548,9 +538,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus quint64 packetSendingEnd = usecTimestampNow(); packetSendingElapsedUsec = (float)(packetSendingEnd - packetSendingStart); - if (wantCompression) { - targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE); - } + targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE); } else { // If we're in compressed mode, then we want to see if we have room for more in this wire packet. // but we've finalized the _packetData, so we want to start a new section, we will do that by @@ -560,7 +548,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus // a larger compressed size then uncompressed size targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE) - COMPRESS_PADDING; } - _packetData.changeSettings(nodeData->getWantCompression(), targetSize); // will do reset + _packetData.changeSettings(true, targetSize); // will do reset - NOTE: Always compressed } OctreeServer::trackTreeWaitTime(lockWaitElapsedUsec); @@ -625,7 +613,6 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus if (nodeData->elementBag.isEmpty()) { nodeData->updateLastKnownViewFrustum(); nodeData->setViewSent(true); - nodeData->map.erase(); // It would be nice if we could save this, and only reset it when the view frustum changes } } // end if bag wasn't empty, and so we sent stuff... diff --git a/assignment-client/src/octree/OctreeSendThread.h b/assignment-client/src/octree/OctreeSendThread.h index 6e640942e7..6775e56820 100644 --- a/assignment-client/src/octree/OctreeSendThread.h +++ b/assignment-client/src/octree/OctreeSendThread.h @@ -17,7 +17,6 @@ #include #include -#include #include "OctreeQueryNode.h" diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 7cd3e59edf..ec34ad4410 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -210,8 +210,8 @@ void OctreeServer::trackProcessWaitTime(float time) { _averageProcessWaitTime.updateAverage(time); } -OctreeServer::OctreeServer(NLPacket& packet) : - ThreadedAssignment(packet), +OctreeServer::OctreeServer(ReceivedMessage& message) : + ThreadedAssignment(message), _argc(0), _argv(NULL), _parsedArgV(NULL), @@ -821,6 +821,11 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url .arg(locale.toString((uint)checkSum).rightJustified(16, ' ')); statsString += "\r\n\r\n"; + + statsString += serverSubclassStats(); + + statsString += "\r\n\r\n"; + statsString += "\r\n"; statsString += ""; @@ -873,12 +878,12 @@ void OctreeServer::parsePayload() { } } -void OctreeServer::handleOctreeQueryPacket(QSharedPointer packet, SharedNodePointer senderNode) { +void OctreeServer::handleOctreeQueryPacket(QSharedPointer message, SharedNodePointer senderNode) { if (!_isFinished) { // If we got a query packet, then we're talking to an agent, and we // need to make sure we have it in our nodeList. auto nodeList = DependencyManager::get(); - nodeList->updateNodeWithDataFromPacket(packet, senderNode); + nodeList->updateNodeWithDataFromPacket(message, senderNode); OctreeQueryNode* nodeData = dynamic_cast(senderNode->getLinkedData()); if (nodeData && !nodeData->isOctreeSendThreadInitalized()) { @@ -887,17 +892,17 @@ void OctreeServer::handleOctreeQueryPacket(QSharedPointer packet, Shar } } -void OctreeServer::handleOctreeDataNackPacket(QSharedPointer packet, SharedNodePointer senderNode) { +void OctreeServer::handleOctreeDataNackPacket(QSharedPointer message, SharedNodePointer senderNode) { // If we got a nack packet, then we're talking to an agent, and we // need to make sure we have it in our nodeList. OctreeQueryNode* nodeData = dynamic_cast(senderNode->getLinkedData()); if (nodeData) { - nodeData->parseNackPacket(*packet); + nodeData->parseNackPacket(*message); } } -void OctreeServer::handleJurisdictionRequestPacket(QSharedPointer packet, SharedNodePointer senderNode) { - _jurisdictionSender->queueReceivedPacket(packet, senderNode); +void OctreeServer::handleJurisdictionRequestPacket(QSharedPointer message, SharedNodePointer senderNode) { + _jurisdictionSender->queueReceivedPacket(message, senderNode); } bool OctreeServer::readOptionBool(const QString& optionName, const QJsonObject& settingsSectionObject, bool& result) { @@ -1179,6 +1184,8 @@ void OctreeServer::nodeKilled(SharedNodePointer node) { if (usecsElapsed > 1000) { qDebug() << qPrintable(_safeServerName) << "server nodeKilled() took: " << usecsElapsed << " usecs for node:" << *node; } + + trackViewerGone(node->getUUID()); } void OctreeServer::forceNodeShutdown(SharedNodePointer node) { diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index 1aea9c960e..80668f841c 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -34,7 +34,7 @@ const int DEFAULT_PACKETS_PER_INTERVAL = 2000; // some 120,000 packets per secon class OctreeServer : public ThreadedAssignment, public HTTPRequestHandler { Q_OBJECT public: - OctreeServer(NLPacket& packet); + OctreeServer(ReceivedMessage& message); ~OctreeServer(); /// allows setting of run arguments @@ -79,6 +79,9 @@ public: virtual void beforeRun() { } virtual bool hasSpecialPacketsToSend(const SharedNodePointer& node) { return false; } virtual int sendSpecialPackets(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent) { return 0; } + virtual QString serverSubclassStats() { return QString(); } + virtual void trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& viewerNode) { } + virtual void trackViewerGone(const QUuid& viewerNode) { } static float SKIP_TIME; // use this for trackXXXTime() calls for non-times @@ -132,9 +135,9 @@ public slots: private slots: void domainSettingsRequestComplete(); - void handleOctreeQueryPacket(QSharedPointer packet, SharedNodePointer senderNode); - void handleOctreeDataNackPacket(QSharedPointer packet, SharedNodePointer senderNode); - void handleJurisdictionRequestPacket(QSharedPointer packet, SharedNodePointer senderNode); + void handleOctreeQueryPacket(QSharedPointer message, SharedNodePointer senderNode); + void handleOctreeDataNackPacket(QSharedPointer message, SharedNodePointer senderNode); + void handleJurisdictionRequestPacket(QSharedPointer message, SharedNodePointer senderNode); protected: virtual OctreePointer createTree() = 0; diff --git a/cmake/externals/gverb/CMakeLists.txt b/cmake/externals/gverb/CMakeLists.txt deleted file mode 100644 index 4da19e1d31..0000000000 --- a/cmake/externals/gverb/CMakeLists.txt +++ /dev/null @@ -1,31 +0,0 @@ -set(EXTERNAL_NAME gverb) - -if (ANDROID) - set(ANDROID_CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DANDROID_NATIVE_API_LEVEL=19") -endif () - -include(ExternalProject) -ExternalProject_Add( - ${EXTERNAL_NAME} - URL http://hifi-public.s3.amazonaws.com/dependencies/gverb-master.zip - URL_MD5 8b16d586390a2102804e46b87820dfc6 - CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_INSTALL_PREFIX:PATH= - BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build - LOG_DOWNLOAD 1 - LOG_CONFIGURE 1 - LOG_BUILD 1 -) - -# Hide this external target (for ide users) -set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") - -ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) - -string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) -set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE FILEPATH "Path to gverb include directory") - -if (WIN32) - set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${INSTALL_DIR}/lib/gverb.lib CACHE FILEPATH "List of gverb libraries") -else () - set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${INSTALL_DIR}/lib/libgverb.a CACHE FILEPATH "List of gverb libraries") -endif () \ No newline at end of file diff --git a/cmake/modules/FindLeapMotion.cmake b/cmake/modules/FindLeapMotion.cmake index eafb031a07..b5d6fe1b69 100644 --- a/cmake/modules/FindLeapMotion.cmake +++ b/cmake/modules/FindLeapMotion.cmake @@ -18,10 +18,16 @@ hifi_library_search_hints("leapmotion") find_path(LEAPMOTION_INCLUDE_DIRS Leap.h PATH_SUFFIXES include HINTS ${LEAPMOTION_SEARCH_DIRS}) if (WIN32) - find_library(LEAPMOTION_LIBRARY_DEBUG Leapd PATH_SUFFIXES lib/x86 HINTS ${LEAPMOTION_SEARCH_DIRS}) - find_library(LEAPMOTION_LIBRARY_RELEASE Leap PATH_SUFFIXES lib/x86 HINTS ${LEAPMOTION_SEARCH_DIRS}) - - find_path(LEAPMOTION_DLL_PATH Leap.dll PATH_SUFFIXES lib/x86 HINTS ${LEAPMOTION_SEARCH_DIRS}) + + if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + set(ARCH_DIR "x64") + else() + set(ARCH_DIR "x86") + endif() + + find_library(LEAPMOTION_LIBRARY_DEBUG Leapd PATH_SUFFIXES "lib/${ARCH_DIR}" HINTS ${LEAPMOTION_SEARCH_DIRS}) + find_library(LEAPMOTION_LIBRARY_RELEASE Leap PATH_SUFFIXES "lib/${ARCH_DIR}" HINTS ${LEAPMOTION_SEARCH_DIRS}) + find_path(LEAPMOTION_DLL_PATH Leap.dll PATH_SUFFIXES "lib/${ARCH_DIR}" HINTS ${LEAPMOTION_SEARCH_DIRS}) elseif (APPLE) find_library(LEAPMOTION_LIBRARY_RELEASE Leap PATH_SUFFIXES lib HINTS ${LEAPMOTION_SEARCH_DIRS}) endif () diff --git a/cmake/modules/FindRSSDK.cmake b/cmake/modules/FindRSSDK.cmake deleted file mode 100644 index c31b0efcd9..0000000000 --- a/cmake/modules/FindRSSDK.cmake +++ /dev/null @@ -1,33 +0,0 @@ -# Try to find the RSSDK library -# -# You must provide a RSSDK_ROOT_DIR which contains lib and include directories -# -# Once done this will define -# -# RSSDK_FOUND - system found RSSDK -# RSSDK_INCLUDE_DIRS - the RSSDK include directory -# RSSDK_LIBRARIES - Link this to use RSSDK -# -# Created on 12/7/2014 by Thijs Wenker -# Copyright (c) 2014 High Fidelity -# - -include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") -hifi_library_search_hints("rssdk") - -find_path(RSSDK_INCLUDE_DIRS pxcbase.h PATH_SUFFIXES include HINTS ${RSSDK_SEARCH_DIRS}) - -if (WIN32) - find_library(RSSDK_LIBRARY_DEBUG libpxc_d PATH_SUFFIXES lib/Win32 HINTS ${RSSDK_SEARCH_DIRS}) - find_library(RSSDK_LIBRARY_RELEASE libpxc PATH_SUFFIXES lib/Win32 HINTS ${RSSDK_SEARCH_DIRS}) -endif () - -include(SelectLibraryConfigurations) -select_library_configurations(RSSDK) - -set(RSSDK_LIBRARIES "${RSSDK_LIBRARY}") - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(RSSDK DEFAULT_MSG RSSDK_INCLUDE_DIRS RSSDK_LIBRARIES) - -mark_as_advanced(RSSDK_INCLUDE_DIRS RSSDK_LIBRARIES RSSDK_SEARCH_DIRS) diff --git a/cmake/modules/FindRtMidi.cmake b/cmake/modules/FindRtMidi.cmake deleted file mode 100644 index 213c990b52..0000000000 --- a/cmake/modules/FindRtMidi.cmake +++ /dev/null @@ -1,30 +0,0 @@ -# -# FindRtMidi.cmake -# -# Try to find the RtMidi library -# -# You can provide a RTMIDI_ROOT_DIR which contains lib and include directories -# -# Once done this will define -# -# RTMIDI_FOUND - system found RtMidi -# RTMIDI_INCLUDE_DIRS - the RtMidi include directory -# RTMIDI_LIBRARIES - link to this to use RtMidi -# -# Created on 6/30/2014 by Stephen Birarda -# Copyright 2014 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("${MACRO_DIR}/HifiLibrarySearchHints.cmake") -hifi_library_search_hints("rtmidi") - -find_path(RTMIDI_INCLUDE_DIRS RtMidi.h PATH_SUFFIXES include HINTS ${RTMIDI_SEARCH_DIRS}) -find_library(RTMIDI_LIBRARIES NAMES rtmidi PATH_SUFFIXES lib HINTS ${RTMIDI_SEARCH_DIRS}) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(RtMidi DEFAULT_MSG RTMIDI_INCLUDE_DIRS RTMIDI_LIBRARIES) - -mark_as_advanced(RTMIDI_INCLUDE_DIRS RTMIDI_LIBRARIES RTMIDI_SEARCH_DIRS) \ No newline at end of file diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 55f0fb2d2b..fb67744d6e 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -51,15 +51,15 @@ const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer << NodeType::AssetServer << NodeType::MessagesMixer; -void DomainGatekeeper::processConnectRequestPacket(QSharedPointer packet) { - if (packet->getPayloadSize() == 0) { +void DomainGatekeeper::processConnectRequestPacket(QSharedPointer message) { + if (message->getSize() == 0) { return; } - QDataStream packetStream(packet.data()); + QDataStream packetStream(message->getMessage()); // read a NodeConnectionData object from the packet so we can pass around this data while we're inspecting it - NodeConnectionData nodeConnection = NodeConnectionData::fromDataStream(packetStream, packet->getSenderSockAddr()); + NodeConnectionData nodeConnection = NodeConnectionData::fromDataStream(packetStream, message->getSenderSockAddr()); if (nodeConnection.localSockAddr.isNull() || nodeConnection.publicSockAddr.isNull()) { qDebug() << "Unexpected data received for node local socket or public socket. Will not allow connection."; @@ -72,7 +72,7 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer pack if (!VALID_NODE_TYPES.contains(nodeConnection.nodeType)) { qDebug() << "Received an invalid node type with connect request. Will not allow connection from" - << nodeConnection.senderSockAddr; + << nodeConnection.senderSockAddr << ": " << nodeConnection.nodeType; return; } @@ -87,11 +87,11 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer pack QString username; QByteArray usernameSignature; - if (packet->bytesLeftToRead() > 0) { + if (message->getBytesLeftToRead() > 0) { // read username from packet packetStream >> username; - if (packet->bytesLeftToRead() > 0) { + if (message->getBytesLeftToRead() > 0) { // read user signature from packet packetStream >> usernameSignature; } @@ -103,14 +103,14 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer pack if (node) { // set the sending sock addr and node interest set on this node DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); - nodeData->setSendingSockAddr(packet->getSenderSockAddr()); + nodeData->setSendingSockAddr(message->getSenderSockAddr()); nodeData->setNodeInterestSet(nodeConnection.interestList.toSet()); // signal that we just connected a node so the DomainServer can get it a list // and broadcast its presence right away emit connectedNode(node); } else { - qDebug() << "Refusing connection from node at" << packet->getSenderSockAddr(); + qDebug() << "Refusing connection from node at" << message->getSenderSockAddr(); } } @@ -572,10 +572,10 @@ void DomainGatekeeper::handlePeerPingTimeout() { } } -void DomainGatekeeper::processICEPeerInformationPacket(QSharedPointer packet) { +void DomainGatekeeper::processICEPeerInformationPacket(QSharedPointer message) { // loop through the packet and pull out network peers // any peer we don't have we add to the hash, otherwise we update - QDataStream iceResponseStream(packet.data()); + QDataStream iceResponseStream(message->getMessage()); NetworkPeer* receivedPeer = new NetworkPeer; iceResponseStream >> *receivedPeer; @@ -600,15 +600,15 @@ void DomainGatekeeper::processICEPeerInformationPacket(QSharedPointer } } -void DomainGatekeeper::processICEPingPacket(QSharedPointer packet) { +void DomainGatekeeper::processICEPingPacket(QSharedPointer message) { auto limitedNodeList = DependencyManager::get(); - auto pingReplyPacket = limitedNodeList->constructICEPingReplyPacket(*packet, limitedNodeList->getSessionUUID()); + auto pingReplyPacket = limitedNodeList->constructICEPingReplyPacket(*message, limitedNodeList->getSessionUUID()); - limitedNodeList->sendPacket(std::move(pingReplyPacket), packet->getSenderSockAddr()); + limitedNodeList->sendPacket(std::move(pingReplyPacket), message->getSenderSockAddr()); } -void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer packet) { - QDataStream packetStream(packet.data()); +void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer message) { + QDataStream packetStream(message->getMessage()); QUuid nodeUUID; packetStream >> nodeUUID; @@ -617,6 +617,6 @@ void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer packet if (sendingPeer) { // we had this NetworkPeer in our connecting list - add the right sock addr to our connected list - sendingPeer->activateMatchingOrNewSymmetricSocket(packet->getSenderSockAddr()); + sendingPeer->activateMatchingOrNewSymmetricSocket(message->getSenderSockAddr()); } } diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index 857b4419da..2fc4be380c 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -41,10 +41,10 @@ public: void removeICEPeer(const QUuid& peerUUID) { _icePeers.remove(peerUUID); } public slots: - void processConnectRequestPacket(QSharedPointer packet); - void processICEPingPacket(QSharedPointer packet); - void processICEPingReplyPacket(QSharedPointer packet); - void processICEPeerInformationPacket(QSharedPointer packet); + void processConnectRequestPacket(QSharedPointer message); + void processICEPingPacket(QSharedPointer message); + void processICEPingReplyPacket(QSharedPointer message); + void processICEPeerInformationPacket(QSharedPointer message); void publicKeyJSONCallback(QNetworkReply& requestReply); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 0cd6728d7e..e219b47571 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -273,7 +273,7 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { packetReceiver.registerListener(PacketType::RequestAssignment, this, "processRequestAssignmentPacket"); packetReceiver.registerListener(PacketType::DomainListRequest, this, "processListRequestPacket"); packetReceiver.registerListener(PacketType::DomainServerPathQuery, this, "processPathQueryPacket"); - packetReceiver.registerMessageListener(PacketType::NodeJsonStats, this, "processNodeJSONStatsPacket"); + packetReceiver.registerListener(PacketType::NodeJsonStats, this, "processNodeJSONStatsPacket"); packetReceiver.registerListener(PacketType::DomainDisconnectRequest, this, "processNodeDisconnectRequestPacket"); // NodeList won't be available to the settings manager when it is created, so call registerListener here @@ -578,10 +578,10 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet packet, SharedNodePointer sendingNode) { +void DomainServer::processListRequestPacket(QSharedPointer message, SharedNodePointer sendingNode) { - QDataStream packetStream(packet.data()); - NodeConnectionData nodeRequestData = NodeConnectionData::fromDataStream(packetStream, packet->getSenderSockAddr(), false); + QDataStream packetStream(message->getMessage()); + NodeConnectionData nodeRequestData = NodeConnectionData::fromDataStream(packetStream, message->getSenderSockAddr(), false); // update this node's sockets in case they have changed sendingNode->setPublicSocket(nodeRequestData.publicSockAddr); @@ -591,7 +591,7 @@ void DomainServer::processListRequestPacket(QSharedPointer packet, Sha DomainServerNodeData* nodeData = reinterpret_cast(sendingNode->getLinkedData()); nodeData->setNodeInterestSet(nodeRequestData.interestList.toSet()); - sendDomainListToNode(sendingNode, packet->getSenderSockAddr()); + sendDomainListToNode(sendingNode, message->getSenderSockAddr()); } unsigned int DomainServer::countConnectedUsers() { @@ -764,9 +764,9 @@ void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) { ); } -void DomainServer::processRequestAssignmentPacket(QSharedPointer packet) { +void DomainServer::processRequestAssignmentPacket(QSharedPointer message) { // construct the requested assignment from the packet data - Assignment requestAssignment(*packet); + Assignment requestAssignment(*message); // Suppress these for Assignment::AgentType to once per 5 seconds static QElapsedTimer noisyMessageTimer; @@ -784,14 +784,14 @@ void DomainServer::processRequestAssignmentPacket(QSharedPointer packe static QString repeatedMessage = LogHandler::getInstance().addOnlyOnceMessageRegex ("Received a request for assignment type [^ ]+ from [^ ]+"); qDebug() << "Received a request for assignment type" << requestAssignment.getType() - << "from" << packet->getSenderSockAddr(); + << "from" << message->getSenderSockAddr(); noisyMessageTimer.restart(); } SharedAssignmentPointer assignmentToDeploy = deployableAssignmentForRequest(requestAssignment); if (assignmentToDeploy) { - qDebug() << "Deploying assignment -" << *assignmentToDeploy.data() << "- to" << packet->getSenderSockAddr(); + qDebug() << "Deploying assignment -" << *assignmentToDeploy.data() << "- to" << message->getSenderSockAddr(); // give this assignment out, either the type matches or the requestor said they will take any static std::unique_ptr assignmentPacket; @@ -812,7 +812,7 @@ void DomainServer::processRequestAssignmentPacket(QSharedPointer packe assignmentStream << uniqueAssignment; auto limitedNodeList = DependencyManager::get(); - limitedNodeList->sendUnreliablePacket(*assignmentPacket, packet->getSenderSockAddr()); + limitedNodeList->sendUnreliablePacket(*assignmentPacket, message->getSenderSockAddr()); // give the information for that deployed assignment to the gatekeeper so it knows to that that node // in when it comes back around @@ -824,7 +824,7 @@ void DomainServer::processRequestAssignmentPacket(QSharedPointer packe static QString repeatedMessage = LogHandler::getInstance().addOnlyOnceMessageRegex ("Unable to fulfill assignment request of type [^ ]+ from [^ ]+"); qDebug() << "Unable to fulfill assignment request of type" << requestAssignment.getType() - << "from" << packet->getSenderSockAddr(); + << "from" << message->getSenderSockAddr(); noisyMessageTimer.restart(); } } @@ -993,7 +993,7 @@ void DomainServer::sendHeartbeatToIceServer() { DependencyManager::get()->sendHeartbeatToIceServer(_iceServerSocket); } -void DomainServer::processNodeJSONStatsPacket(QSharedPointer packetList, SharedNodePointer sendingNode) { +void DomainServer::processNodeJSONStatsPacket(QSharedPointer packetList, SharedNodePointer sendingNode) { auto nodeData = dynamic_cast(sendingNode->getLinkedData()); if (nodeData) { nodeData->updateJSONStats(packetList->getMessage()); @@ -1767,17 +1767,17 @@ void DomainServer::addStaticAssignmentsToQueue() { } } -void DomainServer::processPathQueryPacket(QSharedPointer packet) { +void DomainServer::processPathQueryPacket(QSharedPointer message) { // this is a query for the viewpoint resulting from a path // first pull the query path from the packet // figure out how many bytes the sender said this path is quint16 numPathBytes; - packet->readPrimitive(&numPathBytes); + message->readPrimitive(&numPathBytes); - if (numPathBytes <= packet->bytesLeftToRead()) { + if (numPathBytes <= message->getBytesLeftToRead()) { // the number of path bytes makes sense for the sent packet - pull out the path - QString pathQuery = QString::fromUtf8(packet->getPayload() + packet->pos(), numPathBytes); + QString pathQuery = QString::fromUtf8(message->getRawMessage() + message->getPosition(), numPathBytes); // our settings contain paths that start with a leading slash, so make sure this query has that if (!pathQuery.startsWith("/")) { @@ -1825,7 +1825,7 @@ void DomainServer::processPathQueryPacket(QSharedPointer packet) { // send off the packet - see if we can associate this outbound data to a particular node // TODO: does this senderSockAddr always work for a punched DS client? - nodeList->sendPacket(std::move(pathResponsePacket), packet->getSenderSockAddr()); + nodeList->sendPacket(std::move(pathResponsePacket), message->getSenderSockAddr()); } } @@ -1837,11 +1837,11 @@ void DomainServer::processPathQueryPacket(QSharedPointer packet) { } } -void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer packet) { +void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer message) { // This packet has been matched to a source node and they're asking not to be in the domain anymore auto limitedNodeList = DependencyManager::get(); - const QUuid& nodeUUID = packet->getSourceID(); + const QUuid& nodeUUID = message->getSourceID(); qDebug() << "Received a disconnect request from node with UUID" << nodeUUID; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index e5b3d3b3fd..f5d5ff8f40 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -56,11 +56,11 @@ public slots: void restart(); - void processRequestAssignmentPacket(QSharedPointer packet); - void processListRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode); - void processNodeJSONStatsPacket(QSharedPointer packetList, SharedNodePointer sendingNode); - void processPathQueryPacket(QSharedPointer packet); - void processNodeDisconnectRequestPacket(QSharedPointer packet); + void processRequestAssignmentPacket(QSharedPointer packet); + void processListRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode); + void processNodeJSONStatsPacket(QSharedPointer packetList, SharedNodePointer sendingNode); + void processPathQueryPacket(QSharedPointer packet); + void processNodeDisconnectRequestPacket(QSharedPointer message); private slots: void aboutToQuit(); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 88fc6a6cad..888df219dd 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -67,9 +67,9 @@ DomainServerSettingsManager::DomainServerSettingsManager() : QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection); } -void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer packet) { +void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer message) { Assignment::Type type; - packet->readPrimitive(&type); + message->readPrimitive(&type); QJsonObject responseObject = responseObjectForType(QString::number(type)); auto json = QJsonDocument(responseObject).toJson(); @@ -79,7 +79,7 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointerwrite(json); auto nodeList = DependencyManager::get(); - nodeList->sendPacketList(std::move(packetList), packet->getSenderSockAddr()); + nodeList->sendPacketList(std::move(packetList), message->getSenderSockAddr()); } void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) { diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 321f7b7214..7f71a89da5 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -18,7 +18,7 @@ #include #include -#include +#include const QString SETTINGS_PATHS_KEY = "paths"; @@ -42,7 +42,7 @@ public: QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); } private slots: - void processSettingsRequestPacket(QSharedPointer packet); + void processSettingsRequestPacket(QSharedPointer message); private: QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false); diff --git a/examples/acScripts/AgentPoolController.js b/examples/acScripts/AgentPoolController.js new file mode 100644 index 0000000000..830a8fe1e3 --- /dev/null +++ b/examples/acScripts/AgentPoolController.js @@ -0,0 +1,416 @@ +// +// AgentPoolController.js +// acScripts +// +// Created by Sam Gateau on 11/23/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +function printDebug(message) { + print(message); +} + +(function() { + var SERVICE_CHANNEL = "com.highfidelity.playback.service"; + var COMMAND_CHANNEL = "com.highfidelity.playback.command"; + + // The time between alive messages on the command channel + var ALIVE_PERIOD = 3; + var NUM_CYCLES_BEFORE_RESET = 8; + + // Service Actions + var MASTER_ID = -1; + var INVALID_AGENT = -2; + + var BROADCAST_AGENTS = -3; + + var MASTER_ALIVE = "MASTER_ALIVE"; + var AGENT_ALIVE = "AGENT_ALIVE"; + var AGENT_READY = "READY"; + var MASTER_HIRE_AGENT = "HIRE" + var MASTER_FIRE_AGENT = "FIRE"; + + var makeUniqueUUID = function(SEUUID) { + //return SEUUID + Math.random(); + // forget complexity, just give me a four digit pin + return (Math.random() * 10000).toFixed(0); + } + + var packServiceMessage = function(dest, command, src) { + var message = { + dest: dest, + command: command, + src: src + }; + return JSON.stringify(message); + }; + + var unpackServiceMessage = function(message) { + return JSON.parse(message); + }; + + var packCommandMessage = function(dest, action, argument) { + var message = { + dest_key: dest, + action_key: action, + argument_key: argument + }; + return JSON.stringify(message); + }; + + var unpackCommandMessage = function(message) { + return JSON.parse(message); + }; + + // Actor + //--------------------------------- + var Actor = function() { + this.agentID = INVALID_AGENT; + this.lastAliveCycle = 0; + this.onHired = function(actor) {}; + this.onFired = function(actor) {}; + }; + + Actor.prototype.isConnected = function () { + return (this.agentID != INVALID_AGENT); + } + + Actor.prototype.alive = function () { + printDebug("Agent UUID =" + this.agentID + " Alive was " + this.lastAliveCycle); + this.lastAliveCycle = 0; + } + Actor.prototype.incrementAliveCycle = function () { + printDebug("Actor.prototype.incrementAliveCycle UUID =" + this.agentID + " Alive pre increment " + this.lastAliveCycle); + if (this.isConnected()) { + this.lastAliveCycle++; + printDebug("Agent UUID =" + this.agentID + " Alive incremented " + this.lastAliveCycle); + } + return this.lastAliveCycle; + } + + this.Actor = Actor; + + // master side + //--------------------------------- + var MasterController = function() { + this.timeSinceLastAlive = 0; + this.knownAgents = new Array; + this.hiredActors = new Array; + this.hiringAgentsQueue = new Array; + this.subscribed = false; + }; + + MasterController.prototype.destroy = function() { + if (this.subscribed) { + Messages.unsubscribe(SERVICE_CHANNEL); + Messages.unsubscribe(COMMAND_CHANNEL); + this.subscribed = true; + } + }; + + MasterController.prototype.reset = function() { + this.timeSinceLastAlive = 0; + + if (!this.subscribed) { + Messages.subscribe(COMMAND_CHANNEL); + Messages.subscribe(SERVICE_CHANNEL); + var localThis = this; + Messages.messageReceived.connect(function (channel, message, senderID) { + if (channel == SERVICE_CHANNEL) { + localThis._processServiceMessage(message, senderID); + return; + } + }); + } + // ready to roll, enable + this.subscribed = true; + printDebug("Master Started"); + }; + + MasterController.prototype._processServiceMessage = function(message, senderID) { + var service = unpackServiceMessage(message); + if (service.dest == MASTER_ID) { + if (service.command == AGENT_READY) { + // check to see if we know about this agent + var agentIndex = this.knownAgents.indexOf(service.src); + if (agentIndex < 0) { + this._onAgentAvailableForHiring(service.src); + } else { + // Master think the agent is hired but not the other way around, forget about it + printDebug("New agent still sending ready ? " + service.src + " " + agentIndex + " Forgeting about it"); + // this._removeHiredAgent(agentIndex); + } + } else if (service.command == AGENT_ALIVE) { + // check to see if we know about this agent + var agentIndex = this.knownAgents.indexOf(service.src); + if (agentIndex >= 0) { + // yes so reset its alive beat + this.hiredActors[agentIndex].alive(); + return; + } else { + return; + } + } + } + }; + + MasterController.prototype._onAgentAvailableForHiring = function(agentID) { + if (this.hiringAgentsQueue.length == 0) { + printDebug("No Actor on the hiring queue"); + return; + } + + printDebug("MasterController.prototype._onAgentAvailableForHiring " + agentID); + var newActor = this.hiringAgentsQueue.pop(); + + var indexOfNewAgent = this.knownAgents.push(agentID); + newActor.alive(); + newActor.agentID = agentID; + this.hiredActors.push(newActor); + + printDebug("New agent available to be hired " + agentID + " " + indexOfNewAgent); + var serviceMessage = packServiceMessage(agentID, MASTER_HIRE_AGENT, MASTER_ID); + printDebug("serviceMessage = " + serviceMessage); + Messages.sendMessage(SERVICE_CHANNEL, serviceMessage); + printDebug("message sent calling the actor" + JSON.stringify(newActor) ); + + newActor.onHired(newActor); + } + + MasterController.prototype.sendCommand = function(target, action, argument) { + if (this.subscribed) { + var command = packCommandMessage(target, action, argument); + printDebug(command); + Messages.sendMessage(COMMAND_CHANNEL, command); + } + }; + + MasterController.prototype.update = function(deltaTime) { + this.timeSinceLastAlive += deltaTime; + if (this.timeSinceLastAlive > ALIVE_PERIOD) { + this.timeSinceLastAlive = 0; + Messages.sendMessage(SERVICE_CHANNEL, packServiceMessage(BROADCAST_AGENTS, MASTER_ALIVE, MASTER_ID)); + + { + // Check for alive connected agents + var lostAgents = new Array(); + for (var i = 0; i < this.hiredActors.length; i++) { + var actor = this.hiredActors[i]; + var lastAlive = actor.incrementAliveCycle() + if (lastAlive > NUM_CYCLES_BEFORE_RESET) { + printDebug("Agent Lost, firing Agent #" + i + " ID " + actor.agentID); + lostAgents.push(i); + } + } + + // now fire gathered lost agents from end to begin + while (lostAgents.length > 0) { + printDebug("Firing " + lostAgents.length + " agents" + JSON.stringify(lostAgents)); + this.fireAgent(this.hiredActors[lostAgents.pop()]); + } + } + } + }; + + + MasterController.prototype.hireAgent = function(actor) { + if (actor == null) { + printDebug("trying to hire an agent with a null actor, abort"); + return; + } + if (actor.isConnected()) { + printDebug("trying to hire an agent already connected, abort"); + return; + } + this.hiringAgentsQueue.unshift(actor); + }; + + MasterController.prototype.fireAgent = function(actor) { + // check to see if we know about this agent + printDebug("MasterController.prototype.fireAgent" + actor.agentID); + + // Try the waiting list first + var waitingIndex = this.hiringAgentsQueue.indexOf(actor); + if (waitingIndex >= 0) { + printDebug("fireAgent found actor on waiting queue #" + waitingIndex); + var lostActor = this.hiringAgentsQueue.splice(waitingIndex, 1); + if (lostActor.length) { + lostActor[0].onFired(lostActor[0]); + } + return; + } + + // then the hired agents + var actorIndex = this.knownAgents.indexOf(actor.agentID); + if (actorIndex >= 0) { + printDebug("fired actor found #" + actorIndex); + this._removeHiredAgent(actorIndex); + } + } + + MasterController.prototype._removeHiredAgent = function(actorIndex) { + // check to see if we know about this agent + if (actorIndex >= 0) { + printDebug("MasterController.prototype._removeHiredAgent #" + this.knownAgents[actorIndex]) + this.knownAgents.splice(actorIndex, 1); + + var lostActor = this.hiredActors[actorIndex]; + this.hiredActors.splice(actorIndex, 1); + + var lostAgentID = lostActor.agentID; + lostActor.agentID = INVALID_AGENT; + + if (lostAgentID != INVALID_AGENT) { + printDebug("fired actor is still connected, send fire command"); + Messages.sendMessage(SERVICE_CHANNEL, packServiceMessage(lostAgentID, MASTER_FIRE_AGENT, MASTER_ID)); + } + + lostActor.onFired(lostActor); + } + } + + this.MasterController = MasterController; + + // agent side + //--------------------------------- + var AgentController = function() { + this.subscribed = false; + this._init(); + + this.onHired = function() {}; + this.onCommand = function(command) {}; + this.onFired = function() {}; + }; + + AgentController.prototype._init = function() { + this.isHired= false; + this.timeSinceLastAlive = 0; + this.numCyclesWithoutAlive = 0; + this.agentUUID = makeUniqueUUID(Agent.sessionUUID); + printDebug("this.agentUUID = " + this.agentUUID); + } + + AgentController.prototype.destroy = function() { + if (this.subscribed) { + this.fired(); + Messages.unsubscribe(SERVICE_CHANNEL); + Messages.unsubscribe(COMMAND_CHANNEL); + this.subscribed = true; + } + }; + + AgentController.prototype.reset = function() { + // If already hired, fire + this.fired(); + + if (!this.subscribed) { + Messages.subscribe(COMMAND_CHANNEL); + Messages.subscribe(SERVICE_CHANNEL); + var localThis = this; + Messages.messageReceived.connect(function (channel, message, senderID) { + if (channel == SERVICE_CHANNEL) { + localThis._processServiceMessage(message, senderID); + return; + } + if (channel == COMMAND_CHANNEL) { + localThis._processCommandMessage(message, senderID); + return; + } + }); + } + this.subscribed = true; + printDebug("Client Started"); + }; + + AgentController.prototype._processServiceMessage = function(message, senderID) { + var service = unpackServiceMessage(message); + printDebug("Client " + this.agentUUID + " Received message = " + message); + if (service.dest == this.agentUUID) { + if (service.command != AGENT_READY) { + + // this is potentially a message to hire me if i m not already + if (!this.isHired && (service.command == MASTER_HIRE_AGENT)) { + printDebug(service.command); + this.isHired = true; + printDebug("Client Hired by master UUID" + service.src); + this.onHired(); + this.alive(); + return; + } + + // Or maybe a message to fire me if i m not hired + if (this.isHired && (service.command == MASTER_FIRE_AGENT)) { + printDebug("Client Fired by master UUID" + senderID); + this.fired(); + return; + } + } + } else if ((service.src == MASTER_ID) && (service.command == MASTER_ALIVE)) { + this.numCyclesWithoutAlive = 0; + return; + } + } + + AgentController.prototype._processCommandMessage = function(message, senderID) { + // ONly work if hired + if (this.isHired) { + var command = unpackCommandMessage(message); + if ((command.dest_key == this.agentUUID) || (command.dest_key == BROADCAST_AGENTS)) { + printDebug("Command received = " + JSON.stringify(command) + senderID); + this.onCommand(command); + } + } + }; + + + AgentController.prototype.update = function(deltaTime) { + this.timeSinceLastAlive += deltaTime; + if (this.timeSinceLastAlive > ALIVE_PERIOD) { + if (this.subscribed) { + if (!this.isHired) { + Messages.sendMessage(SERVICE_CHANNEL, packServiceMessage(MASTER_ID, AGENT_READY, this.agentUUID)); + //printDebug("Client Ready" + SERVICE_CHANNEL + AGENT_READY); + } else { + // Send alive beat + printDebug("beat !"); + Messages.sendMessage(SERVICE_CHANNEL, packServiceMessage(MASTER_ID, AGENT_ALIVE, this.agentUUID)); + + // Listen for master beat + this.numCyclesWithoutAlive++; + if (this.numCyclesWithoutAlive > NUM_CYCLES_BEFORE_RESET) { + printDebug("Master Lost, self firing Agent"); + this.fired(); + } + } + } + + this.timeSinceLastAlive = 0; + } + }; + + AgentController.prototype.fired = function() { + // clear the state first + var wasHired = this.isHired; + + // reset + this._init(); + + // then custom fire if was hired + if (wasHired) { + this.onFired(); + } + } + + + this.AgentController = AgentController; + + + + this.BROADCAST_AGENTS = BROADCAST_AGENTS; +})(); + + + diff --git a/examples/acScripts/PlayRecordingOnAC.js b/examples/acScripts/PlayRecordingOnAC.js index b7ae2c7329..0961f65079 100644 --- a/examples/acScripts/PlayRecordingOnAC.js +++ b/examples/acScripts/PlayRecordingOnAC.js @@ -10,20 +10,17 @@ // -var filename = "http://your.recording.url"; +var recordingFile = "http://your.recording.url"; var playFromCurrentLocation = true; var loop = true; -Avatar.skeletonModelURL = "https://hifi-public.s3.amazonaws.com/marketplace/contents/e21c0b95-e502-4d15-8c41-ea2fc40f1125/3585ddf674869a67d31d5964f7b52de1.fst?1427169998"; - // Set position here if playFromCurrentLocation is true Avatar.position = { x:1, y: 1, z: 1 }; Avatar.orientation = Quat.fromPitchYawRollDegrees(0, 0, 0); Avatar.scale = 1.0; - Agent.isAvatar = true; -Avatar.loadRecording(filename); +Recording.loadRecording(recordingFile); count = 300; // This is necessary to wait for the audio mixer to connect function update(event) { @@ -32,22 +29,18 @@ function update(event) { return; } if (count == 0) { - Avatar.setPlayFromCurrentLocation(playFromCurrentLocation); - Avatar.setPlayerLoop(loop); - Avatar.setPlayerUseDisplayName(true); - Avatar.setPlayerUseAttachments(true); - Avatar.setPlayerUseHeadModel(false); - Avatar.setPlayerUseSkeletonModel(true); - Avatar.startPlaying(); - Avatar.play(); + Recording.setPlayFromCurrentLocation(playFromCurrentLocation); + Recording.setPlayerLoop(loop); + Recording.setPlayerUseDisplayName(true); + Recording.setPlayerUseAttachments(true); + Recording.setPlayerUseHeadModel(false); + Recording.setPlayerUseSkeletonModel(true); + Recording.startPlaying(); Vec3.print("Playing from ", Avatar.position); - count--; } - if (Avatar.isPlaying()) { - Avatar.play(); - } else { + if (!Recording.isPlaying()) { Script.update.disconnect(update); } } diff --git a/examples/acScripts/playbackAgents.js b/examples/acScripts/playbackAgents.js index 16dd469a89..cf805623de 100644 --- a/examples/acScripts/playbackAgents.js +++ b/examples/acScripts/playbackAgents.js @@ -9,18 +9,15 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +Script.include("./AgentPoolController.js"); +var agentController = new AgentController(); + // Set the following variables to the values needed -var commandChannel = "com.highfidelity.PlaybackChannel1"; -var clip_url = null; var playFromCurrentLocation = true; var useDisplayName = true; var useAttachments = true; var useAvatarModel = true; -// ID of the agent. Two agents can't have the same ID. -var announceIDChannel = "com.highfidelity.playbackAgent.announceID"; -var UNKNOWN_AGENT_ID = -2; -var id = UNKNOWN_AGENT_ID; // unknown until aknowledged // Set position/orientation/scale here if playFromCurrentLocation is true Avatar.position = { x:0, y: 0, z: 0 }; @@ -28,16 +25,12 @@ Avatar.orientation = Quat.fromPitchYawRollDegrees(0, 0, 0); Avatar.scale = 1.0; var totalTime = 0; -var subscribed = false; var WAIT_FOR_AUDIO_MIXER = 1; // Script. DO NOT MODIFY BEYOND THIS LINE. -var DO_NOTHING = 0; var PLAY = 1; var PLAY_LOOP = 2; var STOP = 3; -var SHOW = 4; -var HIDE = 5; var LOAD = 6; Recording.setPlayFromCurrentLocation(playFromCurrentLocation); @@ -46,29 +39,15 @@ Recording.setPlayerUseAttachments(useAttachments); Recording.setPlayerUseHeadModel(false); Recording.setPlayerUseSkeletonModel(useAvatarModel); -function getAction(channel, message, senderID) { - if(subscribed) { - var command = JSON.parse(message); - print("I'm the agent " + id + " and I received this: ID: " + command.id_key + " Action: " + command.action_key + " URL: " + command.clip_url_key); - - if (command.id_key == id || command.id_key == -1) { - if (command.action_key === 6) { - clip_url = command.clip_url_key; - } - - action = command.action_key; - print("That command was for me!"); - print("My clip is: " + clip_url); - } else { - action = DO_NOTHING; - } - - switch(action) { +function agentCommand(command) { + if(true) { + + // var command = JSON.parse(message); + print("I'm the agent " + this.agentUUID + " and I received this: Dest: " + command.dest_key + " Action: " + command.action_key + " URL: " + command.argument_key); + + switch(command.action_key) { case PLAY: print("Play"); - if (!Agent.isAvatar) { - Agent.isAvatar = true; - } if (!Recording.isPlaying()) { Recording.startPlaying(); } @@ -76,9 +55,6 @@ function getAction(channel, message, senderID) { break; case PLAY_LOOP: print("Play loop"); - if (!Agent.isAvatar) { - Agent.isAvatar = true; - } if (!Recording.isPlaying()) { Recording.startPlaying(); } @@ -90,75 +66,60 @@ function getAction(channel, message, senderID) { Recording.stopPlaying(); } break; - case SHOW: - print("Show"); - if (!Agent.isAvatar) { - Agent.isAvatar = true; - } - break; - case HIDE: - print("Hide"); - if (Recording.isPlaying()) { - Recording.stopPlaying(); - } - Agent.isAvatar = false; - break; case LOAD: - print("Load"); - if(clip_url !== null) { - Recording.loadRecording(clip_url); - } - break; - case DO_NOTHING: + { + print("Load" + command.argument_key); + print("Agent #" + command.dest_key + " loading clip URL: " + command.argument_key); + Recording.loadRecording(command.argument_key); + print("After Load" + command.argument_key); + Recording.setPlayerTime(0); + } break; default: - print("Unknown action: " + action); + print("Unknown action: " + command.action_key); break; } - - if (Recording.isPlaying()) { - Recording.play(); - } } } +function agentHired() { + print("Agent Hired from playbackAgents.js"); + Agent.isAvatar = true; + Recording.stopPlaying(); + Recording.setPlayerLoop(false); + Recording.setPlayerTime(0); +} + +function agentFired() { + print("Agent Fired from playbackAgents.js"); + Recording.stopPlaying(); + Recording.setPlayerTime(0); + Recording.setPlayerLoop(false); + Agent.isAvatar = false; +} + function update(deltaTime) { - totalTime += deltaTime; - if (totalTime > WAIT_FOR_AUDIO_MIXER) { - if (!subscribed) { - Messages.subscribe(commandChannel); // command channel - Messages.subscribe(announceIDChannel); // id announce channel - subscribed = true; - print("I'm the agent and I am ready to receive!"); - } - if (subscribed && id == UNKNOWN_AGENT_ID) { - print("sending ready, id:" + id); - Messages.sendMessage(announceIDChannel, "ready"); + if (!agentController.subscribed) { + agentController.reset(); + agentController.onCommand = agentCommand; + agentController.onHired = agentHired; + agentController.onFired = agentFired; } } + agentController.update(deltaTime); +} + + +function scriptEnding() { + + agentController.destroy(); } -Messages.messageReceived.connect(function (channel, message, senderID) { - if (channel == announceIDChannel && message != "ready") { - // If I don't yet know if my ID has been recieved, then check to see if the master has acknowledged me - if (id == UNKNOWN_AGENT_ID) { - var parts = message.split("."); - var agentID = parts[0]; - var agentIndex = parts[1]; - if (agentID == Agent.sessionUUID) { - id = agentIndex; - Messages.unsubscribe(announceIDChannel); // id announce channel - } - } - } - if (channel == commandChannel) { - getAction(channel, message, senderID); - } -}); Script.update.connect(update); +Script.scriptEnding.connect(scriptEnding); diff --git a/examples/acScripts/playbackMaster.js b/examples/acScripts/playbackMaster.js index 4703f0e4fd..dff0b1d852 100644 --- a/examples/acScripts/playbackMaster.js +++ b/examples/acScripts/playbackMaster.js @@ -8,31 +8,24 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +Script.include("./AgentPoolController.js"); HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; +var masterController = new MasterController(); -var ac_number = 1; // This is the default number of ACs. Their ID need to be unique and between 0 (included) and ac_number (excluded) -var names = new Array(); // It is possible to specify the name of the ACs in this array. ACs names ordered by IDs (Default name is "ACx", x = ID + 1)) -var channel = "com.highfidelity.PlaybackChannel1"; -var subscribed = false; -var clip_url = null; var input_text = null; -var knownAgents = new Array; // We will add our known agents here when we discover them - -// available playbackAgents will announce their sessionID here. -var announceIDChannel = "com.highfidelity.playbackAgent.announceID"; - // Script. DO NOT MODIFY BEYOND THIS LINE. -Script.include("../libraries/toolBars.js"); +//Script.include("../libraries/toolBars.js"); +Script.include(HIFI_PUBLIC_BUCKET + "scripts/libraries/toolBars.js"); +// We want small icons +Tool.IMAGE_HEIGHT /= 2; +Tool.IMAGE_WIDTH /= 2; -var DO_NOTHING = 0; var PLAY = 1; var PLAY_LOOP = 2; var STOP = 3; -var SHOW = 4; -var HIDE = 5; var LOAD = 6; var windowDimensions = Controller.getViewportDimensions(); @@ -44,241 +37,381 @@ var COLOR_MASTER = { red: 0, green: 0, blue: 0 }; var TEXT_HEIGHT = 12; var TEXT_MARGIN = 3; -var toolBars = new Array(); -var nameOverlays = new Array(); -var onOffIcon = new Array(); -var playIcon = new Array(); -var playLoopIcon = new Array(); -var stopIcon = new Array(); -var loadIcon = new Array(); - -setupPlayback(); - -function setupPlayback() { - ac_number = Window.prompt("Insert number of agents: ","1"); - if (ac_number === "" || ac_number === null) { - ac_number = 1; - } - Messages.subscribe(channel); - subscribed = true; - setupToolBars(); +// Add new features to Actor class: +Actor.prototype.destroy = function() { + print("Actor.prototype.destroy"); + print("Need to fire myself" + this.agentID); + masterController.fireAgent(this); } -function setupToolBars() { - if (toolBars.length > 0) { - print("Multiple calls to Recorder.js:setupToolBars()"); - return; - } - Tool.IMAGE_HEIGHT /= 2; - Tool.IMAGE_WIDTH /= 2; - - for (i = 0; i <= ac_number; i++) { - toolBars.push(new ToolBar(0, 0, ToolBar.HORIZONTAL)); - toolBars[i].setBack((i == ac_number) ? COLOR_MASTER : COLOR_TOOL_BAR, ALPHA_OFF); - - onOffIcon.push(toolBars[i].addTool({ - imageURL: TOOL_ICON_URL + "ac-on-off.svg", - subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - x: 0, y: 0, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT, - alpha: ALPHA_ON, - visible: true - }, true, true)); - - playIcon[i] = toolBars[i].addTool({ - imageURL: TOOL_ICON_URL + "play.svg", - subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT, - alpha: ALPHA_OFF, - visible: true - }, false); - - var playLoopWidthFactor = 1.65; - playLoopIcon[i] = toolBars[i].addTool({ - imageURL: TOOL_ICON_URL + "play-and-loop.svg", - subImage: { x: 0, y: 0, width: playLoopWidthFactor * Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: playLoopWidthFactor * Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT, - alpha: ALPHA_OFF, - visible: true - }, false); - - stopIcon[i] = toolBars[i].addTool({ - imageURL: TOOL_ICON_URL + "recording-stop.svg", - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT, - alpha: ALPHA_OFF, - visible: true - }, false); - - loadIcon[i] = toolBars[i].addTool({ - imageURL: TOOL_ICON_URL + "recording-upload.svg", - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT, - alpha: ALPHA_OFF, - visible: true - }, false); - - nameOverlays.push(Overlays.addOverlay("text", { - backgroundColor: { red: 0, green: 0, blue: 0 }, - font: { size: TEXT_HEIGHT }, - text: (i == ac_number) ? "Master" : i + ". " + - ((i < names.length) ? names[i] : - "AC" + i), - x: 0, y: 0, - width: toolBars[i].width + ToolBar.SPACING, - height: TEXT_HEIGHT + TEXT_MARGIN, - leftMargin: TEXT_MARGIN, - topMargin: TEXT_MARGIN, - alpha: ALPHA_OFF, - backgroundAlpha: ALPHA_OFF, - visible: true - })); + +Actor.prototype.resetClip = function(clipURL, onLoadClip) { + this.clipURL = clipURL; + this.onLoadClip = onLoadClip; + if (this.isConnected()) { + this.onLoadClip(this); } } -function sendCommand(id, action) { - if (action === SHOW) { - toolBars[id].selectTool(onOffIcon[id], false); - toolBars[id].setAlpha(ALPHA_ON, playIcon[id]); - toolBars[id].setAlpha(ALPHA_ON, playLoopIcon[id]); - toolBars[id].setAlpha(ALPHA_ON, stopIcon[id]); - toolBars[id].setAlpha(ALPHA_ON, loadIcon[id]); - } else if (action === HIDE) { - toolBars[id].selectTool(onOffIcon[id], true); - toolBars[id].setAlpha(ALPHA_OFF, playIcon[id]); - toolBars[id].setAlpha(ALPHA_OFF, playLoopIcon[id]); - toolBars[id].setAlpha(ALPHA_OFF, stopIcon[id]); - toolBars[id].setAlpha(ALPHA_OFF, loadIcon[id]); - } else if (toolBars[id].toolSelected(onOffIcon[id])) { - return; - } - - if (id == (toolBars.length - 1)) { - id = -1; // Master command becomes broadcast. - } - - var message = { - id_key: id, - action_key: action, - clip_url_key: clip_url - }; - - if(subscribed){ - Messages.sendMessage(channel, JSON.stringify(message)); - print("Message sent!"); - clip_url = null; + +Actor.prototype.onMousePressEvent = function(clickedOverlay) { + if (this.playIcon === this.toolbar.clicked(clickedOverlay, false)) { + masterController.sendCommand(this.agentID, PLAY); + } else if (this.playLoopIcon === this.toolbar.clicked(clickedOverlay, false)) { + masterController.sendCommand(this.agentID, PLAY_LOOP); + } else if (this.stopIcon === this.toolbar.clicked(clickedOverlay, false)) { + masterController.sendCommand(this.agentID, STOP); + } else if (this.nameOverlay === clickedOverlay) { + print("Actor: " + JSON.stringify(this)); + } else { + return false; } + + return true; } -function mousePressEvent(event) { - clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); +Actor.prototype._buildUI = function() { + print("Actor.prototype._buildUI = " + JSON.stringify(this)); - // Check master control - var i = toolBars.length - 1; - if (onOffIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { - if (toolBars[i].toolSelected(onOffIcon[i])) { - sendCommand(i, SHOW); - } else { - sendCommand(i, HIDE); - } - } else if (playIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { - sendCommand(i, PLAY); - } else if (playLoopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { - sendCommand(i, PLAY_LOOP); - } else if (stopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { - sendCommand(i, STOP); - } else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { + this.toolbar = new ToolBar(0, 0, ToolBar.HORIZONTAL); + + this.toolbar.setBack(COLOR_TOOL_BAR, ALPHA_OFF); + + this.playIcon = this.toolbar.addTool({ + imageURL: TOOL_ICON_URL + "play.svg", + subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: ALPHA_OFF, + visible: true + }, false); + + var playLoopWidthFthis = 1.65; + this.playLoopIcon = this.toolbar.addTool({ + imageURL: TOOL_ICON_URL + "play-and-loop.svg", + subImage: { x: 0, y: 0, width: playLoopWidthFthis * Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: playLoopWidthFthis * Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: ALPHA_OFF, + visible: true + }, false); + + this.stopIcon = this.toolbar.addTool({ + imageURL: TOOL_ICON_URL + "recording-stop.svg", + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: ALPHA_OFF, + visible: true + }, false); + + this.nameOverlay = Overlays.addOverlay("text", { + backgroundColor: { red: 0, green: 0, blue: 0 }, + font: { size: TEXT_HEIGHT }, + text: "AC offline", + x: 0, y: 0, + width: this.toolbar.width + ToolBar.SPACING, + height: TEXT_HEIGHT + TEXT_MARGIN, + leftMargin: TEXT_MARGIN, + topMargin: TEXT_MARGIN, + alpha: ALPHA_OFF, + backgroundAlpha: ALPHA_OFF, + visible: true + }); +} + +Actor.prototype._destroyUI = function() { + this.toolbar.cleanup(); + Overlays.deleteOverlay(this.nameOverlay); +} + +Actor.prototype.moveUI = function(pos) { + var textSize = TEXT_HEIGHT + 2 * TEXT_MARGIN; + this.toolbar.move(pos.x, pos.y); + + Overlays.editOverlay(this.nameOverlay, { + x: this.toolbar.x - ToolBar.SPACING, + y: this.toolbar.y - textSize + }); +} + +Director = function() { + this.actors = new Array(); + this.toolbar = null; + this._buildUI(); + this.requestPerformanceLoad = false; + this.performanceURL = ""; +}; + +Director.prototype.destroy = function () { + print("Director.prototype.destroy") + this.clearActors(); + this.toolbar.cleanup(); + Overlays.deleteOverlay(this.nameOverlay); +} + +Director.prototype.clearActors = function () { + print("Director.prototype.clearActors") + while (this.actors.length > 0) { + this.actors.pop().destroy(); + } + + this.actors = new Array();// Brand new actors +} + +Director.prototype._buildUI = function () { + this.toolbar = new ToolBar(0, 0, ToolBar.HORIZONTAL); + + this.toolbar.setBack(COLOR_MASTER, ALPHA_OFF); + + this.onOffIcon = this.toolbar.addTool({ + imageURL: TOOL_ICON_URL + "ac-on-off.svg", + subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + x: 0, y: 0, + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: ALPHA_ON, + visible: true + }, true, true); + + this.playIcon = this.toolbar.addTool({ + imageURL: TOOL_ICON_URL + "play.svg", + subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: ALPHA_OFF, + visible: true + }, false); + + var playLoopWidthFthis = 1.65; + this.playLoopIcon = this.toolbar.addTool({ + imageURL: TOOL_ICON_URL + "play-and-loop.svg", + subImage: { x: 0, y: 0, width: playLoopWidthFthis * Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: playLoopWidthFthis * Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: ALPHA_OFF, + visible: true + }, false); + + this.stopIcon = this.toolbar.addTool({ + imageURL: TOOL_ICON_URL + "recording-stop.svg", + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: ALPHA_OFF, + visible: true + }, false); + + this.loadIcon = this.toolbar.addTool({ + imageURL: TOOL_ICON_URL + "recording-upload.svg", + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: ALPHA_OFF, + visible: true + }, false); + + this.nameOverlay = Overlays.addOverlay("text", { + backgroundColor: { red: 0, green: 0, blue: 0 }, + font: { size: TEXT_HEIGHT }, + text: "Master", + x: 0, y: 0, + width: this.toolbar.width + ToolBar.SPACING, + height: TEXT_HEIGHT + TEXT_MARGIN, + leftMargin: TEXT_MARGIN, + topMargin: TEXT_MARGIN, + alpha: ALPHA_OFF, + backgroundAlpha: ALPHA_OFF, + visible: true + }); +} + +Director.prototype.onMousePressEvent = function(clickedOverlay) { + if (this.playIcon === this.toolbar.clicked(clickedOverlay, false)) { + print("master play"); + masterController.sendCommand(BROADCAST_AGENTS, PLAY); + } else if (this.onOffIcon === this.toolbar.clicked(clickedOverlay, false)) { + this.clearActors(); + return true; + } else if (this.playLoopIcon === this.toolbar.clicked(clickedOverlay, false)) { + masterController.sendCommand(BROADCAST_AGENTS, PLAY_LOOP); + } else if (this.stopIcon === this.toolbar.clicked(clickedOverlay, false)) { + masterController.sendCommand(BROADCAST_AGENTS, STOP); + } else if (this.loadIcon === this.toolbar.clicked(clickedOverlay, false)) { input_text = Window.prompt("Insert the url of the clip: ",""); if (!(input_text === "" || input_text === null)) { - clip_url = input_text; - sendCommand(i, LOAD); - } + print("Performance file ready to be loaded url = " + input_text); + this.requestPerformanceLoad = true; + this.performanceURL = input_text; + } + } else if (this.nameOverlay === clickedOverlay) { + print("Director: " + JSON.stringify(this)); } else { // Check individual controls - for (i = 0; i < ac_number; i++) { - if (onOffIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { - if (toolBars[i].toolSelected(onOffIcon[i], false)) { - sendCommand(i, SHOW); - } else { - sendCommand(i, HIDE); - } - } else if (playIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { - sendCommand(i, PLAY); - } else if (playLoopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { - sendCommand(i, PLAY_LOOP); - } else if (stopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { - sendCommand(i, STOP); - } else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { - input_text = Window.prompt("Insert the url of the clip: ",""); - if (!(input_text === "" || input_text === null)) { - clip_url = input_text; - sendCommand(i, LOAD); - } - } else { - + for (var i = 0; i < this.actors.length; i++) { + if (this.actors[i].onMousePressEvent(clickedOverlay)) { + return true; } } + + return false; // nothing clicked from our known overlays } + + return true; +} + +Director.prototype.moveUI = function(pos) { + var textSize = TEXT_HEIGHT + 2 * TEXT_MARGIN; + var relative = { x: pos.x, y: pos.y + (this.actors.length + 1) * (Tool.IMAGE_HEIGHT + ToolBar.SPACING + textSize) }; + + this.toolbar.move(relative.x, windowDimensions.y - relative.y); + Overlays.editOverlay(this.nameOverlay, { + x: this.toolbar.x - ToolBar.SPACING, + y: this.toolbar.y - textSize + }); + + for (var i = 0; i < this.actors.length; i++) { + this.actors[i].moveUI({x: relative.x, y: windowDimensions.y - relative.y + + (i + 1) * (Tool.IMAGE_HEIGHT + ToolBar.SPACING + textSize)}); + } +} + + +Director.prototype.reloadPerformance = function() { + this.requestPerformanceLoad = false; + + if (this.performanceURL[0] == '{') { + var jsonPerformance = JSON.parse(this.performanceURL); + this.onPerformanceLoaded(jsonPerformance); + } else { + + var urlpartition = this.performanceURL.split("."); + print(urlpartition[0]); + print(urlpartition[1]); + + if ((urlpartition.length > 1) && (urlpartition[urlpartition.length - 1] === "hfr")) { + print("detected a unique clip url"); + var oneClipPerformance = new Object(); + oneClipPerformance.avatarClips = new Array(); + oneClipPerformance.avatarClips[0] = input_text; + + print(JSON.stringify(oneClipPerformance)); + + // we make a local simple performance file with a single clip and pipe in directly + this.onPerformanceLoaded(oneClipPerformance); + return true; + } else { + // FIXME: I cannot pass directly this.onPerformanceLoaded, is that exepected ? + var localThis = this; + Assets.downloadData(input_text, function(data) { localThis.onPerformanceLoaded(JSON.parse(data)); }); + } + } +} + +Director.prototype.onPerformanceLoaded = function(performanceJSON) { + // First fire all the current actors + this.clearActors(); + + print("Director.prototype.onPerformanceLoaded = " + JSON.stringify(performanceJSON)); + if (performanceJSON.avatarClips != null) { + var numClips = performanceJSON.avatarClips.length; + print("Found " + numClips + "in the performance file, and currently using " + this.actors.length + " actor(s)"); + + for (var i = 0; i < numClips; i++) { + this.hireActor(performanceJSON.avatarClips[i]); + } + + } +} + + +Director.prototype.hireActor = function(clipURL) { + print("new actor = " + this.actors.length ); + var newActor = new Actor(); + newActor.clipURL = null; + newActor.onLoadClip = function(clip) {}; + + var localThis = this; + newActor.onHired = function(actor) { + print("agent hired from Director! " + actor.agentID) + Overlays.editOverlay(actor.nameOverlay, { + text: "AC " + actor.agentID, + backgroundColor: { red: 0, green: 255, blue: 0 } + }); + + if (actor.clipURL != null) { + print("agent hired, calling load clip for url " + actor.clipURL); + actor.onLoadClip(actor); + } + }; + + newActor.onFired = function(actor) { + print("agent fired from playbackMaster! " + actor.agentID); + var index = localThis.actors.indexOf(actor); + if (index >= 0) { + localThis.actors.splice(index, 1); + } + + actor._destroyUI(); + + actor.destroy(); + moveUI(); + } + + newActor.resetClip(clipURL, function(actor) { + print("Load clip for agent" + actor.agentID + " calling load clip for url " + actor.clipURL); + masterController.sendCommand(actor.agentID, LOAD, actor.clipURL); + }); + + + masterController.hireAgent(newActor); + newActor._buildUI(); + + this.actors.push(newActor); + + moveUI(); +} + + +masterController.reset(); +var director = new Director(); + +moveUI(); + + +function mousePressEvent(event) { + print("mousePressEvent"); + clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + + // Check director and actors + director.onMousePressEvent(clickedOverlay); } function moveUI() { - var textSize = TEXT_HEIGHT + 2 * TEXT_MARGIN; - var relative = { x: 70, y: 75 + (ac_number) * (Tool.IMAGE_HEIGHT + ToolBar.SPACING + textSize) }; - - for (i = 0; i <= ac_number; i++) { - toolBars[i].move(relative.x, - windowDimensions.y - relative.y + - i * (Tool.IMAGE_HEIGHT + ToolBar.SPACING + textSize)); - - Overlays.editOverlay(nameOverlays[i], { - x: toolBars[i].x - ToolBar.SPACING, - y: toolBars[i].y - textSize - }); - } + director.moveUI({ x: 70, y: 75}); + } -function update() { +function update(deltaTime) { var newDimensions = Controller.getViewportDimensions(); if (windowDimensions.x != newDimensions.x || windowDimensions.y != newDimensions.y) { windowDimensions = newDimensions; moveUI(); } + + if (director.requestPerformanceLoad) { + print("reloadPerformance " + director.performanceURL); + director.reloadPerformance(); + } + + masterController.update(deltaTime); } function scriptEnding() { - for (i = 0; i <= ac_number; i++) { - toolBars[i].cleanup(); - Overlays.deleteOverlay(nameOverlays[i]); - } - - if (subscribed) { - Messages.unsubscribe(channel); - } - Messages.unsubscribe(announceIDChannel); + print("cleanup") + director.destroy(); + masterController.destroy(); } Controller.mousePressEvent.connect(mousePressEvent); Script.update.connect(update); Script.scriptEnding.connect(scriptEnding); - - -Messages.subscribe(announceIDChannel); -Messages.messageReceived.connect(function (channel, message, senderID) { - if (channel == announceIDChannel && message == "ready") { - // check to see if we know about this agent - if (knownAgents.indexOf(senderID) < 0) { - var indexOfNewAgent = knownAgents.length; - knownAgents[indexOfNewAgent] = senderID; - var acknowledgeMessage = senderID + "." + indexOfNewAgent; - Messages.sendMessage(announceIDChannel, acknowledgeMessage); - } - - } -}); - -moveUI(); \ No newline at end of file diff --git a/examples/baseball/assets.json b/examples/baseball/assets.json new file mode 100644 index 0000000000..00edcbe703 --- /dev/null +++ b/examples/baseball/assets.json @@ -0,0 +1,105 @@ +{ + "assets": { + "crowd-boos.wav": { + "s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/crowd-boos.wav", + "atp_url": "atp:c632c92b166ade60aa16b23ff1dfdf712856caeb83bd9311980b2d5edac821af.wav", + "attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/" + }, + "crowd-cheers-organ.wav": { + "s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/crowd-cheers-organ.wav", + "atp_url": "atp:b8044401a846ed29f881a0b9b80cf1ba41f26327180c28fc9c70d144f9b70045.wav", + "attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/" + }, + "crowd-medium.wav": { + "s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/crowd-medium.wav", + "atp_url": "atp:0821bf2ac60dd2f356dfdd948e8bb89c23984dc3584612f6c815765154f02cae.wav", + "attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/" + }, + "baseball-hitting-bat-1.wav": { + "s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/baseball-hitting-bat-1.wav", + "atp_url": "atp:6f0b691a0c9c9ece6557d97fe242b1faec4020fe26efc9c17327993b513c5fe5.wav", + "attribution": "CC BY 3.0 - Credit: https://www.freesound.org/people/SocializedArtist45/" + }, + "baseball-hitting-bat-set-1.wav": { + "s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/baseball-hitting-bat-set-1.wav", + "atp_url": "atp:5be5806205158ebdc5c3623ceb7ae73315028b51ffeae24292aff7042e3fa6a9.wav", + "attribution": "CC BY 3.0 - Credit: CGEffex - https://www.freesound.org/people/CGEffex/" + }, + "baseball-hitting-bat-set-2.wav": { + "s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/baseball-hitting-bat-set-2.wav", + "atp_url": "atp:e68661374e2145c480809c26134782aad11e0de456c7802170c7abccc4028873.wav", + "attribution": "CC BY 3.0 - Credit: CGEffex - https://www.freesound.org/people/CGEffex/" + }, + "baseball-hitting-bat-set-3.wav": { + "s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/baseball-hitting-bat-set-3.wav", + "atp_url": "atp:787e3c9af17dd3929527787176ede83d6806260e63ddd5a4cef48cd22e32c6f7.wav", + "attribution": "CC BY 3.0 - Credit: CGEffex - https://www.freesound.org/people/CGEffex/" + }, + "baseball-hitting-bat-set-4.wav": { + "s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/baseball-hitting-bat-set-4.wav", + "atp_url": "atp:fc65383431a6238c7a4749f0f6f061f75a604ed5e17d775ab1b2955609e67ebb.wav", + "attribution": "CC BY 3.0 - Credit: CGEffex - https://www.freesound.org/people/CGEffex/" + }, + "chatter-loop.wav": { + "s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/chatter-loop.wav", + "atp_url": "atp:d9978e693035d4e2b5c7b546c8cccfb2dde5677834d9eed5206ccb2da55b4732.wav", + "attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/" + }, + "zorba-organ.wav": { + "s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/zorba-organ.wav", + "atp_url": "atp:1ee58f4d929fdef7c2989cd8be964952a24cdd653d80f57b6a89a2ae8e3029e1.wav", + "attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/" + }, + "charge-organ.wav": { + "s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/charge-organ.wav", + "atp_url": "atp:cefaba2d5f1a378382ee046716bffcf6b6a40676649b23a1e81a996efe22d7d3.wav", + "attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/" + }, + "ball-game.wav": { + "s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/ball-game.wav", + "atp_url": "atp:fb41e37f8f8f7b78e546ac78800df6e39edaa09b2df4bfa0afdd8d749dac38b8.wav", + "attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/" + }, + "clapping.wav": { + "s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/clapping.wav", + "atp_url": "atp:44a83a788ccfd2924e35c902c34808b24dbd0309d000299ce01a355f91cf8115.wav", + "attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/" + }, + "pop1.wav": { + "s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/pop1.wav", + "atp_url": "atp:a2bf79c95fe74c2c6c9188acc7230f7cd1b0f6008f2c81954ecd93eca0497ec6.wav" + }, + "pop2.wav": { + "s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/pop2.wav", + "atp_url": "atp:901067ebc2cda4c0d86ec02fcca2ed901e85f9097ad68bbde78b4cad8eaf2ed7.wav" + }, + "pop3.wav": { + "s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/pop3.wav", + "atp_url": "atp:830312930577cb1ea36ba2d743e957debbacceb441b20addead5a6faa05a3771.wav" + }, + "pop4.wav": { + "s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/pop4.wav", + "atp_url": "atp:62e80d0a9f084cf731bcc66ca6e9020ee88587417071a281eee3167307b53560.wav" + }, + "fire1.wav": { + "s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/fire1.wav", + "atp_url": "atp:ee6afe565576c4546c6d6cd89c1af532484c9b60ab30574d6b40c2df022f7260.wav", + "attribution": "CC BY 3.0 - Credir: dcsimon - https://www.freesound.org/people/dcsimon/sounds/160720/" + }, + "fire2.wav": { + "s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/fire2.wav", + "atp_url": "atp:91ef19ba1c78be82d3fd06530cd05ceb90d1e75f4204c66819c208c55da049ef.wav", + "attribution": "CC BY 3.0 - Credit: dcsimon - https://www.freesound.org/people/dcsimon/sounds/160720/" + }, + "fire3.wav": { + "s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/fire3.wav", + "atp_url": "atp:ee56993daf775012cf49293bfd5971eec7e5c396642f8bfbea902ba8f47b56cd.wav", + "attribution": "CC BY 3.0 - Credit: dcsimon - https://www.freesound.org/people/dcsimon/sounds/160720/" + }, + "fire4.wav": { + "s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/fire4.wav", + "atp_url": "atp:37775d267f00f82242a7e7f61f3f3d7bf64a54c5a3799e7f2540fa5f6b79bd02.wav", + "attribution": "CC BY 3.0 - Credit: dcsimon - https://www.freesound.org/people/dcsimon/sounds/160720/" + } + } +} diff --git a/examples/baseball/baseballCrowd.js b/examples/baseball/baseballCrowd.js new file mode 100644 index 0000000000..de9b53f9ec --- /dev/null +++ b/examples/baseball/baseballCrowd.js @@ -0,0 +1,43 @@ +// +// baseballCrowd.js +// examples/acScripts +// +// Created by Stephen Birarda on 10/20/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var chatter = SoundCache.getSound("atp:d9978e693035d4e2b5c7b546c8cccfb2dde5677834d9eed5206ccb2da55b4732.wav"); +var extras = [ + SoundCache.getSound("atp:1ee58f4d929fdef7c2989cd8be964952a24cdd653d80f57b6a89a2ae8e3029e1.wav"), // zorba + SoundCache.getSound("atp:cefaba2d5f1a378382ee046716bffcf6b6a40676649b23a1e81a996efe22d7d3.wav"), // charge + SoundCache.getSound("atp:fb41e37f8f8f7b78e546ac78800df6e39edaa09b2df4bfa0afdd8d749dac38b8.wav"), // take me out to the ball game + SoundCache.getSound("atp:44a83a788ccfd2924e35c902c34808b24dbd0309d000299ce01a355f91cf8115.wav") // clapping +]; + +var CHATTER_VOLUME = 0.20 +var EXTRA_VOLUME = 0.25 + +function playChatter() { + if (chatter.downloaded && !chatter.isPlaying) { + Audio.playSound(chatter, { loop: true, volume: CHATTER_VOLUME }); + } +} + +chatter.ready.connect(playChatter); + +var currentInjector = null; + +function playRandomExtras() { + if ((!currentInjector || !currentInjector.isPlaying) && (Math.random() < (1.0 / 1800.0))) { + // play a random extra sound about every 30s + currentInjector = Audio.playSound( + extras[Math.floor(Math.random() * extras.length)], + { volume: EXTRA_VOLUME } + ); + } +} + +Script.update.connect(playRandomExtras); diff --git a/examples/baseball/bat.js b/examples/baseball/bat.js new file mode 100644 index 0000000000..40c3d43670 --- /dev/null +++ b/examples/baseball/bat.js @@ -0,0 +1,43 @@ +// +// bat.js +// examples/baseball/ +// +// Created by Ryan Huffman on Nov 9, 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + Script.include("pitching.js"); + + var pitchingMachine = null; + + this.pitchAndHideAvatar = function() { + if (!pitchingMachine) { + pitchingMachine = getOrCreatePitchingMachine(); + Script.update.connect(function(dt) { pitchingMachine.update(dt); }); + } + pitchingMachine.start(); + MyAvatar.shouldRenderLocally = false; + }; + + this.startNearGrab = function() { + // send the avatar to the baseball location so that they're ready to bat + location = "/baseball" + + this.pitchAndHideAvatar() + }; + + this.continueNearGrab = function() { + this.pitchAndHideAvatar() + }; + + this.releaseGrab = function() { + if (pitchingMachine) { + pitchingMachine.stop(); + } + MyAvatar.shouldRenderLocally = true; + }; +}); diff --git a/examples/baseball/createBatButton.js b/examples/baseball/createBatButton.js new file mode 100644 index 0000000000..bbd2bea9f2 --- /dev/null +++ b/examples/baseball/createBatButton.js @@ -0,0 +1,76 @@ +// +// createBatButton.js +// examples/baseball/moreBatsButton.js +// +// Created by Stephen Birarda on 10/28/2015. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function(){ + this.clickReleaseOnEntity = function(entityID, mouseEvent) { + if (!mouseEvent.isLeftButton) { + return; + } + this.dropBats(); + }; + this.startNearTrigger = function() { + this.dropBats(); + }; + this.startFarTrigger = function() { + this.dropBats(); + }; + this.dropBats = function() { + // if the bat box is near us, grab it's position + var nearby = Entities.findEntities(this.position, 20); + + nearby.forEach(function(id) { + var properties = Entities.getEntityProperties(id, ["name", "position"]); + if (properties.name && properties.name == "Bat Box") { + boxPosition = properties.position; + } + }); + + var BAT_DROP_HEIGHT = 2.0; + + var dropPosition; + + if (!boxPosition) { + // we got no bat box position, drop in front of the avatar instead + } else { + // drop the bat above the bat box + dropPosition = Vec3.sum(boxPosition, { x: 0.0, y: BAT_DROP_HEIGHT, z: 0.0}); + } + + var BAT_MODEL = "atp:c47deaae09cca927f6bc9cca0e8bbe77fc618f8c3f2b49899406a63a59f885cb.fbx"; + var BAT_COLLISION_HULL = "atp:9eafceb7510c41d50661130090de7e0632aa4da236ebda84a0059a4be2130e0c.obj"; + var SCRIPT_URL = "http://rawgit.com/birarda/hifi/baseball/examples/baseball/bat.js" + + var batUserData = { + grabbableKey: { + spatialKey: { + leftRelativePosition: { x: 0.9, y: 0.05, z: -0.05 }, + rightRelativePosition: { x: 0.9, y: 0.05, z: 0.05 }, + relativeRotation: Quat.fromPitchYawRollDegrees(0, 0, 45) + } + } + } + + // add the fresh bat at the drop position + var bat = Entities.addEntity({ + name: 'Bat', + type: "Model", + modelURL: BAT_MODEL, + position: dropPosition, + compoundShapeURL: BAT_COLLISION_HULL, + collisionsWillMove: true, + velocity: { x: 0, y: 0.05, z: 0}, // workaround for gravity not taking effect on add + gravity: { x: 0, y: -9.81, z: 0}, + rotation: Quat.fromPitchYawRollDegrees(0.0, 0.0, -90.0), + script: SCRIPT_URL, + userData: JSON.stringify(batUserData) + }); + }; +}); diff --git a/examples/baseball/firework.js b/examples/baseball/firework.js new file mode 100644 index 0000000000..a78e1595eb --- /dev/null +++ b/examples/baseball/firework.js @@ -0,0 +1,138 @@ +// +// firework.js +// examples/baseball/ +// +// Created by Ryan Huffman on Nov 9, 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("utils.js"); + +var emitters = []; + +var smokeTrailSettings = { + "name":"ParticlesTest Emitter", + "type": "ParticleEffect", + "color":{"red":205,"green":84.41176470588235,"blue":84.41176470588235}, + "maxParticles":1000, + "velocity": { x: 0, y: 18.0, z: 0 }, + "lifetime": 20, + "lifespan":3, + "emitRate":100, + "emitSpeed":0.5, + "speedSpread":0, + "emitOrientation":{"x":0,"y":0,"z":0,"w":1}, + "emitDimensions":{"x":0,"y":0,"z":0}, + "emitRadiusStart":0.5, + "polarStart":1, + "polarFinish":1, + "azimuthStart":0, + "azimuthFinish":0, + "emitAcceleration":{"x":0,"y":-0.70000001192092896,"z":0}, + "accelerationSpread":{"x":0,"y":0,"z":0}, + "particleRadius":0.03999999910593033, + "radiusSpread":0, + "radiusStart":0.13999999910593033, + "radiusFinish":0.14, + "colorSpread":{"red":0,"green":0,"blue":0}, + "colorStart":{"red":255,"green":255,"blue":255}, + "colorFinish":{"red":255,"green":255,"blue":255}, + "alpha":1, + "alphaSpread":0, + "alphaStart":0, + "alphaFinish":1, + "textures":"https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png" +}; + +var fireworkSettings = { + "name":"ParticlesTest Emitter", + "type": "ParticleEffect", + "color":{"red":205,"green":84.41176470588235,"blue":84.41176470588235}, + "maxParticles":1000, + "lifetime": 20, + "lifespan":4, + "emitRate":1000, + "emitSpeed":1.5, + "speedSpread":1.0, + "emitOrientation":{"x":-0.2,"y":0,"z":0,"w":0.7000000000000001}, + "emitDimensions":{"x":0,"y":0,"z":0}, + "emitRadiusStart":0.5, + "polarStart":1, + "polarFinish":1.2, + "azimuthStart":-Math.PI, + "azimuthFinish":Math.PI, + "emitAcceleration":{"x":0,"y":-0.70000001192092896,"z":0}, + "accelerationSpread":{"x":0,"y":0,"z":0}, + "particleRadius":0.03999999910593033, + "radiusSpread":0, + "radiusStart":0.13999999910593033, + "radiusFinish":0.14, + "colorSpread":{"red":0,"green":0,"blue":0}, + "colorStart":{"red":255,"green":255,"blue":255}, + "colorFinish":{"red":255,"green":255,"blue":255}, + "alpha":1, + "alphaSpread":0, + "alphaStart":0, + "alphaFinish":1, + "textures":"https://hifi-public.s3.amazonaws.com/alan/Particles/spark_2.png", +}; + +var popSounds = getSounds([ + "atp:a2bf79c95fe74c2c6c9188acc7230f7cd1b0f6008f2c81954ecd93eca0497ec6.wav", + "atp:901067ebc2cda4c0d86ec02fcca2ed901e85f9097ad68bbde78b4cad8eaf2ed7.wav", + "atp:830312930577cb1ea36ba2d743e957debbacceb441b20addead5a6faa05a3771.wav", + "atp:62e80d0a9f084cf731bcc66ca6e9020ee88587417071a281eee3167307b53560.wav" +]); + +var launchSounds = getSounds([ + "atp:ee6afe565576c4546c6d6cd89c1af532484c9b60ab30574d6b40c2df022f7260.wav", + "atp:91ef19ba1c78be82d3fd06530cd05ceb90d1e75f4204c66819c208c55da049ef.wav", + "atp:ee56993daf775012cf49293bfd5971eec7e5c396642f8bfbea902ba8f47b56cd.wav", + "atp:37775d267f00f82242a7e7f61f3f3d7bf64a54c5a3799e7f2540fa5f6b79bd02.wav" +]); + +function playRandomSound(sounds, options) { + Audio.playSound(sounds[randomInt(sounds.length)], options); +} + +function shootFirework(position, color, options) { + smokeTrailSettings.position = position; + smokeTrailSettings.velocity = randomVec3(-5, 5, 10, 20, 10, 15); + smokeTrailSettings.gravity = randomVec3(-5, 5, -9.8, -9.8, 20, 40); + + playRandomSound(launchSounds, { position: {x: 0, y: 0 , z: 0}, volume: 3.0 }); + var smokeID = Entities.addEntity(smokeTrailSettings); + + Script.setTimeout(function() { + Entities.editEntity(smokeID, { emitRate: 0 }); + var position = Entities.getEntityProperties(smokeID, ['position']).position; + fireworkSettings.position = position; + fireworkSettings.colorStart = color; + fireworkSettings.colorFinish = color; + var burstID = Entities.addEntity(fireworkSettings); + playRandomSound(popSounds, { position: {x: 0, y: 0 , z: 0}, volume: 3.0 }); + Script.setTimeout(function() { + Entities.editEntity(burstID, { emitRate: 0 }); + }, 500); + Script.setTimeout(function() { + Entities.deleteEntity(smokeID); + Entities.deleteEntity(burstID); + }, 10000); + }, 2000); +} + +playFireworkShow = function(position, numberOfFireworks, duration) { + for (var i = 0; i < numberOfFireworks; i++) { + var randomOffset = randomVec3(-15, 15, -3, 3, -1, 1); + var randomPosition = Vec3.sum(position, randomOffset); + Script.setTimeout(function(position) { + return function() { + var color = randomColor(128, 255, 128, 255, 128, 255); + shootFirework(position, color, fireworkSettings); + } + }(randomPosition), Math.random() * duration) + } +} diff --git a/examples/baseball/line.js b/examples/baseball/line.js new file mode 100644 index 0000000000..c4fac784c4 --- /dev/null +++ b/examples/baseball/line.js @@ -0,0 +1,165 @@ +function info(message) { + print("[INFO] " + message); +} + +function error(message) { + print("[ERROR] " + message); +} + + +/****************************************************************************** + * PolyLine + *****************************************************************************/ +var LINE_DIMENSIONS = { x: 2000, y: 2000, z: 2000 }; +var MAX_LINE_LENGTH = 40; // This must be 2 or greater; +var PolyLine = function(position, color, defaultStrokeWidth) { + //info("Creating polyline"); + //Vec3.print("New line at", position); + this.position = position; + this.color = color; + this.defaultStrokeWidth = 0.10; + this.points = [ + { x: 0, y: 0, z: 0 }, + ]; + this.strokeWidths = [ + this.defaultStrokeWidth, + ] + this.normals = [ + { x: 1, y: 0, z: 0 }, + ] + this.entityID = Entities.addEntity({ + type: "PolyLine", + position: position, + linePoints: this.points, + normals: this.normals, + strokeWidths: this.strokeWidths, + dimensions: LINE_DIMENSIONS, + color: color, + lifetime: 20, + }); +}; + +PolyLine.prototype.enqueuePoint = function(position) { + if (this.isFull()) { + error("Hit max PolyLine size"); + return; + } + + //Vec3.print("pos", position); + //info("Number of points: " + this.points.length); + + position = Vec3.subtract(position, this.position); + this.points.push(position); + this.normals.push({ x: 1, y: 0, z: 0 }); + this.strokeWidths.push(this.defaultStrokeWidth); + Entities.editEntity(this.entityID, { + linePoints: this.points, + normals: this.normals, + strokeWidths: this.strokeWidths, + }); +}; + +PolyLine.prototype.dequeuePoint = function() { + if (this.points.length == 0) { + error("Hit min PolyLine size"); + return; + } + + this.points = this.points.slice(1); + this.normals = this.normals.slice(1); + this.strokeWidths = this.strokeWidths.slice(1); + + Entities.editEntity(this.entityID, { + linePoints: this.points, + normals: this.normals, + strokeWidths: this.strokeWidths, + }); +}; + +PolyLine.prototype.getFirstPoint = function() { + return Vec3.sum(this.position, this.points[0]); +}; + +PolyLine.prototype.getLastPoint = function() { + return Vec3.sum(this.position, this.points[this.points.length - 1]); +}; + +PolyLine.prototype.getSize = function() { + return this.points.length; +} + +PolyLine.prototype.isFull = function() { + return this.points.length >= MAX_LINE_LENGTH; +}; + +PolyLine.prototype.destroy = function() { + Entities.deleteEntity(this.entityID); + this.points = []; +}; + + +/****************************************************************************** + * InfiniteLine + *****************************************************************************/ +InfiniteLine = function(position, color) { + this.position = position; + this.color = color; + this.lines = [new PolyLine(position, color)]; + this.size = 0; +}; + +InfiniteLine.prototype.enqueuePoint = function(position) { + var currentLine; + + if (this.lines.length == 0) { + currentLine = new PolyLine(position, this.color); + this.lines.push(currentLine); + } else { + currentLine = this.lines[this.lines.length - 1]; + } + + if (currentLine.isFull()) { + //info("Current line is full, creating new line"); + //Vec3.print("Last line is", currentLine.getLastPoint()); + //Vec3.print("New line is", position); + var newLine = new PolyLine(currentLine.getLastPoint(), this.color); + this.lines.push(newLine); + currentLine = newLine; + } + + currentLine.enqueuePoint(position); + + ++this.size; +}; + +InfiniteLine.prototype.dequeuePoint = function() { + if (this.lines.length == 0) { + error("Trying to dequeue from InfiniteLine when no points are left"); + return; + } + + var lastLine = this.lines[0]; + lastLine.dequeuePoint(); + + if (lastLine.getSize() <= 1) { + this.lines = this.lines.slice(1); + } + + --this.size; +}; + +InfiniteLine.prototype.getFirstPoint = function() { + return this.lines.length > 0 ? this.lines[0].getFirstPoint() : null; +}; + +InfiniteLine.prototype.getLastPoint = function() { + return this.lines.length > 0 ? this.lines[lines.length - 1].getLastPoint() : null; +}; + +InfiniteLine.prototype.destroy = function() { + for (var i = 0; i < this.lines.length; ++i) { + this.lines[i].destroy(); + } + + this.size = 0; +}; diff --git a/examples/baseball/pitching.js b/examples/baseball/pitching.js new file mode 100644 index 0000000000..09f33c9dd4 --- /dev/null +++ b/examples/baseball/pitching.js @@ -0,0 +1,554 @@ +// +// pitching.js +// examples/baseball/ +// +// Created by Ryan Huffman on Nov 9, 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +print("Loading pitching"); + +Script.include("../libraries/line.js"); +Script.include("firework.js"); +Script.include("utils.js"); + +var DISTANCE_BILLBOARD_NAME = "CurrentScore"; +var HIGH_SCORE_BILLBOARD_NAME = "HighScore"; + +var distanceBillboardEntityID = null; +var highScoreBillboardEntityID = null; + +function getDistanceBillboardEntityID() { + if (distanceBillboardEntityID === null) { + distanceBillboardEntityID = findEntity({name: DISTANCE_BILLBOARD_NAME }, 1000); + } + return distanceBillboardEntityID; +} +function getHighScoreBillboardEntityID() { + if (highScoreBillboardEntityID === null) { + highScoreBillboardEntityID = findEntity({name: HIGH_SCORE_BILLBOARD_NAME }, 1000); + } + return highScoreBillboardEntityID; +} + +var METERS_TO_FEET = 3.28084; + +var AUDIO = { + crowdBoos: [ + SoundCache.getSound("atp:c632c92b166ade60aa16b23ff1dfdf712856caeb83bd9311980b2d5edac821af.wav", false) + ], + crowdCheers: [ + SoundCache.getSound("atp:0821bf2ac60dd2f356dfdd948e8bb89c23984dc3584612f6c815765154f02cae.wav", false), + SoundCache.getSound("atp:b8044401a846ed29f881a0b9b80cf1ba41f26327180c28fc9c70d144f9b70045.wav", false), + ], + batHit: [ + SoundCache.getSound("atp:6f0b691a0c9c9ece6557d97fe242b1faec4020fe26efc9c17327993b513c5fe5.wav", false), + SoundCache.getSound("atp:5be5806205158ebdc5c3623ceb7ae73315028b51ffeae24292aff7042e3fa6a9.wav", false), + SoundCache.getSound("atp:e68661374e2145c480809c26134782aad11e0de456c7802170c7abccc4028873.wav", false), + SoundCache.getSound("atp:787e3c9af17dd3929527787176ede83d6806260e63ddd5a4cef48cd22e32c6f7.wav", false), + SoundCache.getSound("atp:fc65383431a6238c7a4749f0f6f061f75a604ed5e17d775ab1b2955609e67ebb.wav", false), + ], + strike: [ + SoundCache.getSound("atp:2a258076a85fffde4ba04b5ddc1de9034c7ae7d2af8c5d93d4fed0bcdef3472a.wav", false), + SoundCache.getSound("atp:518363524af3ed9b9ae4ca2ceee61f01aecd37e266a51c5a5f5487efe2520fd5.wav", false), + SoundCache.getSound("atp:d51d38b089574acbdfdf53ef733bfb3ab41d848fb8c0b6659c7790a785240009.wav", false), + ], + foul: [ + SoundCache.getSound("atp:316fa18ff9eef457f670452b449a8dc5a41ccabd4e948781c50aaafaae63b0ab.wav", false), + SoundCache.getSound("atp:c84d88352d38437edd7414b26dc74e567618712caeb59fec70822398b0c5a279.wav", false), + ] +} + +var PITCH_THUNK_SOUND_URL = "http://hifi-public.s3.amazonaws.com/sounds/ping_pong_gun/pong_sound.wav"; +var pitchSound = SoundCache.getSound(PITCH_THUNK_SOUND_URL, false); +updateBillboard(""); + +var PITCHING_MACHINE_URL = "atp:87d4879530b698741ecc45f6f31789aac11f7865a2c3bec5fe9b061a182c80d4.fbx"; +// This defines an offset to pitch a ball from with respect to the machine's position. The offset is a +// percentage of the machine's dimensions. So, { x: 0.5, y: -1.0, z: 0.0 } would offset on 50% on the +// machine's x axis, -100% on the y axis, and 0% on the z-axis. For the dimensions { x: 100, y: 100, z: 100 }, +// that would result in an offset of { x: 50, y: -100, z: 0 }. This makes it easy to calculate an offset if +// the machine's dimensions change. +var PITCHING_MACHINE_OUTPUT_OFFSET_PCT = { + x: 0.0, + y: 0.25, + z: -1.05, +}; +var PITCHING_MACHINE_PROPERTIES = { + name: "Pitching Machine", + type: "Model", + position: { + x: -0.93, + y: 0.8, + z: -19.8 + }, + velocity: { + x: 0, + y: -0.01, + z: 0 + }, + gravity: { + x: 0.0, + y: -9.8, + z: 0.0 + }, + registrationPoint: { + x: 0.5, + y: 0.5, + z: 0.5 + }, + rotation: Quat.fromPitchYawRollDegrees(0, 180, 0), + modelURL: PITCHING_MACHINE_URL, + dimensions: { + x: 0.4, + y: 0.61, + z: 0.39 + }, + collisionsWillMove: false, + shapeType: "Box" +}; +PITCHING_MACHINE_PROPERTIES.dimensions = Vec3.multiply(2.5, PITCHING_MACHINE_PROPERTIES.dimensions); +var DISTANCE_FROM_PLATE = PITCHING_MACHINE_PROPERTIES.position.z; + +var PITCH_RATE = 5000; + +getOrCreatePitchingMachine = function() { + // Search for pitching machine + var entities = findEntities({ name: PITCHING_MACHINE_PROPERTIES.name }, 1000); + var pitchingMachineID = null; + + // Create if it doesn't exist + if (entities.length == 0) { + pitchingMachineID = Entities.addEntity(PITCHING_MACHINE_PROPERTIES); + } else { + pitchingMachineID = entities[0]; + } + + // Wrap with PitchingMachine object and return + return new PitchingMachine(pitchingMachineID); +} + +// The pitching machine wraps an entity ID and uses it's position & rotation to determin where to +// pitch the ball from and in which direction, and uses the dimensions to determine the scale of them ball. +function PitchingMachine(pitchingMachineID) { + this.pitchingMachineID = pitchingMachineID; + this.enabled = false; + this.baseball = null; + this.injector = null; +} + +PitchingMachine.prototype = { + pitchBall: function() { + cleanupTrail(); + + if (!this.enabled) { + return; + } + + print("Pitching ball"); + var machineProperties = Entities.getEntityProperties(this.pitchingMachineID, ["dimensions", "position", "rotation"]); + var pitchFromPositionBase = machineProperties.position; + var pitchFromOffset = vec3Mult(machineProperties.dimensions, PITCHING_MACHINE_OUTPUT_OFFSET_PCT); + pitchFromOffset = Vec3.multiplyQbyV(machineProperties.rotation, pitchFromOffset); + var pitchFromPosition = Vec3.sum(pitchFromPositionBase, pitchFromOffset); + var pitchDirection = Quat.getFront(machineProperties.rotation); + var ballScale = machineProperties.dimensions.x / PITCHING_MACHINE_PROPERTIES.dimensions.x; + + var speed = randomFloat(BASEBALL_MIN_SPEED, BASEBALL_MAX_SPEED); + var velocity = Vec3.multiply(speed, pitchDirection); + + this.baseball = new Baseball(pitchFromPosition, velocity, ballScale); + + if (!this.injector) { + this.injector = Audio.playSound(pitchSound, { + position: pitchFromPosition, + volume: 1.0 + }); + } else { + this.injector.restart(); + } + }, + start: function() { + if (this.enabled) { + return; + } + print("Starting Pitching Machine"); + this.enabled = true; + this.pitchBall(); + }, + stop: function() { + if (!this.enabled) { + return; + } + print("Stopping Pitching Machine"); + this.enabled = false; + }, + update: function(dt) { + if (this.baseball) { + this.baseball.update(dt); + if (this.baseball.finished()) { + this.baseball = null; + var self = this; + Script.setTimeout(function() { self.pitchBall(); }, 3000); + } + } + } +}; + +var BASEBALL_MODEL_URL = "atp:7185099f1f650600ca187222573a88200aeb835454bd2f578f12c7fb4fd190fa.fbx"; +var BASEBALL_MIN_SPEED = 7.0; +var BASEBALL_MAX_SPEED = 15.0; +var BASEBALL_RADIUS = 0.07468; +var BASEBALL_PROPERTIES = { + name: "Baseball", + type: "Model", + modelURL: BASEBALL_MODEL_URL, + shapeType: "Sphere", + position: { + x: 0, + y: 0, + z: 0 + }, + dimensions: { + x: BASEBALL_RADIUS, + y: BASEBALL_RADIUS, + z: BASEBALL_RADIUS + }, + collisionsWillMove: true, + angularVelocity: { + x: 17.0, + y: 0, + z: -8.0, + + x: 0.0, + y: 0, + z: 0.0, + }, + angularDamping: 0.0, + damping: 0.0, + restitution: 0.5, + friction: 0.0, + friction: 0.5, + lifetime: 20, + gravity: { + x: 0, + y: 0, + z: 0 + } +}; +var BASEBALL_STATE = { + PITCHING: 0, + HIT: 1, + HIT_LANDED: 2, + STRIKE: 3, + FOUL: 4 +}; + + + +function vec3Mult(a, b) { + return { + x: a.x * b.x, + y: a.y * b.y, + z: a.z * b.z, + }; +} + +function map(value, min1, max1, min2, max2) { + return min2 + (max2 - min2) * ((value - min1) / (max1 - min1)); +} + +function orientationOf(vector) { + var RAD_TO_DEG = 180.0 / Math.PI; + var Y_AXIS = { x: 0, y: 1, z: 0 }; + var X_AXIS = { x: 1, y: 0, z: 0 }; + var direction = Vec3.normalize(vector); + + var yaw = Quat.angleAxis(Math.atan2(direction.x, direction.z) * RAD_TO_DEG, Y_AXIS); + var pitch = Quat.angleAxis(Math.asin(-direction.y) * RAD_TO_DEG, X_AXIS); + + return Quat.multiply(yaw, pitch); +} + +var ACCELERATION_SPREAD = 0.35; + +var TRAIL_COLOR = { red: 128, green: 255, blue: 89 }; +var TRAIL_LIFETIME = 20; + +var trail = null; +var trailInterval = null; +function cleanupTrail() { + if (trail) { + Script.clearInterval(this.trailInterval); + trailInterval = null; + + trail.destroy(); + trail = null; + } +} + +function setupTrail(entityID, position) { + cleanupTrail(); + + var lastPosition = position; + trail = new InfiniteLine(position, { red: 128, green: 255, blue: 89 }, 20); + trailInterval = Script.setInterval(function() { + var properties = Entities.getEntityProperties(entityID, ['position']); + if (Vec3.distance(properties.position, lastPosition)) { + var strokeWidth = Math.log(1 + trail.size) * 0.05; + trail.enqueuePoint(properties.position, strokeWidth); + lastPosition = properties.position; + } + }, 50); +} + +function Baseball(position, velocity, ballScale) { + var self = this; + + this.state = BASEBALL_STATE.PITCHING; + + // Setup entity properties + var properties = shallowCopy(BASEBALL_PROPERTIES); + properties.position = position; + properties.velocity = velocity; + properties.dimensions = Vec3.multiply(ballScale, properties.dimensions); + /* + properties.gravity = { + x: randomFloat(-ACCELERATION_SPREAD, ACCELERATION_SPREAD), + y: randomFloat(-ACCELERATION_SPREAD, ACCELERATION_SPREAD), + z: 0.0, + }; + */ + + // Create entity + this.entityID = Entities.addEntity(properties); + + this.timeSincePitched = 0; + this.timeSinceHit = 0; + this.hitBallAtPosition = null; + this.distanceTravelled = 0; + this.wasHighScore = false; + this.landed = false; + + // Listen for collision for the lifetime of the entity + Script.addEventHandler(this.entityID, "collisionWithEntity", function(entityA, entityB, collision) { + self.collisionCallback(entityA, entityB, collision); + }); + if (Math.random() < 0.5) { + for (var i = 0; i < 50; i++) { + Script.setTimeout(function() { + Entities.editEntity(entityID, { + gravity: { + x: randomFloat(-ACCELERATION_SPREAD, ACCELERATION_SPREAD), + y: randomFloat(-ACCELERATION_SPREAD, ACCELERATION_SPREAD), + z: 0.0, + } + }) + }, i * 100); + } + } +} + +// Update the stadium billboard with the current distance or a text message and update the high score +// if it has been beaten. +function updateBillboard(distanceOrMessage) { + var distanceBillboardEntityID = getDistanceBillboardEntityID(); + if (distanceBillboardEntityID) { + Entities.editEntity(distanceBillboardEntityID, { + text: distanceOrMessage + }); + } + + var highScoreBillboardEntityID = getHighScoreBillboardEntityID(); + // If a number was passed in, let's see if it is larger than the current high score + // and update it if so. + if (!isNaN(distanceOrMessage) && highScoreBillboardEntityID) { + var properties = Entities.getEntityProperties(highScoreBillboardEntityID, ["text"]); + var bestDistance = parseInt(properties.text); + if (distanceOrMessage >= bestDistance) { + Entities.editEntity(highScoreBillboardEntityID, { + text: distanceOrMessage, + }); + return true; + } + } + return false; +} + +var FIREWORKS_SHOW_POSITION = { x: 0, y: 0, z: -78.0 }; +var FIREWORK_PER_X_FEET = 100; +var MAX_FIREWORKS = 10; + +Baseball.prototype = { + finished: function() { + return this.state == BASEBALL_STATE.FOUL + || this.state == BASEBALL_STATE.STRIKE + || this.state == BASEBALL_STATE.HIT_LANDED; + }, + update: function(dt) { + this.timeSincePitched += dt; + if (this.state == BASEBALL_STATE.HIT) { + this.timeSinceHit += dt; + var myProperties = Entities.getEntityProperties(this.entityID, ['position', 'velocity']); + var speed = Vec3.length(myProperties.velocity); + this.distanceTravelled = Vec3.distance(this.hitBallAtPosition, myProperties.position) * METERS_TO_FEET; + var wasHighScore = updateBillboard(Math.ceil(this.distanceTravelled)); + if (this.landed || this.timeSinceHit > 10 || speed < 1) { + this.wasHighScore = wasHighScore; + this.ballLanded(); + } + } else if (this.state == BASEBALL_STATE.PITCHING) { + if (this.timeSincePitched > 10) { + print("TIMED OUT WHILE PITCHING"); + this.state = BASEBALL_STATE.STRIKE; + } + } + }, + ballLanded: function() { + this.state = BASEBALL_STATE.HIT_LANDED; + var numberOfFireworks = Math.floor(this.distanceTravelled / FIREWORK_PER_X_FEET); + if (numberOfFireworks > 0) { + numberOfFireworks = Math.min(MAX_FIREWORKS, numberOfFireworks); + playFireworkShow(FIREWORKS_SHOW_POSITION, numberOfFireworks, 2000); + } + print("Ball took " + this.timeSinceHit.toFixed(3) + " seconds to land"); + print("Ball travelled " + this.distanceTravelled + " feet") + }, + collisionCallback: function(entityA, entityB, collision) { + var self = this; + var myProperties = Entities.getEntityProperties(this.entityID, ['position', 'velocity']); + var myPosition = myProperties.position; + var myVelocity = myProperties.velocity; + + // Activate gravity + Entities.editEntity(self.entityID, { + gravity: { x: 0, y: -9.8, z: 0 } + }); + + var name = Entities.getEntityProperties(entityB, ["name"]).name; + if (name == "Bat") { + if (this.state == BASEBALL_STATE.PITCHING) { + print("HIT"); + + var FOUL_MIN_YAW = -135.0; + var FOUL_MAX_YAW = 135.0; + + var yaw = Math.atan2(myVelocity.x, myVelocity.z) * 180 / Math.PI; + var foul = yaw > FOUL_MIN_YAW && yaw < FOUL_MAX_YAW; + + var speedMultiplier = 2; + + if (foul && myVelocity.z > 0) { + var TUNNELED_PITCH_RANGE = 15.0; + var xzDist = Math.sqrt(myVelocity.x * myVelocity.x + myVelocity.z * myVelocity.z); + var pitch = Math.atan2(myVelocity.y, xzDist) * 180 / Math.PI; + print("Pitch: ", pitch); + // If the pitch is going straight out the back and has a pitch in the range TUNNELED_PITCH_RANGE, + // let's assume the ball tunneled through the bat and reverse its direction. + if (Math.abs(pitch) < TUNNELED_PITCH_RANGE) { + print("Reversing hit"); + myVelocity.x *= -1; + myVelocity.y *= -1; + myVelocity.z *= -1; + + yaw = Math.atan2(myVelocity.x, myVelocity.z) * 180 / Math.PI; + foul = yaw > FOUL_MIN_YAW && yaw < FOUL_MAX_YAW; + + speedMultiplier = 3; + } + } + + // Update ball velocity + Entities.editEntity(self.entityID, { + velocity: Vec3.multiply(speedMultiplier, myVelocity) + }); + + // Setup line update interval + setupTrail(self.entityID, myPosition); + + // Setup bat hit sound + playRandomSound(AUDIO.batHit, { + position: myPosition, + volume: 2.0 + }); + + // Setup crowd reaction sound + var speed = Vec3.length(myVelocity); + Script.setTimeout(function() { + playRandomSound((speed < 5.0) ? AUDIO.crowdBoos : AUDIO.crowdCheers, { + position: { x: 0 ,y: 0, z: 0 }, + volume: 1.0 + }); + }, 500); + + if (foul) { + print("FOUL, yaw: ", yaw); + updateBillboard("FOUL"); + this.state = BASEBALL_STATE.FOUL; + playRandomSound(AUDIO.foul, { + position: myPosition, + volume: 2.0 + }); + } else { + print("HIT ", yaw); + this.state = BASEBALL_STATE.HIT; + } + } + } else if (name == "stadium") { + //entityCollisionWithGround(entityB, this.entityID, collision); + this.landed = true; + } else if (name == "backstop") { + if (this.state == BASEBALL_STATE.PITCHING) { + print("STRIKE"); + this.state = BASEBALL_STATE.STRIKE; + updateBillboard("STRIKE"); + playRandomSound(AUDIO.strike, { + position: myPosition, + volume: 2.0 + }); + } + } + } +} + +function entityCollisionWithGround(ground, entity, collision) { + var ZERO_VEC = { x: 0, y: 0, z: 0 }; + var dVelocityMagnitude = Vec3.length(collision.velocityChange); + var position = Entities.getEntityProperties(entity, "position").position; + var particleRadius = 0.3; + var speed = map(dVelocityMagnitude, 0.05, 3, 0.02, 0.09); + var displayTime = 400; + var orientationChange = orientationOf(collision.velocityChange); + + var dustEffect = Entities.addEntity({ + type: "ParticleEffect", + name: "Dust-Puff", + position: position, + color: {red: 195, green: 170, blue: 185}, + lifespan: 3, + lifetime: 2,//displayTime/1000 * 2, //So we can fade particle system out gracefully + emitRate: 5, + emitSpeed: speed, + emitAcceleration: ZERO_VEC, + accelerationSpread: ZERO_VEC, + isEmitting: true, + polarStart: Math.PI/2, + polarFinish: Math.PI/2, + emitOrientation: orientationChange, + radiusSpread: 0.1, + radiusStart: particleRadius, + radiusFinish: particleRadius + particleRadius / 2, + particleRadius: particleRadius, + alpha: 0.45, + alphaFinish: 0.001, + textures: "https://hifi-public.s3.amazonaws.com/alan/Playa/Particles/Particle-Sprite-Gen.png" + }); +} + +Script.scriptEnding.connect(function() { + cleanupTrail(); + Entities.deleteEntity(pitchingMachineID); +}); diff --git a/examples/baseball/utils.js b/examples/baseball/utils.js new file mode 100644 index 0000000000..80f4c62f32 --- /dev/null +++ b/examples/baseball/utils.js @@ -0,0 +1,92 @@ +// +// utils.js +// examples/baseball/ +// +// Created by Ryan Huffman on Nov 9, 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +randomInt = function(low, high) { + return Math.floor(randomFloat(low, high)); +}; + +randomFloat = function(low, high) { + if (high === undefined) { + high = low; + low = 0; + } + return low + Math.random() * (high - low); +}; + +randomColor = function(redMin, redMax, greenMin, greenMax, blueMin, blueMax) { + return { + red: Math.ceil(randomFloat(redMin, redMax)), + green: Math.ceil(randomFloat(greenMin, greenMax)), + blue: Math.ceil(randomFloat(blueMin, blueMax)), + } +}; + +randomVec3 = function(xMin, xMax, yMin, yMax, zMin, zMax) { + return { + x: randomFloat(xMin, xMax), + y: randomFloat(yMin, yMax), + z: randomFloat(zMin, zMax), + } +}; + +getSounds = function(soundURLs) { + var sounds = []; + for (var i = 0; i < soundURLs.length; ++i) { + sounds.push(SoundCache.getSound(soundURLs[i], false)); + } + return sounds; +}; + +playRandomSound = function(sounds, options) { + if (options === undefined) { + options = { + volume: 1.0, + position: MyAvatar.position, + } + } + return Audio.playSound(sounds[randomInt(sounds.length)], options); +} + +shallowCopy = function(obj) { + var copy = {} + for (var key in obj) { + copy[key] = obj[key]; + } + return copy; +} + +findEntity = function(properties, searchRadius) { + var entities = findEntities(properties, searchRadius); + return entities.length > 0 ? entities[0] : null; +} + +// Return all entities with properties `properties` within radius `searchRadius` +findEntities = function(properties, searchRadius) { + var entities = Entities.findEntities(MyAvatar.position, searchRadius); + var matchedEntities = []; + var keys = Object.keys(properties); + for (var i = 0; i < entities.length; ++i) { + var match = true; + var candidateProperties = Entities.getEntityProperties(entities[i], keys); + for (var key in properties) { + if (candidateProperties[key] != properties[key]) { + // This isn't a match, move to next entity + match = false; + break; + } + } + if (match) { + matchedEntities.push(entities[i]); + } + } + + return matchedEntities; +} diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index ebfa248f58..138240f5d6 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -34,9 +34,10 @@ var BUMPER_ON_VALUE = 0.5; // distant manipulation // -var DISTANCE_HOLDING_RADIUS_FACTOR = 5; // multiplied by distance between hand and object +var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did +var MOVE_WITH_HEAD = true; // experimental head-controll of distantly held objects var NO_INTERSECT_COLOR = { red: 10, @@ -200,6 +201,33 @@ function entityIsGrabbedByOther(entityID) { return false; } +function getSpatialOffsetPosition(hand, spatialKey) { + if (hand !== RIGHT_HAND && spatialKey.leftRelativePosition) { + return spatialKey.leftRelativePosition; + } + if (hand === RIGHT_HAND && spatialKey.rightRelativePosition) { + return spatialKey.rightRelativePosition; + } + if (spatialKey.relativePosition) { + return spatialKey.relativePosition; + } + + return Vec3.ZERO; +} + +function getSpatialOffsetRotation(hand, spatialKey) { + if (hand !== RIGHT_HAND && spatialKey.leftRelativeRotation) { + return spatialKey.leftRelativeRotation; + } + if (hand === RIGHT_HAND && spatialKey.rightRelativeRotation) { + return spatialKey.rightRelativeRotation; + } + if (spatialKey.relativeRotation) { + return spatialKey.relativeRotation; + } + + return Quat.IDENTITY; +} function MyController(hand) { this.hand = hand; @@ -223,20 +251,11 @@ function MyController(hand) { this.triggerValue = 0; // rolling average of trigger value this.rawTriggerValue = 0; this.rawBumperValue = 0; - + this.overlayLine = null; - - this.offsetPosition = { - x: 0.0, - y: 0.0, - z: 0.0 - }; - this.offsetRotation = { - x: 0.0, - y: 0.0, - z: 0.0, - w: 1.0 - }; + + this.offsetPosition = Vec3.ZERO; + this.offsetRotation = Quat.IDENTITY; var _this = this; @@ -658,6 +677,13 @@ function MyController(hand) { this.currentObjectTime = now; this.handRelativePreviousPosition = Vec3.subtract(handControllerPosition, MyAvatar.position); this.handPreviousRotation = handRotation; + this.currentCameraOrientation = Camera.orientation; + + // compute a constant based on the initial conditions which we use below to exagerate hand motion onto the held object + this.radiusScalar = Math.log(Vec3.distance(this.currentObjectPosition, handControllerPosition) + 1.0); + if (this.radiusScalar < 1.0) { + this.radiusScalar = 1.0; + } this.actionID = NULL_ACTION_ID; this.actionID = Entities.addAction("spring", this.grabbedEntity, { @@ -689,8 +715,6 @@ function MyController(hand) { this.currentAvatarOrientation = MyAvatar.orientation; this.overlayLineOff(); - - }; this.continueDistanceHolding = function() { @@ -719,8 +743,12 @@ function MyController(hand) { this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); // the action was set up on a previous call. update the targets. - var radius = Math.max(Vec3.distance(this.currentObjectPosition, handControllerPosition) * - DISTANCE_HOLDING_RADIUS_FACTOR, DISTANCE_HOLDING_RADIUS_FACTOR); + var radius = Vec3.distance(this.currentObjectPosition, handControllerPosition) * + this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; + if (radius < 1.0) { + radius = 1.0; + } + // how far did avatar move this timestep? var currentPosition = MyAvatar.position; var avatarDeltaPosition = Vec3.subtract(currentPosition, this.currentAvatarPosition); @@ -751,11 +779,11 @@ function MyController(hand) { var handMoved = Vec3.subtract(handToAvatar, this.handRelativePreviousPosition); this.handRelativePreviousPosition = handToAvatar; - // magnify the hand movement but not the change from avatar movement & rotation + // magnify the hand movement but not the change from avatar movement & rotation handMoved = Vec3.subtract(handMoved, handMovementFromTurning); var superHandMoved = Vec3.multiply(handMoved, radius); - // Move the object by the magnified amount and then by amount from avatar movement & rotation + // Move the object by the magnified amount and then by amount from avatar movement & rotation var newObjectPosition = Vec3.sum(this.currentObjectPosition, superHandMoved); newObjectPosition = Vec3.sum(newObjectPosition, avatarDeltaPosition); newObjectPosition = Vec3.sum(newObjectPosition, objectMovementFromTurning); @@ -777,6 +805,16 @@ function MyController(hand) { Entities.callEntityMethod(this.grabbedEntity, "continueDistantGrab"); + // mix in head motion + if (MOVE_WITH_HEAD) { + var objDistance = Vec3.length(objectToAvatar); + var before = Vec3.multiplyQbyV(this.currentCameraOrientation, { x: 0.0, y: 0.0, z: objDistance }); + var after = Vec3.multiplyQbyV(Camera.orientation, { x: 0.0, y: 0.0, z: objDistance }); + var change = Vec3.subtract(before, after); + this.currentCameraOrientation = Camera.orientation; + this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change); + } + Entities.updateAction(this.grabbedEntity, this.actionID, { targetPosition: this.currentObjectPosition, linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, @@ -815,12 +853,8 @@ function MyController(hand) { if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) { // if an object is "equipped" and has a spatialKey, use it. - if (grabbableData.spatialKey.relativePosition) { - this.offsetPosition = grabbableData.spatialKey.relativePosition; - } - if (grabbableData.spatialKey.relativeRotation) { - this.offsetRotation = grabbableData.spatialKey.relativeRotation; - } + this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); + this.offsetRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); } else { var objectRotation = grabbedProperties.rotation; this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); @@ -946,23 +980,8 @@ function MyController(hand) { var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); // use a spring to pull the object to where it will be when equipped - var relativeRotation = { - x: 0.0, - y: 0.0, - z: 0.0, - w: 1.0 - }; - var relativePosition = { - x: 0.0, - y: 0.0, - z: 0.0 - }; - if (grabbableData.spatialKey.relativePosition) { - relativePosition = grabbableData.spatialKey.relativePosition; - } - if (grabbableData.spatialKey.relativeRotation) { - relativeRotation = grabbableData.spatialKey.relativeRotation; - } + var relativeRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); + var relativePosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); var handRotation = this.getHandRotation(); var handPosition = this.getHandPosition(); var targetRotation = Quat.multiply(handRotation, relativeRotation); diff --git a/examples/controllers/leap/leapHands.js b/examples/controllers/leap/leapHands.js index 7835df7452..a77fa44e1c 100644 --- a/examples/controllers/leap/leapHands.js +++ b/examples/controllers/leap/leapHands.js @@ -20,6 +20,9 @@ var leapHands = (function () { hasHandAndWristJoints, handToWristOffset = [], // For avatars without a wrist joint we control an estimate of a proper hand joint position HAND_OFFSET = 0.4, // Relative distance of wrist to hand versus wrist to index finger knuckle + handAnimationStateHandlers, + handAnimationStateFunctions, + handAnimationStateProperties, hands, wrists, NUM_HANDS = 2, // 0 = left; 1 = right @@ -37,7 +40,14 @@ var leapHands = (function () { avatarScale, avatarFaceModelURL, avatarSkeletonModelURL, - settingsTimer; + settingsTimer, + HMD_CAMERA_TO_AVATAR_ROTATION = [ + Quat.angleAxis(180.0, { x: 0, y: 0, z: 1 }), + Quat.angleAxis(-180.0, { x: 0, y: 0, z: 1 }) + ], + DESKTOP_CAMERA_TO_AVATAR_ROTATION = + Quat.multiply(Quat.angleAxis(180.0, { x: 0, y: 1, z: 0 }), Quat.angleAxis(90.0, { x: 0, y: 0, z: 1 })), + LEAP_THUMB_ROOT_ADJUST = [Quat.fromPitchYawRollDegrees(0, 0, 20), Quat.fromPitchYawRollDegrees(0, 0, -20)]; function printSkeletonJointNames() { var jointNames, @@ -125,6 +135,26 @@ var leapHands = (function () { */ } + function animateLeftHand() { + var ROTATION_AND_POSITION = 0; + + return { + leftHandType: ROTATION_AND_POSITION, + leftHandPosition: hands[0].position, + leftHandRotation: hands[0].rotation + }; + } + + function animateRightHand() { + var ROTATION_AND_POSITION = 0; + + return { + rightHandType: ROTATION_AND_POSITION, + rightHandPosition: hands[1].position, + rightHandRotation: hands[1].rotation + }; + } + function finishCalibration() { var avatarPosition, handPosition, @@ -166,10 +196,8 @@ var leapHands = (function () { MyAvatar.clearJointData("LeftHand"); MyAvatar.clearJointData("LeftForeArm"); - MyAvatar.clearJointData("LeftArm"); MyAvatar.clearJointData("RightHand"); MyAvatar.clearJointData("RightForeArm"); - MyAvatar.clearJointData("RightArm"); calibrationStatus = CALIBRATED; print("Leap Motion: Calibrated"); @@ -193,12 +221,10 @@ var leapHands = (function () { } // Set avatar arms vertical, forearms horizontal, as "zero" position for calibration - MyAvatar.setJointRotation("LeftArm", Quat.fromPitchYawRollDegrees(90.0, 0.0, 0.0)); - MyAvatar.setJointRotation("LeftForeArm", Quat.fromPitchYawRollDegrees(0.0, 90.0, 90.0)); - MyAvatar.setJointRotation("LeftHand", Quat.fromPitchYawRollRadians(0.0, 0.0, 0.0)); - MyAvatar.setJointRotation("RightArm", Quat.fromPitchYawRollDegrees(90.0, 0.0, 0.0)); - MyAvatar.setJointRotation("RightForeArm", Quat.fromPitchYawRollDegrees(0.0, -90.0, -90.0)); - MyAvatar.setJointRotation("RightHand", Quat.fromPitchYawRollRadians(0.0, 0.0, 0.0)); + MyAvatar.setJointRotation("LeftForeArm", Quat.fromPitchYawRollDegrees(0.0, 0.0, 90.0)); + MyAvatar.setJointRotation("LeftHand", Quat.fromPitchYawRollDegrees(0.0, 90.0, 0.0)); + MyAvatar.setJointRotation("RightForeArm", Quat.fromPitchYawRollDegrees(0.0, 0.0, -90.0)); + MyAvatar.setJointRotation("RightHand", Quat.fromPitchYawRollDegrees(0.0, -90.0, 0.0)); // Wait for arms to assume their positions before calculating Script.setTimeout(finishCalibration, CALIBRATION_TIME); @@ -319,6 +345,13 @@ var leapHands = (function () { ] ]; + handAnimationStateHandlers = [null, null]; + handAnimationStateFunctions = [animateLeftHand, animateRightHand]; + handAnimationStateProperties = [ + ["leftHandType", "leftHandPosition", "leftHandRotation"], + ["rightHandType", "rightHandPosition", "rightHandPosition"] + ]; + setIsOnHMD(); settingsTimer = Script.setInterval(checkSettings, 2000); @@ -348,6 +381,12 @@ var leapHands = (function () { return; } + // Hand animation handlers ... + if (handAnimationStateHandlers[h] === null) { + handAnimationStateHandlers[h] = MyAvatar.addAnimationStateHandler(handAnimationStateFunctions[h], + handAnimationStateProperties[h]); + } + // Hand position ... handOffset = hands[h].controller.getAbsTranslation(); handRotation = hands[h].controller.getAbsRotation(); @@ -362,37 +401,41 @@ var leapHands = (function () { // Hand offset in camera coordinates ... handOffset = { - x: hands[h].zeroPosition.x - handOffset.x, - y: hands[h].zeroPosition.y - handOffset.z, - z: hands[h].zeroPosition.z + handOffset.y + x: -handOffset.x, + y: -handOffset.z, + z: -handOffset.y - hands[h].zeroPosition.z }; - handOffset.z = -handOffset.z; // Hand offset in world coordinates ... cameraOrientation = Camera.getOrientation(); handOffset = Vec3.sum(Camera.getPosition(), Vec3.multiplyQbyV(cameraOrientation, handOffset)); - // Hand offset in avatar coordinates ... + // Hand offset in avatar coordinates ... inverseAvatarOrientation = Quat.inverse(MyAvatar.orientation); handOffset = Vec3.subtract(handOffset, MyAvatar.position); handOffset = Vec3.multiplyQbyV(inverseAvatarOrientation, handOffset); handOffset.z = -handOffset.z; handOffset.x = -handOffset.x; + // Hand rotation in camera coordinates ... handRotation = { - x: -handRotation.x, + x: -handRotation.y, y: -handRotation.z, - z: -handRotation.y, + z: -handRotation.x, w: handRotation.w }; // Hand rotation in avatar coordinates ... - handRotation = Quat.multiply(Quat.angleAxis(180.0, { x: 0, y: 1, z: 0 }), handRotation); - cameraOrientation.x = -cameraOrientation.x; - cameraOrientation.z = -cameraOrientation.z; - handRotation = Quat.multiply(cameraOrientation, handRotation); - handRotation = Quat.multiply(inverseAvatarOrientation, handRotation); + handRotation = Quat.multiply(HMD_CAMERA_TO_AVATAR_ROTATION[h], handRotation); + cameraOrientation = { + x: cameraOrientation.z, + y: cameraOrientation.y, + z: cameraOrientation.x, + w: cameraOrientation.w + }; + cameraOrientation = Quat.multiply(cameraOrientation, Quat.inverse(MyAvatar.orientation)); + handRotation = Quat.multiply(handRotation, cameraOrientation); // Works!!! } else { @@ -411,18 +454,19 @@ var leapHands = (function () { // Hand rotation in camera coordinates ... handRotation = { - x: -handRotation.x, - y: -handRotation.z, - z: -handRotation.y, + x: handRotation.z, + y: handRotation.y, + z: handRotation.x, w: handRotation.w }; // Hand rotation in avatar coordinates ... - handRotation = Quat.multiply(Quat.angleAxis(90.0, { x: 1, y: 0, z: 0 }), handRotation); + handRotation = Quat.multiply(DESKTOP_CAMERA_TO_AVATAR_ROTATION, handRotation); } - // Set hand position and orientation ... - MyAvatar.setJointModelPositionAndOrientation(hands[h].jointName, handOffset, handRotation, true); + // Set hand position and orientation for animation state handler ... + hands[h].position = handOffset; + hands[h].rotation = handRotation; // Set finger joints ... for (i = 0; i < NUM_FINGERS; i += 1) { @@ -436,6 +480,10 @@ var leapHands = (function () { z: side * -locRotation.x, w: locRotation.w }; + if (j === 0) { + // Adjust avatar thumb root joint rotation to make avatar hands look better + locRotation = Quat.multiply(LEAP_THUMB_ROOT_ADJUST[h], locRotation); + } } else { locRotation = { x: -locRotation.x, @@ -458,14 +506,9 @@ var leapHands = (function () { hands[h].inactiveCount += 1; if (hands[h].inactiveCount === MAX_HAND_INACTIVE_COUNT) { - if (h === 0) { - MyAvatar.clearJointData("LeftHand"); - MyAvatar.clearJointData("LeftForeArm"); - MyAvatar.clearJointData("LeftArm"); - } else { - MyAvatar.clearJointData("RightHand"); - MyAvatar.clearJointData("RightForeArm"); - MyAvatar.clearJointData("RightArm"); + if (handAnimationStateHandlers[h] !== null) { + MyAvatar.removeAnimationStateHandler(handAnimationStateHandlers[h]); + handAnimationStateHandlers[h] = null; } } } @@ -483,6 +526,9 @@ var leapHands = (function () { for (h = 0; h < NUM_HANDS; h += 1) { Controller.releaseInputController(hands[h].controller); Controller.releaseInputController(wrists[h].controller); + if (handAnimationStateHandlers[h] !== null) { + MyAvatar.removeAnimationStateHandler(handAnimationStateHandlers[h]); + } for (i = 0; i < NUM_FINGERS; i += 1) { for (j = 0; j < NUM_FINGER_JOINTS; j += 1) { if (fingers[h][i][j].controller !== null) { diff --git a/examples/data_visualization/earthquakes_live.js b/examples/data_visualization/earthquakes_live.js new file mode 100644 index 0000000000..8594f827a0 --- /dev/null +++ b/examples/data_visualization/earthquakes_live.js @@ -0,0 +1,226 @@ +// earthquakes_live.js +// +// exploratory implementation in prep for abstract latlong to earth graphing tool for VR +// shows all of the quakes in the past 24 hours reported by the USGS +// +// created by james b. pollack @imgntn on 12/5/2015 +// Copyright 2015 High Fidelity, Inc. +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// working notes: maybe try doing markers as boxes,rotated to the sphere normal, and with the height representing some value + +Script.include('../libraries/promise.js'); +var Promise = loadPromise(); + +Script.include('../libraries/tinyColor.js'); +var tinyColor = loadTinyColor(); + +//you could make it the size of the actual earth. +var EARTH_SPHERE_RADIUS = 6371; +var EARTH_SPHERE_RADIUS = 2; + +var EARTH_CENTER_POSITION = Vec3.sum(Vec3.sum(MyAvatar.position, { + x: 0, + y: 0.5, + z: 0 +}), Vec3.multiply(EARTH_SPHERE_RADIUS, Quat.getFront(Camera.getOrientation()))); + +var EARTH_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/james/earthquakes_live/models/earth-noclouds.fbx'; + +var SHOULD_SPIN=false; +var POLL_FOR_CHANGES = true; +//USGS updates the data every five minutes +var CHECK_QUAKE_FREQUENCY = 5 * 60 * 1000; + +var QUAKE_MARKER_DIMENSIONS = { + x: 0.01, + y: 0.01, + z: 0.01 +}; + +function createEarth() { + var earthProperties = { + name: 'Earth', + type: 'Model', + modelURL: EARTH_MODEL_URL, + position: EARTH_CENTER_POSITION, + dimensions: { + x: EARTH_SPHERE_RADIUS, + y: EARTH_SPHERE_RADIUS, + z: EARTH_SPHERE_RADIUS + }, + rotation: Quat.fromPitchYawRollDegrees(0, 90, 0), + // collisionsWillMove: true, + //if you have a shapetype it blocks the smaller markers + // shapeType:'sphere' + // userData: JSON.stringify({ + // grabbableKey: { + // grabbable: false + // } + // }) + } + + return Entities.addEntity(earthProperties) +} + +function latLongToVector3(lat, lon, radius, height) { + var phi = (lat) * Math.PI / 180; + var theta = (lon - 180) * Math.PI / 180; + + var x = -(radius + height) * Math.cos(phi) * Math.cos(theta); + var y = (radius + height) * Math.sin(phi); + var z = (radius + height) * Math.cos(phi) * Math.sin(theta); + + return { + x: x, + y: y, + z: z + }; +} + +function getQuakePosition(earthquake) { + var longitude = earthquake.geometry.coordinates[0]; + var latitude = earthquake.geometry.coordinates[1]; + var depth = earthquake.geometry.coordinates[2]; + + var latlng = latLongToVector3(latitude, longitude, EARTH_SPHERE_RADIUS / 2, 0); + + var position = EARTH_CENTER_POSITION; + var finalPosition = Vec3.sum(position, latlng); + + //print('finalpos::' + JSON.stringify(finalPosition)) + return finalPosition +} + +var QUAKE_URL = 'http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson' + +function get(url) { + print('getting' + url) + // Return a new promise. + return new Promise(function(resolve, reject) { + // Do the usual XHR stuff + var req = new XMLHttpRequest(); + req.open('GET', url); + req.onreadystatechange = function() { + print('req status:: ' + JSON.stringify(req.status)) + + if (req.readyState == 4 && req.status == 200) { + var myArr = JSON.parse(req.responseText); + resolve(myArr); + } + }; + + req.send(); + }); +} + +function createQuakeMarker(earthquake) { + var markerProperties = { + name: earthquake.properties.place, + type: 'Sphere', + parentID:earth, + dimensions: QUAKE_MARKER_DIMENSIONS, + position: getQuakePosition(earthquake), + ignoreForCollisions:true, + lifetime: 6000, + color: getQuakeMarkerColor(earthquake) + } + + // print('marker properties::' + JSON.stringify(markerProperties)) + return Entities.addEntity(markerProperties); +} + +function getQuakeMarkerColor(earthquake) { + var color = {}; + var magnitude = earthquake.properties.mag; + //realistic but will never get full red coloring and will probably be pretty dull for most. must experiment + var sValue = scale(magnitude, 0, 10, 0, 100); + var HSL_string = "hsl(0, " + sValue + "%, 50%)" + var color = tinyColor(HSL_string); + var finalColor = { + red: color._r, + green: color._g, + blue: color._b + } + + return finalColor +} + +function scale(value, min1, max1, min2, max2) { + return min2 + (max2 - min2) * ((value - min1) / (max1 - min1)); +} + +function processQuakes(earthquakes) { + print('quakers length' + earthquakes.length) + earthquakes.forEach(function(quake) { + // print('PROCESSING A QUAKE') + var marker = createQuakeMarker(quake); + markers.push(marker); + }) + print('markers length:' + markers.length) +} + +var quakes; +var markers = []; + +var earth = createEarth(); + +function getThenProcessQuakes() { + get(QUAKE_URL).then(function(response) { + print('got it::' + response.features.length) + quakes = response.features; + processQuakes(quakes); + //print("Success!" + JSON.stringify(response)); + }, function(error) { + print('error getting quakes') + }); +} + +function cleanupMarkers() { + print('CLEANING UP MARKERS') + while (markers.length > 0) { + Entities.deleteEntity(markers.pop()); + } +} + +function cleanupEarth() { + Entities.deleteEntity(earth); + Script.update.disconnect(spinEarth); +} + +function cleanupInterval() { + if (pollingInterval !== null) { + Script.clearInterval(pollingInterval) + } +} + +Script.scriptEnding.connect(cleanupMarkers); +Script.scriptEnding.connect(cleanupEarth); +Script.scriptEnding.connect(cleanupInterval); + +getThenProcessQuakes(); + +var pollingInterval = null; + +if (POLL_FOR_CHANGES === true) { + pollingInterval = Script.setInterval(function() { + cleanupMarkers(); + getThenProcessQuakes() + }, CHECK_QUAKE_FREQUENCY) +} + + +function spinEarth(){ +Entities.editEntity(earth,{ + angularVelocity:{ + x:0, + y:0.25, + z:0 + } +}) +} + +if(SHOULD_SPIN===true){ + Script.update.connect(spinEarth); +} diff --git a/examples/edit.js b/examples/edit.js index 74551384c9..5d5d642f47 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -392,6 +392,11 @@ var toolBar = (function() { url, file; + if (!event.isLeftButton) { + // if another mouse button than left is pressed ignore it + return false; + } + clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y diff --git a/examples/example/entities/butterflies.js b/examples/example/entities/butterflies.js index 3a78a2fc1c..9d53fc0ebf 100644 --- a/examples/example/entities/butterflies.js +++ b/examples/example/entities/butterflies.js @@ -32,7 +32,7 @@ function randVector(a, b) { var startTimeInSeconds = new Date().getTime() / 1000; -var NATURAL_SIZE_OF_BUTTERFLY = { x: 1.0, y: 0.4, z: 0.2 }; +var NATURAL_SIZE_OF_BUTTERFLY = { x:0.5, y: 0.2, z: 0.1 }; var lifeTime = 3600; // One hour lifespan var range = 7.0; // Over what distance in meters do you want the flock to fly around @@ -65,8 +65,8 @@ function addButterfly() { var color = { red: 100, green: 100, blue: 100 }; var size = 0; - var MINSIZE = 0.06; - var RANGESIZE = 0.2; + var MINSIZE = 0.01; + var RANGESIZE = 0.05; var maxSize = MINSIZE + RANGESIZE; size = MINSIZE + Math.random() * RANGESIZE; @@ -74,7 +74,7 @@ function addButterfly() { var dimensions = Vec3.multiply(NATURAL_SIZE_OF_BUTTERFLY, (size / maxSize)); var GRAVITY = -0.2; - var newFrameRate = 20 + Math.random() * 30; + var newFrameRate = 29 + Math.random() * 30; var properties = { type: "Model", lifetime: lifeTime, @@ -86,17 +86,13 @@ function addButterfly() { dimensions: dimensions, color: color, animation: { - url: "http://public.highfidelity.io/models/content/butterfly/butterfly.fbx", - firstFrame: 0, + url: "http://hifi-content.s3.amazonaws.com/james/butterfly/butterfly.fbx", fps: newFrameRate, - currentFrame: 0, - hold: false, - lastFrame: 10000, loop: true, running: true, startAutomatically:false }, - modelURL: "http://public.highfidelity.io/models/content/butterfly/butterfly.fbx" + modelURL: "http://hifi-content.s3.amazonaws.com/james/butterfly/butterfly.fbx" }; butterflies.push(Entities.addEntity(properties)); } diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index bc6a6920d1..186b2ee828 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -223,7 +223,10 @@ var elDimensionsZ = document.getElementById("property-dim-z"); var elResetToNaturalDimensions = document.getElementById("reset-to-natural-dimensions"); var elRescaleDimensionsPct = document.getElementById("dimension-rescale-pct"); - var elRescaleDimensionsButton = document.getElementById("dimension-rescale-button"); + var elRescaleDimensionsButton = document.getElementById("dimension-rescale-button"); + + var elParentID = document.getElementById("property-parent-id"); + var elParentJointIndex = document.getElementById("property-parent-joint-index"); var elRegistrationX = document.getElementById("property-reg-x"); var elRegistrationY = document.getElementById("property-reg-y"); @@ -453,6 +456,9 @@ elDimensionsY.value = properties.dimensions.y.toFixed(2); elDimensionsZ.value = properties.dimensions.z.toFixed(2); + elParentID.value = properties.parentID; + elParentJointIndex.value = properties.parentJointIndex; + elRegistrationX.value = properties.registrationPoint.x.toFixed(2); elRegistrationY.value = properties.registrationPoint.y.toFixed(2); elRegistrationZ.value = properties.registrationPoint.z.toFixed(2); @@ -666,6 +672,9 @@ elDimensionsY.addEventListener('change', dimensionsChangeFunction); elDimensionsZ.addEventListener('change', dimensionsChangeFunction); + elParentID.addEventListener('change', createEmitTextPropertyUpdateFunction('parentID')); + elParentJointIndex.addEventListener('change', createEmitNumberPropertyUpdateFunction('parentJointIndex')); + var registrationChangeFunction = createEmitVec3PropertyUpdateFunction( 'registrationPoint', elRegistrationX, elRegistrationY, elRegistrationZ); elRegistrationX.addEventListener('change', registrationChangeFunction); @@ -1055,6 +1064,19 @@ +
+ ParentID +
+ +
+
+
+ ParentJointIndex +
+ +
+
+
Registration
diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index 61aa5b0394..6edbe6844b 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -2340,6 +2340,11 @@ SelectionDisplay = (function () { that.mousePressEvent = function(event) { + if (!event.isLeftButton) { + // if another mouse button than left is pressed ignore it + return false; + } + var somethingClicked = false; var pickRay = Camera.computePickRay(event.x, event.y); diff --git a/examples/libraries/tinyColor.js b/examples/libraries/tinyColor.js new file mode 100644 index 0000000000..18aa462010 --- /dev/null +++ b/examples/libraries/tinyColor.js @@ -0,0 +1,1155 @@ +// TinyColor v1.3.0 +// https://github.com/bgrins/TinyColor +// Brian Grinstead, MIT License + + + +var trimLeft = /^\s+/, + trimRight = /\s+$/, + tinyCounter = 0, + math = Math, + mathRound = math.round, + mathMin = math.min, + mathMax = math.max, + mathRandom = math.random; + +function tinycolor (color, opts) { + + color = (color) ? color : ''; + opts = opts || { }; + + // If input is already a tinycolor, return itself + if (color instanceof tinycolor) { + return color; + } + // If we are called as a function, call using new instead + if (!(this instanceof tinycolor)) { + return new tinycolor(color, opts); + } + + var rgb = inputToRGB(color); + this._originalInput = color, + this._r = rgb.r, + this._g = rgb.g, + this._b = rgb.b, + this._a = rgb.a, + this._roundA = mathRound(100*this._a) / 100, + this._format = opts.format || rgb.format; + this._gradientType = opts.gradientType; + + // Don't let the range of [0,255] come back in [0,1]. + // Potentially lose a little bit of precision here, but will fix issues where + // .5 gets interpreted as half of the total, instead of half of 1 + // If it was supposed to be 128, this was already taken care of by `inputToRgb` + if (this._r < 1) { this._r = mathRound(this._r); } + if (this._g < 1) { this._g = mathRound(this._g); } + if (this._b < 1) { this._b = mathRound(this._b); } + + this._ok = rgb.ok; + this._tc_id = tinyCounter++; +} + +tinycolor.prototype = { + isDark: function() { + return this.getBrightness() < 128; + }, + isLight: function() { + return !this.isDark(); + }, + isValid: function() { + return this._ok; + }, + getOriginalInput: function() { + return this._originalInput; + }, + getFormat: function() { + return this._format; + }, + getAlpha: function() { + return this._a; + }, + getBrightness: function() { + //http://www.w3.org/TR/AERT#color-contrast + var rgb = this.toRgb(); + return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000; + }, + getLuminance: function() { + //http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef + var rgb = this.toRgb(); + var RsRGB, GsRGB, BsRGB, R, G, B; + RsRGB = rgb.r/255; + GsRGB = rgb.g/255; + BsRGB = rgb.b/255; + + if (RsRGB <= 0.03928) {R = RsRGB / 12.92;} else {R = Math.pow(((RsRGB + 0.055) / 1.055), 2.4);} + if (GsRGB <= 0.03928) {G = GsRGB / 12.92;} else {G = Math.pow(((GsRGB + 0.055) / 1.055), 2.4);} + if (BsRGB <= 0.03928) {B = BsRGB / 12.92;} else {B = Math.pow(((BsRGB + 0.055) / 1.055), 2.4);} + return (0.2126 * R) + (0.7152 * G) + (0.0722 * B); + }, + setAlpha: function(value) { + this._a = boundAlpha(value); + this._roundA = mathRound(100*this._a) / 100; + return this; + }, + toHsv: function() { + var hsv = rgbToHsv(this._r, this._g, this._b); + return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this._a }; + }, + toHsvString: function() { + var hsv = rgbToHsv(this._r, this._g, this._b); + var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100); + return (this._a == 1) ? + "hsv(" + h + ", " + s + "%, " + v + "%)" : + "hsva(" + h + ", " + s + "%, " + v + "%, "+ this._roundA + ")"; + }, + toHsl: function() { + var hsl = rgbToHsl(this._r, this._g, this._b); + return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this._a }; + }, + toHslString: function() { + var hsl = rgbToHsl(this._r, this._g, this._b); + var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100); + return (this._a == 1) ? + "hsl(" + h + ", " + s + "%, " + l + "%)" : + "hsla(" + h + ", " + s + "%, " + l + "%, "+ this._roundA + ")"; + }, + toHex: function(allow3Char) { + return rgbToHex(this._r, this._g, this._b, allow3Char); + }, + toHexString: function(allow3Char) { + return '#' + this.toHex(allow3Char); + }, + toHex8: function() { + return rgbaToHex(this._r, this._g, this._b, this._a); + }, + toHex8String: function() { + return '#' + this.toHex8(); + }, + toRgb: function() { + return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a }; + }, + toRgbString: function() { + return (this._a == 1) ? + "rgb(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ")" : + "rgba(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ", " + this._roundA + ")"; + }, + toPercentageRgb: function() { + return { r: mathRound(bound01(this._r, 255) * 100) + "%", g: mathRound(bound01(this._g, 255) * 100) + "%", b: mathRound(bound01(this._b, 255) * 100) + "%", a: this._a }; + }, + toPercentageRgbString: function() { + return (this._a == 1) ? + "rgb(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%)" : + "rgba(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%, " + this._roundA + ")"; + }, + toName: function() { + if (this._a === 0) { + return "transparent"; + } + + if (this._a < 1) { + return false; + } + + return hexNames[rgbToHex(this._r, this._g, this._b, true)] || false; + }, + toFilter: function(secondColor) { + var hex8String = '#' + rgbaToHex(this._r, this._g, this._b, this._a); + var secondHex8String = hex8String; + var gradientType = this._gradientType ? "GradientType = 1, " : ""; + + if (secondColor) { + var s = tinycolor(secondColor); + secondHex8String = s.toHex8String(); + } + + return "progid:DXImageTransform.Microsoft.gradient("+gradientType+"startColorstr="+hex8String+",endColorstr="+secondHex8String+")"; + }, + toString: function(format) { + var formatSet = !!format; + format = format || this._format; + + var formattedString = false; + var hasAlpha = this._a < 1 && this._a >= 0; + var needsAlphaFormat = !formatSet && hasAlpha && (format === "hex" || format === "hex6" || format === "hex3" || format === "name"); + + if (needsAlphaFormat) { + // Special case for "transparent", all other non-alpha formats + // will return rgba when there is transparency. + if (format === "name" && this._a === 0) { + return this.toName(); + } + return this.toRgbString(); + } + if (format === "rgb") { + formattedString = this.toRgbString(); + } + if (format === "prgb") { + formattedString = this.toPercentageRgbString(); + } + if (format === "hex" || format === "hex6") { + formattedString = this.toHexString(); + } + if (format === "hex3") { + formattedString = this.toHexString(true); + } + if (format === "hex8") { + formattedString = this.toHex8String(); + } + if (format === "name") { + formattedString = this.toName(); + } + if (format === "hsl") { + formattedString = this.toHslString(); + } + if (format === "hsv") { + formattedString = this.toHsvString(); + } + + return formattedString || this.toHexString(); + }, + clone: function() { + return tinycolor(this.toString()); + }, + + _applyModification: function(fn, args) { + var color = fn.apply(null, [this].concat([].slice.call(args))); + this._r = color._r; + this._g = color._g; + this._b = color._b; + this.setAlpha(color._a); + return this; + }, + lighten: function() { + return this._applyModification(lighten, arguments); + }, + brighten: function() { + return this._applyModification(brighten, arguments); + }, + darken: function() { + return this._applyModification(darken, arguments); + }, + desaturate: function() { + return this._applyModification(desaturate, arguments); + }, + saturate: function() { + return this._applyModification(saturate, arguments); + }, + greyscale: function() { + return this._applyModification(greyscale, arguments); + }, + spin: function() { + return this._applyModification(spin, arguments); + }, + + _applyCombination: function(fn, args) { + return fn.apply(null, [this].concat([].slice.call(args))); + }, + analogous: function() { + return this._applyCombination(analogous, arguments); + }, + complement: function() { + return this._applyCombination(complement, arguments); + }, + monochromatic: function() { + return this._applyCombination(monochromatic, arguments); + }, + splitcomplement: function() { + return this._applyCombination(splitcomplement, arguments); + }, + triad: function() { + return this._applyCombination(triad, arguments); + }, + tetrad: function() { + return this._applyCombination(tetrad, arguments); + } +}; + +// If input is an object, force 1 into "1.0" to handle ratios properly +// String input requires "1.0" as input, so 1 will be treated as 1 +tinycolor.fromRatio = function(color, opts) { + if (typeof color == "object") { + var newColor = {}; + for (var i in color) { + if (color.hasOwnProperty(i)) { + if (i === "a") { + newColor[i] = color[i]; + } + else { + newColor[i] = convertToPercentage(color[i]); + } + } + } + color = newColor; + } + + return tinycolor(color, opts); +}; + +// Given a string or object, convert that input to RGB +// Possible string inputs: +// +// "red" +// "#f00" or "f00" +// "#ff0000" or "ff0000" +// "#ff000000" or "ff000000" +// "rgb 255 0 0" or "rgb (255, 0, 0)" +// "rgb 1.0 0 0" or "rgb (1, 0, 0)" +// "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1" +// "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1" +// "hsl(0, 100%, 50%)" or "hsl 0 100% 50%" +// "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1" +// "hsv(0, 100%, 100%)" or "hsv 0 100% 100%" +// +function inputToRGB(color) { + + var rgb = { r: 0, g: 0, b: 0 }; + var a = 1; + var ok = false; + var format = false; + + if (typeof color == "string") { + color = stringInputToObject(color); + } + + if (typeof color == "object") { + if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) { + rgb = rgbToRgb(color.r, color.g, color.b); + ok = true; + format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb"; + } + else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) { + color.s = convertToPercentage(color.s); + color.v = convertToPercentage(color.v); + rgb = hsvToRgb(color.h, color.s, color.v); + ok = true; + format = "hsv"; + } + else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) { + color.s = convertToPercentage(color.s); + color.l = convertToPercentage(color.l); + rgb = hslToRgb(color.h, color.s, color.l); + ok = true; + format = "hsl"; + } + + if (color.hasOwnProperty("a")) { + a = color.a; + } + } + + a = boundAlpha(a); + + return { + ok: ok, + format: color.format || format, + r: mathMin(255, mathMax(rgb.r, 0)), + g: mathMin(255, mathMax(rgb.g, 0)), + b: mathMin(255, mathMax(rgb.b, 0)), + a: a + }; +} + + +// Conversion Functions +// -------------------- + +// `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from: +// + +// `rgbToRgb` +// Handle bounds / percentage checking to conform to CSS color spec +// +// *Assumes:* r, g, b in [0, 255] or [0, 1] +// *Returns:* { r, g, b } in [0, 255] +function rgbToRgb(r, g, b){ + return { + r: bound01(r, 255) * 255, + g: bound01(g, 255) * 255, + b: bound01(b, 255) * 255 + }; +} + +// `rgbToHsl` +// Converts an RGB color value to HSL. +// *Assumes:* r, g, and b are contained in [0, 255] or [0, 1] +// *Returns:* { h, s, l } in [0,1] +function rgbToHsl(r, g, b) { + + r = bound01(r, 255); + g = bound01(g, 255); + b = bound01(b, 255); + + var max = mathMax(r, g, b), min = mathMin(r, g, b); + var h, s, l = (max + min) / 2; + + if(max == min) { + h = s = 0; // achromatic + } + else { + var d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch(max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + + h /= 6; + } + + return { h: h, s: s, l: l }; +} + +// `hslToRgb` +// Converts an HSL color value to RGB. +// *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100] +// *Returns:* { r, g, b } in the set [0, 255] +function hslToRgb(h, s, l) { + var r, g, b; + + h = bound01(h, 360); + s = bound01(s, 100); + l = bound01(l, 100); + + function hue2rgb(p, q, t) { + if(t < 0) t += 1; + if(t > 1) t -= 1; + if(t < 1/6) return p + (q - p) * 6 * t; + if(t < 1/2) return q; + if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + } + + if(s === 0) { + r = g = b = l; // achromatic + } + else { + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + + return { r: r * 255, g: g * 255, b: b * 255 }; +} + +// `rgbToHsv` +// Converts an RGB color value to HSV +// *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1] +// *Returns:* { h, s, v } in [0,1] +function rgbToHsv(r, g, b) { + + r = bound01(r, 255); + g = bound01(g, 255); + b = bound01(b, 255); + + var max = mathMax(r, g, b), min = mathMin(r, g, b); + var h, s, v = max; + + var d = max - min; + s = max === 0 ? 0 : d / max; + + if(max == min) { + h = 0; // achromatic + } + else { + switch(max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + } + return { h: h, s: s, v: v }; +} + +// `hsvToRgb` +// Converts an HSV color value to RGB. +// *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100] +// *Returns:* { r, g, b } in the set [0, 255] + function hsvToRgb(h, s, v) { + + h = bound01(h, 360) * 6; + s = bound01(s, 100); + v = bound01(v, 100); + + var i = math.floor(h), + f = h - i, + p = v * (1 - s), + q = v * (1 - f * s), + t = v * (1 - (1 - f) * s), + mod = i % 6, + r = [v, q, p, p, t, v][mod], + g = [t, v, v, q, p, p][mod], + b = [p, p, t, v, v, q][mod]; + + return { r: r * 255, g: g * 255, b: b * 255 }; +} + +// `rgbToHex` +// Converts an RGB color to hex +// Assumes r, g, and b are contained in the set [0, 255] +// Returns a 3 or 6 character hex +function rgbToHex(r, g, b, allow3Char) { + + var hex = [ + pad2(mathRound(r).toString(16)), + pad2(mathRound(g).toString(16)), + pad2(mathRound(b).toString(16)) + ]; + + // Return a 3 character hex if possible + if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) { + return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0); + } + + return hex.join(""); +} + +// `rgbaToHex` +// Converts an RGBA color plus alpha transparency to hex +// Assumes r, g, b and a are contained in the set [0, 255] +// Returns an 8 character hex +function rgbaToHex(r, g, b, a) { + + var hex = [ + pad2(convertDecimalToHex(a)), + pad2(mathRound(r).toString(16)), + pad2(mathRound(g).toString(16)), + pad2(mathRound(b).toString(16)) + ]; + + return hex.join(""); +} + +// `equals` +// Can be called with any tinycolor input +tinycolor.equals = function (color1, color2) { + if (!color1 || !color2) { return false; } + return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString(); +}; + +tinycolor.random = function() { + return tinycolor.fromRatio({ + r: mathRandom(), + g: mathRandom(), + b: mathRandom() + }); +}; + + +// Modification Functions +// ---------------------- +// Thanks to less.js for some of the basics here +// + +function desaturate(color, amount) { + amount = (amount === 0) ? 0 : (amount || 10); + var hsl = tinycolor(color).toHsl(); + hsl.s -= amount / 100; + hsl.s = clamp01(hsl.s); + return tinycolor(hsl); +} + +function saturate(color, amount) { + amount = (amount === 0) ? 0 : (amount || 10); + var hsl = tinycolor(color).toHsl(); + hsl.s += amount / 100; + hsl.s = clamp01(hsl.s); + return tinycolor(hsl); +} + +function greyscale(color) { + return tinycolor(color).desaturate(100); +} + +function lighten (color, amount) { + amount = (amount === 0) ? 0 : (amount || 10); + var hsl = tinycolor(color).toHsl(); + hsl.l += amount / 100; + hsl.l = clamp01(hsl.l); + return tinycolor(hsl); +} + +function brighten(color, amount) { + amount = (amount === 0) ? 0 : (amount || 10); + var rgb = tinycolor(color).toRgb(); + rgb.r = mathMax(0, mathMin(255, rgb.r - mathRound(255 * - (amount / 100)))); + rgb.g = mathMax(0, mathMin(255, rgb.g - mathRound(255 * - (amount / 100)))); + rgb.b = mathMax(0, mathMin(255, rgb.b - mathRound(255 * - (amount / 100)))); + return tinycolor(rgb); +} + +function darken (color, amount) { + amount = (amount === 0) ? 0 : (amount || 10); + var hsl = tinycolor(color).toHsl(); + hsl.l -= amount / 100; + hsl.l = clamp01(hsl.l); + return tinycolor(hsl); +} + +// Spin takes a positive or negative amount within [-360, 360] indicating the change of hue. +// Values outside of this range will be wrapped into this range. +function spin(color, amount) { + var hsl = tinycolor(color).toHsl(); + var hue = (hsl.h + amount) % 360; + hsl.h = hue < 0 ? 360 + hue : hue; + return tinycolor(hsl); +} + +// Combination Functions +// --------------------- +// Thanks to jQuery xColor for some of the ideas behind these +// + +function complement(color) { + var hsl = tinycolor(color).toHsl(); + hsl.h = (hsl.h + 180) % 360; + return tinycolor(hsl); +} + +function triad(color) { + var hsl = tinycolor(color).toHsl(); + var h = hsl.h; + return [ + tinycolor(color), + tinycolor({ h: (h + 120) % 360, s: hsl.s, l: hsl.l }), + tinycolor({ h: (h + 240) % 360, s: hsl.s, l: hsl.l }) + ]; +} + +function tetrad(color) { + var hsl = tinycolor(color).toHsl(); + var h = hsl.h; + return [ + tinycolor(color), + tinycolor({ h: (h + 90) % 360, s: hsl.s, l: hsl.l }), + tinycolor({ h: (h + 180) % 360, s: hsl.s, l: hsl.l }), + tinycolor({ h: (h + 270) % 360, s: hsl.s, l: hsl.l }) + ]; +} + +function splitcomplement(color) { + var hsl = tinycolor(color).toHsl(); + var h = hsl.h; + return [ + tinycolor(color), + tinycolor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l}), + tinycolor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l}) + ]; +} + +function analogous(color, results, slices) { + results = results || 6; + slices = slices || 30; + + var hsl = tinycolor(color).toHsl(); + var part = 360 / slices; + var ret = [tinycolor(color)]; + + for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results; ) { + hsl.h = (hsl.h + part) % 360; + ret.push(tinycolor(hsl)); + } + return ret; +} + +function monochromatic(color, results) { + results = results || 6; + var hsv = tinycolor(color).toHsv(); + var h = hsv.h, s = hsv.s, v = hsv.v; + var ret = []; + var modification = 1 / results; + + while (results--) { + ret.push(tinycolor({ h: h, s: s, v: v})); + v = (v + modification) % 1; + } + + return ret; +} + +// Utility Functions +// --------------------- + +tinycolor.mix = function(color1, color2, amount) { + amount = (amount === 0) ? 0 : (amount || 50); + + var rgb1 = tinycolor(color1).toRgb(); + var rgb2 = tinycolor(color2).toRgb(); + + var p = amount / 100; + var w = p * 2 - 1; + var a = rgb2.a - rgb1.a; + + var w1; + + if (w * a == -1) { + w1 = w; + } else { + w1 = (w + a) / (1 + w * a); + } + + w1 = (w1 + 1) / 2; + + var w2 = 1 - w1; + + var rgba = { + r: rgb2.r * w1 + rgb1.r * w2, + g: rgb2.g * w1 + rgb1.g * w2, + b: rgb2.b * w1 + rgb1.b * w2, + a: rgb2.a * p + rgb1.a * (1 - p) + }; + + return tinycolor(rgba); +}; + + +// Readability Functions +// --------------------- +// false +// tinycolor.isReadable("#000", "#111",{level:"AA",size:"large"}) => false +tinycolor.isReadable = function(color1, color2, wcag2) { + var readability = tinycolor.readability(color1, color2); + var wcag2Parms, out; + + out = false; + + wcag2Parms = validateWCAG2Parms(wcag2); + switch (wcag2Parms.level + wcag2Parms.size) { + case "AAsmall": + case "AAAlarge": + out = readability >= 4.5; + break; + case "AAlarge": + out = readability >= 3; + break; + case "AAAsmall": + out = readability >= 7; + break; + } + return out; + +}; + +// `mostReadable` +// Given a base color and a list of possible foreground or background +// colors for that base, returns the most readable color. +// Optionally returns Black or White if the most readable color is unreadable. +// *Example* +// tinycolor.mostReadable(tinycolor.mostReadable("#123", ["#124", "#125"],{includeFallbackColors:false}).toHexString(); // "#112255" +// tinycolor.mostReadable(tinycolor.mostReadable("#123", ["#124", "#125"],{includeFallbackColors:true}).toHexString(); // "#ffffff" +// tinycolor.mostReadable("#a8015a", ["#faf3f3"],{includeFallbackColors:true,level:"AAA",size:"large"}).toHexString(); // "#faf3f3" +// tinycolor.mostReadable("#a8015a", ["#faf3f3"],{includeFallbackColors:true,level:"AAA",size:"small"}).toHexString(); // "#ffffff" +tinycolor.mostReadable = function(baseColor, colorList, args) { + var bestColor = null; + var bestScore = 0; + var readability; + var includeFallbackColors, level, size ; + args = args || {}; + includeFallbackColors = args.includeFallbackColors ; + level = args.level; + size = args.size; + + for (var i= 0; i < colorList.length ; i++) { + readability = tinycolor.readability(baseColor, colorList[i]); + if (readability > bestScore) { + bestScore = readability; + bestColor = tinycolor(colorList[i]); + } + } + + if (tinycolor.isReadable(baseColor, bestColor, {"level":level,"size":size}) || !includeFallbackColors) { + return bestColor; + } + else { + args.includeFallbackColors=false; + return tinycolor.mostReadable(baseColor,["#fff", "#000"],args); + } +}; + + +// Big List of Colors +// ------------------ +// +var names = tinycolor.names = { + aliceblue: "f0f8ff", + antiquewhite: "faebd7", + aqua: "0ff", + aquamarine: "7fffd4", + azure: "f0ffff", + beige: "f5f5dc", + bisque: "ffe4c4", + black: "000", + blanchedalmond: "ffebcd", + blue: "00f", + blueviolet: "8a2be2", + brown: "a52a2a", + burlywood: "deb887", + burntsienna: "ea7e5d", + cadetblue: "5f9ea0", + chartreuse: "7fff00", + chocolate: "d2691e", + coral: "ff7f50", + cornflowerblue: "6495ed", + cornsilk: "fff8dc", + crimson: "dc143c", + cyan: "0ff", + darkblue: "00008b", + darkcyan: "008b8b", + darkgoldenrod: "b8860b", + darkgray: "a9a9a9", + darkgreen: "006400", + darkgrey: "a9a9a9", + darkkhaki: "bdb76b", + darkmagenta: "8b008b", + darkolivegreen: "556b2f", + darkorange: "ff8c00", + darkorchid: "9932cc", + darkred: "8b0000", + darksalmon: "e9967a", + darkseagreen: "8fbc8f", + darkslateblue: "483d8b", + darkslategray: "2f4f4f", + darkslategrey: "2f4f4f", + darkturquoise: "00ced1", + darkviolet: "9400d3", + deeppink: "ff1493", + deepskyblue: "00bfff", + dimgray: "696969", + dimgrey: "696969", + dodgerblue: "1e90ff", + firebrick: "b22222", + floralwhite: "fffaf0", + forestgreen: "228b22", + fuchsia: "f0f", + gainsboro: "dcdcdc", + ghostwhite: "f8f8ff", + gold: "ffd700", + goldenrod: "daa520", + gray: "808080", + green: "008000", + greenyellow: "adff2f", + grey: "808080", + honeydew: "f0fff0", + hotpink: "ff69b4", + indianred: "cd5c5c", + indigo: "4b0082", + ivory: "fffff0", + khaki: "f0e68c", + lavender: "e6e6fa", + lavenderblush: "fff0f5", + lawngreen: "7cfc00", + lemonchiffon: "fffacd", + lightblue: "add8e6", + lightcoral: "f08080", + lightcyan: "e0ffff", + lightgoldenrodyellow: "fafad2", + lightgray: "d3d3d3", + lightgreen: "90ee90", + lightgrey: "d3d3d3", + lightpink: "ffb6c1", + lightsalmon: "ffa07a", + lightseagreen: "20b2aa", + lightskyblue: "87cefa", + lightslategray: "789", + lightslategrey: "789", + lightsteelblue: "b0c4de", + lightyellow: "ffffe0", + lime: "0f0", + limegreen: "32cd32", + linen: "faf0e6", + magenta: "f0f", + maroon: "800000", + mediumaquamarine: "66cdaa", + mediumblue: "0000cd", + mediumorchid: "ba55d3", + mediumpurple: "9370db", + mediumseagreen: "3cb371", + mediumslateblue: "7b68ee", + mediumspringgreen: "00fa9a", + mediumturquoise: "48d1cc", + mediumvioletred: "c71585", + midnightblue: "191970", + mintcream: "f5fffa", + mistyrose: "ffe4e1", + moccasin: "ffe4b5", + navajowhite: "ffdead", + navy: "000080", + oldlace: "fdf5e6", + olive: "808000", + olivedrab: "6b8e23", + orange: "ffa500", + orangered: "ff4500", + orchid: "da70d6", + palegoldenrod: "eee8aa", + palegreen: "98fb98", + paleturquoise: "afeeee", + palevioletred: "db7093", + papayawhip: "ffefd5", + peachpuff: "ffdab9", + peru: "cd853f", + pink: "ffc0cb", + plum: "dda0dd", + powderblue: "b0e0e6", + purple: "800080", + rebeccapurple: "663399", + red: "f00", + rosybrown: "bc8f8f", + royalblue: "4169e1", + saddlebrown: "8b4513", + salmon: "fa8072", + sandybrown: "f4a460", + seagreen: "2e8b57", + seashell: "fff5ee", + sienna: "a0522d", + silver: "c0c0c0", + skyblue: "87ceeb", + slateblue: "6a5acd", + slategray: "708090", + slategrey: "708090", + snow: "fffafa", + springgreen: "00ff7f", + steelblue: "4682b4", + tan: "d2b48c", + teal: "008080", + thistle: "d8bfd8", + tomato: "ff6347", + turquoise: "40e0d0", + violet: "ee82ee", + wheat: "f5deb3", + white: "fff", + whitesmoke: "f5f5f5", + yellow: "ff0", + yellowgreen: "9acd32" +}; + +// Make it easy to access colors via `hexNames[hex]` +var hexNames = tinycolor.hexNames = flip(names); + + +// Utilities +// --------- + +// `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }` +function flip(o) { + var flipped = { }; + for (var i in o) { + if (o.hasOwnProperty(i)) { + flipped[o[i]] = i; + } + } + return flipped; +} + +// Return a valid alpha value [0,1] with all invalid values being set to 1 +function boundAlpha(a) { + a = parseFloat(a); + + if (isNaN(a) || a < 0 || a > 1) { + a = 1; + } + + return a; +} + +// Take input from [0, n] and return it as [0, 1] +function bound01(n, max) { + if (isOnePointZero(n)) { n = "100%"; } + + var processPercent = isPercentage(n); + n = mathMin(max, mathMax(0, parseFloat(n))); + + // Automatically convert percentage into number + if (processPercent) { + n = parseInt(n * max, 10) / 100; + } + + // Handle floating point rounding errors + if ((math.abs(n - max) < 0.000001)) { + return 1; + } + + // Convert into [0, 1] range if it isn't already + return (n % max) / parseFloat(max); +} + +// Force a number between 0 and 1 +function clamp01(val) { + return mathMin(1, mathMax(0, val)); +} + +// Parse a base-16 hex value into a base-10 integer +function parseIntFromHex(val) { + return parseInt(val, 16); +} + +// Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1 +// +function isOnePointZero(n) { + return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1; +} + +// Check to see if string passed in is a percentage +function isPercentage(n) { + return typeof n === "string" && n.indexOf('%') != -1; +} + +// Force a hex value to have 2 characters +function pad2(c) { + return c.length == 1 ? '0' + c : '' + c; +} + +// Replace a decimal with it's percentage value +function convertToPercentage(n) { + if (n <= 1) { + n = (n * 100) + "%"; + } + + return n; +} + +// Converts a decimal to a hex value +function convertDecimalToHex(d) { + return Math.round(parseFloat(d) * 255).toString(16); +} +// Converts a hex value to a decimal +function convertHexToDecimal(h) { + return (parseIntFromHex(h) / 255); +} + +var matchers = (function() { + + // + var CSS_INTEGER = "[-\\+]?\\d+%?"; + + // + var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?"; + + // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome. + var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")"; + + // Actual matching. + // Parentheses and commas are optional, but not required. + // Whitespace can take the place of commas or opening paren + var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; + var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; + + return { + rgb: new RegExp("rgb" + PERMISSIVE_MATCH3), + rgba: new RegExp("rgba" + PERMISSIVE_MATCH4), + hsl: new RegExp("hsl" + PERMISSIVE_MATCH3), + hsla: new RegExp("hsla" + PERMISSIVE_MATCH4), + hsv: new RegExp("hsv" + PERMISSIVE_MATCH3), + hsva: new RegExp("hsva" + PERMISSIVE_MATCH4), + hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, + hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, + hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/ + }; +})(); + +// `stringInputToObject` +// Permissive string parsing. Take in a number of formats, and output an object +// based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}` +function stringInputToObject(color) { + + color = color.replace(trimLeft,'').replace(trimRight, '').toLowerCase(); + var named = false; + if (names[color]) { + color = names[color]; + named = true; + } + else if (color == 'transparent') { + return { r: 0, g: 0, b: 0, a: 0, format: "name" }; + } + + // Try to match string input using regular expressions. + // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360] + // Just return an object and let the conversion functions handle that. + // This way the result will be the same whether the tinycolor is initialized with string or object. + var match; + if ((match = matchers.rgb.exec(color))) { + return { r: match[1], g: match[2], b: match[3] }; + } + if ((match = matchers.rgba.exec(color))) { + return { r: match[1], g: match[2], b: match[3], a: match[4] }; + } + if ((match = matchers.hsl.exec(color))) { + return { h: match[1], s: match[2], l: match[3] }; + } + if ((match = matchers.hsla.exec(color))) { + return { h: match[1], s: match[2], l: match[3], a: match[4] }; + } + if ((match = matchers.hsv.exec(color))) { + return { h: match[1], s: match[2], v: match[3] }; + } + if ((match = matchers.hsva.exec(color))) { + return { h: match[1], s: match[2], v: match[3], a: match[4] }; + } + if ((match = matchers.hex8.exec(color))) { + return { + a: convertHexToDecimal(match[1]), + r: parseIntFromHex(match[2]), + g: parseIntFromHex(match[3]), + b: parseIntFromHex(match[4]), + format: named ? "name" : "hex8" + }; + } + if ((match = matchers.hex6.exec(color))) { + return { + r: parseIntFromHex(match[1]), + g: parseIntFromHex(match[2]), + b: parseIntFromHex(match[3]), + format: named ? "name" : "hex" + }; + } + if ((match = matchers.hex3.exec(color))) { + return { + r: parseIntFromHex(match[1] + '' + match[1]), + g: parseIntFromHex(match[2] + '' + match[2]), + b: parseIntFromHex(match[3] + '' + match[3]), + format: named ? "name" : "hex" + }; + } + + return false; +} + +function validateWCAG2Parms(parms) { + // return valid WCAG2 parms for isReadable. + // If input parms are invalid, return {"level":"AA", "size":"small"} + var level, size; + parms = parms || {"level":"AA", "size":"small"}; + level = (parms.level || "AA").toUpperCase(); + size = (parms.size || "small").toLowerCase(); + if (level !== "AA" && level !== "AAA") { + level = "AA"; + } + if (size !== "small" && size !== "large") { + size = "small"; + } + return {"level":level, "size":size}; +} + +loadTinyColor = function(){ + return tinycolor +} \ No newline at end of file diff --git a/examples/toybox/basketball/createSingleBasketball.js b/examples/toybox/basketball/createSingleBasketball.js index a1e0140553..6765e5e075 100644 --- a/examples/toybox/basketball/createSingleBasketball.js +++ b/examples/toybox/basketball/createSingleBasketball.js @@ -47,7 +47,12 @@ function makeBasketball() { modelURL: basketballURL, restitution: 1.0, damping: 0.00001, - shapeType: "sphere" + shapeType: "sphere", + userData: JSON.stringify({ + grabbableKey: { + invertSolidWhileHeld: true + } + }) }); originalPosition = position; } diff --git a/examples/toybox/bow/bow.js b/examples/toybox/bow/bow.js index 14e3ed86ec..90199fb70f 100644 --- a/examples/toybox/bow/bow.js +++ b/examples/toybox/bow/bow.js @@ -67,12 +67,17 @@ } var BOW_SPATIAL_KEY = { - relativePosition: { - x: 0, + leftRelativePosition: { + x: 0.05, y: 0.06, - z: 0.11 + z: -0.05 }, - relativeRotation: Quat.fromPitchYawRollDegrees(0, -90, 90) + rightRelativePosition: { + x: -0.05, + y: 0.06, + z: -0.05 + }, + relativeRotation: Quat.fromPitchYawRollDegrees(0, 90, -90) } diff --git a/examples/toybox/bow/createBow.js b/examples/toybox/bow/createBow.js index 880b0920e8..9a9ed98c20 100644 --- a/examples/toybox/bow/createBow.js +++ b/examples/toybox/bow/createBow.js @@ -48,12 +48,17 @@ var bow = Entities.addEntity({ grabbableKey: { invertSolidWhileHeld: true, spatialKey: { - relativePosition: { - x: 0, + leftRelativePosition: { + x: 0.05, y: 0.06, - z: 0.11 + z: -0.05 }, - relativeRotation: Quat.fromPitchYawRollDegrees(0, -90, 90) + rightRelativePosition: { + x: -0.05, + y: 0.06, + z: -0.05 + }, + relativeRotation: Quat.fromPitchYawRollDegrees(0, 90, -90) } } }) diff --git a/examples/toybox/doll/createDoll.js b/examples/toybox/doll/createDoll.js index ffd840f4ea..52ba5a5291 100644 --- a/examples/toybox/doll/createDoll.js +++ b/examples/toybox/doll/createDoll.js @@ -15,12 +15,20 @@ function createDoll() { var scriptURL = Script.resolvePath("doll.js"); - var center = Vec3.sum(Vec3.sum(MyAvatar.position, { x: 0, y: 0.5, z: 0 }), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation()))); + var center = Vec3.sum(Vec3.sum(MyAvatar.position, { + x: 0, + y: 0.5, + z: 0 + }), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation()))); - var naturalDimensions = { x: 1.63, y: 1.67, z: 0.26}; + var naturalDimensions = { + x: 1.63, + y: 1.67, + z: 0.26 + }; var desiredDimensions = Vec3.multiply(naturalDimensions, 0.15); - + var doll = Entities.addEntity({ type: "Model", name: "doll", @@ -39,7 +47,12 @@ function createDoll() { y: 0, z: 0 }, - collisionsWillMove: true + collisionsWillMove: true, + userData: JSON.stringify({ + grabbableKey: { + invertSolidWhileHeld: true + } + }) }); return doll; } diff --git a/examples/toybox/flashlight/createFlashlight.js b/examples/toybox/flashlight/createFlashlight.js index b049d2632e..108d519d3e 100644 --- a/examples/toybox/flashlight/createFlashlight.js +++ b/examples/toybox/flashlight/createFlashlight.js @@ -18,14 +18,27 @@ var scriptURL = Script.resolvePath('flashlight.js'); var modelURL = "https://hifi-public.s3.amazonaws.com/models/props/flashlight.fbx"; -var center = Vec3.sum(Vec3.sum(MyAvatar.position, {x: 0, y: 0.5, z: 0}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation()))); +var center = Vec3.sum(Vec3.sum(MyAvatar.position, { + x: 0, + y: 0.5, + z: 0 +}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation()))); var flashlight = Entities.addEntity({ type: "Model", modelURL: modelURL, position: center, - dimensions: { x: 0.08, y: 0.30, z: 0.08}, + dimensions: { + x: 0.08, + y: 0.30, + z: 0.08 + }, collisionsWillMove: true, shapeType: 'box', - script: scriptURL -}); + script: scriptURL, + userData: JSON.stringify({ + grabbableKey: { + invertSolidWhileHeld: true + } + }) +}); \ No newline at end of file diff --git a/examples/toybox/ping_pong_gun/createPingPongGun.js b/examples/toybox/ping_pong_gun/createPingPongGun.js index 705671e784..4b93842896 100644 --- a/examples/toybox/ping_pong_gun/createPingPongGun.js +++ b/examples/toybox/ping_pong_gun/createPingPongGun.js @@ -35,7 +35,12 @@ var pingPongGun = Entities.addEntity({ z: 0.47 }, collisionsWillMove: true, - collisionSoundURL: COLLISION_SOUND_URL + collisionSoundURL: COLLISION_SOUND_URL, + userData: JSON.stringify({ + grabbableKey: { + invertSolidWhileHeld: true + } + }) }); function cleanUp() { diff --git a/examples/utilities/tools/developerMenuItems.js b/examples/utilities/tools/developerMenuItems.js index ef8be8aaa9..477cbd671b 100644 --- a/examples/utilities/tools/developerMenuItems.js +++ b/examples/utilities/tools/developerMenuItems.js @@ -19,7 +19,6 @@ var createdStereoInputMenuItem = false; var DEVELOPER_MENU = "Developer"; var ENTITIES_MENU = DEVELOPER_MENU + " > Entities"; -var COLLISION_UPDATES_TO_SERVER = "Don't send collision updates to server"; var RENDER_MENU = DEVELOPER_MENU + " > Render"; var ENTITIES_ITEM = "Entities"; @@ -66,7 +65,6 @@ function setupMenus() { Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Don't Do Precision Picking", isCheckable: true, isChecked: false }); Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Disable Light Entities", isCheckable: true, isChecked: false }); */ - Menu.addMenuItem({ menuName: ENTITIES_MENU, menuItemName: COLLISION_UPDATES_TO_SERVER, isCheckable: true, isChecked: false }); } if (!Menu.menuExists(RENDER_MENU)) { @@ -112,11 +110,7 @@ function setupMenus() { Menu.menuItemEvent.connect(function (menuItem) { print("menuItemEvent() in JS... menuItem=" + menuItem); - if (menuItem == COLLISION_UPDATES_TO_SERVER) { - var dontSendUpdates = Menu.isOptionChecked(COLLISION_UPDATES_TO_SERVER); - print(" dontSendUpdates... checked=" + dontSendUpdates); - Entities.setSendPhysicsUpdates(!dontSendUpdates); - } else if (menuItem == ENTITIES_ITEM) { + if (menuItem == ENTITIES_ITEM) { Scene.shouldRenderEntities = Menu.isOptionChecked(ENTITIES_ITEM); } else if (menuItem == AVATARS_ITEM) { Scene.shouldRenderAvatars = Menu.isOptionChecked(AVATARS_ITEM); diff --git a/examples/utilities/tools/reverbTest.js b/examples/utilities/tools/reverbTest.js new file mode 100644 index 0000000000..32c28a993f --- /dev/null +++ b/examples/utilities/tools/reverbTest.js @@ -0,0 +1,69 @@ +// +// reverbTest.js +// examples +// +// Created by Ken Cooke on 11/23/2015. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("cookies.js"); + +var audioOptions = new AudioEffectOptions({ + maxRoomSize: 50, + roomSize: 50, + reverbTime: 4, + damping: 0.50, + inputBandwidth: 0.8, + earlyLevel: 0, + tailLevel: 0, + dryLevel: -6, + wetLevel: -6 +}); + +AudioDevice.setReverbOptions(audioOptions); +AudioDevice.setReverb(true); +print("Reverb is ON."); + +var panel = new Panel(10, 200); + +var parameters = [ + { name: "roomSize", min: 0, max: 100, units: " feet" }, + { name: "reverbTime", min: 0, max: 10, units: " sec" }, + { name: "damping", min: 0, max: 1, units: " " }, + { name: "inputBandwidth", min: 0, max: 1, units: " " }, + { name: "earlyLevel", min: -48, max: 0, units: " dB" }, + { name: "tailLevel", min: -48, max: 0, units: " dB" }, + { name: "wetLevel", min: -48, max: 0, units: " dB" }, +] + +function setter(name) { + return function(value) { audioOptions[name] = value; AudioDevice.setReverbOptions(audioOptions); } +} + +function getter(name) { + return function() { return audioOptions[name]; } +} + +function displayer(units) { + return function(value) { return (value).toFixed(1) + units; }; +} + +// create a slider for each parameter +for (var i = 0; i < parameters.length; i++) { + var p = parameters[i]; + panel.newSlider(p.name, p.min, p.max, setter(p.name), getter(p.name), displayer(p.units)); +} + +Controller.mouseMoveEvent.connect(function panelMouseMoveEvent(event) { return panel.mouseMoveEvent(event); }); +Controller.mousePressEvent.connect( function panelMousePressEvent(event) { return panel.mousePressEvent(event); }); +Controller.mouseReleaseEvent.connect(function(event) { return panel.mouseReleaseEvent(event); }); + +function scriptEnding() { + panel.destroy(); + AudioDevice.setReverb(false); + print("Reverb is OFF."); +} +Script.scriptEnding.connect(scriptEnding); diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 3db23e4d36..4332ae24be 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -2,7 +2,7 @@ set(TARGET_NAME interface) project(${TARGET_NAME}) # set a default root dir for each of our optional externals if it was not passed -set(OPTIONAL_EXTERNALS "LeapMotion" "RtMidi" "RSSDK") +set(OPTIONAL_EXTERNALS "LeapMotion") if(WIN32) list(APPEND OPTIONAL_EXTERNALS "3DConnexionClient") @@ -161,13 +161,6 @@ foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) endif () endforeach() -# special OS X modifications for RtMidi library -if (RTMIDI_FOUND AND NOT DISABLE_RTMIDI AND APPLE) - find_library(CoreMIDI CoreMIDI) - add_definitions(-D__MACOSX_CORE__) - target_link_libraries(${TARGET_NAME} ${CoreMIDI}) -endif () - # include headers for interface and InterfaceConfig. include_directories("${PROJECT_SOURCE_DIR}/src") diff --git a/interface/external/rssdk/readme.txt b/interface/external/rssdk/readme.txt deleted file mode 100644 index fe2246e32a..0000000000 --- a/interface/external/rssdk/readme.txt +++ /dev/null @@ -1,9 +0,0 @@ - -Instructions for adding the Intel Realsense (RSSDK) to Interface -Thijs Wenker, December 19, 2014 - -This is Windows only for now. - -1. Download the SDK at https://software.intel.com/en-us/intel-realsense-sdk/download - -2. Copy the `lib` and `include` folder inside this directory \ No newline at end of file diff --git a/interface/external/rtmidi/readme.txt b/interface/external/rtmidi/readme.txt deleted file mode 100644 index 3b9d6603a9..0000000000 --- a/interface/external/rtmidi/readme.txt +++ /dev/null @@ -1,43 +0,0 @@ - -Instructions for adding the RtMidi library to Interface -Stephen Birarda, June 30, 2014 - -1. Download the RtMidi tarball from High Fidelity S3. - http://public.highfidelity.io/dependencies/rtmidi-2.1.0.tar.gz - -2. Copy RtMidi.h to externals/rtmidi/include. - -3. Compile the RtMidi library. - -3. Copy either librtmidi.dylib (dynamic) or librtmidi.a (static) to externals/rtmidi/lib - -4. Delete your build directory, run cmake and build, and you should be all set. - -========================= - -RtMidi: realtime MIDI i/o C++ classes
-Copyright (c) 2003-2014 Gary P. Scavone - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation files -(the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of the Software, -and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -Any person wishing to distribute modifications to the Software is -asked to send the modifications to the original developer so that -they can be incorporated into the canonical version. This is, -however, not a binding provision of this license. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF -CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/interface/resources/controllers/hydra.json b/interface/resources/controllers/hydra.json index b517d1bad5..0193612d27 100644 --- a/interface/resources/controllers/hydra.json +++ b/interface/resources/controllers/hydra.json @@ -13,12 +13,11 @@ { "from": "Hydra.RB", "to": "Standard.RB" }, { "from": "Hydra.RS", "to": "Standard.RS" }, - { "from": [ "Hydra.L3", "Hydra.L4" ], "to": "Standard.LeftPrimaryThumb" }, - { "from": [ "Hydra.L1", "Hydra.L2" ], "to": "Standard.LeftSecondaryThumb" }, - - { "from": [ "Hydra.R3", "Hydra.R4" ], "to": "Standard.RightPrimaryThumb" }, - { "from": [ "Hydra.R1", "Hydra.R2" ], "to": "Standard.RightSecondaryThumb" }, + { "from": [ "Hydra.L1", "Hydra.L2", "Hydra.L3", "Hydra.L4" ], "to": "Standard.LeftPrimaryThumb" }, + { "from": [ "Hydra.L0" ], "to": "Standard.LeftSecondaryThumb" }, + { "from": [ "Hydra.R1", "Hydra.R2", "Hydra.R3", "Hydra.R4" ], "to": "Standard.RightPrimaryThumb" }, + { "from": [ "Hydra.R0" ], "to": "Standard.RightSecondaryThumb" }, { "from": "Hydra.LeftHand", "to": "Standard.LeftHand" }, { "from": "Hydra.RightHand", "to": "Standard.RightHand" } diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 84381cc754..56d4f9c14b 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -45,7 +45,12 @@ Item { Text { color: root.fontColor; font.pixelSize: root.fontSize - text: "Framerate: " + root.framerate + text: "Render Rate: " + root.renderrate + } + Text { + color: root.fontColor; + font.pixelSize: root.fontSize + text: "Present Rate: " + root.presentrate } Text { color: root.fontColor; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c0315c44e3..c0cfbda15b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -44,6 +44,9 @@ #include +#include +#include + #include #include #include @@ -78,7 +81,7 @@ #include #include #include -#include +#include #include #include #include @@ -110,8 +113,6 @@ #include "devices/EyeTracker.h" #include "devices/Faceshift.h" #include "devices/Leapmotion.h" -#include "devices/MIDIManager.h" -#include "devices/RealSense.h" #include "DiscoverabilityManager.h" #include "GLCanvas.h" #include "InterfaceActionFactory.h" @@ -149,6 +150,8 @@ #include "ui/Stats.h" #include "ui/UpdateDialog.h" #include "Util.h" +#include "InterfaceParentFinder.h" + // ON WIndows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU @@ -297,6 +300,7 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::registerInheritance(); DependencyManager::registerInheritance(); DependencyManager::registerInheritance(); + DependencyManager::registerInheritance(); Setting::init(); @@ -345,6 +349,7 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); return true; } @@ -615,8 +620,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // enable mouse tracking; otherwise, we only get drag events _glWidget->setMouseTracking(true); + _glWidget->makeCurrent(); + _glWidget->initializeGL(); - _offscreenContext = new OffscreenGlCanvas(); + _offscreenContext = new OffscreenGLCanvas(); _offscreenContext->create(_glWidget->context()->contextHandle()); _offscreenContext->makeCurrent(); initializeGL(); @@ -719,12 +726,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // set the local loopback interface for local sounds from audio scripts AudioScriptingInterface::getInstance().setLocalAudioInterface(audioIO.data()); -#ifdef HAVE_RTMIDI - // setup the MIDIManager - MIDIManager& midiManagerInstance = MIDIManager::getInstance(); - midiManagerInstance.openDefaultPort(); -#endif - this->installEventFilter(this); // initialize our face trackers after loading the menu settings @@ -970,7 +971,6 @@ Application::~Application() { nodeThread->wait(); Leapmotion::destroy(); - RealSense::destroy(); #if 0 ConnexionClient::getInstance().destroy(); @@ -1077,6 +1077,11 @@ void Application::initializeUi() { } void Application::paintGL() { + // paintGL uses a queued connection, so we can get messages from the queue even after we've quit + // and the plugins have shutdown + if (_aboutToQuit) { + return; + } _frameCount++; // update fps moving average @@ -1141,7 +1146,7 @@ void Application::paintGL() { _lastInstantaneousFps = instantaneousFps; auto displayPlugin = getActiveDisplayPlugin(); - displayPlugin->preRender(); + // FIXME not needed anymore? _offscreenContext->makeCurrent(); // update the avatar with a fresh HMD pose @@ -1196,6 +1201,9 @@ void Application::paintGL() { QSize size = getDeviceSize(); renderArgs._viewport = glm::ivec4(0, 0, size.width(), size.height()); _applicationOverlay.renderOverlay(&renderArgs); + gpu::FramebufferPointer overlayFramebuffer = _applicationOverlay.getOverlayFramebuffer(); + + } { @@ -1250,7 +1258,7 @@ void Application::paintGL() { * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)) * hmdRotation); glm::vec3 hmdOffset = extractTranslation(myAvatar->getHMDSensorMatrix()); _myCamera.setPosition(myAvatar->getDefaultEyePosition() - + glm::vec3(0, _raiseMirror * myAvatar->getScale(), 0) + + glm::vec3(0, _raiseMirror * myAvatar->getAvatarScale(), 0) + (myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror + (myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))) * hmdOffset); @@ -1258,7 +1266,7 @@ void Application::paintGL() { _myCamera.setRotation(myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))); _myCamera.setPosition(myAvatar->getDefaultEyePosition() - + glm::vec3(0, _raiseMirror * myAvatar->getScale(), 0) + + glm::vec3(0, _raiseMirror * myAvatar->getAvatarScale(), 0) + (myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror); } @@ -1309,6 +1317,13 @@ void Application::paintGL() { auto baseProjection = renderArgs._viewFrustum->getProjection(); auto hmdInterface = DependencyManager::get(); float IPDScale = hmdInterface->getIPDScale(); + + // Tell the plugin what pose we're using to render. In this case we're just using the + // unmodified head pose because the only plugin that cares (the Oculus plugin) uses it + // for rotational timewarp. If we move to support positonal timewarp, we need to + // ensure this contains the full pose composed with the eye offsets. + mat4 headPose = displayPlugin->getHeadPose(_frameCount); + // FIXME we probably don't need to set the projection matrix every frame, // only when the display plugin changes (or in non-HMD modes when the user // changes the FOV manually, which right now I don't think they can. @@ -1324,12 +1339,7 @@ void Application::paintGL() { mat4 eyeOffsetTransform = glm::translate(mat4(), eyeOffset * -1.0f * IPDScale); eyeOffsets[eye] = eyeOffsetTransform; - // Tell the plugin what pose we're using to render. In this case we're just using the - // unmodified head pose because the only plugin that cares (the Oculus plugin) uses it - // for rotational timewarp. If we move to support positonal timewarp, we need to - // ensure this contains the full pose composed with the eye offsets. - mat4 headPose = displayPlugin->getHeadPose(); - displayPlugin->setEyeRenderPose(eye, headPose); + displayPlugin->setEyeRenderPose(_frameCount, eye, headPose); eyeProjections[eye] = displayPlugin->getProjection(eye, baseProjection); }); @@ -1344,6 +1354,7 @@ void Application::paintGL() { } // Overlay Composition, needs to occur after screen space effects have completed + // FIXME migrate composition into the display plugins { PROFILE_RANGE(__FUNCTION__ "/compositor"); PerformanceTimer perfTimer("compositor"); @@ -1372,44 +1383,40 @@ void Application::paintGL() { { PROFILE_RANGE(__FUNCTION__ "/pluginOutput"); PerformanceTimer perfTimer("pluginOutput"); - auto primaryFbo = framebufferCache->getPrimaryFramebuffer(); - GLuint finalTexture = gpu::GLBackend::getTextureID(primaryFbo->getRenderBuffer(0)); - // Ensure the rendering context commands are completed when rendering - GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - // Ensure the sync object is flushed to the driver thread before releasing the context - // CRITICAL for the mac driver apparently. - glFlush(); - _offscreenContext->doneCurrent(); + auto primaryFramebuffer = framebufferCache->getPrimaryFramebuffer(); + auto scratchFramebuffer = framebufferCache->getFramebuffer(); + gpu::doInBatch(renderArgs._context, [=](gpu::Batch& batch) { + gpu::Vec4i rect; + rect.z = size.width(); + rect.w = size.height(); + batch.setFramebuffer(scratchFramebuffer); + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(0.0f, 0.0f, 0.0f, 0.0f)); + batch.blit(primaryFramebuffer, rect, scratchFramebuffer, rect); + batch.setFramebuffer(nullptr); + }); + auto finalTexturePointer = scratchFramebuffer->getRenderBuffer(0); + GLuint finalTexture = gpu::GLBackend::getTextureID(finalTexturePointer); + Q_ASSERT(0 != finalTexture); + + Q_ASSERT(!_lockedFramebufferMap.contains(finalTexture)); + _lockedFramebufferMap[finalTexture] = scratchFramebuffer; - // Switches to the display plugin context - displayPlugin->preDisplay(); - // Ensure all operations from the previous context are complete before we try to read the fbo - glWaitSync(sync, 0, GL_TIMEOUT_IGNORED); - glDeleteSync(sync); uint64_t displayStart = usecTimestampNow(); - + Q_ASSERT(isCurrentContext(_offscreenContext->getContext())); { - PROFILE_RANGE(__FUNCTION__ "/pluginDisplay"); - PerformanceTimer perfTimer("pluginDisplay"); - displayPlugin->display(finalTexture, toGlm(size)); + PROFILE_RANGE(__FUNCTION__ "/pluginSubmitScene"); + PerformanceTimer perfTimer("pluginSubmitScene"); + displayPlugin->submitSceneTexture(_frameCount, finalTexture, toGlm(size)); } + Q_ASSERT(isCurrentContext(_offscreenContext->getContext())); - { - PROFILE_RANGE(__FUNCTION__ "/bufferSwap"); - PerformanceTimer perfTimer("bufferSwap"); - displayPlugin->finishFrame(); - } uint64_t displayEnd = usecTimestampNow(); const float displayPeriodUsec = (float)(displayEnd - displayStart); // usecs _lastPaintWait = displayPeriodUsec / (float)USECS_PER_SECOND; - } { - PerformanceTimer perfTimer("makeCurrent"); - _offscreenContext->makeCurrent(); Stats::getInstance()->setRenderDetails(renderArgs._details); - // Reset the gpu::Context Stages // Back to the default framebuffer; gpu::doInBatch(renderArgs._context, [=](gpu::Batch& batch) { @@ -2412,6 +2419,9 @@ bool Application::exportEntities(const QString& filename, const QVectoraddEntity(entityItem->getEntityItemID(), properties); } + // remap IDs on export so that we aren't publishing the IDs of entities in our domain + exportTree->remapIDs(); + exportTree->writeToJSONFile(filename.toLocal8Bit().constData()); // restore the main window's active state @@ -2434,6 +2444,10 @@ bool Application::exportEntities(const QString& filename, float x, float y, floa properties.setPosition(properties.getPosition() - root); exportTree->addEntity(id, properties); } + + // remap IDs on export so that we aren't publishing the IDs of entities in our domain + exportTree->remapIDs(); + exportTree->writeToSVOFile(filename.toLocal8Bit().constData()); } else { qCDebug(interfaceapp) << "No models were selected"; @@ -2478,6 +2492,7 @@ bool Application::importEntities(const QString& urlOrFilename) { bool success = _entityClipboard->readFromURL(url.toString()); if (success) { + _entityClipboard->remapIDs(); _entityClipboard->reaverageOctreeElements(); } return success; @@ -2519,7 +2534,6 @@ void Application::init() { qCDebug(interfaceapp) << "Loaded settings"; Leapmotion::init(); - RealSense::init(); // fire off an immediate domain-server check in now that settings are loaded DependencyManager::get()->sendDomainServerCheckIn(); @@ -2577,7 +2591,6 @@ void Application::setAvatarUpdateThreading(bool isThreaded) { return; } - auto myAvatar = getMyAvatar(); if (_avatarUpdate) { _avatarUpdate->terminate(); // Must be before we shutdown anim graph. } @@ -2619,7 +2632,7 @@ void Application::updateMyAvatarLookAtPosition() { lookAtPosition.x = -lookAtPosition.x; } if (isHMD) { - glm::mat4 headPose = getActiveDisplayPlugin()->getHeadPose(); + glm::mat4 headPose = getActiveDisplayPlugin()->getHeadPose(_frameCount); glm::quat hmdRotation = glm::quat_cast(headPose); lookAtSpot = _myCamera.getPosition() + myAvatar->getOrientation() * (hmdRotation * lookAtPosition); } else { @@ -3073,13 +3086,6 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node //qCDebug(interfaceapp) << ">>> inside... queryOctree()... _viewFrustum.getFieldOfView()=" << _viewFrustum.getFieldOfView(); bool wantExtraDebugging = getLogger()->extraDebugging(); - // These will be the same for all servers, so we can set them up once and then reuse for each server we send to. - _octreeQuery.setWantLowResMoving(true); - _octreeQuery.setWantColor(true); - _octreeQuery.setWantDelta(true); - _octreeQuery.setWantOcclusionCulling(false); - _octreeQuery.setWantCompression(true); - _octreeQuery.setCameraPosition(_viewFrustum.getPosition()); _octreeQuery.setCameraOrientation(_viewFrustum.getOrientation()); _octreeQuery.setCameraFov(_viewFrustum.getFieldOfView()); @@ -3323,7 +3329,7 @@ MyAvatar* Application::getMyAvatar() const { return DependencyManager::get()->getMyAvatar(); } -const glm::vec3& Application::getAvatarPosition() const { +glm::vec3 Application::getAvatarPosition() const { return getMyAvatar()->getPosition(); } @@ -3782,12 +3788,11 @@ void Application::domainChanged(const QString& domainHostname) { _domainConnectionRefusals.clear(); } -void Application::handleDomainConnectionDeniedPacket(QSharedPointer packet) { +void Application::handleDomainConnectionDeniedPacket(QSharedPointer message) { // Read deny reason from packet quint16 reasonSize; - packet->readPrimitive(&reasonSize); - QString reason = QString::fromUtf8(packet->getPayload() + packet->pos(), reasonSize); - packet->seek(packet->pos() + reasonSize); + message->readPrimitive(&reasonSize); + QString reason = QString::fromUtf8(message->readWithoutCopy(reasonSize)); // output to the log so the user knows they got a denied connection request // and check and signal for an access token so that we can make sure they are logged in @@ -3873,9 +3878,7 @@ void Application::nodeKilled(SharedNodePointer node) { Menu::getInstance()->getActionForOption(MenuOption::UploadAsset)->setEnabled(false); } } - -void Application::trackIncomingOctreePacket(NLPacket& packet, SharedNodePointer sendingNode, bool wasStatsPacket) { - +void Application::trackIncomingOctreePacket(ReceivedMessage& message, SharedNodePointer sendingNode, bool wasStatsPacket) { // Attempt to identify the sender from its address. if (sendingNode) { const QUuid& nodeUUID = sendingNode->getUUID(); @@ -3884,13 +3887,13 @@ void Application::trackIncomingOctreePacket(NLPacket& packet, SharedNodePointer _octreeServerSceneStats.withWriteLock([&] { if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) { OctreeSceneStats& stats = _octreeServerSceneStats[nodeUUID]; - stats.trackIncomingOctreePacket(packet, wasStatsPacket, sendingNode->getClockSkewUsec()); + stats.trackIncomingOctreePacket(message, wasStatsPacket, sendingNode->getClockSkewUsec()); } }); } } -int Application::processOctreeStats(NLPacket& packet, SharedNodePointer sendingNode) { +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 @@ -3902,7 +3905,7 @@ int Application::processOctreeStats(NLPacket& packet, SharedNodePointer sendingN // now that we know the node ID, let's add these stats to the stats for that node... _octreeServerSceneStats.withWriteLock([&] { OctreeSceneStats& octreeStats = _octreeServerSceneStats[nodeUUID]; - statsMessageLength = octreeStats.unpackFromPacket(packet); + statsMessageLength = octreeStats.unpackFromPacket(message); // see if this is the first we've heard of this node... NodeToJurisdictionMap* jurisdiction = NULL; @@ -4070,10 +4073,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerGlobalObject("Scene", DependencyManager::get().data()); scriptEngine->registerGlobalObject("ScriptDiscoveryService", this->getRunningScriptsWidget()); - -#ifdef HAVE_RTMIDI - scriptEngine->registerGlobalObject("MIDI", &MIDIManager::getInstance()); -#endif } bool Application::canAcceptURL(const QString& urlString) { @@ -4085,7 +4084,7 @@ bool Application::canAcceptURL(const QString& urlString) { QString lowerPath = url.path().toLower(); while (i.hasNext()) { i.next(); - if (lowerPath.endsWith(i.key())) { + if (lowerPath.endsWith(i.key(), Qt::CaseInsensitive)) { return true; } } @@ -4105,7 +4104,7 @@ bool Application::acceptURL(const QString& urlString, bool defaultUpload) { QString lowerPath = url.path().toLower(); while (i.hasNext()) { i.next(); - if (lowerPath.endsWith(i.key())) { + if (lowerPath.endsWith(i.key(), Qt::CaseInsensitive)) { AcceptURLMethod method = i.value(); return (this->*method)(urlString); } @@ -4205,7 +4204,7 @@ bool Application::askToUploadAsset(const QString& filename) { messageBox.setDefaultButton(QMessageBox::Ok); // Option to drop model in world for models - if (filename.endsWith(FBX_EXTENSION) || filename.endsWith(OBJ_EXTENSION)) { + if (filename.endsWith(FBX_EXTENSION, Qt::CaseInsensitive) || filename.endsWith(OBJ_EXTENSION, Qt::CaseInsensitive)) { auto checkBox = new QCheckBox(&messageBox); checkBox->setText("Add to scene"); messageBox.setCheckBox(checkBox); @@ -4240,7 +4239,8 @@ void Application::modelUploadFinished(AssetUpload* upload, const QString& hash) auto filename = QFileInfo(upload->getFilename()).fileName(); if ((upload->getError() == AssetUpload::NoError) && - (filename.endsWith(FBX_EXTENSION) || filename.endsWith(OBJ_EXTENSION))) { + (upload->getExtension().endsWith(FBX_EXTENSION, Qt::CaseInsensitive) || + upload->getExtension().endsWith(OBJ_EXTENSION, Qt::CaseInsensitive))) { auto entities = DependencyManager::get(); @@ -4521,7 +4521,7 @@ void Application::takeSnapshot() { player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath())); player->play(); - QString fileName = Snapshot::saveSnapshot(_glWidget->grabFrameBuffer()); + QString fileName = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot()); AccountManager& accountManager = AccountManager::getInstance(); if (!accountManager.isLoggedIn()) { @@ -4532,7 +4532,6 @@ void Application::takeSnapshot() { _snapshotShareDialog = new SnapshotShareDialog(fileName, _glWidget); } _snapshotShareDialog->show(); - } float Application::getRenderResolutionScale() const { @@ -4715,10 +4714,6 @@ const DisplayPlugin* Application::getActiveDisplayPlugin() const { return ((Application*)this)->getActiveDisplayPlugin(); } -bool _activatingDisplayPlugin{ false }; -QVector> _currentDisplayPluginActions; -QVector> _currentInputPluginActions; - static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool active = false) { auto menu = Menu::getInstance(); QString name = displayPlugin->getName(); @@ -4748,9 +4743,10 @@ void Application::updateDisplayMode() { bool first = true; foreach(auto displayPlugin, displayPlugins) { addDisplayPluginToMenu(displayPlugin, first); - QObject::connect(displayPlugin.get(), &DisplayPlugin::requestRender, [this] { - paintGL(); - }); + // This must be a queued connection to avoid a deadlock + QObject::connect(displayPlugin.get(), &DisplayPlugin::requestRender, + this, &Application::paintGL, Qt::QueuedConnection); + QObject::connect(displayPlugin.get(), &DisplayPlugin::recommendedFramebufferSizeChanged, [this](const QSize & size) { resizeGL(); }); @@ -4792,19 +4788,18 @@ void Application::updateDisplayMode() { return; } - if (!_currentDisplayPluginActions.isEmpty()) { + + if (!_pluginContainer->currentDisplayActions().isEmpty()) { auto menu = Menu::getInstance(); - foreach(auto itemInfo, _currentDisplayPluginActions) { + foreach(auto itemInfo, _pluginContainer->currentDisplayActions()) { menu->removeMenuItem(itemInfo.first, itemInfo.second); } - _currentDisplayPluginActions.clear(); + _pluginContainer->currentDisplayActions().clear(); } if (newDisplayPlugin) { _offscreenContext->makeCurrent(); - _activatingDisplayPlugin = true; newDisplayPlugin->activate(); - _activatingDisplayPlugin = false; _offscreenContext->makeCurrent(); offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize())); _offscreenContext->makeCurrent(); @@ -4930,7 +4925,7 @@ mat4 Application::getEyeOffset(int eye) const { mat4 Application::getHMDSensorPose() const { if (isHMDMode()) { - return getActiveDisplayPlugin()->getHeadPose(); + return getActiveDisplayPlugin()->getHeadPose(_frameCount); } return mat4(); } diff --git a/interface/src/Application.h b/interface/src/Application.h index 730158c689..ce33f051ef 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -65,7 +65,7 @@ #include "ui/ToolWindow.h" #include "UndoStackScriptingInterface.h" -class OffscreenGlCanvas; +class OffscreenGLCanvas; class GLCanvas; class FaceTracker; class MainWindow; @@ -158,6 +158,7 @@ public: bool isForeground() const { return _isForeground; } + uint32_t getFrameCount() { return _frameCount; } float getFps() const { return _fps; } float const HMD_TARGET_FRAME_RATE = 75.0f; float const DESKTOP_TARGET_FRAME_RATE = 60.0f; @@ -185,7 +186,7 @@ public: virtual float getSizeScale() const; virtual int getBoundaryLevelAdjust() const; virtual PickRay computePickRay(float x, float y) const; - virtual const glm::vec3& getAvatarPosition() const; + virtual glm::vec3 getAvatarPosition() const; virtual void overrideEnvironmentData(const EnvironmentData& newData) { _environment.override(newData); } virtual void endOverrideEnvironmentData() { _environment.endOverride(); } virtual qreal getDevicePixelRatio(); @@ -328,7 +329,7 @@ private slots: void activeChanged(Qt::ApplicationState state); void domainSettingsReceived(const QJsonObject& domainSettingsObject); - void handleDomainConnectionDeniedPacket(QSharedPointer packet); + void handleDomainConnectionDeniedPacket(QSharedPointer message); void notifyPacketVersionMismatch(); @@ -394,8 +395,8 @@ private: bool importSVOFromURL(const QString& urlString); - int processOctreeStats(NLPacket& packet, SharedNodePointer sendingNode); - void trackIncomingOctreePacket(NLPacket& packet, SharedNodePointer sendingNode, bool wasStatsPacket); + int processOctreeStats(ReceivedMessage& message, SharedNodePointer sendingNode); + void trackIncomingOctreePacket(ReceivedMessage& message, SharedNodePointer sendingNode, bool wasStatsPacket); void resizeEvent(QResizeEvent* size); @@ -421,10 +422,13 @@ private: bool _dependencyManagerIsSetup; - OffscreenGlCanvas* _offscreenContext { nullptr }; + OffscreenGLCanvas* _offscreenContext { nullptr }; DisplayPluginPointer _displayPlugin; InputPluginList _activeInputPlugins; + bool _activatingDisplayPlugin { false }; + QMap _lockedFramebufferMap; + MainWindow* _window; ToolWindow* _toolWindow; diff --git a/interface/src/GLCanvas.cpp b/interface/src/GLCanvas.cpp index d9cde868a9..ec96f7c5d4 100644 --- a/interface/src/GLCanvas.cpp +++ b/interface/src/GLCanvas.cpp @@ -9,133 +9,13 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +// FIXME ordering of headers #include "Application.h" #include "GLCanvas.h" -#include -#include -#include - -#include "MainWindow.h" -#include "Menu.h" - -static QGLFormat& getDesiredGLFormat() { - // Specify an OpenGL 3.3 format using the Core profile. - // That is, no old-school fixed pipeline functionality - static QGLFormat glFormat; - static std::once_flag once; - std::call_once(once, [] { - glFormat.setVersion(4, 1); - glFormat.setProfile(QGLFormat::CoreProfile); // Requires >=Qt-4.8.0 - glFormat.setSampleBuffers(false); - glFormat.setDepth(false); - glFormat.setStencil(false); - }); - return glFormat; -} - -GLCanvas::GLCanvas() : QGLWidget(getDesiredGLFormat()) { -#ifdef Q_OS_LINUX - // Cause GLCanvas::eventFilter to be called. - // It wouldn't hurt to do this on Mac and PC too; but apparently it's only needed on linux. - qApp->installEventFilter(this); -#endif -} - -int GLCanvas::getDeviceWidth() const { - return width() * (windowHandle() ? (float)windowHandle()->devicePixelRatio() : 1.0f); -} - -int GLCanvas::getDeviceHeight() const { - return height() * (windowHandle() ? (float)windowHandle()->devicePixelRatio() : 1.0f); -} - -void GLCanvas::initializeGL() { - setAttribute(Qt::WA_AcceptTouchEvents); - setAcceptDrops(true); - // Note, we *DO NOT* want Qt to automatically swap buffers for us. This results in the "ringing" bug mentioned in WL#19514 when we're throttling the framerate. - setAutoBufferSwap(false); -} - -void GLCanvas::paintGL() { - PROFILE_RANGE(__FUNCTION__); - - // FIXME - I'm not sure why this still remains, it appears as if this GLCanvas gets a single paintGL call near - // the beginning of the application starting up. I'm not sure if we really need to call Application::paintGL() - // in this case, since the display plugins eventually handle all the painting - bool isThrottleFPSEnabled = Menu::getInstance()->isOptionChecked(MenuOption::ThrottleFPSIfNotFocus); - if (!qApp->getWindow()->isMinimized() || !isThrottleFPSEnabled) { - qApp->paintGL(); - } -} - -void GLCanvas::resizeGL(int width, int height) { - qApp->resizeGL(); -} - bool GLCanvas::event(QEvent* event) { - switch (event->type()) { - case QEvent::MouseMove: - case QEvent::MouseButtonPress: - case QEvent::MouseButtonRelease: - case QEvent::MouseButtonDblClick: - case QEvent::KeyPress: - case QEvent::KeyRelease: - case QEvent::FocusIn: - case QEvent::FocusOut: - case QEvent::Resize: - case QEvent::TouchBegin: - case QEvent::TouchEnd: - case QEvent::TouchUpdate: - case QEvent::Wheel: - case QEvent::DragEnter: - case QEvent::Drop: - if (QCoreApplication::sendEvent(QCoreApplication::instance(), event)) { - return true; - } - break; - case QEvent::Paint: - // Ignore paint events that occur after we've decided to quit - if (qApp->isAboutToQuit()) { - return true; - } - break; - - default: - break; + if (QEvent::Paint == event->type() && qApp->isAboutToQuit()) { + return true; } - return QGLWidget::event(event); -} - - -// Pressing Alt (and Meta) key alone activates the menubar because its style inherits the -// SHMenuBarAltKeyNavigation from QWindowsStyle. This makes it impossible for a scripts to -// receive keyPress events for the Alt (and Meta) key in a reliable manner. -// -// This filter catches events before QMenuBar can steal the keyboard focus. -// The idea was borrowed from -// http://www.archivum.info/qt-interest@trolltech.com/2006-09/00053/Re-(Qt4)-Alt-key-focus-QMenuBar-(solved).html - -bool GLCanvas::eventFilter(QObject*, QEvent* event) { - switch (event->type()) { - case QEvent::KeyPress: - case QEvent::KeyRelease: - case QEvent::ShortcutOverride: - { - QKeyEvent* keyEvent = static_cast(event); - if (keyEvent->key() == Qt::Key_Alt || keyEvent->key() == Qt::Key_Meta) { - if (event->type() == QEvent::KeyPress) { - keyPressEvent(keyEvent); - } else if (event->type() == QEvent::KeyRelease) { - keyReleaseEvent(keyEvent); - } else { - QGLWidget::event(event); - } - return true; - } - } - default: - break; - } - return false; + return GLWidget::event(event); } diff --git a/interface/src/GLCanvas.h b/interface/src/GLCanvas.h index 73c5b5e8bf..f707046c7c 100644 --- a/interface/src/GLCanvas.h +++ b/interface/src/GLCanvas.h @@ -12,31 +12,13 @@ #ifndef hifi_GLCanvas_h #define hifi_GLCanvas_h -#include -#include -#include +#include /// customized canvas that simply forwards requests/events to the singleton application -class GLCanvas : public QGLWidget { +class GLCanvas : public GLWidget { Q_OBJECT - -public: - GLCanvas(); - - int getDeviceWidth() const; - int getDeviceHeight() const; - QSize getDeviceSize() const { return QSize(getDeviceWidth(), getDeviceHeight()); } - protected: - - virtual void initializeGL(); - virtual void paintGL(); - virtual void resizeGL(int width, int height); - virtual bool event(QEvent* event); - -private slots: - bool eventFilter(QObject*, QEvent* event); - + virtual bool event(QEvent* event) override; }; diff --git a/interface/src/InterfaceActionFactory.cpp b/interface/src/InterfaceActionFactory.cpp index 67b3b4a649..8ace11c0a0 100644 --- a/interface/src/InterfaceActionFactory.cpp +++ b/interface/src/InterfaceActionFactory.cpp @@ -21,17 +21,17 @@ EntityActionPointer interfaceActionFactory(EntityActionType type, const QUuid& id, EntityItemPointer ownerEntity) { switch (type) { case ACTION_TYPE_NONE: - return nullptr; + return EntityActionPointer(); case ACTION_TYPE_OFFSET: - return (EntityActionPointer) new ObjectActionOffset(id, ownerEntity); + return std::make_shared(id, ownerEntity); case ACTION_TYPE_SPRING: - return (EntityActionPointer) new ObjectActionSpring(id, ownerEntity); + return std::make_shared(id, ownerEntity); case ACTION_TYPE_HOLD: - return (EntityActionPointer) new AvatarActionHold(id, ownerEntity); + return std::make_shared(id, ownerEntity); } - assert(false); - return nullptr; + Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown entity action type"); + return EntityActionPointer(); } diff --git a/interface/src/InterfaceParentFinder.cpp b/interface/src/InterfaceParentFinder.cpp new file mode 100644 index 0000000000..112bae5bb8 --- /dev/null +++ b/interface/src/InterfaceParentFinder.cpp @@ -0,0 +1,37 @@ +// +// InterfaceParentFinder.cpp +// interface/src/ +// +// Created by Seth Alves on 2015-10-21 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include + +#include "InterfaceParentFinder.h" + +SpatiallyNestableWeakPointer InterfaceParentFinder::find(QUuid parentID) const { + SpatiallyNestableWeakPointer parent; + + if (parentID.isNull()) { + return parent; + } + + // search entities + EntityTreeRenderer* treeRenderer = qApp->getEntities(); + EntityTreePointer tree = treeRenderer->getTree(); + parent = tree->findEntityByEntityItemID(parentID); + if (!parent.expired()) { + return parent; + } + + // search avatars + QSharedPointer avatarManager = DependencyManager::get(); + return avatarManager->getAvatarBySessionID(parentID); +} diff --git a/interface/src/InterfaceParentFinder.h b/interface/src/InterfaceParentFinder.h new file mode 100644 index 0000000000..c8e8d4ed9f --- /dev/null +++ b/interface/src/InterfaceParentFinder.h @@ -0,0 +1,27 @@ +// +// InterfaceParentFinder.h +// interface/src/ +// +// Created by Seth Alves on 2015-10-21 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_InterfaceParentFinder_h +#define hifi_InterfaceParentFinder_h + +#include +#include + +#include + +class InterfaceParentFinder : public SpatialParentFinder { +public: + InterfaceParentFinder() { } + virtual ~InterfaceParentFinder() { } + virtual SpatiallyNestableWeakPointer find(QUuid parentID) const; +}; + +#endif // hifi_InterfaceParentFinder_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index d0c8b502c5..86b3987af1 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -29,7 +29,6 @@ #include "avatar/AvatarManager.h" #include "devices/DdeFaceTracker.h" #include "devices/Faceshift.h" -#include "devices/RealSense.h" #include "input-plugins/SpacemouseManager.h" #include "MainWindow.h" #include "scripting/MenuScriptingInterface.h" @@ -433,8 +432,6 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false, avatarManager.data(), SLOT(setShouldShowReceiveStats(bool))); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderSkeletonCollisionShapes); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderHeadCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderBoundingCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderLookAtVectors, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderLookAtTargets, 0, false); @@ -462,12 +459,6 @@ Menu::Menu() { MenuWrapper* leapOptionsMenu = handOptionsMenu->addMenu("Leap Motion"); addCheckableActionToQMenuAndActionHash(leapOptionsMenu, MenuOption::LeapMotionOnHMD, 0, false); -#ifdef HAVE_RSSDK - MenuWrapper* realSenseOptionsMenu = handOptionsMenu->addMenu("RealSense"); - addActionToQMenuAndActionHash(realSenseOptionsMenu, MenuOption::LoadRSSDKFile, 0, - RealSense::getInstance(), SLOT(loadRSSDKFile())); -#endif - MenuWrapper* networkMenu = developerMenu->addMenu("Network"); addActionToQMenuAndActionHash(networkMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches())); addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::DisableNackPackets, 0, false, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 6b51987479..3ff0b149f4 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -211,7 +211,6 @@ namespace MenuOption { const QString LeapMotionOnHMD = "Leap Motion on HMD"; const QString LoadScript = "Open and Run Script File..."; const QString LoadScriptURL = "Open and Run Script from URL..."; - const QString LoadRSSDKFile = "Load .rssdk file"; const QString LodTools = "LOD Tools"; const QString Login = "Login"; const QString Log = "Log"; @@ -239,10 +238,8 @@ namespace MenuOption { const QString ReloadContent = "Reload Content (Clears all caches)"; const QString RenderBoundingCollisionShapes = "Show Bounding Collision Shapes"; const QString RenderFocusIndicator = "Show Eye Focus"; - const QString RenderHeadCollisionShapes = "Show Head Collision Shapes"; const QString RenderLookAtTargets = "Show Look-at Targets"; const QString RenderLookAtVectors = "Show Look-at Vectors"; - const QString RenderSkeletonCollisionShapes = "Show Skeleton Collision Shapes"; const QString RenderResolution = "Scale Resolution"; const QString RenderResolutionOne = "1"; const QString RenderResolutionTwoThird = "2/3"; diff --git a/interface/src/PluginContainerProxy.cpp b/interface/src/PluginContainerProxy.cpp index 2e5c883897..048f079653 100644 --- a/interface/src/PluginContainerProxy.cpp +++ b/interface/src/PluginContainerProxy.cpp @@ -1,17 +1,22 @@ #include "PluginContainerProxy.h" -#include -#include +#include +#include #include #include #include +#include +#include #include "Application.h" #include "MainWindow.h" #include "GLCanvas.h" #include "ui/DialogsManager.h" +#include +#include + PluginContainerProxy::PluginContainerProxy() { } @@ -30,12 +35,7 @@ void PluginContainerProxy::removeMenu(const QString& menuName) { Menu::getInstance()->removeMenu(menuName); } -extern bool _activatingDisplayPlugin; -extern QVector> _currentDisplayPluginActions; -extern QVector> _currentInputPluginActions; -std::map _exclusiveGroups; - -QAction* PluginContainerProxy::addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable, bool checked, const QString& groupName) { +QAction* PluginContainerProxy::addMenuItem(PluginType type, const QString& path, const QString& name, std::function onClicked, bool checkable, bool checked, const QString& groupName) { auto menu = Menu::getInstance(); MenuWrapper* parentItem = menu->getMenu(path); QAction* action = menu->addActionToQMenuAndActionHash(parentItem, name); @@ -54,7 +54,7 @@ QAction* PluginContainerProxy::addMenuItem(const QString& path, const QString& n }); action->setCheckable(checkable); action->setChecked(checked); - if (_activatingDisplayPlugin) { + if (type == PluginType::DISPLAY_PLUGIN) { _currentDisplayPluginActions.push_back({ path, name }); } else { _currentInputPluginActions.push_back({ path, name }); @@ -150,10 +150,37 @@ void PluginContainerProxy::showDisplayPluginsTools() { DependencyManager::get()->hmdTools(true); } -QGLWidget* PluginContainerProxy::getPrimarySurface() { +GLWidget* PluginContainerProxy::getPrimaryWidget() { return qApp->_glWidget; } +QWindow* PluginContainerProxy::getPrimaryWindow() { + return qApp->_glWidget->windowHandle(); +} + +QOpenGLContext* PluginContainerProxy::getPrimaryContext() { + return qApp->_glWidget->context()->contextHandle(); +} + const DisplayPlugin* PluginContainerProxy::getActiveDisplayPlugin() const { return qApp->getActiveDisplayPlugin(); } + +bool PluginContainerProxy::makeRenderingContextCurrent() { + return qApp->_offscreenContext->makeCurrent(); +} + +void PluginContainerProxy::releaseSceneTexture(uint32_t texture) { + Q_ASSERT(QThread::currentThread() == qApp->thread()); + auto& framebufferMap = qApp->_lockedFramebufferMap; + Q_ASSERT(framebufferMap.contains(texture)); + auto framebufferPointer = framebufferMap[texture]; + framebufferMap.remove(texture); + auto framebufferCache = DependencyManager::get(); + framebufferCache->releaseFramebuffer(framebufferPointer); +} + +void PluginContainerProxy::releaseOverlayTexture(uint32_t texture) { + // FIXME implement present thread compositing +} + diff --git a/interface/src/PluginContainerProxy.h b/interface/src/PluginContainerProxy.h index 79f8287b66..3adc696ba9 100644 --- a/interface/src/PluginContainerProxy.h +++ b/interface/src/PluginContainerProxy.h @@ -2,19 +2,21 @@ #ifndef hifi_PluginContainerProxy_h #define hifi_PluginContainerProxy_h -#include -#include +#include +#include #include #include +class QActionGroup; + class PluginContainerProxy : public QObject, PluginContainer { Q_OBJECT PluginContainerProxy(); virtual ~PluginContainerProxy(); virtual void addMenu(const QString& menuName) override; virtual void removeMenu(const QString& menuName) override; - virtual QAction* addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") override; + virtual QAction* addMenuItem(PluginType type, const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") override; virtual void removeMenuItem(const QString& menuName, const QString& menuItem) override; virtual bool isOptionChecked(const QString& name) override; virtual void setIsOptionChecked(const QString& path, bool checked) override; @@ -22,13 +24,20 @@ class PluginContainerProxy : public QObject, PluginContainer { virtual void unsetFullscreen(const QScreen* avoidScreen = nullptr) override; virtual void showDisplayPluginsTools() override; virtual void requestReset() override; - virtual QGLWidget* getPrimarySurface() override; + virtual bool makeRenderingContextCurrent() override; + virtual void releaseSceneTexture(uint32_t texture) override; + virtual void releaseOverlayTexture(uint32_t texture) override; + virtual GLWidget* getPrimaryWidget() override; + virtual QWindow* getPrimaryWindow() override; + virtual QOpenGLContext* getPrimaryContext() override; virtual bool isForeground() override; virtual const DisplayPlugin* getActiveDisplayPlugin() const override; QRect _savedGeometry{ 10, 120, 800, 600 }; + std::map _exclusiveGroups; friend class Application; + }; #endif \ No newline at end of file diff --git a/interface/src/Stars.cpp b/interface/src/Stars.cpp index 6145529b52..c8fd5188e2 100644 --- a/interface/src/Stars.cpp +++ b/interface/src/Stars.cpp @@ -130,9 +130,9 @@ void Stars::render(RenderArgs* renderArgs, float alpha) { std::call_once(once, [&] { { - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(standardTransformPNTC_vert))); - auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(starsGrid_frag))); - auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); + auto vs = gpu::Shader::createVertex(std::string(standardTransformPNTC_vert)); + auto ps = gpu::Shader::createPixel(std::string(starsGrid_frag)); + auto program = gpu::Shader::createProgram(vs, ps); gpu::Shader::makeProgram((*program)); _timeSlot = program->getBuffers().findLocation(UNIFORM_TIME_NAME); if (_timeSlot == gpu::Shader::INVALID_LOCATION) { @@ -143,12 +143,12 @@ void Stars::render(RenderArgs* renderArgs, float alpha) { state->setDepthTest(gpu::State::DepthTest(false)); state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); - _gridPipeline.reset(gpu::Pipeline::create(program, state)); + _gridPipeline = gpu::Pipeline::create(program, state); } { - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(stars_vert))); - auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(stars_frag))); - auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); + auto vs = gpu::Shader::createVertex(std::string(stars_vert)); + auto ps = gpu::Shader::createPixel(std::string(stars_frag)); + auto program = gpu::Shader::createProgram(vs, ps); gpu::Shader::makeProgram((*program)); auto state = gpu::StatePointer(new gpu::State()); // enable decal blend @@ -156,7 +156,7 @@ void Stars::render(RenderArgs* renderArgs, float alpha) { state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); state->setAntialiasedLineEnable(true); // line smoothing also smooth points state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); - _starsPipeline.reset(gpu::Pipeline::create(program, state)); + _starsPipeline = gpu::Pipeline::create(program, state); } diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index bf4ddadb62..e2b92cc06f 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -38,7 +38,6 @@ #include "Hand.h" #include "Head.h" #include "Menu.h" -#include "ModelReferential.h" #include "Physics.h" #include "Util.h" #include "world.h" @@ -91,7 +90,6 @@ Avatar::Avatar(RigPointer rig) : _angularAcceleration(0.0f), _lastOrientation(), _leanScale(0.5f), - _scale(1.0f), _worldUpDirection(DEFAULT_UP_DIRECTION), _moving(false), _initialized(false), @@ -101,6 +99,7 @@ Avatar::Avatar(RigPointer rig) : // we may have been created in the network thread, but we live in the main thread moveToThread(qApp->thread()); + setAvatarScale(1.0f); // give the pointer to our head to inherited _headData variable from AvatarData _headData = static_cast(new Head(this)); _handData = static_cast(new Hand(this)); @@ -125,12 +124,12 @@ void Avatar::init() { glm::vec3 Avatar::getChestPosition() const { // for now, let's just assume that the "chest" is halfway between the root and the neck glm::vec3 neckPosition; - return _skeletonModel.getNeckPosition(neckPosition) ? (_position + neckPosition) * 0.5f : _position; + return _skeletonModel.getNeckPosition(neckPosition) ? (getPosition() + neckPosition) * 0.5f : getPosition(); } glm::vec3 Avatar::getNeckPosition() const { glm::vec3 neckPosition; - return _skeletonModel.getNeckPosition(neckPosition) ? neckPosition : _position; + return _skeletonModel.getNeckPosition(neckPosition) ? neckPosition : getPosition(); } @@ -144,38 +143,14 @@ AABox Avatar::getBounds() const { float Avatar::getLODDistance() const { return DependencyManager::get()->getAvatarLODDistanceMultiplier() * - glm::distance(qApp->getCamera()->getPosition(), _position) / _scale; + glm::distance(qApp->getCamera()->getPosition(), getPosition()) / getAvatarScale(); } void Avatar::simulate(float deltaTime) { PerformanceTimer perfTimer("simulate"); - // update the avatar's position according to its referential - if (_referential) { - if (_referential->hasExtraData()) { - EntityTreePointer tree = qApp->getEntities()->getTree(); - switch (_referential->type()) { - case Referential::MODEL: - _referential = new ModelReferential(_referential, - tree, - this); - break; - case Referential::JOINT: - _referential = new JointReferential(_referential, - tree, - this); - break; - default: - qCDebug(interfaceapp) << "[WARNING] Avatar::simulate(): Unknown referential type."; - break; - } - } - - _referential->update(); - } - - if (_scale != _targetScale) { - setScale(_targetScale); + if (getAvatarScale() != _targetScale) { + setAvatarScale(_targetScale); } // update the billboard render flag @@ -193,7 +168,7 @@ void Avatar::simulate(float deltaTime) { const bool isControllerLogging = DependencyManager::get()->getRenderDistanceControllerIsLogging(); float renderDistance = DependencyManager::get()->getRenderDistance(); const float SKIP_HYSTERESIS_PROPORTION = isControllerLogging ? 0.0f : BILLBOARD_HYSTERESIS_PROPORTION; - float distance = glm::distance(qApp->getCamera()->getPosition(), _position); + float distance = glm::distance(qApp->getCamera()->getPosition(), getPosition()); if (_shouldSkipRender) { if (distance < renderDistance * (1.0f - SKIP_HYSTERESIS_PROPORTION)) { _shouldSkipRender = false; @@ -212,7 +187,7 @@ void Avatar::simulate(float deltaTime) { // simple frustum check float boundingRadius = getBillboardSize(); - bool inViewFrustum = qApp->getViewFrustum()->sphereInFrustum(_position, boundingRadius) != + bool inViewFrustum = qApp->getViewFrustum()->sphereInFrustum(getPosition(), boundingRadius) != ViewFrustum::OUTSIDE; { @@ -226,16 +201,17 @@ void Avatar::simulate(float deltaTime) { _skeletonModel.getRig()->copyJointsFromJointData(_jointData); _skeletonModel.simulate(deltaTime, _hasNewJointRotations || _hasNewJointTranslations); simulateAttachments(deltaTime); + locationChanged(); // joints changed, so if there are any children, update them. _hasNewJointRotations = false; _hasNewJointTranslations = false; } { PerformanceTimer perfTimer("head"); - glm::vec3 headPosition = _position; + glm::vec3 headPosition = getPosition(); _skeletonModel.getHeadPosition(headPosition); Head* head = getHead(); head->setPosition(headPosition); - head->setScale(_scale); + head->setScale(getAvatarScale()); head->simulate(deltaTime, false, _shouldRenderBillboard); } } @@ -268,7 +244,7 @@ bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) { glm::vec3 theirLookAt = dynamic_pointer_cast(avatar)->getHead()->getLookAtPosition(); glm::vec3 myEyePosition = getHead()->getEyePosition(); - return glm::distance(theirLookAt, myEyePosition) <= (HEAD_SPHERE_RADIUS * getScale()); + return glm::distance(theirLookAt, myEyePosition) <= (HEAD_SPHERE_RADIUS * getAvatarScale()); } void Avatar::slamPosition(const glm::vec3& newPosition) { @@ -279,7 +255,7 @@ void Avatar::slamPosition(const glm::vec3& newPosition) { } void Avatar::applyPositionDelta(const glm::vec3& delta) { - _position += delta; + setPosition(getPosition() + delta); _positionDeltaAccumulator += delta; } @@ -345,15 +321,10 @@ void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptrupdate(); - } - auto& batch = *renderArgs->_batch; PROFILE_RANGE_BATCH(batch, __FUNCTION__); - if (glm::distance(DependencyManager::get()->getMyAvatar()->getPosition(), _position) < 10.0f) { + if (glm::distance(DependencyManager::get()->getMyAvatar()->getPosition(), getPosition()) < 10.0f) { auto geometryCache = DependencyManager::get(); auto deferredLighting = DependencyManager::get(); @@ -453,7 +424,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { const float BASE_LIGHT_DISTANCE = 2.0f; const float LIGHT_EXPONENT = 1.0f; const float LIGHT_CUTOFF = glm::radians(80.0f); - float distance = BASE_LIGHT_DISTANCE * _scale; + float distance = BASE_LIGHT_DISTANCE * getAvatarScale(); glm::vec3 position = glm::mix(_skeletonModel.getTranslation(), getHead()->getFaceModel().getTranslation(), 0.9f); glm::quat orientation = getOrientation(); foreach (const AvatarManager::LocalLight& light, DependencyManager::get()->getLocalLights()) { @@ -463,16 +434,6 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { } } - /* - // TODO: re-implement these when we have more detailed avatar collision shapes - bool renderSkeleton = Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes); - if (renderSkeleton) { - } - bool renderHead = Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes); - if (renderHead && shouldRenderHead(renderArgs)) { - } - */ - bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes); if (renderBounding && shouldRenderHead(renderArgs) && _skeletonModel.isRenderable()) { PROFILE_RANGE_BATCH(batch, __FUNCTION__":skeletonBoundingCollisionShapes"); @@ -484,7 +445,8 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { static const float INDICATOR_OFFSET = 0.22f; static const float INDICATOR_RADIUS = 0.03f; static const glm::vec4 LOOK_AT_INDICATOR_COLOR = { 0.8f, 0.0f, 0.0f, 0.75f }; - glm::vec3 position = glm::vec3(_position.x, getDisplayNamePosition().y + INDICATOR_OFFSET, _position.z); + glm::vec3 avatarPosition = getPosition(); + glm::vec3 position = glm::vec3(avatarPosition.x, getDisplayNamePosition().y + INDICATOR_OFFSET, avatarPosition.z); PROFILE_RANGE_BATCH(batch, __FUNCTION__":renderFocusIndicator"); Transform transform; transform.setTranslation(position); @@ -518,7 +480,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { } DependencyManager::get()->renderSolidSphereInstance(batch, - Transform(transform).postScale(eyeDiameter * _scale / 2.0f + RADIUS_INCREMENT), + Transform(transform).postScale(eyeDiameter * getAvatarScale() / 2.0f + RADIUS_INCREMENT), glm::vec4(LOOKING_AT_ME_COLOR, alpha)); position = getHead()->getRightEyePosition(); @@ -528,7 +490,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { eyeDiameter = DEFAULT_EYE_DIAMETER; } DependencyManager::get()->renderSolidSphereInstance(batch, - Transform(transform).postScale(eyeDiameter * _scale / 2.0f + RADIUS_INCREMENT), + Transform(transform).postScale(eyeDiameter * getAvatarScale() / 2.0f + RADIUS_INCREMENT), glm::vec4(LOOKING_AT_ME_COLOR, alpha)); } @@ -559,7 +521,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { Transform transform; - transform.setTranslation(_position); + transform.setTranslation(getPosition()); transform.setScale(height); transform.postScale(sphereRadius); DependencyManager::get()->renderSolidSphereInstance(batch, @@ -662,9 +624,9 @@ void Avatar::simulateAttachments(float deltaTime) { glm::quat jointRotation; if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) && _skeletonModel.getJointRotationInWorldFrame(jointIndex, jointRotation)) { - model->setTranslation(jointPosition + jointRotation * attachment.translation * _scale); + model->setTranslation(jointPosition + jointRotation * attachment.translation * getAvatarScale()); model->setRotation(jointRotation * attachment.rotation); - model->setScaleToFit(true, _scale * attachment.scale, true); // hack to force rescale + model->setScaleToFit(true, getAvatarScale() * attachment.scale, true); // hack to force rescale model->setSnapModelToCenter(false); // hack to force resnap model->setSnapModelToCenter(true); model->simulate(deltaTime); @@ -694,14 +656,14 @@ void Avatar::renderBillboard(RenderArgs* renderArgs) { } // rotate about vertical to face the camera glm::quat rotation = getOrientation(); - glm::vec3 cameraVector = glm::inverse(rotation) * (qApp->getCamera()->getPosition() - _position); + glm::vec3 cameraVector = glm::inverse(rotation) * (qApp->getCamera()->getPosition() - getPosition()); rotation = rotation * glm::angleAxis(atan2f(-cameraVector.x, -cameraVector.z), glm::vec3(0.0f, 1.0f, 0.0f)); // compute the size from the billboard camera parameters and scale float size = getBillboardSize(); Transform transform; - transform.setTranslation(_position); + transform.setTranslation(getPosition()); transform.setRotation(rotation); transform.setScale(size); @@ -719,7 +681,7 @@ void Avatar::renderBillboard(RenderArgs* renderArgs) { } float Avatar::getBillboardSize() const { - return _scale * BILLBOARD_DISTANCE * glm::tan(glm::radians(BILLBOARD_FIELD_OF_VIEW / 2.0f)); + return getAvatarScale() * BILLBOARD_DISTANCE * glm::tan(glm::radians(BILLBOARD_FIELD_OF_VIEW / 2.0f)); } #ifdef DEBUG @@ -754,9 +716,9 @@ glm::vec3 Avatar::getDisplayNamePosition() const { const float HEAD_PROPORTION = 0.75f; float billboardSize = getBillboardSize(); - DEBUG_VALUE("_position =", _position); + DEBUG_VALUE("_position =", getPosition()); DEBUG_VALUE("billboardSize =", billboardSize); - namePosition = _position + bodyUpDirection * (billboardSize * HEAD_PROPORTION); + namePosition = getPosition() + bodyUpDirection * (billboardSize * HEAD_PROPORTION); } if (glm::any(glm::isnan(namePosition)) || glm::any(glm::isinf(namePosition))) { @@ -868,7 +830,7 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, co } void Avatar::setSkeletonOffset(const glm::vec3& offset) { - const float MAX_OFFSET_LENGTH = _scale * 0.5f; + const float MAX_OFFSET_LENGTH = getAvatarScale() * 0.5f; float offsetLength = glm::length(offset); if (offsetLength > MAX_OFFSET_LENGTH) { _skeletonOffset = (MAX_OFFSET_LENGTH / offsetLength) * offset; @@ -881,7 +843,7 @@ glm::vec3 Avatar::getSkeletonPosition() const { // The avatar is rotated PI about the yAxis, so we have to correct for it // to get the skeleton offset contribution in the world-frame. const glm::quat FLIP = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)); - return _position + getOrientation() * FLIP * _skeletonOffset; + return getPosition() + getOrientation() * FLIP * _skeletonOffset; } QVector Avatar::getJointRotations() const { @@ -896,23 +858,28 @@ QVector Avatar::getJointRotations() const { } glm::quat Avatar::getJointRotation(int index) const { - if (QThread::currentThread() != thread()) { - return AvatarData::getJointRotation(index); - } glm::quat rotation; _skeletonModel.getJointRotation(index, rotation); return rotation; } glm::vec3 Avatar::getJointTranslation(int index) const { - if (QThread::currentThread() != thread()) { - return AvatarData::getJointTranslation(index); - } glm::vec3 translation; _skeletonModel.getJointTranslation(index, translation); return translation; } +glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const { + glm::quat rotation; + _skeletonModel.getAbsoluteJointRotationInRigFrame(index, rotation); + return Quaternions::Y_180 * rotation; +} + +glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const { + glm::vec3 translation; + _skeletonModel.getAbsoluteJointTranslationInRigFrame(index, translation); + return Quaternions::Y_180 * translation; +} int Avatar::getJointIndex(const QString& name) const { if (QThread::currentThread() != thread()) { @@ -960,7 +927,7 @@ glm::vec3 Avatar::getJointPosition(const QString& name) const { void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const { //Scale a world space vector as if it was relative to the position - positionToScale = _position + _scale * (positionToScale - _position); + positionToScale = getPosition() + getAvatarScale() * (positionToScale - getPosition()); } void Avatar::setFaceModelURL(const QUrl& faceModelURL) { @@ -1000,7 +967,7 @@ void Avatar::setAttachmentData(const QVector& attachmentData) { for (int i = 0; i < attachmentData.size(); i++) { _attachmentModels[i]->setURL(attachmentData.at(i).modelURL); _attachmentModels[i]->setSnapModelToCenter(true); - _attachmentModels[i]->setScaleToFit(true, _scale * _attachmentData.at(i).scale); + _attachmentModels[i]->setScaleToFit(true, getAvatarScale() * _attachmentData.at(i).scale); } } @@ -1019,12 +986,12 @@ int Avatar::parseDataFromBuffer(const QByteArray& buffer) { } // change in position implies movement - glm::vec3 oldPosition = _position; + glm::vec3 oldPosition = getPosition(); int bytesRead = AvatarData::parseDataFromBuffer(buffer); const float MOVE_DISTANCE_THRESHOLD = 0.001f; - _moving = glm::distance(oldPosition, _position) > MOVE_DISTANCE_THRESHOLD; + _moving = glm::distance(oldPosition, getPosition()) > MOVE_DISTANCE_THRESHOLD; if (_moving && _motionState) { _motionState->addDirtyFlags(Simulation::DIRTY_POSITION); } @@ -1088,12 +1055,12 @@ void Avatar::renderJointConnectingCone(gpu::Batch& batch, glm::vec3 position1, g } } -void Avatar::setScale(float scale) { - _scale = scale; - - if (_targetScale * (1.0f - RESCALING_TOLERANCE) < _scale && - _scale < _targetScale * (1.0f + RESCALING_TOLERANCE)) { - _scale = _targetScale; +void Avatar::setAvatarScale(float scale) { + if (_targetScale * (1.0f - RESCALING_TOLERANCE) < scale && + scale < _targetScale * (1.0f + RESCALING_TOLERANCE)) { + setScale(glm::vec3(_targetScale)); + } else { + setScale(glm::vec3(scale)); } } @@ -1108,7 +1075,7 @@ float Avatar::getHeadHeight() const { // HACK: We have a really odd case when fading out for some models where this value explodes float result = extents.maximum.y - extents.minimum.y; - if (result >= 0.0f && result < 100.0f * _scale ) { + if (result >= 0.0f && result < 100.0f * getAvatarScale() ) { return result; } } @@ -1116,7 +1083,7 @@ float Avatar::getHeadHeight() const { extents = _skeletonModel.getMeshExtents(); glm::vec3 neckPosition; if (!extents.isEmpty() && extents.isValid() && _skeletonModel.getNeckPosition(neckPosition)) { - return extents.maximum.y / 2.0f - neckPosition.y + _position.y; + return extents.maximum.y / 2.0f - neckPosition.y + getPosition().y; } const float DEFAULT_HEAD_HEIGHT = 0.25f; @@ -1189,3 +1156,13 @@ glm::quat Avatar::getRightPalmRotation() { getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightRotation); return rightRotation; } + +void Avatar::setPosition(const glm::vec3& position) { + AvatarData::setPosition(position); + updateAttitude(); +} + +void Avatar::setOrientation(const glm::quat& orientation) { + AvatarData::setOrientation(orientation); + updateAttitude(); +} diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 305eab473c..99a4fc52a9 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -89,7 +89,7 @@ public: const SkeletonModel& getSkeletonModel() const { return _skeletonModel; } const QVector& getAttachmentModels() const { return _attachmentModels; } glm::vec3 getChestPosition() const; - float getScale() const { return _scale; } + float getAvatarScale() const { return getScale().y; } const Head* getHead() const { return static_cast(_headData); } Head* getHead() { return static_cast(_headData); } Hand* getHand() { return static_cast(_handData); } @@ -108,6 +108,9 @@ public: virtual int getJointIndex(const QString& name) const; virtual QStringList getJointNames() const; + virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; + virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; + virtual void setFaceModelURL(const QUrl& faceModelURL); virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); virtual void setAttachmentData(const QVector& attachmentData); @@ -155,6 +158,9 @@ public: void setMotionState(AvatarMotionState* motionState) { _motionState = motionState; } AvatarMotionState* getMotionState() { return _motionState; } + virtual void setPosition(const glm::vec3& position) override; + virtual void setOrientation(const glm::quat& orientation) override; + public slots: // FIXME - these should be migrated to use Pose data instead @@ -186,7 +192,6 @@ protected: glm::quat _lastOrientation; float _leanScale; - float _scale; glm::vec3 _worldUpDirection; float _stringLength; bool _moving; ///< set when position is changing @@ -198,7 +203,7 @@ protected: glm::vec3 getBodyUpDirection() const { return getOrientation() * IDENTITY_UP; } glm::vec3 getBodyFrontDirection() const { return getOrientation() * IDENTITY_FRONT; } glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const; - void setScale(float scale); + void setAvatarScale(float scale); void measureMotionDerivatives(float deltaTime); float getSkeletonHeight() const; diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index b06d66a035..fab838aa68 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -9,63 +9,72 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "QVariantGLM.h" -#include "avatar/AvatarManager.h" - #include "AvatarActionHold.h" +#include + +#include "avatar/AvatarManager.h" + const uint16_t AvatarActionHold::holdVersion = 1; AvatarActionHold::AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntity) : - ObjectActionSpring(id, ownerEntity), - _relativePosition(glm::vec3(0.0f)), - _relativeRotation(glm::quat()), - _hand("right"), - _holderID(QUuid()) { + ObjectActionSpring(id, ownerEntity) +{ _type = ACTION_TYPE_HOLD; - #if WANT_DEBUG +#if WANT_DEBUG qDebug() << "AvatarActionHold::AvatarActionHold"; - #endif +#endif } AvatarActionHold::~AvatarActionHold() { - #if WANT_DEBUG +#if WANT_DEBUG qDebug() << "AvatarActionHold::~AvatarActionHold"; - #endif +#endif } std::shared_ptr AvatarActionHold::getTarget(glm::quat& rotation, glm::vec3& position) { - std::shared_ptr holdingAvatar = nullptr; + auto avatarManager = DependencyManager::get(); + auto holdingAvatar = std::static_pointer_cast(avatarManager->getAvatarBySessionID(_holderID)); + + if (!holdingAvatar) { + return holdingAvatar; + } withTryReadLock([&]{ - QSharedPointer avatarManager = DependencyManager::get(); - AvatarSharedPointer holdingAvatarData = avatarManager->getAvatarBySessionID(_holderID); - holdingAvatar = std::static_pointer_cast(holdingAvatarData); - - if (holdingAvatar) { - glm::vec3 offset; - glm::vec3 palmPosition; - glm::quat palmRotation; - if (_hand == "right") { + bool isRightHand = (_hand == "right"); + glm::vec3 palmPosition { Vectors::ZERO }; + glm::quat palmRotation { Quaternions::IDENTITY }; + + if (_ignoreIK && holdingAvatar->isMyAvatar()) { + // We cannot ignore other avatars IK and this is not the point of this option + // This is meant to make the grabbing behavior more reactive. + if (isRightHand) { + palmPosition = holdingAvatar->getHand()->getCopyOfPalmData(HandData::RightHand).getPosition(); + palmRotation = holdingAvatar->getHand()->getCopyOfPalmData(HandData::RightHand).getRotation(); + } else { + palmPosition = holdingAvatar->getHand()->getCopyOfPalmData(HandData::LeftHand).getPosition(); + palmRotation = holdingAvatar->getHand()->getCopyOfPalmData(HandData::LeftHand).getRotation(); + } + } else { + if (isRightHand) { palmPosition = holdingAvatar->getRightPalmPosition(); palmRotation = holdingAvatar->getRightPalmRotation(); } else { palmPosition = holdingAvatar->getLeftPalmPosition(); palmRotation = holdingAvatar->getLeftPalmRotation(); } - - rotation = palmRotation * _relativeRotation; - offset = rotation * _relativePosition; - position = palmPosition + offset; } + + rotation = palmRotation * _relativeRotation; + position = palmPosition + rotation * _relativePosition; }); return holdingAvatar; } void AvatarActionHold::updateActionWorker(float deltaTimeStep) { - glm::quat rotation; - glm::vec3 position; + glm::quat rotation { Quaternions::IDENTITY }; + glm::vec3 position { Vectors::ZERO }; bool valid = false; int holdCount = 0; @@ -168,6 +177,7 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) { QUuid holderID; bool kinematic; bool kinematicSetVelocity; + bool ignoreIK; bool needUpdate = false; bool somethingChanged = ObjectAction::updateArguments(arguments); @@ -203,14 +213,20 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) { ok = true; kinematic = EntityActionInterface::extractBooleanArgument("hold", arguments, "kinematic", ok, false); if (!ok) { - _kinematic = false; + kinematic = _kinematic; } ok = true; kinematicSetVelocity = EntityActionInterface::extractBooleanArgument("hold", arguments, "kinematicSetVelocity", ok, false); if (!ok) { - _kinematicSetVelocity = false; + kinematicSetVelocity = _kinematicSetVelocity; + } + + ok = true; + ignoreIK = EntityActionInterface::extractBooleanArgument("hold", arguments, "ignoreIK", ok, false); + if (!ok) { + ignoreIK = _ignoreIK; } if (somethingChanged || @@ -220,7 +236,8 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) { hand != _hand || holderID != _holderID || kinematic != _kinematic || - kinematicSetVelocity != _kinematicSetVelocity) { + kinematicSetVelocity != _kinematicSetVelocity || + ignoreIK != _ignoreIK) { needUpdate = true; } }); @@ -236,6 +253,7 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) { _holderID = holderID; _kinematic = kinematic; _kinematicSetVelocity = kinematicSetVelocity; + _ignoreIK = ignoreIK; _active = true; auto ownerEntity = _ownerEntity.lock(); @@ -260,6 +278,7 @@ QVariantMap AvatarActionHold::getArguments() { arguments["hand"] = _hand; arguments["kinematic"] = _kinematic; arguments["kinematicSetVelocity"] = _kinematicSetVelocity; + arguments["ignoreIK"] = _ignoreIK; }); return arguments; } diff --git a/interface/src/avatar/AvatarActionHold.h b/interface/src/avatar/AvatarActionHold.h index b8b1a64e84..63f30a75d9 100644 --- a/interface/src/avatar/AvatarActionHold.h +++ b/interface/src/avatar/AvatarActionHold.h @@ -38,17 +38,19 @@ public: std::shared_ptr getTarget(glm::quat& rotation, glm::vec3& position); private: + void doKinematicUpdate(float deltaTimeStep); + static const uint16_t holdVersion; - glm::vec3 _relativePosition; - glm::quat _relativeRotation; - QString _hand; + glm::vec3 _relativePosition { Vectors::ZERO }; + glm::quat _relativeRotation { Quaternions::IDENTITY }; + QString _hand { "right" }; QUuid _holderID; - void doKinematicUpdate(float deltaTimeStep); bool _kinematic { false }; bool _kinematicSetVelocity { false }; bool _previousSet { false }; + bool _ignoreIK { false }; glm::vec3 _previousPositionalTarget; glm::quat _previousRotationalTarget; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 9df597109c..4e3d9b92fe 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -27,6 +27,8 @@ #include #include +#include +#include #include #include "Application.h" @@ -35,7 +37,6 @@ #include "Menu.h" #include "MyAvatar.h" #include "SceneScriptingInterface.h" -#include // 70 times per second - target is 60hz, but this helps account for any small deviations // in the update loop @@ -75,6 +76,13 @@ AvatarManager::AvatarManager(QObject* parent) : packetReceiver.registerListener(PacketType::AvatarBillboard, this, "processAvatarBillboardPacket"); } +const float SMALLEST_REASONABLE_HORIZON = 5.0f; // meters +Setting::Handle avatarRenderDistanceInverseHighLimit("avatarRenderDistanceHighLimit", 1.0f / SMALLEST_REASONABLE_HORIZON); +void AvatarManager::setRenderDistanceInverseHighLimit(float newValue) { + avatarRenderDistanceInverseHighLimit.set(newValue); + _renderDistanceController.setControlledValueHighLimit(newValue); +} + void AvatarManager::init() { _myAvatar->init(); { @@ -93,8 +101,7 @@ void AvatarManager::init() { const float target_fps = qApp->getTargetFrameRate(); _renderDistanceController.setMeasuredValueSetpoint(target_fps); - const float SMALLEST_REASONABLE_HORIZON = 5.0f; // meters - _renderDistanceController.setControlledValueHighLimit(1.0f / SMALLEST_REASONABLE_HORIZON); + _renderDistanceController.setControlledValueHighLimit(avatarRenderDistanceInverseHighLimit.get()); _renderDistanceController.setControlledValueLowLimit(1.0f / (float) TREE_SCALE); // Advice for tuning parameters: // See PIDController.h. There's a section on tuning in the reference. @@ -191,7 +198,7 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { while (fadingIterator != _avatarFades.end()) { auto avatar = std::static_pointer_cast(*fadingIterator); avatar->startUpdate(); - avatar->setTargetScale(avatar->getScale() * SHRINK_RATE, true); + avatar->setTargetScale(avatar->getAvatarScale() * SHRINK_RATE); if (avatar->getTargetScale() <= MIN_FADE_SCALE) { avatar->removeFromScene(*fadingIterator, scene, pendingChanges); fadingIterator = _avatarFades.erase(fadingIterator); @@ -402,7 +409,7 @@ void AvatarManager::updateAvatarRenderStatus(bool shouldRenderAvatars) { AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID) { if (sessionID == _myAvatar->getSessionUUID()) { - return std::static_pointer_cast(_myAvatar); + return _myAvatar; } return findAvatar(sessionID); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 96383b7e60..84a4bc44b8 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -70,14 +70,16 @@ public: // Expose results and parameter-tuning operations to other systems, such as stats and javascript. Q_INVOKABLE float getRenderDistance() { return _renderDistance; } + Q_INVOKABLE float getRenderDistanceInverseLowLimit() { return _renderDistanceController.getControlledValueLowLimit(); } + Q_INVOKABLE float getRenderDistanceInverseHighLimit() { return _renderDistanceController.getControlledValueHighLimit(); } Q_INVOKABLE int getNumberInRenderRange() { return _renderedAvatarCount; } Q_INVOKABLE bool getRenderDistanceControllerIsLogging() { return _renderDistanceController.getIsLogging(); } Q_INVOKABLE void setRenderDistanceControllerHistory(QString label, int size) { return _renderDistanceController.setHistorySize(label, size); } Q_INVOKABLE void setRenderDistanceKP(float newValue) { _renderDistanceController.setKP(newValue); } Q_INVOKABLE void setRenderDistanceKI(float newValue) { _renderDistanceController.setKI(newValue); } Q_INVOKABLE void setRenderDistanceKD(float newValue) { _renderDistanceController.setKD(newValue); } - Q_INVOKABLE void setRenderDistanceLowLimit(float newValue) { _renderDistanceController.setControlledValueLowLimit(newValue); } - Q_INVOKABLE void setRenderDistanceHighLimit(float newValue) { _renderDistanceController.setControlledValueHighLimit(newValue); } + Q_INVOKABLE void setRenderDistanceInverseLowLimit(float newValue) { _renderDistanceController.setControlledValueLowLimit(newValue); } + Q_INVOKABLE void setRenderDistanceInverseHighLimit(float newValue); public slots: void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; } diff --git a/interface/src/avatar/AvatarUpdate.cpp b/interface/src/avatar/AvatarUpdate.cpp index 52fa568879..99e5d2acaa 100644 --- a/interface/src/avatar/AvatarUpdate.cpp +++ b/interface/src/avatar/AvatarUpdate.cpp @@ -30,7 +30,8 @@ void AvatarUpdate::synchronousProcess() { // Keep our own updated value, so that our asynchronous code can consult it. _isHMDMode = qApp->isHMDMode(); - _headPose = qApp->getActiveDisplayPlugin()->getHeadPose(); + auto frameCount = qApp->getFrameCount(); + _headPose = qApp->getActiveDisplayPlugin()->getHeadPose(frameCount); if (_updateBillboard) { DependencyManager::get()->getMyAvatar()->doUpdateBillboard(); diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index 15a3163998..4f16449aa2 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -37,7 +37,7 @@ void Hand::simulate(float deltaTime, bool isMine) { void Hand::renderHandTargets(RenderArgs* renderArgs, bool isMine) { float avatarScale = 1.0f; if (_owningAvatar) { - avatarScale = _owningAvatar->getScale(); + avatarScale = _owningAvatar->getAvatarScale(); } const float alpha = 1.0f; diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 29ec781f23..aa2d268f3d 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -43,9 +43,11 @@ Head::Head(Avatar* owningAvatar) : _longTermAverageLoudness(-1.0f), _audioAttack(0.0f), _audioJawOpen(0.0f), + _trailingAudioJawOpen(0.0f), _mouth2(0.0f), _mouth3(0.0f), _mouth4(0.0f), + _mouthTime(0.0f), _renderLookatVectors(false), _renderLookatTarget(false), _saccade(0.0f, 0.0f, 0.0f), @@ -246,6 +248,16 @@ void Head::calculateMouthShapes() { const float JAW_OPEN_SCALE = 0.015f; const float JAW_OPEN_RATE = 0.9f; const float JAW_CLOSE_RATE = 0.90f; + const float TIMESTEP_CONSTANT = 0.0032f; + const float MMMM_POWER = 0.10f; + const float SMILE_POWER = 0.10f; + const float FUNNEL_POWER = 0.35f; + const float MMMM_SPEED = 2.685f; + const float SMILE_SPEED = 1.0f; + const float FUNNEL_SPEED = 2.335f; + const float STOP_GAIN = 5.0f; + + // From the change in loudness, decide how much to open or close the jaw float audioDelta = sqrtf(glm::max(_averageLoudness - _longTermAverageLoudness, 0.0f)) * JAW_OPEN_SCALE; if (audioDelta > _audioJawOpen) { _audioJawOpen += (audioDelta - _audioJawOpen) * JAW_OPEN_RATE; @@ -253,21 +265,14 @@ void Head::calculateMouthShapes() { _audioJawOpen *= JAW_CLOSE_RATE; } _audioJawOpen = glm::clamp(_audioJawOpen, 0.0f, 1.0f); + _trailingAudioJawOpen = glm::mix(_trailingAudioJawOpen, _audioJawOpen, 0.99f); - // _mouth2 = "mmmm" shape - // _mouth3 = "funnel" shape - // _mouth4 = "smile" shape - const float FUNNEL_PERIOD = 0.985f; - const float FUNNEL_RANDOM_PERIOD = 0.01f; - const float MMMM_POWER = 0.25f; - const float MMMM_PERIOD = 0.91f; - const float MMMM_RANDOM_PERIOD = 0.15f; - const float SMILE_PERIOD = 0.925f; - const float SMILE_RANDOM_PERIOD = 0.05f; - - _mouth3 = glm::mix(_audioJawOpen, _mouth3, FUNNEL_PERIOD + randFloat() * FUNNEL_RANDOM_PERIOD); - _mouth2 = glm::mix(_audioJawOpen * MMMM_POWER, _mouth2, MMMM_PERIOD + randFloat() * MMMM_RANDOM_PERIOD); - _mouth4 = glm::mix(_audioJawOpen, _mouth4, SMILE_PERIOD + randFloat() * SMILE_RANDOM_PERIOD); + // Advance time at a rate proportional to loudness, and move the mouth shapes through + // a cycle at differing speeds to create a continuous random blend of shapes. + _mouthTime += sqrtf(_averageLoudness) * TIMESTEP_CONSTANT; + _mouth2 = (sinf(_mouthTime * MMMM_SPEED) + 1.0f) * MMMM_POWER * glm::min(1.0f, _trailingAudioJawOpen * STOP_GAIN); + _mouth3 = (sinf(_mouthTime * FUNNEL_SPEED) + 1.0f) * FUNNEL_POWER * glm::min(1.0f, _trailingAudioJawOpen * STOP_GAIN); + _mouth4 = (sinf(_mouthTime * SMILE_SPEED) + 1.0f) * SMILE_POWER * glm::min(1.0f, _trailingAudioJawOpen * STOP_GAIN); } void Head::applyEyelidOffset(glm::quat headOrientation) { diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index 1fbfceca92..ec88b295f7 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -124,9 +124,11 @@ private: float _longTermAverageLoudness; float _audioAttack; float _audioJawOpen; + float _trailingAudioJawOpen; float _mouth2; float _mouth3; float _mouth4; + float _mouthTime; bool _renderLookatVectors; bool _renderLookatTarget; glm::vec3 _saccade; diff --git a/interface/src/avatar/ModelReferential.cpp b/interface/src/avatar/ModelReferential.cpp deleted file mode 100644 index 18c5e36e7a..0000000000 --- a/interface/src/avatar/ModelReferential.cpp +++ /dev/null @@ -1,192 +0,0 @@ -// -// ModelReferential.cpp -// -// -// Created by Clement on 7/30/14. -// Copyright 2014 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 "InterfaceLogging.h" -#include "ModelReferential.h" - -ModelReferential::ModelReferential(Referential* referential, EntityTreePointer tree, AvatarData* avatar) : - Referential(MODEL, avatar), - _tree(tree) -{ - _translation = referential->getTranslation(); - _rotation = referential->getRotation(); - unpackExtraData(reinterpret_cast(referential->getExtraData().data()), - referential->getExtraData().size()); - - if (!isValid()) { - qCDebug(interfaceapp) << "ModelReferential::copyConstructor(): Not Valid"; - return; - } - - EntityItemPointer item = _tree->findEntityByID(_entityID); - if (item != NULL) { - _lastRefDimension = item->getDimensions(); - _refRotation = item->getRotation(); - _refPosition = item->getPosition(); - update(); - } -} - -ModelReferential::ModelReferential(const QUuid& entityID, EntityTreePointer tree, AvatarData* avatar) : - Referential(MODEL, avatar), - _entityID(entityID), - _tree(tree) -{ - EntityItemPointer item = _tree->findEntityByID(_entityID); - if (!isValid() || item == NULL) { - qCDebug(interfaceapp) << "ModelReferential::constructor(): Not Valid"; - _isValid = false; - return; - } - - _lastRefDimension = item->getDimensions(); - _refRotation = item->getRotation(); - _refPosition = item->getPosition(); - - glm::quat refInvRot = glm::inverse(_refRotation); - _rotation = refInvRot * _avatar->getOrientation(); - _translation = refInvRot * (avatar->getPosition() - _refPosition); -} - -void ModelReferential::update() { - EntityItemPointer item = _tree->findEntityByID(_entityID); - if (!isValid() || item == NULL || _avatar == NULL) { - return; - } - - bool somethingChanged = false; - if (item->getDimensions() != _lastRefDimension) { - glm::vec3 oldDimension = _lastRefDimension; - _lastRefDimension = item->getDimensions(); - _translation *= _lastRefDimension / oldDimension; - somethingChanged = true; - } - if (item->getRotation() != _refRotation) { - _refRotation = item->getRotation(); - _avatar->setOrientation(_refRotation * _rotation, true); - somethingChanged = true; - } - if (item->getPosition() != _refPosition || somethingChanged) { - _refPosition = item->getPosition(); - _avatar->setPosition(_refPosition + _refRotation * _translation, true); - } -} - -int ModelReferential::packExtraData(unsigned char* destinationBuffer) const { - QByteArray encodedEntityID = _entityID.toRfc4122(); - memcpy(destinationBuffer, encodedEntityID.constData(), encodedEntityID.size()); - return encodedEntityID.size(); -} - -int ModelReferential::unpackExtraData(const unsigned char *sourceBuffer, int size) { - QByteArray encodedEntityID((const char*)sourceBuffer, NUM_BYTES_RFC4122_UUID); - _entityID = QUuid::fromRfc4122(encodedEntityID); - return NUM_BYTES_RFC4122_UUID; -} - -JointReferential::JointReferential(Referential* referential, EntityTreePointer tree, AvatarData* avatar) : - ModelReferential(referential, tree, avatar) -{ - _type = JOINT; - if (!isValid()) { - qCDebug(interfaceapp) << "JointReferential::copyConstructor(): Not Valid"; - return; - } - - EntityItemPointer item = _tree->findEntityByID(_entityID); - const Model* model = getModel(item); - if (isValid() && model != NULL && _jointIndex < (uint32_t)(model->getJointStateCount())) { - _lastRefDimension = item->getDimensions(); - model->getJointRotationInWorldFrame(_jointIndex, _refRotation); - model->getJointPositionInWorldFrame(_jointIndex, _refPosition); - } - update(); -} - -JointReferential::JointReferential(uint32_t jointIndex, const QUuid& entityID, EntityTreePointer tree, AvatarData* avatar) : - ModelReferential(entityID, tree, avatar), - _jointIndex(jointIndex) -{ - _type = JOINT; - EntityItemPointer item = _tree->findEntityByID(_entityID); - const Model* model = getModel(item); - if (!isValid() || model == NULL || _jointIndex >= (uint32_t)(model->getJointStateCount())) { - qCDebug(interfaceapp) << "JointReferential::constructor(): Not Valid"; - _isValid = false; - return; - } - - _lastRefDimension = item->getDimensions(); - model->getJointRotationInWorldFrame(_jointIndex, _refRotation); - model->getJointPositionInWorldFrame(_jointIndex, _refPosition); - - glm::quat refInvRot = glm::inverse(_refRotation); - _rotation = refInvRot * _avatar->getOrientation(); - // BUG! _refPosition is in domain units, but avatar is in meters - _translation = refInvRot * (avatar->getPosition() - _refPosition); -} - -void JointReferential::update() { - EntityItemPointer item = _tree->findEntityByID(_entityID); - const Model* model = getModel(item); - if (!isValid() || model == NULL || _jointIndex >= (uint32_t)(model->getJointStateCount())) { - return; - } - - bool somethingChanged = false; - if (item->getDimensions() != _lastRefDimension) { - glm::vec3 oldDimension = _lastRefDimension; - _lastRefDimension = item->getDimensions(); - _translation *= _lastRefDimension / oldDimension; - somethingChanged = true; - } - if (item->getRotation() != _refRotation) { - model->getJointRotationInWorldFrame(_jointIndex, _refRotation); - _avatar->setOrientation(_refRotation * _rotation, true); - somethingChanged = true; - } - if (item->getPosition() != _refPosition || somethingChanged) { - model->getJointPositionInWorldFrame(_jointIndex, _refPosition); - _avatar->setPosition(_refPosition + _refRotation * _translation, true); - } -} - -const Model* JointReferential::getModel(EntityItemPointer item) { - EntityItemFBXService* fbxService = _tree->getFBXService(); - if (item != NULL && fbxService != NULL) { - return fbxService->getModelForEntityItem(item); - } - return NULL; -} - -int JointReferential::packExtraData(unsigned char* destinationBuffer) const { - unsigned char* startPosition = destinationBuffer; - destinationBuffer += ModelReferential::packExtraData(destinationBuffer); - - memcpy(destinationBuffer, &_jointIndex, sizeof(_jointIndex)); - destinationBuffer += sizeof(_jointIndex); - - return destinationBuffer - startPosition; -} - -int JointReferential::unpackExtraData(const unsigned char *sourceBuffer, int size) { - const unsigned char* startPosition = sourceBuffer; - sourceBuffer += ModelReferential::unpackExtraData(sourceBuffer, size); - - memcpy(&_jointIndex, sourceBuffer, sizeof(_jointIndex)); - sourceBuffer += sizeof(_jointIndex); - - return sourceBuffer - startPosition; -} diff --git a/interface/src/avatar/ModelReferential.h b/interface/src/avatar/ModelReferential.h deleted file mode 100644 index 810db4b8e5..0000000000 --- a/interface/src/avatar/ModelReferential.h +++ /dev/null @@ -1,48 +0,0 @@ -// -// ModelReferential.h -// -// -// Created by Clement on 7/30/14. -// Copyright 2014 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_ModelReferential_h -#define hifi_ModelReferential_h - -#include - -class EntityTree; -class Model; - -class ModelReferential : public Referential { -public: - ModelReferential(Referential* ref, EntityTreePointer tree, AvatarData* avatar); - ModelReferential(const QUuid& entityID, EntityTreePointer tree, AvatarData* avatar); - virtual void update(); - -protected: - virtual int packExtraData(unsigned char* destinationBuffer) const; - virtual int unpackExtraData(const unsigned char* sourceBuffer, int size); - - QUuid _entityID; - EntityTreePointer _tree; -}; - -class JointReferential : public ModelReferential { -public: - JointReferential(Referential* ref, EntityTreePointer tree, AvatarData* avatar); - JointReferential(uint32_t jointIndex, const QUuid& entityID, EntityTreePointer tree, AvatarData* avatar); - virtual void update(); - -protected: - const Model* getModel(EntityItemPointer item); - virtual int packExtraData(unsigned char* destinationBuffer) const; - virtual int unpackExtraData(const unsigned char* sourceBuffer, int size); - - uint32_t _jointIndex; -}; - -#endif // hifi_ModelReferential_h diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 39b09fb9de..6637331b64 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -45,7 +45,6 @@ #include "AvatarManager.h" #include "Environment.h" #include "Menu.h" -#include "ModelReferential.h" #include "MyAvatar.h" #include "Physics.h" #include "Util.h" @@ -204,13 +203,14 @@ MyAvatar::~MyAvatar() { QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) { CameraMode mode = qApp->getCamera()->getMode(); + _globalPosition = getPosition(); if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT) { // fake the avatar position that is sent up to the AvatarMixer - glm::vec3 oldPosition = _position; - _position = getSkeletonPosition(); + glm::vec3 oldPosition = getPosition(); + setPosition(getSkeletonPosition()); QByteArray array = AvatarData::toByteArray(cullSmallChanges, sendAll); // copy the correct position back - _position = oldPosition; + setPosition(oldPosition); return array; } return AvatarData::toByteArray(cullSmallChanges, sendAll); @@ -232,30 +232,27 @@ void MyAvatar::reset(bool andReload) { setThrust(glm::vec3(0.0f)); if (andReload) { - // Get fresh data, in case we're really slow and out of wack. - _hmdSensorMatrix = qApp->getHMDSensorPose(); - _hmdSensorPosition = extractTranslation(_hmdSensorMatrix); - _hmdSensorOrientation = glm::quat_cast(_hmdSensorMatrix); - - // Reset body position/orientation under the head. + // derive the desired body orientation from the *old* hmd orientation, before the sensor reset. auto newBodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. + + // transform this body into world space auto worldBodyMatrix = _sensorToWorldMatrix * newBodySensorMatrix; - glm::vec3 worldBodyPos = extractTranslation(worldBodyMatrix); - glm::quat worldBodyRot = glm::normalize(glm::quat_cast(worldBodyMatrix)); - - // FIXME: Hack to retain the previous behavior wrt height. - // I'd like to make the body match head height, but that will have to wait for separate PR. - worldBodyPos.y = getPosition().y; + auto worldBodyPos = extractTranslation(worldBodyMatrix); + auto worldBodyRot = glm::normalize(glm::quat_cast(worldBodyMatrix)); + // this will become our new position. setPosition(worldBodyPos); setOrientation(worldBodyRot); - // If there is any discrepency between positioning and the head (as there is in initial deriveBodyFromHMDSensor), - // we can make that right by setting _bodySensorMatrix = newBodySensorMatrix. - // However, doing so will make the head want to point to the previous body orientation, as cached above. - //_bodySensorMatrix = newBodySensorMatrix; - //updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes - qApp->setRawAvatarUpdateThreading(); + // now sample the new hmd orientation AFTER sensor reset. + updateFromHMDSensorMatrix(qApp->getHMDSensorPose()); + + // update the body in sensor space using the new hmd sensor sample + _bodySensorMatrix = deriveBodyFromHMDSensor(); + + // rebuild the sensor to world matrix such that, the HMD will point in the desired orientation. + // i.e. the along avatar's current position and orientation. + updateSensorToWorldMatrix(); } } @@ -272,10 +269,6 @@ void MyAvatar::update(float deltaTime) { updateSensorToWorldMatrix(); } - if (_referential) { - _referential->update(); - } - Head* head = getHead(); head->relaxLean(deltaTime); updateFromTrackers(deltaTime); @@ -294,9 +287,9 @@ extern void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avata void MyAvatar::simulate(float deltaTime) { PerformanceTimer perfTimer("simulate"); - if (_scale != _targetScale) { - float scale = (1.0f - SMOOTHING_RATIO) * _scale + SMOOTHING_RATIO * _targetScale; - setScale(scale); + if (getAvatarScale() != _targetScale) { + float scale = (1.0f - SMOOTHING_RATIO) * getAvatarScale() + SMOOTHING_RATIO * _targetScale; + setAvatarScale(scale); } { @@ -346,10 +339,10 @@ void MyAvatar::simulate(float deltaTime) { Head* head = getHead(); glm::vec3 headPosition; if (!_skeletonModel.getHeadPosition(headPosition)) { - headPosition = _position; + headPosition = getPosition(); } head->setPosition(headPosition); - head->setScale(_scale); + head->setScale(getAvatarScale()); head->simulate(deltaTime, true); } @@ -567,36 +560,10 @@ void MyAvatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { if (!_shouldRender) { return; // exit early } - + Avatar::render(renderArgs, cameraPosition); } -void MyAvatar::clearReferential() { - changeReferential(NULL); -} - -bool MyAvatar::setModelReferential(const QUuid& id) { - EntityTreePointer tree = qApp->getEntities()->getTree(); - changeReferential(new ModelReferential(id, tree, this)); - if (_referential->isValid()) { - return true; - } else { - changeReferential(NULL); - return false; - } -} - -bool MyAvatar::setJointReferential(const QUuid& id, int jointIndex) { - EntityTreePointer tree = qApp->getEntities()->getTree(); - changeReferential(new JointReferential(jointIndex, id, tree, this)); - if (!_referential->isValid()) { - return true; - } else { - changeReferential(NULL); - return false; - } -} - void MyAvatar::overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "overrideAnimation", Q_ARG(const QString&, url), Q_ARG(float, fps), @@ -735,7 +702,7 @@ void MyAvatar::loadData() { _leanScale = loadSetting(settings, "leanScale", 0.05f); _targetScale = loadSetting(settings, "scale", 1.0f); - setScale(_scale); + setAvatarScale(getAvatarScale()); _animGraphUrl = settings.value("animGraphURL", "").toString(); _fullAvatarURLFromPreferences = settings.value("fullAvatarURL", AvatarData::defaultFullAvatarModelUrl()).toUrl(); @@ -862,7 +829,8 @@ void MyAvatar::updateLookAtTargetAvatar() { bool isCurrentTarget = avatar->getIsLookAtTarget(); float distanceTo = glm::length(avatar->getHead()->getEyePosition() - cameraPosition); avatar->setIsLookAtTarget(false); - if (!avatar->isMyAvatar() && avatar->isInitialized() && (distanceTo < GREATEST_LOOKING_AT_DISTANCE * getScale())) { + if (!avatar->isMyAvatar() && avatar->isInitialized() && + (distanceTo < GREATEST_LOOKING_AT_DISTANCE * getAvatarScale())) { float angleTo = glm::angle(lookForward, glm::normalize(avatar->getHead()->getEyePosition() - cameraPosition)); if (angleTo < (smallestAngleTo * (isCurrentTarget ? KEEP_LOOKING_AT_CURRENT_ANGLE_FACTOR : 1.0f))) { _lookAtTargetAvatar = avatarPointer; @@ -999,8 +967,6 @@ void MyAvatar::clearJointData(int index) { QMetaObject::invokeMethod(this, "clearJointData", Q_ARG(int, index)); return; } - // HACK: ATM only JS scripts call clearJointData() on MyAvatar so we hardcode the priority - _rig->setJointState(index, false, glm::quat(), glm::vec3(), 0.0f); _rig->clearJointAnimationPriority(index); } @@ -1074,7 +1040,7 @@ glm::vec3 MyAvatar::getSkeletonPosition() const { // The avatar is rotated PI about the yAxis, so we have to correct for it // to get the skeleton offset contribution in the world-frame. const glm::quat FLIP = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)); - return _position + getOrientation() * FLIP * _skeletonOffset; + return getPosition() + getOrientation() * FLIP * _skeletonOffset; } return Avatar::getPosition(); } @@ -1220,7 +1186,7 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl if (!_skeletonModel.isRenderable()) { return; // wait until all models are loaded } - + fixupModelsInScene(); // Render head so long as the camera isn't inside it @@ -1232,7 +1198,7 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl if (qApp->isHMDMode()) { glm::vec3 cameraPosition = qApp->getCamera()->getPosition(); - glm::mat4 headPose = qApp->getActiveDisplayPlugin()->getHeadPose(); + glm::mat4 headPose = qApp->getActiveDisplayPlugin()->getHeadPose(qApp->getFrameCount()); glm::mat4 leftEyePose = qApp->getActiveDisplayPlugin()->getEyeToHeadTransform(Eye::Left); leftEyePose = leftEyePose * headPose; glm::vec3 leftEyePosition = extractTranslation(leftEyePose); @@ -1286,6 +1252,16 @@ void MyAvatar::initHeadBones() { } } +void MyAvatar::setAnimGraphUrl(const QUrl& url) { + if (_animGraphUrl == url) { + return; + } + destroyAnimGraph(); + _skeletonModel.reset(); // Why is this necessary? Without this, we crash in the next render. + _animGraphUrl = url; + initAnimGraph(); +} + void MyAvatar::initAnimGraph() { // avatar.json // https://gist.github.com/hyperlogic/7d6a0892a7319c69e2b9 @@ -1302,9 +1278,9 @@ void MyAvatar::initAnimGraph() { // or run a local web-server // python -m SimpleHTTPServer& //auto graphUrl = QUrl("http://localhost:8000/avatar.json"); - auto graphUrl = QUrl(_animGraphUrl.isEmpty() ? - QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_full/avatar-animation.json") : - _animGraphUrl); + auto graphUrl =_animGraphUrl.isEmpty() ? + QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_full/avatar-animation.json") : + QUrl(_animGraphUrl); _rig->initAnimGraph(graphUrl); _bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. @@ -1364,7 +1340,7 @@ const float RENDER_HEAD_CUTOFF_DISTANCE = 0.50f; bool MyAvatar::cameraInsideHead() const { const Head* head = getHead(); const glm::vec3 cameraPosition = qApp->getCamera()->getPosition(); - return glm::length(cameraPosition - head->getEyePosition()) < (RENDER_HEAD_CUTOFF_DISTANCE * _scale); + return glm::length(cameraPosition - head->getEyePosition()) < (RENDER_HEAD_CUTOFF_DISTANCE * getAvatarScale()); } bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const { @@ -1494,11 +1470,11 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe if (isHovering) { // we're flying --> complex acceleration curve with high max speed float motorSpeed = glm::length(_keyboardMotorVelocity); - float finalMaxMotorSpeed = _scale * MAX_KEYBOARD_MOTOR_SPEED; + float finalMaxMotorSpeed = getAvatarScale() * MAX_KEYBOARD_MOTOR_SPEED; float speedGrowthTimescale = 2.0f; float speedIncreaseFactor = 1.8f; motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale , 0.0f, 1.0f) * speedIncreaseFactor; - const float maxBoostSpeed = _scale * MAX_BOOST_SPEED; + const float maxBoostSpeed = getAvatarScale() * MAX_BOOST_SPEED; if (motorSpeed < maxBoostSpeed) { // an active keyboard motor should never be slower than this float boostCoefficient = (maxBoostSpeed - motorSpeed) / maxBoostSpeed; @@ -1808,7 +1784,10 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { int neckIndex = _rig->indexOfJoint("Neck"); int hipsIndex = _rig->indexOfJoint("Hips"); - glm::vec3 rigMiddleEyePos = leftEyeIndex != -1 ? _rig->getAbsoluteDefaultPose(leftEyeIndex).trans : DEFAULT_RIG_MIDDLE_EYE_POS; + glm::vec3 rigMiddleEyePos = DEFAULT_RIG_MIDDLE_EYE_POS; + if (leftEyeIndex >= 0 && rightEyeIndex >= 0) { + rigMiddleEyePos = (_rig->getAbsoluteDefaultPose(leftEyeIndex).trans + _rig->getAbsoluteDefaultPose(rightEyeIndex).trans) / 2.0f; + } glm::vec3 rigNeckPos = neckIndex != -1 ? _rig->getAbsoluteDefaultPose(neckIndex).trans : DEFAULT_RIG_NECK_POS; glm::vec3 rigHipsPos = hipsIndex != -1 ? _rig->getAbsoluteDefaultPose(hipsIndex).trans : DEFAULT_RIG_HIPS_POS; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 13575388e3..309e600978 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -247,19 +247,16 @@ public slots: Q_INVOKABLE void updateMotionBehaviorFromMenu(); - void clearReferential(); - bool setModelReferential(const QUuid& id); - bool setJointReferential(const QUuid& id, int jointIndex); - virtual void rebuildSkeletonBody() override; - const QString& getAnimGraphUrl() const { return _animGraphUrl; } + Q_INVOKABLE QUrl getAnimGraphUrl() const { return _animGraphUrl; } void setEnableDebugDrawDefaultPose(bool isEnabled); void setEnableDebugDrawAnimPose(bool isEnabled); void setEnableDebugDrawPosition(bool isEnabled); + bool getEnableMeshVisible() const { return _skeletonModel.isVisible(); } void setEnableMeshVisible(bool isEnabled); - void setAnimGraphUrl(const QString& url) { _animGraphUrl = url; } + Q_INVOKABLE void setAnimGraphUrl(const QUrl& url); glm::vec3 getPositionForAudio(); glm::quat getOrientationForAudio(); @@ -280,7 +277,7 @@ private: virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPositio) override; virtual void renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, float glowLevel = 0.0f) override; virtual bool shouldRenderHead(const RenderArgs* renderArgs) const override; - void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; } + void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; setEnableMeshVisible(shouldRender); } bool getShouldRenderLocally() const { return _shouldRender; } bool getDriveKeys(int key) { return _driveKeys[key] != 0.0f; }; bool isMyAvatar() const override { return true; } @@ -360,7 +357,7 @@ private: // Avatar Preferences QUrl _fullAvatarURLFromPreferences; QString _fullAvatarModelName; - QString _animGraphUrl {""}; + QUrl _animGraphUrl {""}; // cache of the current HMD sensor position and orientation // in sensor space. diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index d438e6f528..342f8315e1 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -42,7 +42,6 @@ SkeletonModel::~SkeletonModel() { void SkeletonModel::initJointStates() { const FBXGeometry& geometry = _geometry->getFBXGeometry(); - glm::mat4 geometryOffset = geometry.offset; glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset); _rig->initJointStates(geometry, modelOffset); @@ -237,11 +236,6 @@ void SkeletonModel::applyPalmData(int jointIndex, const PalmData& palm) { if (parentJointIndex == -1) { return; } - - // the palm's position must be transformed into the model-frame - glm::quat inverseRotation = glm::inverse(_rotation); - glm::vec3 palmPosition = inverseRotation * (palm.getPosition() - _translation); - glm::quat palmRotation = inverseRotation * palm.getRotation(); } bool SkeletonModel::getLeftHandPosition(glm::vec3& position) const { diff --git a/interface/src/devices/MIDIManager.cpp b/interface/src/devices/MIDIManager.cpp deleted file mode 100644 index a21f5d49f5..0000000000 --- a/interface/src/devices/MIDIManager.cpp +++ /dev/null @@ -1,73 +0,0 @@ -// -// MIDIManager.cpp -// -// -// Created by Stephen Birarda on 2014-06-30. -// Copyright 2014 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 "InterfaceLogging.h" -#include "MIDIManager.h" - -MIDIManager& MIDIManager::getInstance() { - static MIDIManager sharedInstance; - return sharedInstance; -} - -void MIDIManager::midiCallback(double deltaTime, std::vector* message, void* userData) { -#ifdef HAVE_RTMIDI - - MIDIEvent callbackEvent; - callbackEvent.deltaTime = deltaTime; - - callbackEvent.type = message->at(0); - - if (message->size() > 1) { - callbackEvent.data1 = message->at(1); - } - - if (message->size() > 2) { - callbackEvent.data2 = message->at(2); - } - - emit getInstance().midiEvent(callbackEvent); -#endif -} - -MIDIManager::~MIDIManager() { -#ifdef HAVE_RTMIDI - delete _midiInput; -#endif -} - -#ifdef HAVE_RTMIDI -const int DEFAULT_MIDI_PORT = 0; -#endif - -void MIDIManager::openDefaultPort() { -#ifdef HAVE_RTMIDI - if (!_midiInput) { - _midiInput = new RtMidiIn(); - - if (_midiInput->getPortCount() > 0) { - qCDebug(interfaceapp) << "MIDIManager opening port" << DEFAULT_MIDI_PORT; - - _midiInput->openPort(DEFAULT_MIDI_PORT); - - // don't ignore sysex, timing, or active sensing messages - _midiInput->ignoreTypes(false, false, false); - - _midiInput->setCallback(&MIDIManager::midiCallback); - } else { - qCDebug(interfaceapp) << "MIDIManager openDefaultPort called but there are no ports available."; - delete _midiInput; - _midiInput = NULL; - } - } -#endif -} diff --git a/interface/src/devices/MIDIManager.h b/interface/src/devices/MIDIManager.h deleted file mode 100644 index 9fc55d11da..0000000000 --- a/interface/src/devices/MIDIManager.h +++ /dev/null @@ -1,58 +0,0 @@ -// -// MIDIManager.h -// interface/src/devices -// -// Created by Stephen Birarda on 2014-06-30. -// Copyright 2014 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_MIDIManager_h -#define hifi_MIDIManager_h - -#include -#include - -#include - -#ifdef HAVE_RTMIDI -#include -#endif - -class MIDIManager : public QObject { - Q_OBJECT - - Q_PROPERTY(unsigned int NoteOn READ NoteOn) - Q_PROPERTY(unsigned int NoteOff READ NoteOff) - Q_PROPERTY(unsigned int ModWheel READ ModWheel) - Q_PROPERTY(unsigned int PitchWheel READ PitchWheel) - -public: - static MIDIManager& getInstance(); - static void midiCallback(double deltaTime, std::vector* message, void* userData); - - ~MIDIManager(); - - void openDefaultPort(); -#ifdef HAVE_RTMIDI - bool hasDevice() const { return !!_midiInput; } -#endif -public slots: - unsigned int NoteOn() const { return 144; } - unsigned int NoteOff() const { return 128; } - unsigned int ModWheel() const { return 176; } - unsigned int PitchWheel() const { return 224; } -signals: - void midiEvent(const MIDIEvent& event); - -private: -#ifdef HAVE_RTMIDI - RtMidiIn* _midiInput; -#endif -}; - - -#endif // hifi_MIDIManager_h - diff --git a/interface/src/devices/RealSense.cpp b/interface/src/devices/RealSense.cpp deleted file mode 100644 index ce28e40f2b..0000000000 --- a/interface/src/devices/RealSense.cpp +++ /dev/null @@ -1,259 +0,0 @@ -// -// RealSense.cpp -// interface/src/devices -// -// Created by Thijs Wenker on 12/10/14 -// Copyright 2014 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 "Application.h" -#include "RealSense.h" -#include "MainWindow.h" -#include "Menu.h" -#include "SharedUtil.h" - -#ifdef HAVE_RSSDK -const int PALMROOT_NUM_JOINTS = 3; -const int FINGER_NUM_JOINTS = 4; -#endif // HAVE_RSSDK - -const DeviceTracker::Name RealSense::NAME = "RealSense"; - -// find the index of a joint from -// the side: true = right -// the finger & the bone: -// finger in [0..4] : bone in [0..3] a finger phalange -// [-1] up the hand branch : bone in [0..1] <=> [ hand, forearm] -MotionTracker::Index evalRealSenseJointIndex(bool isRightSide, int finger, int bone) { -#ifdef HAVE_RSSDK - MotionTracker::Index offset = 1 // start after root - + (int(isRightSide) * PXCHandData::NUMBER_OF_JOINTS) // then offset for side - + PALMROOT_NUM_JOINTS; // then add the arm/forearm/hand chain - if (finger >= 0) { - // from there go down in the correct finger and bone - return offset + (finger * FINGER_NUM_JOINTS) + bone; - } else { - // or go back up for the correct root bone - return offset - 1 - bone; - } -#else - return -1; -#endif // HAVE_RSSDK -} - -// static -void RealSense::init() { - DeviceTracker* device = DeviceTracker::getDevice(NAME); - if (!device) { - // create a new RealSense and register it - RealSense* realSense = new RealSense(); - DeviceTracker::registerDevice(NAME, realSense); - } -} - -// static -void RealSense::destroy() { - DeviceTracker::destroyDevice(NAME); -} - -// static -RealSense* RealSense::getInstance() { - DeviceTracker* device = DeviceTracker::getDevice(NAME); - if (!device) { - // create a new RealSense and register it - RealSense* realSense = new RealSense(); - DeviceTracker::registerDevice(NAME, realSense); - } - return dynamic_cast< RealSense* > (device); -} - -RealSense::RealSense() : - MotionTracker(), - _active(false) -{ -#ifdef HAVE_RSSDK - _handData = NULL; - _session = PXCSession_Create(); - initSession(false, NULL); - - // Create the RealSense joint hierarchy - std::vector< Semantic > sides; - sides.push_back("joint_L_"); - sides.push_back("joint_R_"); - - std::vector< Semantic > rootBones; - rootBones.push_back("wrist"); - rootBones.push_back("hand"); - - std::vector< Semantic > fingers; - fingers.push_back("thumb"); - fingers.push_back("index"); - fingers.push_back("middle"); - fingers.push_back("ring"); - fingers.push_back("pinky"); - - std::vector< Semantic > fingerBones; - fingerBones.push_back("1"); - fingerBones.push_back("2"); - fingerBones.push_back("3"); - fingerBones.push_back("4"); - - std::vector< Index > palms; - for (unsigned int s = 0; s < sides.size(); s++) { - Index rootJoint = 0; - for (unsigned int rb = 0; rb < rootBones.size(); rb++) { - rootJoint = addJoint(sides[s] + rootBones[rb], rootJoint); - } - - // capture the hand index for debug - palms.push_back(rootJoint); - - for (unsigned int f = 0; f < fingers.size(); f++) { - for (unsigned int b = 0; b < fingerBones.size(); b++) { - rootJoint = addJoint(sides[s] + fingers[f] + fingerBones[b], rootJoint); - } - } - } -#endif // HAVE_RSSDK -} - -RealSense::~RealSense() { -#ifdef HAVE_RSSDK - _manager->Release(); -#endif // HAVE_RSSDK -} - -void RealSense::initSession(bool playback, QString filename) { -#ifdef HAVE_RSSDK - _active = false; - _properlyInitialized = false; - if (_handData != NULL) { - _handData->Release(); - _handController->Release(); - _session->Release(); - _config->Release(); - } - _manager = _session->CreateSenseManager(); - if (playback) { - _manager->QueryCaptureManager()->SetFileName(filename.toStdWString().c_str(), false); - } - _manager->QueryCaptureManager()->SetRealtime(!playback); - _manager->EnableHand(0); - _handController = _manager->QueryHand(); - - if (_manager->Init() == PXC_STATUS_NO_ERROR) { - _handData = _handController->CreateOutput(); - - PXCCapture::Device *device = _manager->QueryCaptureManager()->QueryDevice(); - PXCCapture::DeviceInfo dinfo; - _manager->QueryCaptureManager()->QueryDevice()->QueryDeviceInfo(&dinfo); - if (dinfo.model == PXCCapture::DEVICE_MODEL_IVCAM) - { - device->SetDepthConfidenceThreshold(1); - device->SetMirrorMode(PXCCapture::Device::MIRROR_MODE_DISABLED); - device->SetIVCAMFilterOption(6); - } - _properlyInitialized = true; - } - - _config = _handController->CreateActiveConfiguration(); - _config->EnableStabilizer(true); - _config->SetTrackingMode(PXCHandData::TRACKING_MODE_FULL_HAND); - _config->ApplyChanges(); -#endif // HAVE_RSSDK -} - -#ifdef HAVE_RSSDK -glm::quat quatFromPXCPoint4DF32(const PXCPoint4DF32& basis) { - return glm::normalize(glm::quat(basis.w, basis.x, basis.y, basis.z) * glm::quat(glm::vec3(0, M_PI, 0))); -} - -glm::vec3 vec3FromPXCPoint3DF32(const PXCPoint3DF32& vec) { - return glm::vec3(-vec.x, vec.y, -vec.z); -} -#endif // HAVE_RSSDK - -void RealSense::update() { -#ifdef HAVE_RSSDK - bool wasActive = _active; - _active = _manager->IsConnected() && _properlyInitialized; - if (_active || wasActive) { - // Go through all the joints and increment their counter since last update. - // Increment all counters once after controller first becomes inactive so that each joint reports itself as inactive. - // TODO C++11 for (auto jointIt = _jointsArray.begin(); jointIt != _jointsArray.end(); jointIt++) { - for (JointTracker::Vector::iterator jointIt = _jointsArray.begin(); jointIt != _jointsArray.end(); jointIt++) { - (*jointIt).tickNewFrame(); - } - } - - if (!_active) { - return; - } - - pxcStatus sts = _manager->AcquireFrame(true); - _handData->Update(); - PXCHandData::JointData nodes[2][PXCHandData::NUMBER_OF_JOINTS] = {}; - PXCHandData::ExtremityData extremitiesPointsNodes[2][PXCHandData::NUMBER_OF_EXTREMITIES] = {}; - for (pxcI32 i = 0; i < _handData->QueryNumberOfHands(); i++) { - PXCHandData::IHand* handData; - if (_handData->QueryHandData(PXCHandData::ACCESS_ORDER_BY_TIME, i, handData) == PXC_STATUS_NO_ERROR) { - int rightSide = handData->QueryBodySide() == PXCHandData::BODY_SIDE_RIGHT; - PXCHandData::JointData jointData; - JointTracker* parentJointTracker = _jointsArray.data(); - //Iterate Joints - int rootBranchIndex = -1; - JointTracker* palmJoint = NULL; - for (int j = 0; j < PXCHandData::NUMBER_OF_JOINTS; j++) { - handData->QueryTrackedJoint((PXCHandData::JointType)j, jointData); - nodes[i][j] = jointData; - if (j == PXCHandData::JOINT_WRIST) { - JointTracker* wrist = editJointTracker(evalRealSenseJointIndex(rightSide, rootBranchIndex, 1)); // 1 is the index of the wrist joint - wrist->editAbsFrame().setTranslation(vec3FromPXCPoint3DF32(jointData.positionWorld)); - wrist->editAbsFrame().setRotation(quatFromPXCPoint4DF32(jointData.globalOrientation)); - wrist->updateLocFromAbsTransform(parentJointTracker); - wrist->activeFrame(); - parentJointTracker = wrist; - continue; - } else if (j == PXCHandData::JOINT_CENTER) { - palmJoint = editJointTracker(evalRealSenseJointIndex(rightSide, rootBranchIndex, 0)); // 0 is the index of the palm joint - palmJoint->editAbsFrame().setTranslation(vec3FromPXCPoint3DF32(jointData.positionWorld)); - palmJoint->editAbsFrame().setRotation(quatFromPXCPoint4DF32(jointData.globalOrientation)); - palmJoint->updateLocFromAbsTransform(parentJointTracker); - palmJoint->activeFrame(); - parentJointTracker = palmJoint; - continue; - } - int finger_index = j - PALMROOT_NUM_JOINTS; - int finger = finger_index / FINGER_NUM_JOINTS; - int finger_bone = finger_index % FINGER_NUM_JOINTS; - JointTracker* ljointTracker = editJointTracker(evalRealSenseJointIndex(rightSide, finger, finger_bone)); - if (jointData.confidence > 0) { - ljointTracker->editAbsFrame().setTranslation(vec3FromPXCPoint3DF32(jointData.positionWorld)); - ljointTracker->editAbsFrame().setRotation(quatFromPXCPoint4DF32(jointData.globalOrientation)); - ljointTracker->updateLocFromAbsTransform(parentJointTracker); - ljointTracker->activeFrame(); - } - if (finger_bone == (FINGER_NUM_JOINTS - 1)) { - parentJointTracker = palmJoint; - continue; - } - parentJointTracker = ljointTracker; - } - } - } - _manager->ReleaseFrame(); -#endif // HAVE_RSSDK -} - -void RealSense::loadRSSDKFile() { - QString locationDir(QStandardPaths::displayName(QStandardPaths::DesktopLocation)); - QString fileNameString = QFileDialog::getOpenFileName(qApp->getWindow(), tr("Open RSSDK clip"), - locationDir, - tr("RSSDK Recordings (*.rssdk)")); - if (!fileNameString.isEmpty()) { - initSession(true, fileNameString); - } -} diff --git a/interface/src/devices/RealSense.h b/interface/src/devices/RealSense.h deleted file mode 100644 index c958ab1e53..0000000000 --- a/interface/src/devices/RealSense.h +++ /dev/null @@ -1,65 +0,0 @@ -// -// RealSense.h -// interface/src/devices -// -// Created by Thijs Wenker on 12/10/14 -// Copyright 2014 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_RealSense_h -#define hifi_RealSense_h - -#include - -#include "MotionTracker.h" - -#ifdef HAVE_RSSDK -#include -#include -#include -#include -#include -#endif - -/// Handles interaction with the RealSense skeleton tracking suit. -class RealSense : public QObject, public MotionTracker { - Q_OBJECT - -public: - static const Name NAME; - - static void init(); - static void destroy(); - - /// RealSense MotionTracker factory - static RealSense* getInstance(); - - bool isActive() const { return _active; } - - virtual void update(); - -public slots: - void loadRSSDKFile(); - -protected: - RealSense(); - virtual ~RealSense(); - - void initSession(bool playback, QString filename); - -private: -#ifdef HAVE_RSSDK - PXCSession* _session; - PXCSenseManager* _manager; - PXCHandModule* _handController; - PXCHandData* _handData; - PXCHandConfiguration* _config; -#endif - bool _properlyInitialized; - bool _active; -}; - -#endif // hifi_RealSense_h diff --git a/interface/src/octree/OctreePacketProcessor.cpp b/interface/src/octree/OctreePacketProcessor.cpp index 2fa0267dc6..5e1a7213b5 100644 --- a/interface/src/octree/OctreePacketProcessor.cpp +++ b/interface/src/octree/OctreePacketProcessor.cpp @@ -23,11 +23,11 @@ OctreePacketProcessor::OctreePacketProcessor() { this, "handleOctreePacket"); } -void OctreePacketProcessor::handleOctreePacket(QSharedPointer packet, SharedNodePointer senderNode) { - queueReceivedPacket(packet, senderNode); +void OctreePacketProcessor::handleOctreePacket(QSharedPointer message, SharedNodePointer senderNode) { + queueReceivedPacket(message, senderNode); } -void OctreePacketProcessor::processPacket(QSharedPointer packet, SharedNodePointer sendingNode) { +void OctreePacketProcessor::processPacket(QSharedPointer message, SharedNodePointer sendingNode) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "OctreePacketProcessor::processPacket()"); @@ -39,41 +39,42 @@ void OctreePacketProcessor::processPacket(QSharedPointer packet, Share bool wasStatsPacket = false; - PacketType octreePacketType = packet->getType(); + PacketType octreePacketType = message->getType(); // note: PacketType_OCTREE_STATS can have PacketType_VOXEL_DATA // immediately following them inside the same packet. So, we process the PacketType_OCTREE_STATS first // then process any remaining bytes as if it was another packet if (octreePacketType == PacketType::OctreeStats) { - int statsMessageLength = qApp->processOctreeStats(*packet, sendingNode); + int statsMessageLength = qApp->processOctreeStats(*message, sendingNode); wasStatsPacket = true; - int piggybackBytes = packet->getPayloadSize() - statsMessageLength; + int piggybackBytes = message->getSize() - statsMessageLength; if (piggybackBytes) { // construct a new packet from the piggybacked one auto buffer = std::unique_ptr(new char[piggybackBytes]); - memcpy(buffer.get(), packet->getPayload() + statsMessageLength, piggybackBytes); + memcpy(buffer.get(), message->getRawMessage() + statsMessageLength, piggybackBytes); - auto newPacket = NLPacket::fromReceivedPacket(std::move(buffer), piggybackBytes, packet->getSenderSockAddr()); - packet = QSharedPointer(newPacket.release()); + auto newPacket = NLPacket::fromReceivedPacket(std::move(buffer), piggybackBytes, message->getSenderSockAddr()); + message = QSharedPointer::create(*newPacket.release()); } else { // Note... stats packets don't have sequence numbers, so we don't want to send those to trackIncomingVoxelPacket() return; // bail since no piggyback data } } // fall through to piggyback message - PacketType packetType = packet->getType(); + PacketType packetType = message->getType(); // check version of piggyback packet against expected version - if (packet->getVersion() != versionForPacketType(packet->getType())) { + if (message->getVersion() != versionForPacketType(message->getType())) { static QMultiMap versionDebugSuppressMap; - const QUuid& senderUUID = packet->getSourceID(); + const QUuid& senderUUID = message->getSourceID(); if (!versionDebugSuppressMap.contains(senderUUID, packetType)) { + qDebug() << "Was stats packet? " << wasStatsPacket; qDebug() << "OctreePacketProcessor - piggyback packet version mismatch on" << packetType << "- Sender" - << senderUUID << "sent" << (int) packet->getVersion() << "but" + << senderUUID << "sent" << (int) message->getVersion() << "but" << (int) versionForPacketType(packetType) << "expected."; emit packetVersionMismatch(); @@ -83,21 +84,21 @@ void OctreePacketProcessor::processPacket(QSharedPointer packet, Share return; // bail since piggyback version doesn't match } - qApp->trackIncomingOctreePacket(*packet, sendingNode, wasStatsPacket); + qApp->trackIncomingOctreePacket(*message, sendingNode, wasStatsPacket); // seek back to beginning of packet after tracking - packet->seek(0); + message->seek(0); switch(packetType) { case PacketType::EntityErase: { if (DependencyManager::get()->shouldRenderEntities()) { - qApp->getEntities()->processEraseMessage(*packet, sendingNode); + qApp->getEntities()->processEraseMessage(*message, sendingNode); } } break; case PacketType::EntityData: { if (DependencyManager::get()->shouldRenderEntities()) { - qApp->getEntities()->processDatagram(*packet, sendingNode); + qApp->getEntities()->processDatagram(*message, sendingNode); } } break; diff --git a/interface/src/octree/OctreePacketProcessor.h b/interface/src/octree/OctreePacketProcessor.h index 47ebdc73bc..d04cab3584 100644 --- a/interface/src/octree/OctreePacketProcessor.h +++ b/interface/src/octree/OctreePacketProcessor.h @@ -13,6 +13,7 @@ #define hifi_OctreePacketProcessor_h #include +#include /// Handles processing of incoming voxel packets for the interface application. As with other ReceivedPacketProcessor classes /// the user is responsible for reading inbound packets and adding them to the processing queue by calling queueReceivedPacket() @@ -25,9 +26,9 @@ signals: void packetVersionMismatch(); protected: - virtual void processPacket(QSharedPointer packet, SharedNodePointer sendingNode); + virtual void processPacket(QSharedPointer message, SharedNodePointer sendingNode) override; private slots: - void handleOctreePacket(QSharedPointer packet, SharedNodePointer senderNode); + void handleOctreePacket(QSharedPointer message, SharedNodePointer senderNode); }; #endif // hifi_OctreePacketProcessor_h diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index a2886c8b77..0bf94f02a9 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -622,7 +622,8 @@ QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QS fileDialog.setAcceptMode(acceptMode); QUrl fileUrl(directory); if (acceptMode == QFileDialog::AcceptSave) { - fileDialog.setFileMode(QFileDialog::Directory); + // TODO -- Setting this breaks the dialog on Linux. Does it help something on other platforms? + // fileDialog.setFileMode(QFileDialog::Directory); fileDialog.selectFile(fileUrl.fileName()); } if (fileDialog.exec()) { diff --git a/interface/src/ui/ApplicationCompositor.cpp b/interface/src/ui/ApplicationCompositor.cpp index 5b7bbc2aba..4cee190a47 100644 --- a/interface/src/ui/ApplicationCompositor.cpp +++ b/interface/src/ui/ApplicationCompositor.cpp @@ -287,7 +287,7 @@ void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int mat4 camMat; _cameraBaseTransform.getMatrix(camMat); auto displayPlugin = qApp->getActiveDisplayPlugin(); - auto headPose = displayPlugin->getHeadPose(); + auto headPose = displayPlugin->getHeadPose(qApp->getFrameCount()); auto eyeToHead = displayPlugin->getEyeToHeadTransform((Eye)eye); camMat = (headPose * eyeToHead) * camMat; batch.setViewportTransform(renderArgs->_viewport); @@ -416,7 +416,7 @@ bool ApplicationCompositor::calculateRayUICollisionPoint(const glm::vec3& positi glm::vec3 relativeDirection = glm::normalize(inverseOrientation * direction); float t; - if (raySphereIntersect(relativeDirection, relativePosition, _oculusUIRadius * myAvatar->getScale(), &t)){ + if (raySphereIntersect(relativeDirection, relativePosition, _oculusUIRadius * myAvatar->getAvatarScale(), &t)){ result = position + direction * t; return true; } diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index c37755b823..a25f868172 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -33,8 +33,8 @@ const int PREFERENCES_HEIGHT_PADDING = 20; PreferencesDialog::PreferencesDialog(QWidget* parent) : - QDialog(parent) { - + QDialog(parent) +{ setAttribute(Qt::WA_DeleteOnClose); ui.setupUi(this); @@ -48,10 +48,8 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) : connect(ui.buttonReloadDefaultScripts, &QPushButton::clicked, qApp, &Application::loadDefaultScripts); connect(ui.buttonChangeAppearance, &QPushButton::clicked, this, &PreferencesDialog::openFullAvatarModelBrowser); - connect(ui.appearanceDescription, &QLineEdit::textChanged, this, [this](const QString& url) { - DependencyManager::get()->getMyAvatar()->useFullAvatarURL(url, ""); - this->fullAvatarURLChanged(url, ""); - }); + connect(ui.appearanceDescription, &QLineEdit::editingFinished, this, &PreferencesDialog::changeFullAvatarURL); + connect(qApp, &Application::fullAvatarURLChanged, this, &PreferencesDialog::fullAvatarURLChanged); // move dialog to left side @@ -61,6 +59,11 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) : UIUtil::scaleWidgetFontSizes(this); } +void PreferencesDialog::changeFullAvatarURL() { + DependencyManager::get()->getMyAvatar()->useFullAvatarURL(ui.appearanceDescription->text(), ""); + this->fullAvatarURLChanged(ui.appearanceDescription->text(), ""); +} + void PreferencesDialog::fullAvatarURLChanged(const QString& newValue, const QString& modelName) { ui.appearanceDescription->setText(newValue); const QString APPEARANCE_LABEL_TEXT("Appearance: "); @@ -69,9 +72,17 @@ void PreferencesDialog::fullAvatarURLChanged(const QString& newValue, const QStr void PreferencesDialog::accept() { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + + // if there is an attempted change to the full avatar URL, apply it now + if (QUrl(ui.appearanceDescription->text()) != myAvatar->getFullAvatarURLFromPreferences()) { + changeFullAvatarURL(); + } + _lastGoodAvatarURL = myAvatar->getFullAvatarURLFromPreferences(); _lastGoodAvatarName = myAvatar->getFullAvatarModelName(); + savePreferences(); + close(); delete _marketplaceWindow; _marketplaceWindow = NULL; @@ -188,9 +199,9 @@ void PreferencesDialog::loadPreferences() { ui.fieldOfViewSpin->setValue(qApp->getFieldOfView()); ui.leanScaleSpin->setValue(myAvatar->getLeanScale()); - - ui.avatarScaleSpin->setValue(myAvatar->getScale()); - ui.avatarAnimationEdit->setText(myAvatar->getAnimGraphUrl()); + + ui.avatarScaleSpin->setValue(myAvatar->getAvatarScale()); + ui.avatarAnimationEdit->setText(myAvatar->getAnimGraphUrl().toString()); ui.maxOctreePPSSpin->setValue(qApp->getMaxOctreePacketsPerSecond()); @@ -204,6 +215,7 @@ void PreferencesDialog::loadPreferences() { auto lodManager = DependencyManager::get(); ui.desktopMinimumFPSSpin->setValue(lodManager->getDesktopLODDecreaseFPS()); ui.hmdMinimumFPSSpin->setValue(lodManager->getHMDLODDecreaseFPS()); + ui.avatarRenderSmallestReasonableHorizon->setValue(1.0f / DependencyManager::get()->getRenderDistanceInverseHighLimit()); } void PreferencesDialog::savePreferences() { @@ -294,4 +306,5 @@ void PreferencesDialog::savePreferences() { auto lodManager = DependencyManager::get(); lodManager->setDesktopLODDecreaseFPS(ui.desktopMinimumFPSSpin->value()); lodManager->setHMDLODDecreaseFPS(ui.hmdMinimumFPSSpin->value()); + DependencyManager::get()->setRenderDistanceInverseHighLimit(1.0f / ui.avatarRenderSmallestReasonableHorizon->value()); } diff --git a/interface/src/ui/PreferencesDialog.h b/interface/src/ui/PreferencesDialog.h index 6d7a87b97c..1536eca3ee 100644 --- a/interface/src/ui/PreferencesDialog.h +++ b/interface/src/ui/PreferencesDialog.h @@ -49,6 +49,7 @@ private slots: void openFullAvatarModelBrowser(); void openSnapshotLocationBrowser(); void openScriptsLocationBrowser(); + void changeFullAvatarURL(); void fullAvatarURLChanged(const QString& newValue, const QString& modelName); }; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 12692698e7..55751d8631 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "BandwidthRecorder.h" #include "Menu.h" @@ -118,7 +119,12 @@ void Stats::updateStats(bool force) { STAT_UPDATE(avatarRenderableCount, avatarManager->getNumberInRenderRange()); STAT_UPDATE(avatarRenderDistance, (int) round(avatarManager->getRenderDistance())); // deliberately truncating STAT_UPDATE(serverCount, nodeList->size()); - STAT_UPDATE(framerate, (int)qApp->getFps()); + STAT_UPDATE(renderrate, (int)qApp->getFps()); + if (qApp->getActiveDisplayPlugin()) { + STAT_UPDATE(presentrate, (int)round(qApp->getActiveDisplayPlugin()->presentRate())); + } else { + STAT_UPDATE(presentrate, -1); + } STAT_UPDATE(simrate, (int)qApp->getAverageSimsPerSecond()); STAT_UPDATE(avatarSimrate, (int)qApp->getAvatarSimrate()); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index d1c0dd19d7..eb28883001 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -32,7 +32,8 @@ class Stats : public QQuickItem { Q_PROPERTY(float audioPacketlossDownstream READ getAudioPacketLossDownstream) STATS_PROPERTY(int, serverCount, 0) - STATS_PROPERTY(int, framerate, 0) + STATS_PROPERTY(int, renderrate, 0) + STATS_PROPERTY(int, presentrate, 0) STATS_PROPERTY(int, simrate, 0) STATS_PROPERTY(int, avatarSimrate, 0) STATS_PROPERTY(int, avatarCount, 0) @@ -115,7 +116,8 @@ signals: void expandedChanged(); void timingExpandedChanged(); void serverCountChanged(); - void framerateChanged(); + void renderrateChanged(); + void presentrateChanged(); void simrateChanged(); void avatarSimrateChanged(); void avatarCountChanged(); diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index 0acd7ecc1e..9dc609af31 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -12,6 +12,7 @@ #include #include +#include QString const Line3DOverlay::TYPE = "line3d"; @@ -53,6 +54,7 @@ void Line3DOverlay::render(RenderArgs* args) { auto batch = args->_batch; if (batch) { batch->setModelTransform(_transform); + DependencyManager::get()->bindSimpleProgram(*batch); if (getIsDashedLine()) { // TODO: add support for color to renderDashedLine() diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 96553843c8..f6e6851c38 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -533,15 +533,13 @@ bool Overlays::isLoaded(unsigned int id) { QSizeF Overlays::textSize(unsigned int id, const QString& text) const { Overlay::Pointer thisOverlay = _overlaysHUD[id]; if (thisOverlay) { - if (typeid(*thisOverlay) == typeid(TextOverlay)) { - return std::dynamic_pointer_cast(thisOverlay)->textSize(text); + if (auto textOverlay = std::dynamic_pointer_cast(thisOverlay)) { + return textOverlay->textSize(text); } } else { thisOverlay = _overlaysWorld[id]; - if (thisOverlay) { - if (typeid(*thisOverlay) == typeid(Text3DOverlay)) { - return std::dynamic_pointer_cast(thisOverlay)->textSize(text); - } + if (auto text3dOverlay = std::dynamic_pointer_cast(thisOverlay)) { + return text3dOverlay->textSize(text); } } return QSizeF(0.0f, 0.0f); diff --git a/interface/src/ui/overlays/OverlaysPayload.cpp b/interface/src/ui/overlays/OverlaysPayload.cpp index 02d432ea81..1b1c48c3ca 100644 --- a/interface/src/ui/overlays/OverlaysPayload.cpp +++ b/interface/src/ui/overlays/OverlaysPayload.cpp @@ -68,7 +68,7 @@ namespace render { glm::vec3 myAvatarPosition = avatar->getPosition(); float angle = glm::degrees(glm::angle(myAvatarRotation)); glm::vec3 axis = glm::axis(myAvatarRotation); - float myAvatarScale = avatar->getScale(); + float myAvatarScale = avatar->getAvatarScale(); Transform transform = Transform(); transform.setTranslation(myAvatarPosition); transform.setRotation(glm::angleAxis(angle, axis)); diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui index a1137a2bf2..e6a5e2228d 100644 --- a/interface/ui/preferencesDialog.ui +++ b/interface/ui/preferencesDialog.ui @@ -912,6 +912,85 @@ + + + + 0 + + + 7 + + + 0 + + + 7 + + + + + + Arial + + + + + + + Minimum Avatar Display Distance + + + 0 + + + + + + + + Arial + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + 100 + 0 + + + + + 95 + 36 + + + + + Arial + + + + 5 + + + 32768 + + + + + diff --git a/libraries/animation/src/AnimPose.cpp b/libraries/animation/src/AnimPose.cpp index 5914031a3a..0c6af2d5bd 100644 --- a/libraries/animation/src/AnimPose.cpp +++ b/libraries/animation/src/AnimPose.cpp @@ -11,6 +11,7 @@ #include "AnimPose.h" #include #include +#include const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f), glm::quat(), @@ -18,7 +19,9 @@ const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f), AnimPose::AnimPose(const glm::mat4& mat) { scale = extractScale(mat); - rot = glmExtractRotation(mat); + // quat_cast doesn't work so well with scaled matrices, so cancel it out. + glm::mat4 tmp = glm::scale(mat, 1.0f / scale); + rot = glm::normalize(glm::quat_cast(tmp)); trans = extractTranslation(mat); } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index c0b399ee0f..a80ef5d2eb 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include @@ -158,10 +160,10 @@ void Rig::destroyAnimGraph() { _animSkeleton.reset(); _animLoader.reset(); _animNode.reset(); - _relativePoses.clear(); - _absolutePoses.clear(); - _overridePoses.clear(); - _overrideFlags.clear(); + _internalPoseSet._relativePoses.clear(); + _internalPoseSet._absolutePoses.clear(); + _internalPoseSet._overridePoses.clear(); + _internalPoseSet._overrideFlags.clear(); } void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset) { @@ -173,16 +175,16 @@ void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOff computeEyesInRootFrame(_animSkeleton->getRelativeDefaultPoses()); - _relativePoses.clear(); - _relativePoses = _animSkeleton->getRelativeDefaultPoses(); + _internalPoseSet._relativePoses.clear(); + _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); - buildAbsoluteRigPoses(_relativePoses, _absolutePoses); + buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); - _overridePoses.clear(); - _overridePoses = _animSkeleton->getRelativeDefaultPoses(); + _internalPoseSet._overridePoses.clear(); + _internalPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses(); - _overrideFlags.clear(); - _overrideFlags.resize(_animSkeleton->getNumJoints(), false); + _internalPoseSet._overrideFlags.clear(); + _internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false); buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); @@ -201,16 +203,16 @@ void Rig::reset(const FBXGeometry& geometry) { computeEyesInRootFrame(_animSkeleton->getRelativeDefaultPoses()); - _relativePoses.clear(); - _relativePoses = _animSkeleton->getRelativeDefaultPoses(); + _internalPoseSet._relativePoses.clear(); + _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); - buildAbsoluteRigPoses(_relativePoses, _absolutePoses); + buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); - _overridePoses.clear(); - _overridePoses = _animSkeleton->getRelativeDefaultPoses(); + _internalPoseSet._overridePoses.clear(); + _internalPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses(); - _overrideFlags.clear(); - _overrideFlags.resize(_animSkeleton->getNumJoints(), false); + _internalPoseSet._overrideFlags.clear(); + _internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false); buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); @@ -228,11 +230,11 @@ void Rig::reset(const FBXGeometry& geometry) { } bool Rig::jointStatesEmpty() { - return _relativePoses.empty(); + return _internalPoseSet._relativePoses.empty(); } int Rig::getJointStateCount() const { - return _relativePoses.size(); + return _internalPoseSet._relativePoses.size(); } int Rig::indexOfJoint(const QString& jointName) const { @@ -262,7 +264,7 @@ void Rig::setModelOffset(const glm::mat4& modelOffsetMat) { bool Rig::getJointStateRotation(int index, glm::quat& rotation) const { if (isIndexValid(index)) { - rotation = _relativePoses[index].rot; + rotation = _internalPoseSet._relativePoses[index].rot; return !isEqual(rotation, _animSkeleton->getRelativeDefaultPose(index).rot); } else { return false; @@ -271,7 +273,7 @@ bool Rig::getJointStateRotation(int index, glm::quat& rotation) const { bool Rig::getJointStateTranslation(int index, glm::vec3& translation) const { if (isIndexValid(index)) { - translation = _relativePoses[index].trans; + translation = _internalPoseSet._relativePoses[index].trans; return !isEqual(translation, _animSkeleton->getRelativeDefaultPose(index).trans); } else { return false; @@ -280,46 +282,47 @@ bool Rig::getJointStateTranslation(int index, glm::vec3& translation) const { void Rig::clearJointState(int index) { if (isIndexValid(index)) { - _overrideFlags[index] = false; + _internalPoseSet._overrideFlags[index] = false; } } void Rig::clearJointStates() { - _overrideFlags.clear(); - _overrideFlags.resize(_animSkeleton->getNumJoints()); + _internalPoseSet._overrideFlags.clear(); + _internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints()); } void Rig::clearJointAnimationPriority(int index) { if (isIndexValid(index)) { - _overrideFlags[index] = false; + _internalPoseSet._overrideFlags[index] = false; + _internalPoseSet._overridePoses[index] = _animSkeleton->getRelativeDefaultPose(index); } } void Rig::setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority) { if (isIndexValid(index)) { if (valid) { - assert(_overrideFlags.size() == _overridePoses.size()); - _overrideFlags[index] = true; - _overridePoses[index].trans = translation; + assert(_internalPoseSet._overrideFlags.size() == _internalPoseSet._overridePoses.size()); + _internalPoseSet._overrideFlags[index] = true; + _internalPoseSet._overridePoses[index].trans = translation; } } } void Rig::setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority) { if (isIndexValid(index)) { - assert(_overrideFlags.size() == _overridePoses.size()); - _overrideFlags[index] = true; - _overridePoses[index].rot = rotation; - _overridePoses[index].trans = translation; + assert(_internalPoseSet._overrideFlags.size() == _internalPoseSet._overridePoses.size()); + _internalPoseSet._overrideFlags[index] = true; + _internalPoseSet._overridePoses[index].rot = rotation; + _internalPoseSet._overridePoses[index].trans = translation; } } void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, float priority) { if (isIndexValid(index)) { if (valid) { - ASSERT(_overrideFlags.size() == _overridePoses.size()); - _overrideFlags[index] = true; - _overridePoses[index].rot = rotation; + ASSERT(_internalPoseSet._overrideFlags.size() == _internalPoseSet._overridePoses.size()); + _internalPoseSet._overrideFlags[index] = true; + _internalPoseSet._overridePoses[index].rot = rotation; } } } @@ -336,7 +339,7 @@ void Rig::restoreJointTranslation(int index, float fraction, float priority) { bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const { if (isIndexValid(jointIndex)) { - position = (rotation * _absolutePoses[jointIndex].trans) + translation; + position = (rotation * _internalPoseSet._absolutePoses[jointIndex].trans) + translation; return true; } else { return false; @@ -345,7 +348,7 @@ bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm: bool Rig::getJointPosition(int jointIndex, glm::vec3& position) const { if (isIndexValid(jointIndex)) { - position = _absolutePoses[jointIndex].trans; + position = _internalPoseSet._absolutePoses[jointIndex].trans; return true; } else { return false; @@ -354,7 +357,7 @@ bool Rig::getJointPosition(int jointIndex, glm::vec3& position) const { bool Rig::getJointRotationInWorldFrame(int jointIndex, glm::quat& result, const glm::quat& rotation) const { if (isIndexValid(jointIndex)) { - result = rotation * _absolutePoses[jointIndex].rot; + result = rotation * _internalPoseSet._absolutePoses[jointIndex].rot; return true; } else { return false; @@ -362,8 +365,19 @@ bool Rig::getJointRotationInWorldFrame(int jointIndex, glm::quat& result, const } bool Rig::getJointRotation(int jointIndex, glm::quat& rotation) const { - if (isIndexValid(jointIndex)) { - rotation = _relativePoses[jointIndex].rot; + QReadLocker readLock(&_externalPoseSetLock); + if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._relativePoses.size()) { + rotation = _externalPoseSet._relativePoses[jointIndex].rot; + return true; + } else { + return false; + } +} + +bool Rig::getAbsoluteJointRotationInRigFrame(int jointIndex, glm::quat& rotation) const { + QReadLocker readLock(&_externalPoseSetLock); + if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._absolutePoses.size()) { + rotation = _externalPoseSet._absolutePoses[jointIndex].rot; return true; } else { return false; @@ -371,8 +385,19 @@ bool Rig::getJointRotation(int jointIndex, glm::quat& rotation) const { } bool Rig::getJointTranslation(int jointIndex, glm::vec3& translation) const { - if (isIndexValid(jointIndex)) { - translation = _relativePoses[jointIndex].trans; + QReadLocker readLock(&_externalPoseSetLock); + if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._relativePoses.size()) { + translation = _externalPoseSet._relativePoses[jointIndex].trans; + return true; + } else { + return false; + } +} + +bool Rig::getAbsoluteJointTranslationInRigFrame(int jointIndex, glm::vec3& translation) const { + QReadLocker readLock(&_externalPoseSetLock); + if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._absolutePoses.size()) { + translation = _externalPoseSet._absolutePoses[jointIndex].trans; return true; } else { return false; @@ -708,21 +733,27 @@ void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) { // evaluate the animation AnimNode::Triggers triggersOut; - _relativePoses = _animNode->evaluate(_animVars, deltaTime, triggersOut); - if ((int)_relativePoses.size() != _animSkeleton->getNumJoints()) { + _internalPoseSet._relativePoses = _animNode->evaluate(_animVars, deltaTime, triggersOut); + if ((int)_internalPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) { // animations haven't fully loaded yet. - _relativePoses = _animSkeleton->getRelativeDefaultPoses(); + _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); } _animVars.clearTriggers(); for (auto& trigger : triggersOut) { _animVars.setTrigger(trigger); } - computeEyesInRootFrame(_relativePoses); + computeEyesInRootFrame(_internalPoseSet._relativePoses); } applyOverridePoses(); - buildAbsoluteRigPoses(_relativePoses, _absolutePoses); + buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); + + // copy internal poses to external poses + { + QWriteLocker writeLock(&_externalPoseSetLock); + _externalPoseSet = _internalPoseSet; + } } void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority, @@ -884,7 +915,7 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm if (isIndexValid(index)) { glm::mat4 rigToWorld = createMatFromQuatAndPos(modelRotation, modelTranslation); glm::mat4 worldToRig = glm::inverse(rigToWorld); - glm::vec3 zAxis = glm::normalize(_absolutePoses[index].trans - transformPoint(worldToRig, lookAtSpot)); + glm::vec3 zAxis = glm::normalize(_internalPoseSet._absolutePoses[index].trans - transformPoint(worldToRig, lookAtSpot)); glm::quat q = rotationBetween(IDENTITY_FRONT, zAxis); // limit rotation @@ -892,7 +923,7 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm q = glm::angleAxis(glm::clamp(glm::angle(q), -MAX_ANGLE, MAX_ANGLE), glm::axis(q)); // directly set absolutePose rotation - _absolutePoses[index].rot = q; + _internalPoseSet._absolutePoses[index].rot = q; } } @@ -989,13 +1020,13 @@ void Rig::applyOverridePoses() { return; } - ASSERT(_animSkeleton->getNumJoints() == (int)_relativePoses.size()); - ASSERT(_animSkeleton->getNumJoints() == (int)_overrideFlags.size()); - ASSERT(_animSkeleton->getNumJoints() == (int)_overridePoses.size()); + ASSERT(_animSkeleton->getNumJoints() == (int)_internalPoseSet._relativePoses.size()); + ASSERT(_animSkeleton->getNumJoints() == (int)_internalPoseSet._overrideFlags.size()); + ASSERT(_animSkeleton->getNumJoints() == (int)_internalPoseSet._overridePoses.size()); - for (size_t i = 0; i < _overrideFlags.size(); i++) { - if (_overrideFlags[i]) { - _relativePoses[i] = _overridePoses[i]; + for (size_t i = 0; i < _internalPoseSet._overrideFlags.size(); i++) { + if (_internalPoseSet._overrideFlags[i]) { + _internalPoseSet._relativePoses[i] = _internalPoseSet._overridePoses[i]; } } } @@ -1020,14 +1051,14 @@ void Rig::buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& a // transform all absolute poses into rig space. AnimPose geometryToRigTransform(_geometryToRigTransform); - for (int i = 0; i < (int)_absolutePoses.size(); i++) { + for (int i = 0; i < (int)absolutePosesOut.size(); i++) { absolutePosesOut[i] = geometryToRigTransform * absolutePosesOut[i]; } } glm::mat4 Rig::getJointTransform(int jointIndex) const { if (isIndexValid(jointIndex)) { - return _absolutePoses[jointIndex]; + return _internalPoseSet._absolutePoses[jointIndex]; } else { return glm::mat4(); } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 8d1d768c18..e3ec5d18cf 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -19,6 +19,7 @@ #include #include #include +#include #include "AnimNode.h" #include "AnimNodeLoader.h" @@ -27,6 +28,9 @@ class Rig; typedef std::shared_ptr RigPointer; +// Rig instances are reentrant. +// However only specific methods thread-safe. Noted below. + class Rig : public QObject, public std::enable_shared_from_this { public: struct StateHandler { @@ -123,12 +127,14 @@ public: // if rotation is identity, result will be in rig space bool getJointRotationInWorldFrame(int jointIndex, glm::quat& result, const glm::quat& rotation) const; - // geometry space + // geometry space (thread-safe) bool getJointRotation(int jointIndex, glm::quat& rotation) const; - - // geometry space bool getJointTranslation(int jointIndex, glm::vec3& translation) const; + // rig space (thread-safe) + bool getAbsoluteJointRotationInRigFrame(int jointIndex, glm::quat& rotation) const; + bool getAbsoluteJointTranslationInRigFrame(int jointIndex, glm::vec3& translation) const; + // legacy bool getJointCombinedRotation(int jointIndex, glm::quat& result, const glm::quat& rotation) const; @@ -217,10 +223,19 @@ public: AnimPose _modelOffset; // model to rig space AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets) - AnimPoseVec _relativePoses; // geometry space relative to parent. - AnimPoseVec _absolutePoses; // rig space, not relative to parent. - AnimPoseVec _overridePoses; // geometry space relative to parent. - std::vector _overrideFlags; + struct PoseSet { + AnimPoseVec _relativePoses; // geometry space relative to parent. + AnimPoseVec _absolutePoses; // rig space, not relative to parent. + AnimPoseVec _overridePoses; // geometry space relative to parent. + std::vector _overrideFlags; + }; + + // Only accessed by the main thread + PoseSet _internalPoseSet; + + // Copy of the _poseSet for external threads. + PoseSet _externalPoseSet; + mutable QReadWriteLock _externalPoseSetLock; AnimPoseVec _absoluteDefaultPoses; // rig space, not relative to parent. diff --git a/libraries/audio-client/CMakeLists.txt b/libraries/audio-client/CMakeLists.txt index 90937edc5d..2c0fc0a9cd 100644 --- a/libraries/audio-client/CMakeLists.txt +++ b/libraries/audio-client/CMakeLists.txt @@ -6,11 +6,6 @@ link_hifi_libraries(audio) target_include_directories(${TARGET_NAME} PUBLIC "${HIFI_LIBRARY_DIR}/audio/src") # have CMake grab externals for us -add_dependency_external_projects(gverb) -find_package(Gverb REQUIRED) -target_link_libraries(${TARGET_NAME} ${GVERB_LIBRARIES}) -target_include_directories(${TARGET_NAME} PRIVATE ${GVERB_INCLUDE_DIRS}) - if (APPLE) find_library(CoreAudio CoreAudio) find_library(CoreFoundation CoreFoundation) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 2cf42f9b85..c998c57e47 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -33,29 +33,6 @@ #include #include -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdouble-promotion" -#endif - -#ifdef WIN32 -#pragma warning (push) -#pragma warning (disable: 4273 4305) -#endif - -extern "C" { - #include - #include -} - -#ifdef WIN32 -#pragma warning (pop) -#endif - -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic pop -#endif - #include #include #include @@ -120,7 +97,6 @@ AudioClient::AudioClient() : _audioSourceInjectEnabled(false), _reverb(false), _reverbOptions(&_scriptReverbOptions), - _gverb(NULL), _inputToNetworkResampler(NULL), _networkToOutputResampler(NULL), _loopbackResampler(NULL), @@ -145,9 +121,7 @@ AudioClient::AudioClient() : connect(updateTimer, &QTimer::timeout, this, &AudioClient::checkDevices); updateTimer->start(DEVICE_CHECK_INTERVAL_MSECS); - // create GVerb filter - _gverb = createGverbFilter(); - configureGverbFilter(_gverb); + configureReverb(); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListener(PacketType::AudioStreamStats, &_stats, "processStreamStatsPacket"); @@ -160,10 +134,6 @@ AudioClient::AudioClient() : AudioClient::~AudioClient() { stop(); - - if (_gverb) { - gverb_free(_gverb); - } } void AudioClient::reset() { @@ -173,8 +143,8 @@ void AudioClient::reset() { _toneSource.reset(); _sourceGain.reset(); _inputGain.reset(); - - gverb_flush(_gverb); + _sourceReverb.reset(); + _listenerReverb.reset(); } void AudioClient::audioMixerKilled() { @@ -492,24 +462,24 @@ void AudioClient::stop() { } } -void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer packet) { +void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer message) { char bitset; - packet->readPrimitive(&bitset); + message->readPrimitive(&bitset); bool hasReverb = oneAtBit(bitset, HAS_REVERB_BIT); if (hasReverb) { float reverbTime, wetLevel; - packet->readPrimitive(&reverbTime); - packet->readPrimitive(&wetLevel); + message->readPrimitive(&reverbTime); + message->readPrimitive(&wetLevel); _receivedAudioStream.setReverb(reverbTime, wetLevel); } else { _receivedAudioStream.clearReverb(); } } -void AudioClient::handleAudioDataPacket(QSharedPointer packet) { +void AudioClient::handleAudioDataPacket(QSharedPointer message) { auto nodeList = DependencyManager::get(); nodeList->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveFirstAudioPacket); @@ -523,11 +493,11 @@ void AudioClient::handleAudioDataPacket(QSharedPointer packet) { } // Audio output must exist and be correctly set up if we're going to process received audio - _receivedAudioStream.parseData(*packet); + _receivedAudioStream.parseData(*message); } } -void AudioClient::handleNoisyMutePacket(QSharedPointer packet) { +void AudioClient::handleNoisyMutePacket(QSharedPointer message) { if (!_muted) { toggleMute(); @@ -536,12 +506,12 @@ void AudioClient::handleNoisyMutePacket(QSharedPointer packet) { } } -void AudioClient::handleMuteEnvironmentPacket(QSharedPointer packet) { +void AudioClient::handleMuteEnvironmentPacket(QSharedPointer message) { glm::vec3 position; float radius; - packet->readPrimitive(&position); - packet->readPrimitive(&radius); + message->readPrimitive(&position); + message->readPrimitive(&radius); emit muteEnvironmentRequested(position, radius); } @@ -569,27 +539,32 @@ bool AudioClient::switchOutputToAudioDevice(const QString& outputDeviceName) { return switchOutputToAudioDevice(getNamedAudioDeviceForMode(QAudio::AudioOutput, outputDeviceName)); } -ty_gverb* AudioClient::createGverbFilter() { - // Initialize a new gverb instance - ty_gverb* filter = gverb_new(_outputFormat.sampleRate(), _reverbOptions->getMaxRoomSize(), _reverbOptions->getRoomSize(), - _reverbOptions->getReverbTime(), _reverbOptions->getDamping(), _reverbOptions->getSpread(), - _reverbOptions->getInputBandwidth(), _reverbOptions->getEarlyLevel(), - _reverbOptions->getTailLevel()); +void AudioClient::configureReverb() { + ReverbParameters p; + _listenerReverb.getParameters(&p); - return filter; + // for now, reuse the gverb parameters + p.sampleRate = _outputFormat.sampleRate(); + p.roomSize = _reverbOptions->getRoomSize(); + p.reverbTime = _reverbOptions->getReverbTime(); + p.highGain = -24.0f * (1.0f - _reverbOptions->getDamping()); + p.bandwidth = 10000.0f * _reverbOptions->getInputBandwidth(); + p.earlyGain = _reverbOptions->getEarlyLevel(); + p.lateGain = _reverbOptions->getTailLevel(); + p.wetDryMix = 100.0f * powf(10.0f, _reverbOptions->getWetLevel() * (1/20.0f)); + _listenerReverb.setParameters(&p); + + // used for adding self-reverb to loopback audio + p.wetDryMix = 100.0f; + p.preDelay = 0.0f; + p.earlyGain = -96.0f; // disable ER + p.lateGain -= 12.0f; // quieter than listener reverb + p.lateMixLeft = 0.0f; + p.lateMixRight = 0.0f; + _sourceReverb.setParameters(&p); } -void AudioClient::configureGverbFilter(ty_gverb* filter) { - // Configure the instance (these functions are not super well named - they actually set several internal variables) - gverb_set_roomsize(filter, _reverbOptions->getRoomSize()); - gverb_set_revtime(filter, _reverbOptions->getReverbTime()); - gverb_set_damping(filter, _reverbOptions->getDamping()); - gverb_set_inputbandwidth(filter, _reverbOptions->getInputBandwidth()); - gverb_set_earlylevel(filter, DB_CO(_reverbOptions->getEarlyLevel())); - gverb_set_taillevel(filter, DB_CO(_reverbOptions->getTailLevel())); -} - -void AudioClient::updateGverbOptions() { +void AudioClient::updateReverbOptions() { bool reverbChanged = false; if (_receivedAudioStream.hasReverb()) { @@ -599,7 +574,7 @@ void AudioClient::updateGverbOptions() { } if (_zoneReverbOptions.getWetLevel() != _receivedAudioStream.getWetLevel()) { _zoneReverbOptions.setWetLevel(_receivedAudioStream.getWetLevel()); - // Not part of actual filter config, no need to set reverbChanged to true + reverbChanged = true; } if (_reverbOptions != &_zoneReverbOptions) { @@ -612,9 +587,7 @@ void AudioClient::updateGverbOptions() { } if (reverbChanged) { - gverb_free(_gverb); - _gverb = createGverbFilter(); - configureGverbFilter(_gverb); + configureReverb(); } } @@ -622,7 +595,8 @@ void AudioClient::setReverb(bool reverb) { _reverb = reverb; if (!_reverb) { - gverb_flush(_gverb); + _sourceReverb.reset(); + _listenerReverb.reset(); } } @@ -642,47 +616,7 @@ void AudioClient::setReverbOptions(const AudioEffectOptions* options) { if (_reverbOptions == &_scriptReverbOptions) { // Apply them to the reverb instances - gverb_free(_gverb); - _gverb = createGverbFilter(); - configureGverbFilter(_gverb); - } -} - -void AudioClient::addReverb(ty_gverb* gverb, int16_t* samplesData, int16_t* reverbAlone, int numSamples, - QAudioFormat& audioFormat, bool noEcho) { - float wetFraction = DB_CO(_reverbOptions->getWetLevel()); - float dryFraction = 1.0f - wetFraction; - - float lValue,rValue; - for (int sample = 0; sample < numSamples; sample += audioFormat.channelCount()) { - // Run GVerb - float value = (float)samplesData[sample]; - gverb_do(gverb, value, &lValue, &rValue); - - // Mix, accounting for clipping, the left and right channels. Ignore the rest. - for (int j = sample; j < sample + audioFormat.channelCount(); j++) { - if (j == sample) { - // left channel - int lResult = glm::clamp((int)(samplesData[j] * dryFraction + lValue * wetFraction), - AudioConstants::MIN_SAMPLE_VALUE, AudioConstants::MAX_SAMPLE_VALUE); - samplesData[j] = (int16_t)lResult; - - if (noEcho) { - reverbAlone[j] = (int16_t)lValue * wetFraction; - } - } else if (j == (sample + 1)) { - // right channel - int rResult = glm::clamp((int)(samplesData[j] * dryFraction + rValue * wetFraction), - AudioConstants::MIN_SAMPLE_VALUE, AudioConstants::MAX_SAMPLE_VALUE); - samplesData[j] = (int16_t)rResult; - - if (noEcho) { - reverbAlone[j] = (int16_t)rValue * wetFraction; - } - } else { - // ignore channels above 2 - } - } + configureReverb(); } } @@ -716,30 +650,28 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { _loopbackResampler = new AudioSRC(_inputFormat.sampleRate(), _outputFormat.sampleRate(), channelCount); } - static QByteArray reverbAlone; // Intermediary for local reverb with no echo static QByteArray loopBackByteArray; int numInputSamples = inputByteArray.size() / sizeof(int16_t); int numLoopbackSamples = numDestinationSamplesRequired(_inputFormat, _outputFormat, numInputSamples); - reverbAlone.resize(numInputSamples * sizeof(int16_t)); loopBackByteArray.resize(numLoopbackSamples * sizeof(int16_t)); int16_t* inputSamples = reinterpret_cast(inputByteArray.data()); - int16_t* reverbAloneSamples = reinterpret_cast(reverbAlone.data()); int16_t* loopbackSamples = reinterpret_cast(loopBackByteArray.data()); - if (hasReverb) { - updateGverbOptions(); - addReverb(_gverb, inputSamples, reverbAloneSamples, numInputSamples, - _inputFormat, !_shouldEchoLocally); - } - possibleResampling(_loopbackResampler, - (_shouldEchoLocally) ? inputSamples : reverbAloneSamples, loopbackSamples, + inputSamples, loopbackSamples, numInputSamples, numLoopbackSamples, _inputFormat, _outputFormat); + // apply stereo reverb at the source, to the loopback audio + if (!_shouldEchoLocally && hasReverb) { + assert(_outputFormat.channelCount() == 2); + updateReverbOptions(); + _sourceReverb.render(loopbackSamples, loopbackSamples, numLoopbackSamples/2); + } + _loopbackOutputDevice->write(loopBackByteArray); } @@ -871,12 +803,20 @@ void AudioClient::processReceivedSamples(const QByteArray& inputBuffer, QByteArr outputBuffer.resize(numDeviceOutputSamples * sizeof(int16_t)); const int16_t* receivedSamples = reinterpret_cast(inputBuffer.data()); + int16_t* outputSamples = reinterpret_cast(outputBuffer.data()); // copy the packet from the RB to the output - possibleResampling(_networkToOutputResampler, receivedSamples, - reinterpret_cast(outputBuffer.data()), + possibleResampling(_networkToOutputResampler, receivedSamples, outputSamples, numNetworkOutputSamples, numDeviceOutputSamples, _desiredOutputFormat, _outputFormat); + + // apply stereo reverb at the listener, to the received audio + bool hasReverb = _reverb || _receivedAudioStream.hasReverb(); + if (hasReverb) { + assert(_outputFormat.channelCount() == 2); + updateReverbOptions(); + _listenerReverb.render(outputSamples, outputSamples, numDeviceOutputSamples/2); + } } void AudioClient::sendMuteEnvironmentPacket() { diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index fe04de4bdc..c80c50ba6c 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -46,6 +46,7 @@ #include "AudioIOStats.h" #include "AudioNoiseGate.h" #include "AudioSRC.h" +#include "AudioReverb.h" #ifdef _WIN32 #pragma warning( push ) @@ -75,8 +76,6 @@ class QAudioOutput; class QIODevice; -typedef struct ty_gverb ty_gverb; - class Transform; class NLPacket; @@ -141,10 +140,10 @@ public slots: void start(); void stop(); - void handleAudioEnvironmentDataPacket(QSharedPointer packet); - void handleAudioDataPacket(QSharedPointer packet); - void handleNoisyMutePacket(QSharedPointer packet); - void handleMuteEnvironmentPacket(QSharedPointer packet); + void handleAudioEnvironmentDataPacket(QSharedPointer message); + void handleAudioDataPacket(QSharedPointer message); + void handleNoisyMutePacket(QSharedPointer message); + void handleMuteEnvironmentPacket(QSharedPointer message); void sendDownstreamAudioStatsPacket() { _stats.sendDownstreamAudioStatsPacket(); } void handleAudioInput(); @@ -263,7 +262,8 @@ private: AudioEffectOptions _scriptReverbOptions; AudioEffectOptions _zoneReverbOptions; AudioEffectOptions* _reverbOptions; - ty_gverb* _gverb; + AudioReverb _sourceReverb { AudioConstants::SAMPLE_RATE }; + AudioReverb _listenerReverb { AudioConstants::SAMPLE_RATE }; // possible streams needed for resample AudioSRC* _inputToNetworkResampler; @@ -271,10 +271,8 @@ private: AudioSRC* _loopbackResampler; // Adds Reverb - ty_gverb* createGverbFilter(); - void configureGverbFilter(ty_gverb* filter); - void updateGverbOptions(); - void addReverb(ty_gverb* gverb, int16_t* samples, int16_t* reverbAlone, int numSamples, QAudioFormat& format, bool noEcho = false); + void configureReverb(); + void updateReverbOptions(); void handleLocalEchoAndReverb(QByteArray& inputByteArray); diff --git a/libraries/audio-client/src/AudioIOStats.cpp b/libraries/audio-client/src/AudioIOStats.cpp index 0ad6860b5a..6896c7fd6b 100644 --- a/libraries/audio-client/src/AudioIOStats.cpp +++ b/libraries/audio-client/src/AudioIOStats.cpp @@ -63,11 +63,12 @@ void AudioIOStats::sentPacket() { _lastSentAudioPacket = now; } } -void AudioIOStats::processStreamStatsPacket(QSharedPointer packet, SharedNodePointer sendingNode) { + +void AudioIOStats::processStreamStatsPacket(QSharedPointer message, SharedNodePointer sendingNode) { // parse the appendFlag, clear injected audio stream stats if 0 quint8 appendFlag; - packet->readPrimitive(&appendFlag); + message->readPrimitive(&appendFlag); if (!appendFlag) { _mixerInjectedStreamStatsMap.clear(); @@ -75,12 +76,12 @@ void AudioIOStats::processStreamStatsPacket(QSharedPointer packet, Sha // parse the number of stream stats structs to follow quint16 numStreamStats; - packet->readPrimitive(&numStreamStats); + message->readPrimitive(&numStreamStats); // parse the stream stats AudioStreamStats streamStats; for (quint16 i = 0; i < numStreamStats; i++) { - packet->readPrimitive(&streamStats); + message->readPrimitive(&streamStats); if (streamStats._streamType == PositionalAudioStream::Microphone) { _mixerAvatarStreamStats = streamStats; diff --git a/libraries/audio-client/src/AudioIOStats.h b/libraries/audio-client/src/AudioIOStats.h index 11f1d74185..2745deac2c 100644 --- a/libraries/audio-client/src/AudioIOStats.h +++ b/libraries/audio-client/src/AudioIOStats.h @@ -45,7 +45,7 @@ public: void sendDownstreamAudioStatsPacket(); public slots: - void processStreamStatsPacket(QSharedPointer packet, SharedNodePointer sendingNode); + void processStreamStatsPacket(QSharedPointer message, SharedNodePointer sendingNode); private: MixedProcessedAudioStream* _receivedAudioStream; diff --git a/libraries/audio/src/AudioEffectOptions.cpp b/libraries/audio/src/AudioEffectOptions.cpp index 221d70aa75..a61213d9c4 100644 --- a/libraries/audio/src/AudioEffectOptions.cpp +++ b/libraries/audio/src/AudioEffectOptions.cpp @@ -28,10 +28,10 @@ AudioEffectOptions::AudioEffectOptions(QScriptValue arguments) : _damping(0.5f), _spread(15.0f), _inputBandwidth(0.75f), - _earlyLevel(-22.0f), - _tailLevel(-28.0f), + _earlyLevel(-12.0f), + _tailLevel(-18.0f), _dryLevel(0.0f), - _wetLevel(6.0f) { + _wetLevel(0.0f) { if (arguments.property(MAX_ROOM_SIZE_HANDLE).isNumber()) { _maxRoomSize = arguments.property(MAX_ROOM_SIZE_HANDLE).toNumber(); } diff --git a/libraries/audio/src/AudioEffectOptions.h b/libraries/audio/src/AudioEffectOptions.h index 97aac7c82c..be5e1cca5e 100644 --- a/libraries/audio/src/AudioEffectOptions.h +++ b/libraries/audio/src/AudioEffectOptions.h @@ -15,6 +15,8 @@ #include #include +#include "AudioReverb.h" + class AudioEffectOptions : public QObject { Q_OBJECT diff --git a/libraries/audio/src/AudioReverb.cpp b/libraries/audio/src/AudioReverb.cpp new file mode 100644 index 0000000000..5c57e92ce5 --- /dev/null +++ b/libraries/audio/src/AudioReverb.cpp @@ -0,0 +1,1968 @@ +// +// AudioReverb.cpp +// libraries/audio/src +// +// Created by Ken Cooke on 10/11/15. +// Copyright 2015 High Fidelity, Inc. +// + +#include +#include +#include + +#include "AudioReverb.h" + +#ifdef _MSC_VER +#pragma warning(disable : 4351) // new behavior: elements of array will be default initialized + +#include +inline static int MULHI(int a, int b) { + long long c = __emul(a, b); + return ((int*)&c)[1]; +} +#else + +#define MULHI(a,b) (int)(((long long)(a) * (b)) >> 32) + +#endif // _MSC_VER + +#ifndef MAX +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#endif +#ifndef MIN +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +static const float PHI = 0.6180339887f; // maximum allpass diffusion +static const float TWOPI = 6.283185307f; + +static const double PI = 3.14159265358979323846; +static const double SQRT2 = 1.41421356237309504880; + +static const double FIXQ31 = 2147483648.0; +static const double FIXQ32 = 4294967296.0; + +// Round an integer to the next power-of-two, at compile time. +// VS2013 does not support constexpr so macros are used instead. +#define SETBITS0(x) (x) +#define SETBITS1(x) (SETBITS0(x) | (SETBITS0(x) >> 1)) +#define SETBITS2(x) (SETBITS1(x) | (SETBITS1(x) >> 2)) +#define SETBITS3(x) (SETBITS2(x) | (SETBITS2(x) >> 4)) +#define SETBITS4(x) (SETBITS3(x) | (SETBITS3(x) >> 8)) +#define SETBITS5(x) (SETBITS4(x) | (SETBITS4(x) >> 16)) +#define NEXTPOW2(x) (SETBITS5((x) - 1) + 1) + +// +// Allpass delay modulation +// +static const int MOD_INTBITS = 4; +static const int MOD_FRACBITS = 31 - MOD_INTBITS; +static const uint32_t MOD_FRACMASK = (1 << MOD_FRACBITS) - 1; +static const float QMOD_TO_FLOAT = 1.0f / (1 << MOD_FRACBITS); + +// +// Reverb delay values, defined for sampleRate=48000 roomSize=100% density=100% +// +// max path should already be prime, to prevent getPrime(NEXTPOW2(N)) > NEXTPOW2(N) +// +static const int M_PD0 = 16000; // max predelay = 333ms + +static const int M_AP0 = 83; +static const int M_AP1 = 211; +static const int M_AP2 = 311; +static const int M_AP3 = 97; +static const int M_AP4 = 223; +static const int M_AP5 = 293; + +static const int M_MT0 = 1017; +static const int M_MT1 = 4077; +static const int M_MT2 = 2039; +static const int M_MT3 = 1017; +static const int M_MT4 = 2593; +static const int M_MT5 = 2039; + +static const int M_MT1_2 = 600; +static const int M_MT4_2 = 600; +static const int M_MT1_MAX = MAX(M_MT1, M_MT1_2); +static const int M_MT4_MAX = MAX(M_MT4, M_MT4_2); + +static const int M_AP6 = 817; +static const int M_AP7 = 513; +static const int M_AP8 = 765; +static const int M_AP9 = 465; +static const int M_AP10 = 3021; +static const int M_AP11 = 2121; +static const int M_AP12 = 1705; +static const int M_AP13 = 1081; +static const int M_AP14 = 3313; +static const int M_AP15 = 2205; +static const int M_AP16 = 1773; +static const int M_AP17 = 981; + +static const int M_AP7_MAX = M_AP7 + (1 << MOD_INTBITS) + 1; // include max excursion +static const int M_AP9_MAX = M_AP9 + (1 << MOD_INTBITS) + 1; + +static const int M_MT6 = 6863; +static const int M_MT7 = 7639; +static const int M_MT8 = 3019; +static const int M_MT9 = 2875; + +static const int M_LD0 = 8000; // max late delay = 166ms +static const int M_MT6_MAX = MAX(M_MT6, M_LD0); +static const int M_MT7_MAX = MAX(M_MT7, M_LD0); +static const int M_MT8_MAX = MAX(M_MT8, M_LD0); +static const int M_MT9_MAX = MAX(M_MT9, M_LD0); + +static const int M_AP18 = 131; +static const int M_AP19 = 113; +static const int M_AP20 = 107; +static const int M_AP21 = 127; + +// +// Filter design tools using analog-matched response. +// All filter types approximate the s-plane response, including cutoff > Nyquist. +// + +static float dBToGain(float dB) { + return powf(10.0f, dB * (1/20.0f)); +} + +static double dBToGain(double dB) { + return pow(10.0, dB * (1/20.0)); +} + +// Returns the gain of analog (s-plane) peak-filter evaluated at w +static double analogPeak(double w0, double G, double Q, double w) { + double w0sq, wsq, Gsq, Qsq; + double num, den; + + w0sq = w0 * w0; + wsq = w * w; + Gsq = G * G; + Qsq = Q * Q; + + num = Qsq * (wsq - w0sq) * (wsq - w0sq) + wsq * w0sq * Gsq; + den = Qsq * (wsq - w0sq) * (wsq - w0sq) + wsq * w0sq; + + return sqrt(num / den); +} + +// Returns the gain of analog (s-plane) shelf evaluated at w +// non-resonant Q = sqrt(0.5) so 1/Q^2 = 2.0 +static double analogShelf(double w0, double G, double resonance, int isHigh, double w) { + double w0sq, wsq, Qrsq; + double num, den; + + resonance = MIN(MAX(resonance, 0.0), 1.0); + Qrsq = 2.0 * pow(G, 1.0 - resonance); // resonant 1/Q^2 + + w0sq = w0 * w0; + wsq = w * w; + + if (isHigh) { + num = (G*wsq - w0sq) * (G*wsq - w0sq) + wsq * w0sq * Qrsq; + } else { + num = (wsq - G*w0sq) * (wsq - G*w0sq) + wsq * w0sq * Qrsq; + } + den = wsq * wsq + w0sq * w0sq; + + return sqrt(num / den); +} + +// Returns the gain of analog (s-plane) highpass/lowpass evaluated at w +// Q = sqrt(0.5) = 2nd order Butterworth +static double analogFilter(double w0, int isHigh, double w) { + double w0sq, wsq; + double num, den; + + w0sq = w0 * w0; + wsq = w * w; + + if (isHigh) { + num = wsq * wsq; + } else { + num = w0sq * w0sq; + } + den = wsq * wsq + w0sq * w0sq; + + return sqrt(num / den); +} + +// +// Biquad Peaking EQ using analog matching. +// NOTE: Peak topology becomes ill-conditioned as w0 approaches pi. +// +static void BQPeakBelowPi(double coef[5], double w0, double dbgain, double Q) { + double G, G1, Gsq, G1sq, Qsq; + double G11, G00, Gratio; + double wpi, wh, w0sq, whsq; + double Wsq, B, A; + double b0, b1, b2, a0, a1, a2; + double temp, scale; + int isUnity; + + // convert cut into boost, invert later + G = dBToGain(fabs(dbgain)); + + // special case near unity gain + isUnity = G < 1.001; + G = MAX(G, 1.001); + + // compute the Nyquist gain + wpi = w0 + 2.8 * (1.0 - w0/PI); // minimax-like error + wpi = MIN(wpi, PI); + G1 = analogPeak(w0, G, Q, wpi); + + G1sq = G1 * G1; + Gsq = G * G; + Qsq = Q * Q; + + // compute the analog half-gain frequency + temp = G + 2.0 * Qsq - sqrt(Gsq + 4.0 * Qsq * G); + wh = sqrt(temp) * w0 / (Q * SQRT2); + + // prewarp freqs of w0 and wh + w0 = tan(0.5 * w0); + wh = tan(0.5 * wh); + w0sq = w0 * w0; + whsq = wh * wh; + + // compute Wsq, from asymmetry due to G1 + G11 = Gsq - G1sq; + G00 = Gsq - 1.0; + Gratio = G11 / G00; + Wsq = w0sq * sqrt(Gratio); + + // compute B, matching gains at w0 and wh + temp = 2.0 * Wsq * (G1 - sqrt(Gratio)); + temp += Gsq * whsq * (1.0 - G1sq) / G00; + temp += G * Gratio * (w0sq - whsq) * (w0sq - whsq) / whsq; + B = sqrt(temp); + + // compute A, matching gains at w0 and wh + temp = 2.0 * Wsq; + temp += (2.0 * G11 * w0sq + (G1sq - G) * whsq) / (G - Gsq); + temp += Gratio * w0sq * w0sq / (G * whsq); + A = sqrt(temp); + + // design digital filter via bilinear transform + b0 = G1 + B + Wsq; + b1 = 2.0 * (Wsq - G1); + b2 = G1 - B + Wsq; + a0 = 1.0 + A + Wsq; + a1 = 2.0 * (Wsq - 1.0); + a2 = 1.0 - A + Wsq; + + // for unity gain, ensure poles/zeros in the right place. + // needed for smooth interpolation when gain is changed. + if (isUnity) { + b0 = a0; + b1 = a1; + b2 = a2; + } + + // invert filter for cut + if (dbgain < 0.0) { + temp = b0; b0 = a0; a0 = temp; + temp = b1; b1 = a1; a1 = temp; + temp = b2; b2 = a2; a2 = temp; + } + + // normalize + scale = 1.0 / a0; + coef[0] = b0 * scale; + coef[1] = b1 * scale; + coef[2] = b2 * scale; + coef[3] = a1 * scale; + coef[4] = a2 * scale; +} + +// +// Biquad Peaking EQ using a shelf instead of peak. +// +// This uses a shelf topology, matched to the analog peaking filter located above Nyquist. +// +// NOTE: the result is close, but not identical to BQPeakBelowPi(), since a pole/zero +// pair must jump to the right side of the real axis. Eg. inflection at the peak goes away. +// However, interpolation from peak to shelf is well behaved if the switch is made near pi, +// as the pole/zero be near (and travel down) the real axis. +// +static void BQPeakAbovePi(double coef[5], double w0, double dbgain, double Q) { + double G, G1, Gsq, Qsq; + double wpi, wh, wn, wd; + double wna, wda; + double gn, gd, gnsq, gdsq; + double num, den; + double Wnsq, Wdsq, B, A; + double b0, b1, b2, a0, a1, a2; + double temp, scale; + int isUnity; + + // convert cut into boost, invert later + G = dBToGain(fabs(dbgain)); + + // special case near unity gain + isUnity = G < 1.001; + G = MAX(G, 1.001); + + // compute the Nyquist gain + wpi = PI; + if (w0 < PI) { + G1 = G; // use the peak gain + } else { + G1 = analogPeak(w0, G, Q, wpi); // use Nyquist gain of analog response + } + + Gsq = G * G; + Qsq = Q * Q; + + // compute the analog half-gain frequency + temp = G + 2.0 * Qsq - sqrt(Gsq + 4.0 * Qsq * G); + wh = sqrt(temp) * w0 / (Q * SQRT2); + + // approximate wn and wd + // use half-gain frequency as mapping + wn = 0.5 * wh / sqrt(sqrt((G1))); + wd = wn * sqrt(G1); + Wnsq = wn * wn; + Wdsq = wd * wd; + + // analog freqs of wn and wd + wna = 2.0 * atan(wn); + wda = 2.0 * atan(wd); + + // normalized analog gains at wna and wda + temp = 1.0 / G1; + gn = temp * analogPeak(w0, G, Q, wna); + gd = temp * analogPeak(w0, G, Q, wda); + gnsq = gn * gn; + gdsq = gd * gd; + + // compute B, matching gains at wn and wd + temp = 1.0 / (wn * wd); + den = fabs(gnsq - gdsq); + num = gnsq * (Wnsq - Wdsq) * (Wnsq - Wdsq) * (Wnsq + gdsq * Wdsq); + B = temp * sqrt(num / den); + + // compute A, matching gains at wn and wd + num = (Wnsq - Wdsq) * (Wnsq - Wdsq) * (Wnsq + gnsq * Wdsq); + A = temp * sqrt(num / den); + + // design digital filter via bilinear transform + b0 = G1 * (1.0 + B + Wnsq); + b1 = G1 * 2.0 * (Wnsq - 1.0); + b2 = G1 * (1.0 - B + Wnsq); + a0 = 1.0 + A + Wdsq; + a1 = 2.0 * (Wdsq - 1.0); + a2 = 1.0 - A + Wdsq; + + // for unity gain, ensure poles/zeros are in the right place. + // allows smooth coefficient interpolation when gain is changed. + if (isUnity) { + b0 = a0; + b1 = a1; + b2 = a2; + } + + // invert filter for cut + if (dbgain < 0.0) { + temp = b0; b0 = a0; a0 = temp; + temp = b1; b1 = a1; a1 = temp; + temp = b2; b2 = a2; a2 = temp; + } + + // normalize + scale = 1.0 / a0; + coef[0] = b0 * scale; + coef[1] = b1 * scale; + coef[2] = b2 * scale; + coef[3] = a1 * scale; + coef[4] = a2 * scale; +} + +// +// Biquad Peaking EQ using analog matching. +// Supports full range of w0. +// +void BQPeak(double coef[5], double w0, double dbgain, double Q) { + w0 = MAX(w0, 0.0); // allow w0 > pi + + Q = MIN(MAX(Q, 1.0e-6), 1.0e+6); + + // Switch from peak to shelf, just before w0 = pi. + // Too early causes a jump in the peak location, which interpolates to lowered gains. + // Too late becomes ill-conditioned, and makes no improvement to interpolated response. + // 3.14 is a good choice. + if (w0 > 3.14) { + BQPeakAbovePi(coef, w0, dbgain, Q); + } else { + BQPeakBelowPi(coef, w0, dbgain, Q); + } +} + +// +// Biquad Shelf using analog matching. +// +void BQShelf(double coef[5], double w0, double dbgain, double resonance, int isHigh) { + double G, G1; + double wpi, wn, wd; + double wna, wda; + double gn, gd, gnsq, gdsq; + double num, den; + double Wnsq, Wdsq, B, A; + double b0, b1, b2, a0, a1, a2; + double temp, scale; + int isUnity; + + w0 = MAX(w0, 0.0); // allow w0 > pi + + resonance = MIN(MAX(resonance, 0.0), 1.0); + + // convert cut into boost, invert later + G = dBToGain(fabs(dbgain)); + + // special case near unity gain + isUnity = G < 1.001; + G = MAX(G, 1.001); + + // compute the Nyquist gain + wpi = w0 + 2.8 * (1.0 - w0/PI); // minimax-like error + wpi = MIN(wpi, PI); + G1 = analogShelf(w0, G, resonance, isHigh, wpi); + + // approximate wn and wd + if (isHigh) { + // use center as mapping + wn = 0.5 * w0 / sqrt(sqrt((G * G1))); + wd = wn * sqrt(G1); + } else { + // use wd as mapping + wd = 0.5 * w0; + wn = wd * sqrt(G/G1); + } + Wnsq = wn * wn; + Wdsq = wd * wd; + + // analog freqs of wn and wd + wna = 2.0 * atan(wn); + wda = 2.0 * atan(wd); + + // normalized analog gains at wna and wda + temp = 1.0 / G1; + gn = temp * analogShelf(w0, G, resonance, isHigh, wna); + gd = temp * analogShelf(w0, G, resonance, isHigh, wda); + gnsq = gn * gn; + gdsq = gd * gd; + + // compute B, matching gains at wn and wd + temp = 1.0 / (wn * wd); + den = fabs(gnsq - gdsq); + num = gnsq * (Wnsq - Wdsq) * (Wnsq - Wdsq) * (Wnsq + gdsq * Wdsq); + B = temp * sqrt(num / den); + + // compute A, matching gains at wn and wd + num = (Wnsq - Wdsq) * (Wnsq - Wdsq) * (Wnsq + gnsq * Wdsq); + A = temp * sqrt(num / den); + + // design digital filter via bilinear transform + b0 = G1 * (1.0 + B + Wnsq); + b1 = G1 * 2.0 * (Wnsq - 1.0); + b2 = G1 * (1.0 - B + Wnsq); + a0 = 1.0 + A + Wdsq; + a1 = 2.0 * (Wdsq - 1.0); + a2 = 1.0 - A + Wdsq; + + // for unity gain, ensure poles/zeros in the right place. + // needed for smooth interpolation when gain is changed. + if (isUnity) { + b0 = a0; + b1 = a1; + b2 = a2; + } + + // invert filter for cut + if (dbgain < 0.0) { + temp = b0; b0 = a0; a0 = temp; + temp = b1; b1 = a1; a1 = temp; + temp = b2; b2 = a2; a2 = temp; + } + + // normalize + scale = 1.0 / a0; + coef[0] = b0 * scale; + coef[1] = b1 * scale; + coef[2] = b2 * scale; + coef[3] = a1 * scale; + coef[4] = a2 * scale; +} + +// +// Biquad Lowpass/Highpass using analog matching. +// Q = sqrt(0.5) = 2nd order Butterworth +// +void BQFilter(double coef[5], double w0, int isHigh) { + double G1; + double wpi, wn, wd; + double wna, wda; + double gn, gd, gnsq, gdsq; + double num, den; + double Wnsq, Wdsq, B, A; + double b0, b1, b2, a0, a1, a2; + double temp, scale; + + w0 = MAX(w0, 0.0); // allow w0 > pi for lowpass + + if (isHigh) { + + w0 = MIN(w0, PI); // disallow w0 > pi for highpass + + // compute the Nyquist gain + wpi = PI; + G1 = analogFilter(w0, isHigh, wpi); + + // approximate wn and wd + wn = 0.0; // zeros at zero + wd = 0.5 * w0; + + Wnsq = wn * wn; + Wdsq = wd * wd; + + // compute B and A + B = 0.0; + A = SQRT2 * Wdsq / atan(wd); // Qd = sqrt(0.5) * atan(wd)/wd; + + } else { + + // compute the Nyquist gain + wpi = w0 + 2.8 * (1.0 - w0/PI); // minimax-like error + wpi = MIN(wpi, PI); + G1 = analogFilter(w0, isHigh, wpi); + + // approximate wn and wd + wd = 0.5 * w0; + wn = wd * sqrt(1.0/G1); // down G1 at pi, instead of zeros + + Wnsq = wn * wn; + Wdsq = wd * wd; + + // analog freqs of wn and wd + wna = 2.0 * atan(wn); + wda = 2.0 * atan(wd); + + // normalized analog gains at wna and wda + temp = 1.0 / G1; + gn = temp * analogFilter(w0, isHigh, wna); + gd = temp * analogFilter(w0, isHigh, wda); + gnsq = gn * gn; + gdsq = gd * gd; + + // compute B, matching gains at wn and wd + temp = 1.0 / (wn * wd); + den = fabs(gnsq - gdsq); + num = gnsq * (Wnsq - Wdsq) * (Wnsq - Wdsq) * (Wnsq + gdsq * Wdsq); + B = temp * sqrt(num / den); + + // compute A, matching gains at wn and wd + num = (Wnsq - Wdsq) * (Wnsq - Wdsq) * (Wnsq + gnsq * Wdsq); + A = temp * sqrt(num / den); + } + + // design digital filter via bilinear transform + b0 = G1 * (1.0 + B + Wnsq); + b1 = G1 * 2.0 * (Wnsq - 1.0); + b2 = G1 * (1.0 - B + Wnsq); + a0 = 1.0 + A + Wdsq; + a1 = 2.0 * (Wdsq - 1.0); + a2 = 1.0 - A + Wdsq; + + // normalize + scale = 1.0 / a0; + coef[0] = b0 * scale; + coef[1] = b1 * scale; + coef[2] = b2 * scale; + coef[3] = a1 * scale; + coef[4] = a2 * scale; +} + +// +// PoleZero Shelf. For Lowpass/Highpass, setCoef dbgain to -100dB. +// NOTE: w0 always sets the pole frequency (3dB corner from unity gain) +// +void PZShelf(double coef[3], double w0, double dbgain, int isHigh) { + double G, G0, G1; + double b0, b1, a0, a1; + double temp, scale; + + w0 = MAX(w0, 0.0); // allow w0 > pi + + // convert boost into cut, invert later + G = dBToGain(-fabs(dbgain)); + + if (isHigh) { + G0 = 1.0; // gain at DC + G1 = G; // gain at Nyquist + } else { + G0 = G; + G1 = 1.0; + } + + b0 = 1.0; + a0 = 1.0; + b1 = -exp(-w0 * G0 / G1); + a1 = -exp(-w0); + + b1 += 0.12 * (1.0 + b1) * (1.0 + b1) * (1.0 - G1); // analog-matched gain near Nyquist + + scale = G0 * (1.0 + a1) / (1.0 + b1); + b0 *= scale; + b1 *= scale; + + // invert filter for boost + if (dbgain > 0.0) { + temp = b0; b0 = a0; a0 = temp; + temp = b1; b1 = a1; a1 = temp; + } + + // normalize + scale = 1.0 / a0; + coef[0] = b0 * scale; + coef[1] = b1 * scale; + coef[2] = a1 * scale; +} + +class BandwidthEQ { + + float _buffer[4] {}; + + float _output0 = 0.0f; + float _output1 = 0.0f; + + float _dcL = 0.0f; + float _dcR = 0.0f; + + float _b0 = 1.0f; + float _b1 = 0.0f; + float _b2 = 0.0f; + float _a1 = 0.0f; + float _a2 = 0.0f; + + float _alpha = 0.0f; + +public: + void setFreq(float freq, float sampleRate) { + freq = MIN(MAX(freq, 1.0f), 24000.0f); + + // lowpass filter, -3dB @ freq + double coef[5]; + BQFilter(coef, TWOPI * freq / sampleRate, 0); + _b0 = (float)coef[0]; + _b1 = (float)coef[1]; + _b2 = (float)coef[2]; + _a1 = (float)coef[3]; + _a2 = (float)coef[4]; + + // DC-blocking filter, -3dB @ 10Hz + _alpha = 1.0f - expf(-TWOPI * 10.0f / sampleRate); + } + + void process(float input0, float input1, float& output0, float& output1) { + output0 = _output0; + output1 = _output1; + + // prevent denormalized zero-input limit cycles in the reverb + input0 += 1.0e-20f; + input1 += 1.0e-20f; + + // remove DC + input0 -= _dcL; + input1 -= _dcR; + + _dcL += _alpha * input0; + _dcR += _alpha * input1; + + // transposed Direct Form II + _output0 = _b0 * input0 + _buffer[0]; + _buffer[0] = _b1 * input0 - _a1 * _output0 + _buffer[1]; + _buffer[1] = _b2 * input0 - _a2 * _output0; + + _output1 = _b0 * input1 + _buffer[2]; + _buffer[2] = _b1 * input1 - _a1 * _output1 + _buffer[3]; + _buffer[3] = _b2 * input1 - _a2 * _output1; + } + + void reset() { + memset(_buffer, 0, sizeof(_buffer)); + _output0 = 0.0f; + _output1 = 0.0f; + _dcL = 0.0f; + _dcR = 0.0f; + } +}; + +template +class DelayLine { + + float _buffer[N] {}; + + float _output = 0.0f; + + int _index = 0; + int _delay = N; + +public: + void setDelay(int d) { + d = MIN(MAX(d, 1), N); + + _delay = d; + } + + void process(float input, float& output) { + output = _output; + + int k = (_index - _delay) & (N - 1); + + _output = _buffer[k]; + + _buffer[_index] = input; + _index = (_index + 1) & (N - 1); + } + + void reset() { + memset(_buffer, 0, sizeof(_buffer)); + _output = 0.0f; + } +}; + +template +class Allpass { + + float _buffer[N] {}; + + float _output = 0.0f; + float _coef = 0.5f; + + int _index0 = 0; + int _index1 = 0; + int _delay = N; + +public: + void setDelay(int d) { + d = MIN(MAX(d, 1), N); + + _index1 = (_index0 - d) & (N - 1); + _delay = d; + } + + int getDelay() { + return _delay; + } + + void setCoef(float coef) { + coef = MIN(MAX(coef, -1.0f), 1.0f); + + _coef = coef; + } + + void process(float input, float& output) { + output = _output; + + _output = _buffer[_index1] - _coef * input; // feedforward path + _buffer[_index0] = input + _coef * _output; // feedback path + + _index0 = (_index0 + 1) & (N - 1); + _index1 = (_index1 + 1) & (N - 1); + } + + void getOutput(float& output) { + output = _output; + } + + void reset() { + memset(_buffer, 0, sizeof(_buffer)); + _output = 0.0f; + } +}; + +class RandomLFO { + + int32_t _y0 = 0; + int32_t _y1 = 0; + int32_t _k = 0; + + int32_t _gain = 0; + + uint32_t _rand0 = 0; + int32_t _q0 = 0; // prev + int32_t _r0 = 0; // next + int32_t _m0 = _r0/2 - _q0/2; // slope + int32_t _b0 = _r0/2 + _q0/2; // offset + + uint32_t _rand1 = 0; + int32_t _q1 = 0; + int32_t _r1 = 0; + int32_t _m1 = _q1/2 - _r1/2; + int32_t _b1 = _q1/2 + _r1/2; + +public: + void setFreq(float freq, float sampleRate) { + freq = MIN(freq, 1/16.0f * sampleRate); + freq = MAX(freq, 1/16777216.0f * sampleRate); + + double w = PI * (double)freq / (double)sampleRate; + + // amplitude slightly less than 1.0 + _y0 = 0; + _y1 = (int32_t)(0.999 * cos(w) * FIXQ31); + + _k = (int32_t)(2.0 * sin(w) * FIXQ32); + } + + void setGain(int32_t gain) { + gain = MIN(MAX(gain, 0), 0x7fffffff); + + _gain = gain; + } + + void process(int32_t& lfoSin, int32_t& lfoCos) { + lfoSin = _y0; + lfoCos = _y1; + + // "Magic Circle" quadrature oscillator + _y1 -= MULHI(_k, _y0); + _y0 += MULHI(_k, _y1); + + // since the oscillators are in quadrature, zero-crossing in one detects a peak in the other + if ((lfoCos ^ _y1) < 0) { + //_rand0 = 69069 * _rand0 + 1; + _rand0 = (11 * _rand0 + 0x04000000) & 0xfc000000; // periodic version + + _q0 = _r0; + _r0 = 2 * MULHI((int32_t)_rand0, _gain); // Q31 + + // scale the peak-to-peak segment to traverse from q0 to r0 + _m0 = _r0/2 - _q0/2; // slope in Q31 + _b0 = _r0/2 + _q0/2; // offset in Q31 + + int32_t sign = _y1 >> 31; + _m0 = (_m0 ^ sign) - sign; + } + if ((lfoSin ^ _y0) < 0) { + //_rand1 = 69069 * _rand1 + 1; + _rand1 = (11 * _rand1 + 0x04000000) & 0xfc000000; // periodic version + + _q1 = _r1; + _r1 = 2 * MULHI((int32_t)_rand1, _gain); // Q31 + + // scale the peak-to-peak segment to traverse from q1 to r1 + _m1 = _q1/2 - _r1/2; // slope in Q31 + _b1 = _q1/2 + _r1/2; // offset in Q31 + + int32_t sign = _y0 >> 31; + _m1 = (_m1 ^ sign) - sign; + } + + lfoSin = 2 * MULHI(lfoSin, _m0) + _b0; // Q31 + lfoCos = 2 * MULHI(lfoCos, _m1) + _b1; // Q31 + } +}; + +template +class AllPassMod { + + float _buffer[N] {}; + + float _output = 0.0f; + float _coef = 0.5f; + + int _index = 0; + int _delay = N; + +public: + void setDelay(int d) { + d = MIN(MAX(d, 1), N); + + _delay = d; + } + + int getDelay() { + return _delay; + } + + void setCoef(float coef) { + coef = MIN(MAX(coef, -1.0f), 1.0f); + + _coef = coef; + } + + void process(float input, int32_t mod, float& output) { + output = _output; + + // add modulation to delay + int32_t offset = _delay + (mod >> MOD_FRACBITS); + float frac = (mod & MOD_FRACMASK) * QMOD_TO_FLOAT; + + // 3rd-order Lagrange interpolation + int k0 = (_index - (offset-1)) & (N - 1); + int k1 = (_index - (offset+0)) & (N - 1); + int k2 = (_index - (offset+1)) & (N - 1); + int k3 = (_index - (offset+2)) & (N - 1); + + float x0 = _buffer[k0]; + float x1 = _buffer[k1]; + float x2 = _buffer[k2]; + float x3 = _buffer[k3]; + + // compute the polynomial coefficients + float c0 = (1/6.0f) * (x3 - x0) + (1/2.0f) * (x1 - x2); + float c1 = (1/2.0f) * (x0 + x2) - x1; + float c2 = x2 - (1/3.0f) * x0 - (1/2.0f) * x1 - (1/6.0f) * x3; + float c3 = x1; + + // compute the polynomial + float delayMod = ((c0 * frac + c1) * frac + c2) * frac + c3; + + _output = delayMod - _coef * input; // feedforward path + _buffer[_index] = input + _coef * _output; // feedback path + + _index = (_index + 1) & (N - 1); + } + + void reset() { + memset(_buffer, 0, sizeof(_buffer)); + _output = 0.0f; + } +}; + +class LowpassEQ { + + float _buffer[2] {}; + + float _output = 0.0f; + + float _b0 = 1.0f; + float _b1 = 0.0f; + float _b2 = 0.0f; + +public: + void setFreq(float sampleRate) { + sampleRate = MIN(MAX(sampleRate, 24000.0f), 48000.0f); + + // two-zero lowpass filter, with zeros at approximately 12khz + // zero radius is adjusted to match the response from 0..9khz + _b0 = 0.5f; + _b1 = 0.5f * sqrtf(2.0f * 12000.0f/(0.5f * sampleRate) - 1.0f); + _b2 = 0.5f - _b1; + } + + void process(float input, float& output) { + output = _output; + + _output = _b0 * input + _b1 * _buffer[0] + _b2 * _buffer[1]; + _buffer[1] = _buffer[0]; + _buffer[0] = input; + } + + void reset() { + memset(_buffer, 0, sizeof(_buffer)); + _output = 0.0f; + } +}; + +class DampingEQ { + + float _buffer[2] {}; + + float _output = 0.0f; + + float _b0 = 1.0f; + float _b1 = 0.0f; + float _b2 = 0.0f; + float _a1 = 0.0f; + float _a2 = 0.0f; + +public: + void setCoef(float dBgain0, float dBgain1, float freq0, float freq1, float sampleRate) { + dBgain0 = MIN(MAX(dBgain0, -100.0f), 100.0f); + dBgain1 = MIN(MAX(dBgain1, -100.0f), 100.0f); + freq0 = MIN(MAX(freq0, 1.0f), 24000.0f); + freq1 = MIN(MAX(freq1, 1.0f), 24000.0f); + + double coefLo[3], coefHi[3]; + PZShelf(coefLo, TWOPI * freq0 / sampleRate, dBgain0, 0); // low shelf + PZShelf(coefHi, TWOPI * freq1 / sampleRate, dBgain1, 1); // high shelf + + // convolve into a single biquad + _b0 = (float)(coefLo[0] * coefHi[0]); + _b1 = (float)(coefLo[0] * coefHi[1] + coefLo[1] * coefHi[0]); + _b2 = (float)(coefLo[1] * coefHi[1]); + _a1 = (float)(coefLo[2] + coefHi[2]); + _a2 = (float)(coefLo[2] * coefHi[2]); + } + + void process(float input, float& output) { + output = _output; + + // transposed Direct Form II + _output = _b0 * input + _buffer[0]; + _buffer[0] = _b1 * input - _a1 * _output + _buffer[1]; + _buffer[1] = _b2 * input - _a2 * _output; + } + + void reset() { + memset(_buffer, 0, sizeof(_buffer)); + _output = 0.0f; + } +}; + +template +class MultiTap2 { + + float _buffer[N] {}; + + float _output0 = 0.0f; + float _output1 = 0.0f; + + float _gain0 = 1.0f; + float _gain1 = 1.0f; + + int _index = 0; + int _delay0 = N; + int _delay1 = N; + +public: + void setDelay(int d0, int d1) { + d0 = MIN(MAX(d0, 1), N); + d1 = MIN(MAX(d1, 1), N); + + _delay0 = d0; + _delay1 = d1; + } + + int getDelay(int k) { + switch (k) { + case 0: return _delay0; + case 1: return _delay1; + default: return 0; + } + } + + void setGain(float g0, float g1) { + _gain0 = g0; + _gain1 = g1; + } + + void process(float input, float& output0, float& output1) { + output0 = _output0; + output1 = _output1; + + int k0 = (_index - _delay0) & (N - 1); + int k1 = (_index - _delay1) & (N - 1); + + _output0 = _gain0 * _buffer[k0]; + _output1 = _gain1 * _buffer[k1]; + + _buffer[_index] = input; + _index = (_index + 1) & (N - 1); + } + + void reset() { + memset(_buffer, 0, sizeof(_buffer)); + _output0 = 0.0f; + _output1 = 0.0f; + } +}; + +template +class MultiTap3 { + + float _buffer[N] {}; + + float _output0 = 0.0f; + float _output1 = 0.0f; + float _output2 = 0.0f; + + float _gain0 = 1.0f; + float _gain1 = 1.0f; + float _gain2 = 1.0f; + + int _index = 0; + int _delay0 = N; + int _delay1 = N; + int _delay2 = N; + +public: + void setDelay(int d0, int d2) { + d0 = MIN(MAX(d0, 1), N); + d2 = MIN(MAX(d2, 1), N); + + _delay0 = d0; + _delay1 = d0 - 1; + _delay2 = d2; + } + + int getDelay(int k) { + switch (k) { + case 0: return _delay0; + case 1: return _delay1; + case 2: return _delay2; + default: return 0; + } + } + + void setGain(float g0, float g1, float g2) { + _gain0 = g0; + _gain1 = g1; + _gain2 = g2; + } + + void process(float input, float& output0, float& output1, float& output2) { + output0 = _output0; + output1 = _output1; + output2 = _output2; + + int k0 = (_index - _delay0) & (N - 1); + int k1 = (_index - _delay1) & (N - 1); + int k2 = (_index - _delay2) & (N - 1); + + _output0 = _gain0 * _buffer[k0]; + _output1 = _gain1 * _buffer[k1]; + _output2 = _gain2 * _buffer[k2]; + + _buffer[_index] = input; + _index = (_index + 1) & (N - 1); + } + + void reset() { + memset(_buffer, 0, sizeof(_buffer)); + _output0 = 0.0f; + _output1 = 0.0f; + _output2 = 0.0f; + } +}; + +// +// Stereo Reverb +// +class ReverbImpl { + + // Preprocess + BandwidthEQ _bw; + DelayLine _dl0; + DelayLine _dl1; + + // Early Left + float _earlyMix1L = 0.0f; + float _earlyMix2L = 0.0f; + + MultiTap3 _mt0; + Allpass _ap0; + MultiTap3 _mt1; + Allpass _ap1; + Allpass _ap2; + MultiTap2 _mt2; + + // Early Right + float _earlyMix1R = 0.0f; + float _earlyMix2R = 0.0f; + + MultiTap3 _mt3; + Allpass _ap3; + MultiTap3 _mt4; + Allpass _ap4; + Allpass _ap5; + MultiTap2 _mt5; + + RandomLFO _lfo; + + // Late + Allpass _ap6; + AllPassMod _ap7; + DampingEQ _eq0; + MultiTap2 _mt6; + + Allpass _ap8; + AllPassMod _ap9; + DampingEQ _eq1; + MultiTap2 _mt7; + + Allpass _ap10; + Allpass _ap11; + Allpass _ap12; + Allpass _ap13; + MultiTap2 _mt8; + LowpassEQ _lp0; + + Allpass _ap14; + Allpass _ap15; + Allpass _ap16; + Allpass _ap17; + MultiTap2 _mt9; + LowpassEQ _lp1; + + // Output Left + Allpass _ap18; + Allpass _ap19; + + // Output Right + Allpass _ap20; + Allpass _ap21; + + float _earlyGain = 0.0f; + float _wetDryMix = 0.0f; + +public: + void setParameters(ReverbParameters *p); + void process(float** inputs, float** outputs, int numFrames); + void reset(); +}; + +static const short primeTable[] = { + 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, + 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, + 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, + 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, + 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, + 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, + 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, + 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, + 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, + 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, + 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, + 977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, + 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, + 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, + 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423, + 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, + 1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, + 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, + 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, 1861, + 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, + 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, + 2087, 2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, + 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297, 2309, 2311, + 2333, 2339, 2341, 2347, 2351, 2357, 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, + 2423, 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, + 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, + 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, + 2789, 2791, 2797, 2801, 2803, 2819, 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, + 2903, 2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, 3001, 3011, 3019, 3023, + 3037, 3041, 3049, 3061, 3067, 3079, 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, + 3181, 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, 3259, 3271, 3299, 3301, + 3307, 3313, 3319, 3323, 3329, 3331, 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, + 3413, 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533, + 3539, 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, + 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727, 3733, 3739, 3761, 3767, + 3769, 3779, 3793, 3797, 3803, 3821, 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, + 3907, 3911, 3917, 3919, 3923, 3929, 3931, 3943, 3947, 3967, 3989, 4001, 4003, 4007, 4013, + 4019, 4021, 4027, 4049, 4051, 4057, 4073, 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, + 4139, 4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, 4231, 4241, 4243, 4253, 4259, + 4261, 4271, 4273, 4283, 4289, 4297, 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, + 4409, 4421, 4423, 4441, 4447, 4451, 4457, 4463, 4481, 4483, 4493, 4507, 4513, 4517, 4519, + 4523, 4547, 4549, 4561, 4567, 4583, 4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, + 4657, 4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751, 4759, 4783, 4787, 4789, + 4793, 4799, 4801, 4813, 4817, 4831, 4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, + 4937, 4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003, 5009, 5011, 5021, 5023, + 5039, 5051, 5059, 5077, 5081, 5087, 5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, + 5179, 5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279, 5281, 5297, 5303, 5309, + 5323, 5333, 5347, 5351, 5381, 5387, 5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, + 5443, 5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521, 5527, 5531, 5557, 5563, + 5569, 5573, 5581, 5591, 5623, 5639, 5641, 5647, 5651, 5653, 5657, 5659, 5669, 5683, 5689, + 5693, 5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791, 5801, 5807, 5813, 5821, + 5827, 5839, 5843, 5849, 5851, 5857, 5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, + 5939, 5953, 5981, 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053, 6067, 6073, 6079, 6089, + 6091, 6101, 6113, 6121, 6131, 6133, 6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, + 6221, 6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301, 6311, 6317, 6323, 6329, + 6337, 6343, 6353, 6359, 6361, 6367, 6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, + 6473, 6481, 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571, 6577, 6581, 6599, 6607, + 6619, 6637, 6653, 6659, 6661, 6673, 6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, + 6761, 6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, 6833, 6841, 6857, 6863, 6869, + 6871, 6883, 6899, 6907, 6911, 6917, 6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, + 6997, 7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103, 7109, 7121, 7127, 7129, + 7151, 7159, 7177, 7187, 7193, 7207, 7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, + 7297, 7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 7411, 7417, 7433, 7451, 7457, + 7459, 7477, 7481, 7487, 7489, 7499, 7507, 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, + 7561, 7573, 7577, 7583, 7589, 7591, 7603, 7607, 7621, 7639, 7643, 7649, 7669, 7673, 7681, + 7687, 7691, 7699, 7703, 7717, 7723, 7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, + 7829, 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919, 7927, 7933, 7937, 7949, + 7951, 7963, 7993, 8009, 8011, 8017, 8039, 8053, 8059, 8069, 8081, 8087, 8089, 8093, 8101, + 8111, 8117, 8123, 8147, 8161, 8167, 8171, 8179, 8191, +}; + +static int getPrime(int n) { + int low = 0; + int high = sizeof(primeTable) / sizeof(primeTable[0]) - 1; + + // clip to table limits + if (n <= primeTable[low]) { + return primeTable[low]; + } + if (n >= primeTable[high]) { + return primeTable[high]; + } + + // binary search + while (low <= high) { + int mid = (low + high) >> 1; + + if (n < primeTable[mid]) { + high = mid - 1; + } else if (n > primeTable[mid]) { + low = mid + 1; + } else { + return n; // found it + } + } + + //return primeTable[high]; // lower prime + //return (n - primeTable[high]) < (primeTable[low] - n) ? primeTable[high] : primeTable[low]; // nearest prime + return primeTable[low]; // higher prime +} + +static int scaleDelay(float delay, float sampleRate) { + return getPrime((int)(delay * (sampleRate/48000.0f) + 0.5f)); +} + +// +// Piecewise-linear lookup tables +// input clamped to [0.0f, 100.0f] +// +static const float earlyMix0Table[][2] = { + {0.0000f, 0.6000f}, + {63.3333f, 0.0800f}, + {83.3333f, 0.0200f}, + {93.3333f, 0.0048f}, + {100.0000f, 0.0048f}, +}; + +static const float earlyMix1Table[][2] = { + {0.0000f, 0.3360f}, + {20.0000f, 0.6000f}, + {100.0000f, 0.0240f}, +}; + +static const float earlyMix2Table[][2] = { + {0.0000f, 0.0480f}, + {13.3333f, 0.0960f}, + {53.3333f, 0.9600f}, + {100.0000f, 0.1200f}, +}; + +static const float lateMix0Table[][2] = { + {0.0000f, 0.1250f}, + {13.3333f, 0.1875f}, + {66.6666f, 0.7500f}, + {100.0000f, 0.8750f}, +}; + +static const float lateMix1Table[][2] = { + {0.0000f, 0.9990f}, + {33.3333f, 0.5000f}, + {66.6666f, 0.9990f}, + {93.3333f, 0.6000f}, + {100.0000f, 0.6000f}, +}; + +static const float lateMix2Table[][2] = { + {0.0000f, 0.9990f}, + {33.3333f, 0.9990f}, + {63.3333f, 0.4500f}, + {100.0000f, 0.9990f}, +}; + +static const float diffusionCoefTable[][2] = { + {0.0000f, 0.0000f}, + {20.0000f, 0.0470f}, + {33.3333f, 0.0938f}, + {46.6666f, 0.1563f}, + {60.0000f, 0.2344f}, + {73.3333f, 0.3125f}, + {93.3333f, 0.5000f}, + {100.0000f, PHI}, +}; + +static const float roomSizeTable[][2] = { + {0.0000f, 0.1500f}, + {25.0000f, 0.3000f}, + {50.0000f, 0.5000f}, + {100.0000f, 1.0000f}, +}; + +static float interpolateTable(const float table[][2], float x) { + x = MIN(MAX(x, 0.0f), 100.0f); + + // locate the segment in the table + int i = 0; + while (x > table[i+1][0]) { + i++; + } + + // linear interpolate + float frac = (x - table[i+0][0]) / (table[i+1][0] - table[i+0][0]); + return table[i+0][1] + frac * (table[i+1][1] - table[i+0][1]); +} + +void ReverbImpl::setParameters(ReverbParameters *p) { + + float sampleRate = MIN(MAX(p->sampleRate, 24000.0f), 48000.0f); + + // Bandwidth + _bw.setFreq(p->bandwidth, sampleRate); + + // Modulation + _lfo.setFreq(p->modRate, sampleRate); + _lfo.setGain((int32_t)MIN(MAX((double)p->modDepth * (1/100.0) * FIXQ31, 0.0), (double)0x7fffffff)); + + // + // Set delays + // + int preDelay = (int)(p->preDelay * (1/1000.0f) * sampleRate + 0.5f); + preDelay = MIN(MAX(preDelay, 1), M_PD0); + _dl0.setDelay(preDelay); + _dl1.setDelay(preDelay); + + // RoomSize scalefactor + float roomSize = interpolateTable(roomSizeTable, p->roomSize); + + // Density scalefactors + float density0 = p->density * (1/25.0f) - 0.0f; + float density1 = p->density * (1/25.0f) - 1.0f; + float density2 = p->density * (1/25.0f) - 2.0f; + float density3 = p->density * (1/25.0f) - 3.0f; + density0 = MIN(MAX(density0, 0.0f), 1.0f); + density1 = MIN(MAX(density1, 0.0f), 1.0f); + density2 = MIN(MAX(density2, 0.0f), 1.0f); + density3 = MIN(MAX(density3, 0.0f), 1.0f); + + // Early delays + _ap0.setDelay(scaleDelay(M_AP0 * 1.0f, sampleRate)); + _ap1.setDelay(scaleDelay(M_AP1 * 1.0f, sampleRate)); + _ap2.setDelay(scaleDelay(M_AP2 * 1.0f, sampleRate)); + _ap3.setDelay(scaleDelay(M_AP3 * 1.0f, sampleRate)); + _ap4.setDelay(scaleDelay(M_AP4 * 1.0f, sampleRate)); + _ap5.setDelay(scaleDelay(M_AP5 * 1.0f, sampleRate)); + + _mt0.setDelay(scaleDelay(M_MT0 * roomSize, sampleRate), 1); + _mt1.setDelay(scaleDelay(M_MT1 * roomSize, sampleRate), scaleDelay(M_MT1_2 * 1.0f, sampleRate)); + _mt2.setDelay(scaleDelay(M_MT2 * roomSize, sampleRate), 1); + _mt3.setDelay(scaleDelay(M_MT3 * roomSize, sampleRate), 1); + _mt4.setDelay(scaleDelay(M_MT4 * roomSize, sampleRate), scaleDelay(M_MT4_2 * 1.0f, sampleRate)); + _mt5.setDelay(scaleDelay(M_MT5 * roomSize, sampleRate), 1); + + // Late delays + _ap6.setDelay(scaleDelay(M_AP6 * roomSize * density3, sampleRate)); + _ap7.setDelay(scaleDelay(M_AP7 * roomSize, sampleRate)); + _ap8.setDelay(scaleDelay(M_AP8 * roomSize * density3, sampleRate)); + _ap9.setDelay(scaleDelay(M_AP9 * roomSize, sampleRate)); + _ap10.setDelay(scaleDelay(M_AP10 * roomSize * density1, sampleRate)); + _ap11.setDelay(scaleDelay(M_AP11 * roomSize * density2, sampleRate)); + _ap12.setDelay(scaleDelay(M_AP12 * roomSize, sampleRate)); + _ap13.setDelay(scaleDelay(M_AP13 * roomSize * density3, sampleRate)); + _ap14.setDelay(scaleDelay(M_AP14 * roomSize * density1, sampleRate)); + _ap15.setDelay(scaleDelay(M_AP15 * roomSize * density2, sampleRate)); + _ap16.setDelay(scaleDelay(M_AP16 * roomSize * density3, sampleRate)); + _ap17.setDelay(scaleDelay(M_AP17 * roomSize * density3, sampleRate)); + + int lateDelay = scaleDelay(p->lateDelay * (1/1000.0f) * 48000, sampleRate); + lateDelay = MIN(MAX(lateDelay, 1), M_LD0); + + _mt6.setDelay(scaleDelay(M_MT6 * roomSize * density3, sampleRate), lateDelay); + _mt7.setDelay(scaleDelay(M_MT7 * roomSize * density2, sampleRate), lateDelay); + _mt8.setDelay(scaleDelay(M_MT8 * roomSize * density0, sampleRate), lateDelay); + _mt9.setDelay(scaleDelay(M_MT9 * roomSize, sampleRate), lateDelay); + + // Output delays + _ap18.setDelay(scaleDelay(M_AP18 * 1.0f, sampleRate)); + _ap19.setDelay(scaleDelay(M_AP19 * 1.0f, sampleRate)); + _ap20.setDelay(scaleDelay(M_AP20 * 1.0f, sampleRate)); + _ap21.setDelay(scaleDelay(M_AP21 * 1.0f, sampleRate)); + + // RT60 is determined by mean delay of feedback paths + int loopDelay; + loopDelay = _ap6.getDelay(); + loopDelay += _ap7.getDelay(); + loopDelay += _ap8.getDelay(); + loopDelay += _ap9.getDelay(); + loopDelay += _ap10.getDelay(); + loopDelay += _ap11.getDelay(); + loopDelay += _ap12.getDelay(); + loopDelay += _ap13.getDelay(); + loopDelay += _ap14.getDelay(); + loopDelay += _ap15.getDelay(); + loopDelay += _ap16.getDelay(); + loopDelay += _ap17.getDelay(); + loopDelay += _mt6.getDelay(0); + loopDelay += _mt7.getDelay(0); + loopDelay += _mt8.getDelay(0); + loopDelay += _mt9.getDelay(0); + loopDelay /= 2; + + // + // Set gains + // + float rt60 = MIN(MAX(p->reverbTime, 0.01f), 100.0f); + float rt60Gain = -60.0f * loopDelay / (rt60 * sampleRate); // feedback gain (dB) for desired RT + + float bassMult = MIN(MAX(p->bassMult, 0.1f), 10.0f); + float bassGain = (1.0f / bassMult - 1.0f) * rt60Gain; // filter gain (dB) that results in RT *= mult + + float loopGain1 = sqrtf(0.5f * dBToGain(rt60Gain)); // distributed (series-parallel) loop gain + float loopGain2 = 0.9f - (0.63f * loopGain1); + + // Damping + _eq0.setCoef(bassGain, p->highGain, p->bassFreq, p->highFreq, sampleRate); + _eq1.setCoef(bassGain, p->highGain, p->bassFreq, p->highFreq, sampleRate); + _lp0.setFreq(sampleRate); + _lp1.setFreq(sampleRate); + + float earlyDiffusionCoef = interpolateTable(diffusionCoefTable, p->earlyDiffusion); + + // Early Left + _earlyMix1L = interpolateTable(earlyMix1Table, p->earlyMixRight); + _earlyMix2L = interpolateTable(earlyMix2Table, p->earlyMixLeft); + + _mt0.setGain(0.2f, 0.4f, interpolateTable(earlyMix0Table, p->earlyMixLeft)); + + _ap0.setCoef(earlyDiffusionCoef); + _ap1.setCoef(earlyDiffusionCoef); + _ap2.setCoef(earlyDiffusionCoef); + + _mt1.setGain(0.2f, 0.6f, interpolateTable(lateMix0Table, p->lateMixLeft) * 0.125f); + + _mt2.setGain(interpolateTable(lateMix1Table, p->lateMixLeft) * loopGain2, + interpolateTable(lateMix2Table, p->lateMixLeft) * loopGain2); + + // Early Right + _earlyMix1R = interpolateTable(earlyMix1Table, p->earlyMixLeft); + _earlyMix2R = interpolateTable(earlyMix2Table, p->earlyMixRight); + + _mt3.setGain(0.2f, 0.4f, interpolateTable(earlyMix0Table, p->earlyMixRight)); + + _ap3.setCoef(earlyDiffusionCoef); + _ap4.setCoef(earlyDiffusionCoef); + _ap5.setCoef(earlyDiffusionCoef); + + _mt4.setGain(0.2f, 0.6f, interpolateTable(lateMix0Table, p->lateMixRight) * 0.125f); + + _mt5.setGain(interpolateTable(lateMix1Table, p->lateMixRight) * loopGain2, + interpolateTable(lateMix2Table, p->lateMixRight) * loopGain2); + + _earlyGain = dBToGain(p->earlyGain); + + // Late + float lateDiffusionCoef = interpolateTable(diffusionCoefTable, p->lateDiffusion); + _ap6.setCoef(lateDiffusionCoef); + _ap7.setCoef(lateDiffusionCoef); + _ap8.setCoef(lateDiffusionCoef); + _ap9.setCoef(lateDiffusionCoef); + + _ap10.setCoef(PHI); + _ap11.setCoef(PHI); + _ap12.setCoef(lateDiffusionCoef); + _ap13.setCoef(lateDiffusionCoef); + + _ap14.setCoef(PHI); + _ap15.setCoef(PHI); + _ap16.setCoef(lateDiffusionCoef); + _ap17.setCoef(lateDiffusionCoef); + + float lateGain = dBToGain(p->lateGain) * 2.0f; + _mt6.setGain(loopGain1, lateGain * interpolateTable(lateMix0Table, p->lateMixLeft)); + _mt7.setGain(loopGain1, lateGain * interpolateTable(lateMix0Table, p->lateMixRight)); + _mt8.setGain(loopGain1, lateGain * interpolateTable(lateMix2Table, p->lateMixLeft) * loopGain2 * 0.125f); + _mt9.setGain(loopGain1, lateGain * interpolateTable(lateMix2Table, p->lateMixRight) * loopGain2 * 0.125f); + + // Output + float outputDiffusionCoef = lateDiffusionCoef * 0.6f; + _ap18.setCoef(outputDiffusionCoef); + _ap19.setCoef(outputDiffusionCoef); + _ap20.setCoef(outputDiffusionCoef); + _ap21.setCoef(outputDiffusionCoef); + + _wetDryMix = p->wetDryMix * (1/100.0f); + _wetDryMix = MIN(MAX(_wetDryMix, 0.0f), 1.0f); +} + +void ReverbImpl::process(float** inputs, float** outputs, int numFrames) { + + for (int i = 0; i < numFrames; i++) { + float x0, x1, y0, y1, y2, y3; + + // Preprocess + x0 = inputs[0][i]; + x1 = inputs[1][i]; + _bw.process(x0, x1, x0, x1); + + float preL, preR; + _dl0.process(x0, preL); + _dl1.process(x1, preR); + + // Early Left + float early0L, early1L, early2L, earlyOutL; + _mt0.process(preL, x0, x1, y0); + _ap0.process(x0 + x1, y1); + _mt1.process(y1, x0, x1, early0L); + _ap1.process(x0 + x1, y2); + _ap2.process(y2, x0); + _mt2.process(x0, early1L, early2L); + + earlyOutL = (y0 + y1 * _earlyMix1L + y2 * _earlyMix2L) * _earlyGain; + + // Early Right + float early0R, early1R, early2R, earlyOutR; + _mt3.process(preR, x0, x1, y0); + _ap3.process(x0 + x1, y1); + _mt4.process(y1, x0, x1, early0R); + _ap4.process(x0 + x1, y2); + _ap5.process(y2, x0); + _mt5.process(x0, early1R, early2R); + + earlyOutR = (y0 + y1 * _earlyMix1R + y2 * _earlyMix2R) * _earlyGain; + + // LFO update + int32_t lfoSin, lfoCos; + _lfo.process(lfoSin, lfoCos); + + // Late + float lateOut0; + _ap6.getOutput(x0); + _ap7.process(x0, lfoSin, x0); + _eq0.process(-early0L + x0, x0); + _mt6.process(x0, y0, lateOut0); + + float lateOut1; + _ap8.getOutput(x0); + _ap9.process(x0, lfoCos, x0); + _eq1.process(-early0R + x0, x0); + _mt7.process(x0, y1, lateOut1); + + float lateOut2; + _ap10.getOutput(x0); + _ap11.process(-early2L + x0, x0); + _ap12.process(x0, x0); + _ap13.process(-early2L - x0, x0); + _mt8.process(-early0L + x0, x0, lateOut2); + _lp0.process(x0, y2); + + float lateOut3; + _ap14.getOutput(x0); + _ap15.process(-early2R + x0, x0); + _ap16.process(x0, x0); + _ap17.process(-early2R - x0, x0); + _mt9.process(-early0R + x0, x0, lateOut3); + _lp1.process(x0, y3); + + // Feedback matrix + _ap6.process(early1L + y2 - y3, x0); + _ap8.process(early1R - y2 - y3, x0); + _ap10.process(-early2R + y0 + y1, x0); + _ap14.process(-early2L - y0 + y1, x0); + + // Output Left + _ap18.process(-earlyOutL + lateOut0 + lateOut3, x0); + _ap19.process(x0, y0); + + // Output Right + _ap20.process(-earlyOutR + lateOut1 + lateOut2, x1); + _ap21.process(x1, y1); + + x0 = inputs[0][i]; + x1 = inputs[1][i]; + outputs[0][i] = x0 + (y0 - x0) * _wetDryMix; + outputs[1][i] = x1 + (y1 - x1) * _wetDryMix; + } +} + +// clear internal state, but retain settings +void ReverbImpl::reset() { + + _bw.reset(); + + _dl0.reset(); + _dl1.reset(); + + _mt0.reset(); + _mt1.reset(); + _mt2.reset(); + _mt3.reset(); + _mt4.reset(); + _mt5.reset(); + _mt6.reset(); + _mt7.reset(); + _mt8.reset(); + _mt9.reset(); + + _ap0.reset(); + _ap1.reset(); + _ap2.reset(); + _ap3.reset(); + _ap4.reset(); + _ap5.reset(); + _ap6.reset(); + _ap7.reset(); + _ap8.reset(); + _ap9.reset(); + _ap10.reset(); + _ap11.reset(); + _ap12.reset(); + _ap13.reset(); + _ap14.reset(); + _ap15.reset(); + _ap16.reset(); + _ap17.reset(); + _ap18.reset(); + _ap19.reset(); + _ap20.reset(); + _ap21.reset(); + + _eq0.reset(); + _eq1.reset(); + + _lp0.reset(); + _lp1.reset(); +} + +// +// Public API +// + +static const int REVERB_BLOCK = 1024; + +AudioReverb::AudioReverb(float sampleRate) { + + _impl = new ReverbImpl; + + // format conversion buffers + _inout[0] = new float[REVERB_BLOCK]; + _inout[1] = new float[REVERB_BLOCK]; + + // default parameters + ReverbParameters p; + p.sampleRate = sampleRate; + p.bandwidth = 10000.0f; + + p.preDelay = 20.0f; + p.lateDelay = 0.0f; + + p.reverbTime = 2.0f; + + p.earlyDiffusion = 100.0f; + p.lateDiffusion = 100.0f; + + p.roomSize = 50.0f; + p.density = 100.0f; + + p.bassMult = 1.5f; + p.bassFreq = 250.0f; + p.highGain = -6.0f; + p.highFreq = 3000.0f; + + p.modRate = 2.3f; + p.modDepth = 50.0f; + + p.earlyGain = 0.0f; + p.lateGain = 0.0f; + + p.earlyMixLeft = 20.0f; + p.earlyMixRight = 20.0f; + p.lateMixLeft = 90.0f; + p.lateMixRight = 90.0f; + + p.wetDryMix = 100.0f; + + setParameters(&p); +} + +AudioReverb::~AudioReverb() { + delete _impl; + + delete[] _inout[0]; + delete[] _inout[1]; +} + +void AudioReverb::setParameters(ReverbParameters *p) { + _params = *p; + _impl->setParameters(p); +}; + +void AudioReverb::getParameters(ReverbParameters *p) { + *p = _params; +}; + +void AudioReverb::reset() { + _impl->reset(); +} + +void AudioReverb::render(float** inputs, float** outputs, int numFrames) { + _impl->process(inputs, outputs, numFrames); +} + +// +// on x86 architecture, assume that SSE2 is present +// +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__) + +#include + +// convert int16_t to float, deinterleave stereo +void AudioReverb::convertInputFromInt16(const int16_t* input, float** outputs, int numFrames) { + __m128 scale = _mm_set1_ps(1/32768.0f); + + int i = 0; + for (; i < numFrames - 3; i += 4) { + __m128i a0 = _mm_loadu_si128((__m128i*)&input[2*i]); + __m128i a1 = a0; + + // deinterleave and sign-extend + a0 = _mm_madd_epi16(a0, _mm_set1_epi32(0x00000001)); + a1 = _mm_madd_epi16(a1, _mm_set1_epi32(0x00010000)); + + __m128 f0 = _mm_mul_ps(_mm_cvtepi32_ps(a0), scale); + __m128 f1 = _mm_mul_ps(_mm_cvtepi32_ps(a1), scale); + + _mm_storeu_ps(&outputs[0][i], f0); + _mm_storeu_ps(&outputs[1][i], f1); + } + for (; i < numFrames; i++) { + __m128i a0 = _mm_cvtsi32_si128(*(int32_t*)&input[2*i]); + __m128i a1 = a0; + + // deinterleave and sign-extend + a0 = _mm_madd_epi16(a0, _mm_set1_epi32(0x00000001)); + a1 = _mm_madd_epi16(a1, _mm_set1_epi32(0x00010000)); + + __m128 f0 = _mm_mul_ps(_mm_cvtepi32_ps(a0), scale); + __m128 f1 = _mm_mul_ps(_mm_cvtepi32_ps(a1), scale); + + _mm_store_ss(&outputs[0][i], f0); + _mm_store_ss(&outputs[1][i], f1); + } +} + +// fast TPDF dither in [-1.0f, 1.0f] +static inline __m128 dither4() { + static __m128i rz; + + // update the 8 different maximum-length LCGs + rz = _mm_mullo_epi16(rz, _mm_set_epi16(25173, -25511, -5975, -23279, 19445, -27591, 30185, -3495)); + rz = _mm_add_epi16(rz, _mm_set_epi16(13849, -32767, 105, -19675, -7701, -32679, -13225, 28013)); + + // promote to 32-bit + __m128i r0 = _mm_unpacklo_epi16(rz, _mm_setzero_si128()); + __m128i r1 = _mm_unpackhi_epi16(rz, _mm_setzero_si128()); + + // return (r0 - r1) * (1/65536.0f); + __m128 d0 = _mm_cvtepi32_ps(_mm_sub_epi32(r0, r1)); + return _mm_mul_ps(d0, _mm_set1_ps(1/65536.0f)); +} + +// convert float to int16_t, interleave stereo +void AudioReverb::convertOutputToInt16(float** inputs, int16_t* output, int numFrames) { + __m128 scale = _mm_set1_ps(32768.0f); + + int i = 0; + for (; i < numFrames - 3; i += 4) { + __m128 f0 = _mm_mul_ps(_mm_loadu_ps(&inputs[0][i]), scale); + __m128 f1 = _mm_mul_ps(_mm_loadu_ps(&inputs[1][i]), scale); + + __m128 d0 = dither4(); + f0 = _mm_add_ps(f0, d0); + f1 = _mm_add_ps(f1, d0); + + // round and saturate + __m128i a0 = _mm_cvtps_epi32(f0); + __m128i a1 = _mm_cvtps_epi32(f1); + a0 = _mm_packs_epi32(a0, a0); + a1 = _mm_packs_epi32(a1, a1); + + // interleave + a0 = _mm_unpacklo_epi16(a0, a1); + _mm_storeu_si128((__m128i*)&output[2*i], a0); + } + for (; i < numFrames; i++) { + __m128 f0 = _mm_mul_ps(_mm_load_ss(&inputs[0][i]), scale); + __m128 f1 = _mm_mul_ps(_mm_load_ss(&inputs[1][i]), scale); + + __m128 d0 = dither4(); + f0 = _mm_add_ps(f0, d0); + f1 = _mm_add_ps(f1, d0); + + // round and saturate + __m128i a0 = _mm_cvtps_epi32(f0); + __m128i a1 = _mm_cvtps_epi32(f1); + a0 = _mm_packs_epi32(a0, a0); + a1 = _mm_packs_epi32(a1, a1); + + // interleave + a0 = _mm_unpacklo_epi16(a0, a1); + *(int32_t*)&output[2*i] = _mm_cvtsi128_si32(a0); + } +} + +#else + +// convert int16_t to float, deinterleave stereo +void AudioReverb::convertInputFromInt16(const int16_t* input, float** outputs, int numFrames) { + const float scale = 1/32768.0f; + + for (int i = 0; i < numFrames; i++) { + outputs[0][i] = (float)input[2*i + 0] * scale; + outputs[1][i] = (float)input[2*i + 1] * scale; + } +} + +// fast TPDF dither in [-1.0f, 1.0f] +static inline float dither() { + static uint32_t rz = 0; + rz = rz * 69069 + 1; + int32_t r0 = rz & 0xffff; + int32_t r1 = rz >> 16; + return (int32_t)(r0 - r1) * (1/65536.0f); +} + +// convert float to int16_t, interleave stereo +void AudioReverb::convertOutputToInt16(float** inputs, int16_t* output, int numFrames) { + const float scale = 32768.0f; + + for (int i = 0; i < numFrames; i++) { + + float f0 = inputs[0][i] * scale; + float f1 = inputs[1][i] * scale; + + float d = dither(); + f0 += d; + f1 += d; + + // round and saturate + f0 += (f0 < 0.0f ? -0.5f : +0.5f); + f1 += (f1 < 0.0f ? -0.5f : +0.5f); + f0 = MIN(MAX(f0, -32768.0f), 32767.0f); + f1 = MIN(MAX(f1, -32768.0f), 32767.0f); + + // interleave + output[2*i + 0] = (int16_t)f0; + output[2*i + 1] = (int16_t)f1; + } +} + +#endif + +// +// This version handles input/output as interleaved int16_t +// +void AudioReverb::render(const int16_t* input, int16_t* output, int numFrames) { + + while (numFrames) { + + int n = MIN(numFrames, REVERB_BLOCK); + + convertInputFromInt16(input, _inout, n); + + _impl->process(_inout, _inout, n); + + convertOutputToInt16(_inout, output, n); + + input += 2 * n; + output += 2 * n; + numFrames -= n; + } +} diff --git a/libraries/audio/src/AudioReverb.h b/libraries/audio/src/AudioReverb.h new file mode 100644 index 0000000000..639d62d8ec --- /dev/null +++ b/libraries/audio/src/AudioReverb.h @@ -0,0 +1,76 @@ +// +// AudioReverb.h +// libraries/audio/src +// +// Created by Ken Cooke on 10/11/15. +// Copyright 2015 High Fidelity, Inc. +// + +#ifndef hifi_AudioReverb_h +#define hifi_AudioReverb_h + +#include + +typedef struct ReverbParameters { + + float sampleRate; // [24000, 48000] Hz + float bandwidth; // [20, 24000] Hz + + float preDelay; // [0, 333] ms + float lateDelay; // [0, 166] ms + + float reverbTime; // [0.1, 100] seconds + + float earlyDiffusion; // [0, 100] percent + float lateDiffusion; // [0, 100] percent + + float roomSize; // [0, 100] percent + float density; // [0, 100] percent + + float bassMult; // [0.1, 10] ratio + float bassFreq; // [10, 500] Hz + float highGain; // [-24, 0] dB + float highFreq; // [1000, 12000] Hz + + float modRate; // [0.1, 10] Hz + float modDepth; // [0, 100] percent + + float earlyGain; // [-96, +24] dB + float lateGain; // [-96, +24] dB + + float earlyMixLeft; // [0, 100] percent + float earlyMixRight; // [0, 100] percent + float lateMixLeft; // [0, 100] percent + float lateMixRight; // [0, 100] percent + + float wetDryMix; // [0, 100] percent + +} ReverbParameters; + +class ReverbImpl; + +class AudioReverb { +public: + AudioReverb(float sampleRate); + ~AudioReverb(); + + void setParameters(ReverbParameters *p); + void getParameters(ReverbParameters *p); + void reset(); + + // deinterleaved float input/output (native format) + void render(float** inputs, float** outputs, int numFrames); + + // interleaved int16_t input/output + void render(const int16_t* input, int16_t* output, int numFrames); + +private: + ReverbImpl *_impl; + ReverbParameters _params; + + float* _inout[2]; + void convertInputFromInt16(const int16_t* input, float** outputs, int numFrames); + void convertOutputToInt16(float** inputs, int16_t* output, int numFrames); +}; + +#endif // hifi_AudioReverb_h diff --git a/libraries/audio/src/AudioSRC.cpp b/libraries/audio/src/AudioSRC.cpp index 59fe29df36..c187d381a4 100644 --- a/libraries/audio/src/AudioSRC.cpp +++ b/libraries/audio/src/AudioSRC.cpp @@ -1218,7 +1218,7 @@ static inline float dither() { rz = rz * 69069 + 1; int32_t r0 = rz & 0xffff; int32_t r1 = rz >> 16; - return (r0 - r1) * (1/65536.0f); + return (int32_t)(r0 - r1) * (1/65536.0f); } // convert float to int16_t, interleave stereo diff --git a/libraries/audio/src/AudioSRC.h b/libraries/audio/src/AudioSRC.h index 5b00ca9e77..920ea8aef0 100644 --- a/libraries/audio/src/AudioSRC.h +++ b/libraries/audio/src/AudioSRC.h @@ -12,7 +12,7 @@ #ifndef hifi_AudioSRC_h #define hifi_AudioSRC_h -#include "stdint.h" +#include class AudioSRC { diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 3363bb7196..8b32ada296 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -98,22 +98,22 @@ void InboundAudioStream::perSecondCallbackForUpdatingStats() { _timeGapStatsForStatsPacket.currentIntervalComplete(); } -int InboundAudioStream::parseData(NLPacket& packet) { +int InboundAudioStream::parseData(ReceivedMessage& message) { // parse sequence number and track it quint16 sequence; - packet.readPrimitive(&sequence); + message.readPrimitive(&sequence); SequenceNumberStats::ArrivalInfo arrivalInfo = _incomingSequenceNumberStats.sequenceNumberReceived(sequence, - packet.getSourceID()); + message.getSourceID()); packetReceivedUpdateTimingStats(); int networkSamples; // parse the info after the seq number and before the audio data (the stream properties) - int prePropertyPosition = packet.pos(); - int propertyBytes = parseStreamProperties(packet.getType(), packet.readWithoutCopy(packet.bytesLeftToRead()), networkSamples); - packet.seek(prePropertyPosition + propertyBytes); + int prePropertyPosition = message.getPosition(); + int propertyBytes = parseStreamProperties(message.getType(), message.readWithoutCopy(message.getBytesLeftToRead()), networkSamples); + message.seek(prePropertyPosition + propertyBytes); // handle this packet based on its arrival status. switch (arrivalInfo._status) { @@ -128,10 +128,10 @@ int InboundAudioStream::parseData(NLPacket& packet) { } case SequenceNumberStats::OnTime: { // Packet is on time; parse its data to the ringbuffer - if (packet.getType() == PacketType::SilentAudioFrame) { + if (message.getType() == PacketType::SilentAudioFrame) { writeDroppableSilentSamples(networkSamples); } else { - parseAudioData(packet.getType(), packet.readWithoutCopy(packet.bytesLeftToRead()), networkSamples); + parseAudioData(message.getType(), message.readWithoutCopy(message.getBytesLeftToRead()), networkSamples); } break; } @@ -161,7 +161,7 @@ int InboundAudioStream::parseData(NLPacket& packet) { framesAvailableChanged(); - return packet.pos(); + return message.getPosition(); } int InboundAudioStream::parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) { diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index 5dfb75272b..28cb1307e6 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "AudioRingBuffer.h" @@ -107,7 +108,7 @@ public: virtual void resetStats(); void clearBuffer(); - virtual int parseData(NLPacket& packet); + virtual int parseData(ReceivedMessage& packet) override; int popFrames(int maxFrames, bool allOrNothing, bool starveIfNoFramesPopped = true); int popSamples(int maxSamples, bool allOrNothing, bool starveIfNoSamplesPopped = true); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 597c4c5986..3f2cd92dbc 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -47,13 +47,8 @@ const QString AvatarData::FRAME_NAME = "com.highfidelity.recording.AvatarData"; static std::once_flag frameTypeRegistration; AvatarData::AvatarData() : - _sessionUUID(), - _position(0.0f), + SpatiallyNestable(NestableTypes::Avatar, QUuid()), _handPosition(0.0f), - _referential(NULL), - _bodyYaw(-90.0f), - _bodyPitch(0.0f), - _bodyRoll(0.0f), _targetScale(1.0f), _handState(0), _keyState(NO_KEY_DOWN), @@ -72,12 +67,14 @@ AvatarData::AvatarData() : _targetVelocity(0.0f), _localAABox(DEFAULT_LOCAL_AABOX_CORNER, DEFAULT_LOCAL_AABOX_SCALE) { + setBodyPitch(0.0f); + setBodyYaw(-90.0f); + setBodyRoll(0.0f); } AvatarData::~AvatarData() { delete _headData; delete _handData; - delete _referential; } // We cannot have a file-level variable (const or otherwise) in the header if it uses PathUtils, because that references Application, which will not yet initialized. @@ -90,49 +87,18 @@ const QUrl& AvatarData::defaultFullAvatarModelUrl() { return _defaultFullAvatarModelUrl; } -const glm::vec3& AvatarData::getPosition() const { - if (_referential) { - _referential->update(); - } - return _position; -} - -void AvatarData::setPosition(const glm::vec3 position, bool overideReferential) { - if (!_referential || overideReferential) { - _position = position; - } -} - -glm::quat AvatarData::getOrientation() const { - if (_referential) { - _referential->update(); - } - - return glm::quat(glm::radians(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll))); -} - -void AvatarData::setOrientation(const glm::quat& orientation, bool overideReferential) { - if (!_referential || overideReferential) { - glm::vec3 eulerAngles = glm::degrees(safeEulerAngles(orientation)); - _bodyPitch = eulerAngles.x; - _bodyYaw = eulerAngles.y; - _bodyRoll = eulerAngles.z; - } -} - // There are a number of possible strategies for this set of tools through endRender, below. void AvatarData::nextAttitude(glm::vec3 position, glm::quat orientation) { avatarLock.lock(); - setPosition(position, true); - setOrientation(orientation, true); + Transform trans; + trans.setTranslation(position); + trans.setRotation(orientation); + SpatiallyNestable::setTransform(trans); avatarLock.unlock(); + updateAttitude(); } void AvatarData::startCapture() { avatarLock.lock(); - assert(_nextAllowed); - _nextAllowed = false; - _nextPosition = getPosition(); - _nextOrientation = getOrientation(); } void AvatarData::endCapture() { avatarLock.unlock(); @@ -145,57 +111,42 @@ void AvatarData::endUpdate() { } void AvatarData::startRenderRun() { // I'd like to get rid of this and just (un)lock at (end-)startRender. - // But somehow that causes judder in rotations. + // But somehow that causes judder in rotations. avatarLock.lock(); } void AvatarData::endRenderRun() { avatarLock.unlock(); } void AvatarData::startRender() { - glm::vec3 pos = getPosition(); - glm::quat rot = getOrientation(); - setPosition(_nextPosition, true); - setOrientation(_nextOrientation, true); updateAttitude(); - _nextPosition = pos; - _nextOrientation = rot; } void AvatarData::endRender() { - setPosition(_nextPosition, true); - setOrientation(_nextOrientation, true); updateAttitude(); - _nextAllowed = true; } float AvatarData::getTargetScale() const { - if (_referential) { - _referential->update(); - } - return _targetScale; } -void AvatarData::setTargetScale(float targetScale, bool overideReferential) { - if (!_referential || overideReferential) { - _targetScale = std::max(MIN_AVATAR_SCALE, std::min(MAX_AVATAR_SCALE, targetScale)); - } +void AvatarData::setTargetScale(float targetScale) { + _targetScale = std::max(MIN_AVATAR_SCALE, std::min(MAX_AVATAR_SCALE, targetScale)); } -void AvatarData::setClampedTargetScale(float targetScale, bool overideReferential) { +void AvatarData::setClampedTargetScale(float targetScale) { targetScale = glm::clamp(targetScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); - setTargetScale(targetScale, overideReferential); + setTargetScale(targetScale); qCDebug(avatars) << "Changed scale to " << _targetScale; } glm::vec3 AvatarData::getHandPosition() const { - return getOrientation() * _handPosition + _position; + return getOrientation() * _handPosition + getPosition(); } void AvatarData::setHandPosition(const glm::vec3& handPosition) { // store relative to position/orientation - _handPosition = glm::inverse(getOrientation()) * (handPosition - _position); + _handPosition = glm::inverse(getOrientation()) * (handPosition - getPosition()); } QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { @@ -216,13 +167,18 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { unsigned char* destinationBuffer = reinterpret_cast(avatarDataByteArray.data()); unsigned char* startPosition = destinationBuffer; - memcpy(destinationBuffer, &_position, sizeof(_position)); - destinationBuffer += sizeof(_position); + const glm::vec3& position = getLocalPosition(); + memcpy(destinationBuffer, &position, sizeof(position)); + destinationBuffer += sizeof(position); - // Body rotation (NOTE: This needs to become a quaternion to save two bytes) - destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _bodyYaw); - destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _bodyPitch); - destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _bodyRoll); + memcpy(destinationBuffer, &_globalPosition, sizeof(_globalPosition)); + destinationBuffer += sizeof(_globalPosition); + + // Body rotation + glm::vec3 bodyEulerAngles = glm::degrees(safeEulerAngles(getLocalOrientation())); + destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, bodyEulerAngles.y); + destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, bodyEulerAngles.x); + destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, bodyEulerAngles.z); // Body scale destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _targetScale); @@ -255,14 +211,18 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { setAtBit(bitItems, IS_EYE_TRACKER_CONNECTED); } // referential state - if (_referential != NULL && _referential->isValid()) { + SpatiallyNestablePointer parent = getParentPointer(); + if (parent) { setAtBit(bitItems, HAS_REFERENTIAL); } *destinationBuffer++ = bitItems; - // Add referential - if (_referential != NULL && _referential->isValid()) { - destinationBuffer += _referential->packReferential(destinationBuffer); + if (parent) { + QByteArray referentialAsBytes = parent->getID().toRfc4122(); + memcpy(destinationBuffer, referentialAsBytes.data(), referentialAsBytes.size()); + destinationBuffer += referentialAsBytes.size(); + memcpy(destinationBuffer, &_parentJointIndex, sizeof(_parentJointIndex)); + destinationBuffer += sizeof(_parentJointIndex); } // If it is connected, pack up the data @@ -496,13 +456,16 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { memcpy(&position, sourceBuffer, sizeof(position)); sourceBuffer += sizeof(position); + memcpy(&_globalPosition, sourceBuffer, sizeof(_globalPosition)); + sourceBuffer += sizeof(_globalPosition); + if (glm::isnan(position.x) || glm::isnan(position.y) || glm::isnan(position.z)) { if (shouldLogError(now)) { qCDebug(avatars) << "Discard nan AvatarData::position; displayName = '" << _displayName << "'"; } return maxAvailableSize; } - setPosition(position); + setLocalPosition(position); // rotation (NOTE: This needs to become a quaternion to save two bytes) float yaw, pitch, roll; @@ -515,11 +478,18 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } return maxAvailableSize; } - if (_bodyYaw != yaw || _bodyPitch != pitch || _bodyRoll != roll) { + + // TODO is this safe? will the floats not exactly match? + // Andrew says: + // Yes, there is a possibility that the transmitted will not quite match the extracted despite being originally + // extracted from the exact same quaternion. I followed the code through and it appears the risk is that the + // avatar's SkeletonModel might fall into the CPU expensive part of Model::updateClusterMatrices() when otherwise it + // would not have required it. However, we know we can update many simultaneously animating avatars, and most + // avatars will be moving constantly anyway, so I don't think we need to worry. + if (getBodyYaw() != yaw || getBodyPitch() != pitch || getBodyRoll() != roll) { _hasNewJointRotations = true; - _bodyYaw = yaw; - _bodyPitch = pitch; - _bodyRoll = roll; + glm::vec3 eulerAngles(pitch, yaw, roll); + setLocalOrientation(glm::quat(glm::radians(eulerAngles))); } // scale @@ -581,21 +551,17 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { _headData->_isEyeTrackerConnected = oneAtBit(bitItems, IS_EYE_TRACKER_CONNECTED); bool hasReferential = oneAtBit(bitItems, HAS_REFERENTIAL); - // Referential if (hasReferential) { - Referential* ref = new Referential(sourceBuffer, this); - if (_referential == NULL || - ref->version() != _referential->version()) { - changeReferential(ref); - } else { - delete ref; - } - _referential->update(); - } else if (_referential != NULL) { - changeReferential(NULL); + const int sizeOfPackedUuid = 16; + QByteArray referentialAsBytes((const char*)sourceBuffer, sizeOfPackedUuid); + _parentID = QUuid::fromRfc4122(referentialAsBytes); + sourceBuffer += sizeOfPackedUuid; + memcpy(&_parentJointIndex, sourceBuffer, sizeof(_parentJointIndex)); + sourceBuffer += sizeof(_parentJointIndex); + } else { + _parentID = QUuid(); } - if (_headData->_isFaceTrackerConnected) { float leftEyeBlink, rightEyeBlink, averageLoudness, browAudioLift; minPossibleSize += sizeof(leftEyeBlink) + sizeof(rightEyeBlink) + sizeof(averageLoudness) + sizeof(browAudioLift); @@ -788,19 +754,10 @@ int AvatarData::getReceiveRate() const { return lrint(1.0f / _averageBytesReceived.getEventDeltaAverage()); } -bool AvatarData::hasReferential() { - return _referential != NULL; -} - std::shared_ptr AvatarData::getRecordingBasis() const { return _recordingBasis; } -void AvatarData::changeReferential(Referential* ref) { - delete _referential; - _referential = ref; -} - void AvatarData::setRawJointData(QVector data) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "setRawJointData", Q_ARG(QVector, data)); @@ -1028,8 +985,8 @@ void AvatarData::clearJointsData() { } } -bool AvatarData::hasIdentityChangedAfterParsing(NLPacket& packet) { - QDataStream packetStream(&packet); +bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray& data) { + QDataStream packetStream(data); QUuid avatarUUID; QUrl faceModelURL, skeletonModelURL; @@ -1073,12 +1030,11 @@ QByteArray AvatarData::identityByteArray() { return identityData; } -bool AvatarData::hasBillboardChangedAfterParsing(NLPacket& packet) { - QByteArray newBillboard = packet.readAll(); - if (newBillboard == _billboard) { +bool AvatarData::hasBillboardChangedAfterParsing(const QByteArray& data) { + if (data == _billboard) { return false; } - _billboard = newBillboard; + _billboard = data; return true; } @@ -1437,14 +1393,6 @@ void AvatarData::clearRecordingBasis() { _recordingBasis.reset(); } -Transform AvatarData::getTransform() const { - Transform result; - result.setRotation(getOrientation()); - result.setTranslation(getPosition()); - result.setScale(getTargetScale()); - return result; -} - static const QString JSON_AVATAR_BASIS = QStringLiteral("basisTransform"); static const QString JSON_AVATAR_RELATIVE = QStringLiteral("relativeTransform"); static const QString JSON_AVATAR_JOINT_ARRAY = QStringLiteral("jointArray"); @@ -1495,15 +1443,17 @@ QJsonObject AvatarData::toJson() const { } auto recordingBasis = getRecordingBasis(); + Transform avatarTransform = getTransform(); + avatarTransform.setScale(getTargetScale()); if (recordingBasis) { root[JSON_AVATAR_BASIS] = Transform::toJson(*recordingBasis); // Find the relative transform - auto relativeTransform = recordingBasis->relativeTransform(getTransform()); + auto relativeTransform = recordingBasis->relativeTransform(avatarTransform); if (!relativeTransform.isIdentity()) { root[JSON_AVATAR_RELATIVE] = Transform::toJson(relativeTransform); } } else { - root[JSON_AVATAR_RELATIVE] = Transform::toJson(getTransform()); + root[JSON_AVATAR_RELATIVE] = Transform::toJson(avatarTransform); } auto scale = getTargetScale(); @@ -1641,3 +1591,44 @@ void AvatarData::fromFrame(const QByteArray& frameData, AvatarData& result) { #endif result.fromJson(doc.object()); } + +float AvatarData::getBodyYaw() const { + glm::vec3 eulerAngles = glm::degrees(safeEulerAngles(getOrientation())); + return eulerAngles.y; +} + +void AvatarData::setBodyYaw(float bodyYaw) { + glm::vec3 eulerAngles = glm::degrees(safeEulerAngles(getOrientation())); + eulerAngles.y = bodyYaw; + setOrientation(glm::quat(glm::radians(eulerAngles))); +} + +float AvatarData::getBodyPitch() const { + glm::vec3 eulerAngles = glm::degrees(safeEulerAngles(getOrientation())); + return eulerAngles.x; +} + +void AvatarData::setBodyPitch(float bodyPitch) { + glm::vec3 eulerAngles = glm::degrees(safeEulerAngles(getOrientation())); + eulerAngles.x = bodyPitch; + setOrientation(glm::quat(glm::radians(eulerAngles))); +} + +float AvatarData::getBodyRoll() const { + glm::vec3 eulerAngles = glm::degrees(safeEulerAngles(getOrientation())); + return eulerAngles.z; +} + +void AvatarData::setBodyRoll(float bodyRoll) { + glm::vec3 eulerAngles = glm::degrees(safeEulerAngles(getOrientation())); + eulerAngles.z = bodyRoll; + setOrientation(glm::quat(glm::radians(eulerAngles))); +} + +void AvatarData::setPosition(const glm::vec3& position) { + SpatiallyNestable::setPosition(position); +} + +void AvatarData::setOrientation(const glm::quat& orientation) { + SpatiallyNestable::setOrientation(orientation); +} diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 2d5a395e2a..1fa33ff606 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -51,12 +51,12 @@ typedef unsigned long long quint64; #include #include #include +#include #include "AABox.h" #include "HandData.h" #include "HeadData.h" #include "PathUtils.h" -#include "Referential.h" using AvatarSharedPointer = std::shared_ptr; using AvatarWeakPointer = std::weak_ptr; @@ -135,7 +135,7 @@ class AttachmentData; class Transform; using TransformPointer = std::shared_ptr; -class AvatarData : public QObject { +class AvatarData : public QObject, public SpatiallyNestable { Q_OBJECT Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPosition) @@ -177,10 +177,7 @@ public: virtual bool isMyAvatar() const { return false; } - const QUuid& getSessionUUID() const { return _sessionUUID; } - - const glm::vec3& getPosition() const; - virtual void setPosition(const glm::vec3 position, bool overideReferential = false); + const QUuid& getSessionUUID() const { return getID(); } glm::vec3 getHandPosition() const; void setHandPosition(const glm::vec3& handPosition); @@ -196,16 +193,16 @@ public: /// \return number of bytes parsed virtual int parseDataFromBuffer(const QByteArray& buffer); - // Body Rotation (degrees) - float getBodyYaw() const { return _bodyYaw; } - void setBodyYaw(float bodyYaw) { _bodyYaw = bodyYaw; } - float getBodyPitch() const { return _bodyPitch; } - void setBodyPitch(float bodyPitch) { _bodyPitch = bodyPitch; } - float getBodyRoll() const { return _bodyRoll; } - void setBodyRoll(float bodyRoll) { _bodyRoll = bodyRoll; } + // Body Rotation (degrees) + float getBodyYaw() const; + void setBodyYaw(float bodyYaw); + float getBodyPitch() const; + void setBodyPitch(float bodyPitch); + float getBodyRoll() const; + void setBodyRoll(float bodyRoll); - glm::quat getOrientation() const; - virtual void setOrientation(const glm::quat& orientation, bool overideReferential = false); + virtual void setPosition(const glm::vec3& position) override; + virtual void setOrientation(const glm::quat& orientation) override; void nextAttitude(glm::vec3 position, glm::quat orientation); // Can be safely called at any time. void startCapture(); // start/end of the period in which the latest values are about to be captured for camera, etc. @@ -239,8 +236,8 @@ public: // Scale float getTargetScale() const; - void setTargetScale(float targetScale, bool overideReferential = false); - void setClampedTargetScale(float targetScale, bool overideReferential = false); + void setTargetScale(float targetScale); + void setClampedTargetScale(float targetScale); // Hand State Q_INVOKABLE void setHandState(char s) { _handState = s; } @@ -287,10 +284,10 @@ public: const HeadData* getHeadData() const { return _headData; } const HandData* getHandData() const { return _handData; } - bool hasIdentityChangedAfterParsing(NLPacket& packet); + bool hasIdentityChangedAfterParsing(const QByteArray& data); QByteArray identityByteArray(); - bool hasBillboardChangedAfterParsing(NLPacket& packet); + bool hasBillboardChangedAfterParsing(const QByteArray& data); const QUrl& getFaceModelURL() const { return _faceModelURL; } QString getFaceModelURLString() const { return _faceModelURL.toString(); } @@ -326,7 +323,6 @@ public: void setOwningAvatarMixer(const QWeakPointer& owningAvatarMixer) { _owningAvatarMixer = owningAvatarMixer; } const AABox& getLocalAABox() const { return _localAABox; } - const Referential* getReferential() const { return _referential; } int getUsecsSinceLastUpdate() const { return _averageBytesReceived.getUsecsSinceLastEvent(); } int getAverageBytesReceivedPerSecond() const; @@ -338,13 +334,14 @@ public: bool shouldDie() const { return _owningAvatarMixer.isNull() || getUsecsSinceLastUpdate() > AVATAR_SILENCE_THRESHOLD_USECS; } - Transform getTransform() const; void clearRecordingBasis(); TransformPointer getRecordingBasis() const; void setRecordingBasis(TransformPointer recordingBasis = TransformPointer()); QJsonObject toJson() const; void fromJson(const QJsonObject& json); + glm::vec3 getClientGlobalPosition() { return _globalPosition; } + public slots: void sendAvatarDataPacket(); void sendIdentityPacket(); @@ -352,24 +349,10 @@ public slots: void setBillboardFromNetworkReply(); void setJointMappingsFromNetworkReply(); - void setSessionUUID(const QUuid& sessionUUID) { _sessionUUID = sessionUUID; } - bool hasReferential(); + void setSessionUUID(const QUuid& sessionUUID) { setID(sessionUUID); } protected: - QUuid _sessionUUID; - glm::vec3 _position = START_LOCATION; glm::vec3 _handPosition; - - Referential* _referential; - - // Body rotation - float _bodyYaw; // degrees - float _bodyPitch; // degrees - float _bodyRoll; // degrees - - glm::vec3 _nextPosition {}; - glm::quat _nextOrientation {}; - bool _nextAllowed {true}; // Body scale float _targetScale; @@ -411,7 +394,6 @@ protected: /// Loads the joint indices, names from the FST file (if any) virtual void updateJointMappings(); - void changeReferential(Referential* ref); glm::vec3 _velocity; glm::vec3 _targetVelocity; @@ -426,6 +408,11 @@ protected: // During playback, it holds the origin from which to play the relative positions in the clip TransformPointer _recordingBasis; + // _globalPosition is sent along with localPosition + parent because the avatar-mixer doesn't know + // where Entities are located. This is currently only used by the mixer to decide how often to send + // updates about one avatar to another. + glm::vec3 _globalPosition; + private: friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar); static QUrl _defaultFullAvatarModelUrl; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index c195ab4c32..845a6a6245 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -65,38 +65,40 @@ AvatarSharedPointer AvatarHashMap::newOrExistingAvatar(const QUuid& sessionUUID, AvatarSharedPointer AvatarHashMap::findAvatar(const QUuid& sessionUUID) { QReadLocker locker(&_hashLock); - return _avatarHash.value(sessionUUID); + if (_avatarHash.contains(sessionUUID)) { + return _avatarHash.value(sessionUUID); + } + return nullptr; } -void AvatarHashMap::processAvatarDataPacket(QSharedPointer packet, SharedNodePointer sendingNode) { - +void AvatarHashMap::processAvatarDataPacket(QSharedPointer message, SharedNodePointer sendingNode) { // enumerate over all of the avatars in this packet // only add them if mixerWeakPointer points to something (meaning that mixer is still around) - while (packet->bytesLeftToRead()) { - QUuid sessionUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + while (message->getBytesLeftToRead()) { + QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); - int positionBeforeRead = packet->pos(); + int positionBeforeRead = message->getPosition(); - QByteArray byteArray = packet->readWithoutCopy(packet->bytesLeftToRead()); + QByteArray byteArray = message->readWithoutCopy(message->getBytesLeftToRead()); if (sessionUUID != _lastOwnerSessionUUID) { auto avatar = newOrExistingAvatar(sessionUUID, sendingNode); // have the matching (or new) avatar parse the data from the packet int bytesRead = avatar->parseDataFromBuffer(byteArray); - packet->seek(positionBeforeRead + bytesRead); + message->seek(positionBeforeRead + bytesRead); } else { // create a dummy AvatarData class to throw this data on the ground AvatarData dummyData; int bytesRead = dummyData.parseDataFromBuffer(byteArray); - packet->seek(positionBeforeRead + bytesRead); + message->seek(positionBeforeRead + bytesRead); } } } -void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer packet, SharedNodePointer sendingNode) { +void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer message, SharedNodePointer sendingNode) { // setup a data stream to parse the packet - QDataStream identityStream(packet.data()); + QDataStream identityStream(message->getMessage()); QUuid sessionUUID; @@ -128,20 +130,20 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer packet, } } -void AvatarHashMap::processAvatarBillboardPacket(QSharedPointer packet, SharedNodePointer sendingNode) { - QUuid sessionUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); +void AvatarHashMap::processAvatarBillboardPacket(QSharedPointer message, SharedNodePointer sendingNode) { + QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); auto avatar = newOrExistingAvatar(sessionUUID, sendingNode); - QByteArray billboard = packet->read(packet->bytesLeftToRead()); + QByteArray billboard = message->read(message->getBytesLeftToRead()); if (avatar->getBillboard() != billboard) { avatar->setBillboard(billboard); } } -void AvatarHashMap::processKillAvatar(QSharedPointer packet, SharedNodePointer sendingNode) { +void AvatarHashMap::processKillAvatar(QSharedPointer message, SharedNodePointer sendingNode) { // read the node id - QUuid sessionUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); removeAvatar(sessionUUID); } diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 5881b779a1..cb6c6cb0cc 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -45,10 +45,10 @@ public slots: private slots: void sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& oldUUID); - void processAvatarDataPacket(QSharedPointer packet, SharedNodePointer sendingNode); - void processAvatarIdentityPacket(QSharedPointer packet, SharedNodePointer sendingNode); - void processAvatarBillboardPacket(QSharedPointer packet, SharedNodePointer sendingNode); - void processKillAvatar(QSharedPointer packet, SharedNodePointer sendingNode); + void processAvatarDataPacket(QSharedPointer message, SharedNodePointer sendingNode); + void processAvatarIdentityPacket(QSharedPointer message, SharedNodePointer sendingNode); + void processAvatarBillboardPacket(QSharedPointer message, SharedNodePointer sendingNode); + void processKillAvatar(QSharedPointer message, SharedNodePointer sendingNode); protected: AvatarHashMap(); diff --git a/libraries/avatars/src/Referential.cpp b/libraries/avatars/src/Referential.cpp deleted file mode 100644 index 0683580093..0000000000 --- a/libraries/avatars/src/Referential.cpp +++ /dev/null @@ -1,109 +0,0 @@ -// -// Referential.cpp -// -// -// Created by Clement on 7/30/14. -// Copyright 2014 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 "AvatarData.h" -#include "AvatarLogging.h" -#include "Referential.h" - -Referential::Referential(Type type, AvatarData* avatar) : - _type(type), - _version(0), - _isValid(true), - _avatar(avatar) -{ - if (_avatar == NULL) { - _isValid = false; - return; - } - if (_avatar->hasReferential()) { - _version = _avatar->getReferential()->version() + 1; - } -} - -Referential::Referential(const unsigned char*& sourceBuffer, AvatarData* avatar) : - _isValid(false), - _avatar(avatar) -{ - // Since we can't return how many byte have been read - // We take a reference to the pointer as argument and increment the pointer ouself. - sourceBuffer += unpackReferential(sourceBuffer); - // The actual unpacking to the right referential type happens in Avatar::simulate() - // If subclassed, make sure to add a case there to unpack the new referential type correctly -} - -Referential::~Referential() { -} - -int Referential::packReferential(unsigned char* destinationBuffer) const { - const unsigned char* startPosition = destinationBuffer; - destinationBuffer += pack(destinationBuffer); - - unsigned char* sizePosition = destinationBuffer++; // Save a spot for the extra data size - char size = packExtraData(destinationBuffer); - *sizePosition = size; // write extra data size in saved spot here - destinationBuffer += size; - return destinationBuffer - startPosition; -} - -int Referential::unpackReferential(const unsigned char* sourceBuffer) { - const unsigned char* startPosition = sourceBuffer; - sourceBuffer += unpack(sourceBuffer); - char expectedSize = *sourceBuffer++; - char bytesRead = unpackExtraData(sourceBuffer, expectedSize); - _isValid = (bytesRead == expectedSize); - if (!_isValid) { - // Will occur if the new instance unpacking is of the wrong type - qCDebug(avatars) << "[ERROR] Referential extra data overflow"; - } - sourceBuffer += expectedSize; - return sourceBuffer - startPosition; -} - -int Referential::pack(unsigned char* destinationBuffer) const { - unsigned char* startPosition = destinationBuffer; - *destinationBuffer++ = (unsigned char)_type; - memcpy(destinationBuffer, &_version, sizeof(_version)); - destinationBuffer += sizeof(_version); - - destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, _translation, 0); - destinationBuffer += packOrientationQuatToBytes(destinationBuffer, _rotation); - return destinationBuffer - startPosition; -} - -int Referential::unpack(const unsigned char* sourceBuffer) { - const unsigned char* startPosition = sourceBuffer; - _type = (Type)*sourceBuffer++; - if (_type < 0 || _type >= NUM_TYPES) { - _type = UNKNOWN; - } - memcpy(&_version, sourceBuffer, sizeof(_version)); - sourceBuffer += sizeof(_version); - - sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, _translation, 0); - sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, _rotation); - return sourceBuffer - startPosition; -} - -int Referential::packExtraData(unsigned char *destinationBuffer) const { - // Since we can't interpret that data, just store it in a buffer for later use. - memcpy(destinationBuffer, _extraDataBuffer.data(), _extraDataBuffer.size()); - return _extraDataBuffer.size(); -} - - -int Referential::unpackExtraData(const unsigned char* sourceBuffer, int size) { - _extraDataBuffer.clear(); - _extraDataBuffer.append(reinterpret_cast(sourceBuffer), size); - return size; -} - diff --git a/libraries/avatars/src/Referential.h b/libraries/avatars/src/Referential.h deleted file mode 100644 index 70edecda62..0000000000 --- a/libraries/avatars/src/Referential.h +++ /dev/null @@ -1,73 +0,0 @@ -// -// Referential.h -// -// -// Created by Clement on 7/30/14. -// Copyright 2014 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_Referential_h -#define hifi_Referential_h - -#include -#include - -class AvatarData; - -/// Stores and enforce the relative position of an avatar to a given referential (ie. model, joint, ...) -class Referential { -public: - enum Type { - UNKNOWN, - MODEL, - JOINT, - AVATAR, - - NUM_TYPES - }; - - Referential(const unsigned char*& sourceBuffer, AvatarData* avatar); - virtual ~Referential(); - - Type type() const { return _type; } - quint8 version() const { return _version; } - bool isValid() const { return _isValid; } - bool hasExtraData() const { return !_extraDataBuffer.isEmpty(); } - - glm::vec3 getTranslation() const { return _translation; } - glm::quat getRotation() const { return _rotation; } - QByteArray getExtraData() const { return _extraDataBuffer; } - - virtual void update() {} - int packReferential(unsigned char* destinationBuffer) const; - int unpackReferential(const unsigned char* sourceBuffer); - -protected: - Referential(Type type, AvatarData* avatar); - - // packs the base class data - int pack(unsigned char* destinationBuffer) const; - int unpack(const unsigned char* sourceBuffer); - // virtual functions that pack fthe extra data of subclasses (needs to be reimplemented in subclass) - virtual int packExtraData(unsigned char* destinationBuffer) const; - virtual int unpackExtraData(const unsigned char* sourceBuffer, int size); - - Type _type; - quint8 _version; - bool _isValid; - AvatarData* _avatar; - QByteArray _extraDataBuffer; - - glm::vec3 _refPosition; // position of object in world-frame - glm::quat _refRotation; // rotation of object in world-frame - glm::vec3 _lastRefDimension; // dimension of object when _translation was last computed - - glm::vec3 _translation; // offset of avatar in object local-frame - glm::quat _rotation; // rotation of avatar in object local-frame -}; - - -#endif // hifi_Referential_h diff --git a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.h index e07dc9e4c8..1073dc6593 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.h @@ -23,10 +23,10 @@ class ActionEndpoint : public Endpoint { public: ActionEndpoint(const Input& id = Input::INVALID_INPUT) : Endpoint(id) { } - virtual float peek() const { return _currentValue; } + virtual float peek() const override { return _currentValue; } virtual void apply(float newValue, const Pointer& source) override; - virtual Pose peekPose() const { return _currentPose; } + virtual Pose peekPose() const override { return _currentPose; } virtual void apply(const Pose& value, const Pointer& source) override; virtual void reset() override; diff --git a/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.h index 663168bedc..7e4560dcf9 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.h @@ -28,9 +28,9 @@ public: virtual Pose pose() override; virtual void apply(const Pose& value, const Pointer& source) override { } - virtual bool writeable() const { return false; } - virtual bool readable() const { return !_read; } - virtual void reset() { _read = false; } + virtual bool writeable() const override { return false; } + virtual bool readable() const override { return !_read; } + virtual void reset() override { _read = false; } private: bool _read { false }; diff --git a/libraries/controllers/src/controllers/impl/filters/PulseFilter.h b/libraries/controllers/src/controllers/impl/filters/PulseFilter.h index dbe2eba81b..271f4a04f6 100644 --- a/libraries/controllers/src/controllers/impl/filters/PulseFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/PulseFilter.h @@ -23,7 +23,7 @@ public: virtual float apply(float value) const override; - virtual bool parseParameters(const QJsonValue& parameters); + virtual bool parseParameters(const QJsonValue& parameters) override; private: static const float DEFAULT_LAST_EMIT_TIME; diff --git a/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h b/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h index 39c5edd4e5..670da53fe8 100644 --- a/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h @@ -23,7 +23,7 @@ public: virtual float apply(float value) const override { return value * _scale; } - virtual bool parseParameters(const QJsonValue& parameters); + virtual bool parseParameters(const QJsonValue& parameters) override; private: float _scale = 1.0f; diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index 9366ec4403..6c450e0735 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -30,12 +30,11 @@ const QString& Basic2DWindowOpenGLDisplayPlugin::getName() const { return NAME; } -std::vector _framerateActions; -QAction* _vsyncAction{ nullptr }; - void Basic2DWindowOpenGLDisplayPlugin::activate() { + WindowOpenGLDisplayPlugin::activate(); + _framerateActions.clear(); - _container->addMenuItem(MENU_PATH(), FULLSCREEN, + _container->addMenuItem(PluginType::DISPLAY_PLUGIN, MENU_PATH(), FULLSCREEN, [this](bool clicked) { if (clicked) { _container->setFullscreen(getFullscreenTarget()); @@ -45,26 +44,24 @@ void Basic2DWindowOpenGLDisplayPlugin::activate() { }, true, false); _container->addMenu(FRAMERATE); _framerateActions.push_back( - _container->addMenuItem(FRAMERATE, FRAMERATE_UNLIMITED, + _container->addMenuItem(PluginType::DISPLAY_PLUGIN, FRAMERATE, FRAMERATE_UNLIMITED, [this](bool) { updateFramerate(); }, true, true, FRAMERATE)); _framerateActions.push_back( - _container->addMenuItem(FRAMERATE, FRAMERATE_60, + _container->addMenuItem(PluginType::DISPLAY_PLUGIN, FRAMERATE, FRAMERATE_60, [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); _framerateActions.push_back( - _container->addMenuItem(FRAMERATE, FRAMERATE_50, + _container->addMenuItem(PluginType::DISPLAY_PLUGIN, FRAMERATE, FRAMERATE_50, [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); _framerateActions.push_back( - _container->addMenuItem(FRAMERATE, FRAMERATE_40, + _container->addMenuItem(PluginType::DISPLAY_PLUGIN, FRAMERATE, FRAMERATE_40, [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); _framerateActions.push_back( - _container->addMenuItem(FRAMERATE, FRAMERATE_30, + _container->addMenuItem(PluginType::DISPLAY_PLUGIN, FRAMERATE, FRAMERATE_30, [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); - WindowOpenGLDisplayPlugin::activate(); - // Vsync detection happens in the parent class activate, so we need to check after that if (_vsyncSupported) { - _vsyncAction = _container->addMenuItem(MENU_PATH(), VSYNC_ON, [this](bool) {}, true, true); + _vsyncAction = _container->addMenuItem(PluginType::DISPLAY_PLUGIN, MENU_PATH(), VSYNC_ON, [this](bool) {}, true, true); } else { _vsyncAction = nullptr; } @@ -72,22 +69,20 @@ void Basic2DWindowOpenGLDisplayPlugin::activate() { updateFramerate(); } -void Basic2DWindowOpenGLDisplayPlugin::deactivate() { - WindowOpenGLDisplayPlugin::deactivate(); -} - -void Basic2DWindowOpenGLDisplayPlugin::display(GLuint sceneTexture, const glm::uvec2& sceneSize) { +void Basic2DWindowOpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) { if (_vsyncAction) { - bool wantVsync = _vsyncAction->isChecked(); - bool vsyncEnabed = isVsyncEnabled(); - if (vsyncEnabed ^ wantVsync) { - enableVsync(wantVsync); - } + _wantVsync = _vsyncAction->isChecked(); } - WindowOpenGLDisplayPlugin::display(sceneTexture, sceneSize); + WindowOpenGLDisplayPlugin::submitSceneTexture(frameIndex, sceneTexture, sceneSize); } +void Basic2DWindowOpenGLDisplayPlugin::internalPresent() { + if (_wantVsync != isVsyncEnabled()) { + enableVsync(_wantVsync); + } + WindowOpenGLDisplayPlugin::internalPresent(); +} int Basic2DWindowOpenGLDisplayPlugin::getDesiredInterval() const { static const int THROTTLED_PAINT_TIMER_DELAY_MS = MSECS_PER_SECOND / 15; diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h index f4655ab79f..36a1a73b94 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h @@ -10,6 +10,8 @@ #include "WindowOpenGLDisplayPlugin.h" class QScreen; +class QAction; + class Basic2DWindowOpenGLDisplayPlugin : public WindowOpenGLDisplayPlugin { Q_OBJECT @@ -17,9 +19,10 @@ public: virtual const QString & getName() const override; virtual void activate() override; - virtual void deactivate() override; - virtual void display(GLuint sceneTexture, const glm::uvec2& sceneSize) override; + virtual void submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) override; + + virtual void internalPresent() override; virtual bool isThrottled() const override; @@ -31,6 +34,9 @@ private: void updateFramerate(); static const QString NAME; QScreen* getFullscreenTarget(); - uint32_t _framerateTarget{ 0 }; + std::vector _framerateActions; + QAction* _vsyncAction { nullptr }; + uint32_t _framerateTarget { 0 }; int _fullscreenTarget{ -1 }; + bool _wantVsync { true }; }; diff --git a/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp index 8155d69826..6c34612e8c 100644 --- a/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp @@ -25,8 +25,8 @@ const QString& DisplayPlugin::MENU_PATH() { DisplayPluginList getDisplayPlugins() { DisplayPlugin* PLUGIN_POOL[] = { new Basic2DWindowOpenGLDisplayPlugin(), -#ifdef DEBUG new NullDisplayPlugin(), +#ifdef DEBUG #endif // Stereo modes @@ -37,10 +37,10 @@ DisplayPluginList getDisplayPlugins() { new InterleavedStereoDisplayPlugin(), // HMDs -#ifdef Q_OS_WIN - // SteamVR SDK - new OpenVrDisplayPlugin(), -#endif +//#ifdef Q_OS_WIN +// // SteamVR SDK +// new OpenVrDisplayPlugin(), +//#endif nullptr }; diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp index ce512962ff..f780534bc9 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp @@ -9,6 +9,9 @@ // #include "NullDisplayPlugin.h" +#include +#include + const QString NullDisplayPlugin::NAME("NullDisplayPlugin"); const QString & NullDisplayPlugin::getName() const { @@ -23,8 +26,16 @@ bool NullDisplayPlugin::hasFocus() const { return false; } -void NullDisplayPlugin::preRender() {} -void NullDisplayPlugin::preDisplay() {} -void NullDisplayPlugin::display(uint32_t sceneTexture, const glm::uvec2& sceneSize) {} -void NullDisplayPlugin::finishFrame() {} +void NullDisplayPlugin::submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) { + _container->releaseSceneTexture(sceneTexture); +} + +void NullDisplayPlugin::submitOverlayTexture(uint32_t overlayTexture, const glm::uvec2& overlaySize) { + _container->releaseOverlayTexture(overlayTexture); +} + void NullDisplayPlugin::stop() {} + +QImage NullDisplayPlugin::getScreenshot() const { + return QImage(); +} diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h index 8cd5c2bc37..23e23e2c4e 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h @@ -19,11 +19,9 @@ public: virtual glm::uvec2 getRecommendedRenderSize() const override; virtual bool hasFocus() const override; - virtual void preRender() override; - virtual void preDisplay() override; - virtual void display(uint32_t sceneTexture, const glm::uvec2& sceneSize) override; - virtual void finishFrame() override; - + virtual void submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) override; + virtual void submitOverlayTexture(uint32_t overlayTexture, const glm::uvec2& overlaySize) override; + virtual QImage getScreenshot() const override; private: static const QString NAME; }; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 3ef882fe76..9a0db0ad97 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -6,76 +6,231 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "OpenGLDisplayPlugin.h" -#include -#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include #include -#include +#include #include +class PresentThread : public QThread, public Dependency { + using Mutex = std::mutex; + using Condition = std::condition_variable; + using Lock = std::unique_lock; +public: + + PresentThread() { + connect(qApp, &QCoreApplication::aboutToQuit, [this]{ + _shutdown = true; + }); + } + + ~PresentThread() { + _shutdown = true; + wait(); + } + + void setNewDisplayPlugin(OpenGLDisplayPlugin* plugin) { + Lock lock(_mutex); + _newPlugin = plugin; + } + + void setContext(QGLContext * context) { + // Move the OpenGL context to the present thread + // Extra code because of the widget 'wrapper' context + _context = context; + _context->moveToThread(this); + } + + virtual void run() override { + Q_ASSERT(_context); + while (!_shutdown) { + if (_pendingMainThreadOperation) { + { + Lock lock(_mutex); + // Move the context to the main thread + _context->moveToThread(qApp->thread()); + _pendingMainThreadOperation = false; + // Release the main thread to do it's action + _condition.notify_one(); + } + + + { + // Main thread does it's thing while we wait on the lock to release + Lock lock(_mutex); + _condition.wait(lock, [&] { return _finishedMainThreadOperation; }); + } + } + + // Check before lock + if (_newPlugin != nullptr) { + Lock lock(_mutex); + _context->makeCurrent(); + // Check if we have a new plugin to activate + if (_newPlugin != nullptr) { + // Deactivate the old plugin + if (_activePlugin != nullptr) { + _activePlugin->uncustomizeContext(); + } + + _newPlugin->customizeContext(); + _activePlugin = _newPlugin; + _newPlugin = nullptr; + } + _context->doneCurrent(); + lock.unlock(); + } + + // If there's no active plugin, just sleep + if (_activePlugin == nullptr) { + QThread::usleep(100); + continue; + } + + // take the latest texture and present it + _context->makeCurrent(); + _activePlugin->present(); + _context->doneCurrent(); + } + + _context->makeCurrent(); + if (_activePlugin) { + _activePlugin->uncustomizeContext(); + } + _context->doneCurrent(); + _context->moveToThread(qApp->thread()); + } + + void withMainThreadContext(std::function f) { + // Signal to the thread that there is work to be done on the main thread + Lock lock(_mutex); + _pendingMainThreadOperation = true; + _finishedMainThreadOperation = false; + _condition.wait(lock, [&] { return !_pendingMainThreadOperation; }); + + _context->makeCurrent(); + f(); + _context->doneCurrent(); + + // Move the context back to the presentation thread + _context->moveToThread(this); + + // restore control of the context to the presentation thread and signal + // the end of the operation + _finishedMainThreadOperation = true; + lock.unlock(); + _condition.notify_one(); + } + + +private: + void makeCurrent(); + void doneCurrent(); + + bool _shutdown { false }; + Mutex _mutex; + // Used to allow the main thread to perform context operations + Condition _condition; + bool _pendingMainThreadOperation { false }; + bool _finishedMainThreadOperation { false }; + QThread* _mainThread { nullptr }; + OpenGLDisplayPlugin* _newPlugin { nullptr }; + OpenGLDisplayPlugin* _activePlugin { nullptr }; + QGLContext* _context { nullptr }; +}; OpenGLDisplayPlugin::OpenGLDisplayPlugin() { + _sceneTextureEscrow.setRecycler([this](GLuint texture){ + cleanupForSceneTexture(texture); + _container->releaseSceneTexture(texture); + }); + + _overlayTextureEscrow.setRecycler([this](GLuint texture) { + _container->releaseOverlayTexture(texture); + }); + connect(&_timer, &QTimer::timeout, this, [&] { - if (_active) { +#ifdef Q_OS_MAC + // On Mac, QT thread timing is such that we can miss one or even two cycles quite often, giving a render rate (including update/simulate) + // far lower than what we want. This hack keeps that rate more natural, at the expense of some wasted rendering. + // This is likely to be mooted by further planned changes. + if (_active && _sceneTextureEscrow.depth() <= 1) { +#else + if (_active && _sceneTextureEscrow.depth() < 1) { +#endif emit requestRender(); } }); } -OpenGLDisplayPlugin::~OpenGLDisplayPlugin() { +void OpenGLDisplayPlugin::cleanupForSceneTexture(uint32_t sceneTexture) { + Lock lock(_mutex); + Q_ASSERT(_sceneTextureToFrameIndexMap.contains(sceneTexture)); + _sceneTextureToFrameIndexMap.remove(sceneTexture); } -void OpenGLDisplayPlugin::preDisplay() { - makeCurrent(); -}; - -void OpenGLDisplayPlugin::preRender() { - // NOOP -} - -void OpenGLDisplayPlugin::finishFrame() { - swapBuffers(); - doneCurrent(); -}; - -void OpenGLDisplayPlugin::customizeContext() { - using namespace oglplus; - // TODO: write the poper code for linux -#if defined(Q_OS_WIN) - _vsyncSupported = wglewGetExtension("WGL_EXT_swap_control"); -#endif - - Context::BlendFunc(BlendFunction::SrcAlpha, BlendFunction::OneMinusSrcAlpha); - Context::Disable(Capability::Blend); - Context::Disable(Capability::DepthTest); - Context::Disable(Capability::CullFace); - - - _program = loadDefaultShader(); - _plane = loadPlane(_program); - - enableVsync(); -} void OpenGLDisplayPlugin::activate() { - DisplayPlugin::activate(); _timer.start(1); + _vsyncSupported = _container->getPrimaryWidget()->isVsyncSupported(); + + // Start the present thread if necessary + auto presentThread = DependencyManager::get(); + if (!presentThread) { + auto widget = _container->getPrimaryWidget(); + + + DependencyManager::set(); + presentThread = DependencyManager::get(); + presentThread->setObjectName("Presentation Thread"); + presentThread->setContext(widget->context()); + // Start execution + presentThread->start(); + } + presentThread->setNewDisplayPlugin(this); + DisplayPlugin::activate(); } void OpenGLDisplayPlugin::stop() { - DisplayPlugin::activate(); _timer.stop(); } void OpenGLDisplayPlugin::deactivate() { - _active = false; _timer.stop(); + DisplayPlugin::deactivate(); +} - makeCurrent(); - Q_ASSERT(0 == glGetError()); +void OpenGLDisplayPlugin::customizeContext() { + auto presentThread = DependencyManager::get(); + Q_ASSERT(thread() == presentThread->thread()); + + enableVsync(); + + using namespace oglplus; + Context::BlendFunc(BlendFunction::SrcAlpha, BlendFunction::OneMinusSrcAlpha); + Context::Disable(Capability::Blend); + Context::Disable(Capability::DepthTest); + Context::Disable(Capability::CullFace); + + _program = loadDefaultShader(); + _plane = loadPlane(_program); +} + +void OpenGLDisplayPlugin::uncustomizeContext() { _program.reset(); _plane.reset(); - doneCurrent(); } // Pressing Alt (and Meta) key alone activates the menubar because its style inherits the @@ -120,13 +275,65 @@ bool OpenGLDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { return false; } -void OpenGLDisplayPlugin::display( - GLuint finalTexture, const glm::uvec2& sceneSize) { +void OpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) { + { + Lock lock(_mutex); + _sceneTextureToFrameIndexMap[sceneTexture] = frameIndex; + } + + // Submit it to the presentation thread via escrow + _sceneTextureEscrow.submit(sceneTexture); +} + +void OpenGLDisplayPlugin::submitOverlayTexture(GLuint sceneTexture, const glm::uvec2& sceneSize) { + // Submit it to the presentation thread via escrow + _overlayTextureEscrow.submit(sceneTexture); +} + +void OpenGLDisplayPlugin::updateTextures() { + _currentSceneTexture = _sceneTextureEscrow.fetchAndRelease(_currentSceneTexture); + _currentOverlayTexture = _overlayTextureEscrow.fetchAndRelease(_currentOverlayTexture); +} + +void OpenGLDisplayPlugin::updateFramerate() { + uint64_t now = usecTimestampNow(); + static uint64_t lastSwapEnd { now }; + uint64_t diff = now - lastSwapEnd; + lastSwapEnd = now; + if (diff != 0) { + Lock lock(_mutex); + _usecsPerFrame.updateAverage(diff); + } +} + + +void OpenGLDisplayPlugin::internalPresent() { using namespace oglplus; - uvec2 size = getSurfaceSize(); + uvec2 size = getSurfacePixels(); Context::Viewport(size.x, size.y); - glBindTexture(GL_TEXTURE_2D, finalTexture); + Context::Clear().DepthBuffer(); + glBindTexture(GL_TEXTURE_2D, _currentSceneTexture); drawUnitQuad(); + swapBuffers(); +} + +void OpenGLDisplayPlugin::present() { + updateTextures(); + if (_currentSceneTexture) { + internalPresent(); + updateFramerate(); + } +} + +float OpenGLDisplayPlugin::presentRate() { + float result { -1.0f }; + { + Lock lock(_mutex); + result = _usecsPerFrame.getAverage(); + result = 1.0f / result; + result *= USECS_PER_SECOND; + } + return result; } void OpenGLDisplayPlugin::drawUnitQuad() { @@ -153,3 +360,23 @@ bool OpenGLDisplayPlugin::isVsyncEnabled() { return true; #endif } + +void OpenGLDisplayPlugin::swapBuffers() { + static auto widget = _container->getPrimaryWidget(); + widget->swapBuffers(); +} + +void OpenGLDisplayPlugin::withMainThreadContext(std::function f) const { + static auto presentThread = DependencyManager::get(); + presentThread->withMainThreadContext(f); + _container->makeRenderingContextCurrent(); +} + +QImage OpenGLDisplayPlugin::getScreenshot() const { + QImage result; + withMainThreadContext([&] { + static auto widget = _container->getPrimaryWidget(); + result = widget->grabFrameBuffer(); + }); + return result; +} diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 43d8e5af6b..ef78374994 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -9,42 +9,79 @@ #include "DisplayPlugin.h" -#include -#include +#include -class GlWindow; -class QOpenGLContext; +#include +#include +#include +#include class OpenGLDisplayPlugin : public DisplayPlugin { +protected: + using Mutex = std::recursive_mutex; + using Lock = std::unique_lock; public: OpenGLDisplayPlugin(); - virtual ~OpenGLDisplayPlugin(); - virtual void preRender() override; - virtual void preDisplay() override; - virtual void finishFrame() override; - virtual void activate() override; virtual void deactivate() override; virtual void stop() override; virtual bool eventFilter(QObject* receiver, QEvent* event) override; - virtual void display(GLuint sceneTexture, const glm::uvec2& sceneSize) override; + virtual void submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) override; + virtual void submitOverlayTexture(uint32_t overlayTexture, const glm::uvec2& overlaySize) override; + virtual float presentRate() override; + + virtual glm::uvec2 getRecommendedRenderSize() const override { + return getSurfacePixels(); + } + + virtual glm::uvec2 getRecommendedUiSize() const override { + return getSurfaceSize(); + } + + virtual QImage getScreenshot() const override; protected: - virtual void customizeContext(); - virtual void drawUnitQuad(); - virtual glm::uvec2 getSurfaceSize() const = 0; - virtual void makeCurrent() = 0; - virtual void doneCurrent() = 0; - virtual void swapBuffers() = 0; + friend class PresentThread; + + virtual glm::uvec2 getSurfaceSize() const = 0; + virtual glm::uvec2 getSurfacePixels() const = 0; + + // FIXME make thread safe? virtual bool isVsyncEnabled(); virtual void enableVsync(bool enable = true); + // These functions must only be called on the presentation thread + virtual void customizeContext(); + virtual void uncustomizeContext(); + virtual void cleanupForSceneTexture(uint32_t sceneTexture); + void withMainThreadContext(std::function f) const; + + + void present(); + void updateTextures(); + void updateFramerate(); + void drawUnitQuad(); + void swapBuffers(); + // Plugin specific functionality to composite the scene and overlay and present the result + virtual void internalPresent(); + mutable QTimer _timer; ProgramPtr _program; ShapeWrapperPtr _plane; - bool _vsyncSupported{ false }; + + Mutex _mutex; + SimpleMovingAverage _usecsPerFrame { 10 }; + QMap _sceneTextureToFrameIndexMap; + + GLuint _currentSceneTexture { 0 }; + GLuint _currentOverlayTexture { 0 }; + + GLTextureEscrow _overlayTextureEscrow; + GLTextureEscrow _sceneTextureEscrow; + + bool _vsyncSupported { false }; }; diff --git a/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp index 6ddc791503..c1922599a5 100644 --- a/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp @@ -7,19 +7,11 @@ // #include "WindowOpenGLDisplayPlugin.h" -#include -#include +#include #include "plugins/PluginContainer.h" -WindowOpenGLDisplayPlugin::WindowOpenGLDisplayPlugin() { -} - -glm::uvec2 WindowOpenGLDisplayPlugin::getRecommendedRenderSize() const { - return getSurfaceSize(); -} - -glm::uvec2 WindowOpenGLDisplayPlugin::getSurfaceSize() const { +glm::uvec2 WindowOpenGLDisplayPlugin::getSurfacePixels() const { uvec2 result; if (_window) { result = toGlm(_window->geometry().size() * _window->devicePixelRatio()); @@ -27,8 +19,7 @@ glm::uvec2 WindowOpenGLDisplayPlugin::getSurfaceSize() const { return result; } - -glm::uvec2 WindowOpenGLDisplayPlugin::getRecommendedUiSize() const { +glm::uvec2 WindowOpenGLDisplayPlugin::getSurfaceSize() const { uvec2 result; if (_window) { result = toGlm(_window->geometry().size()); @@ -41,11 +32,8 @@ bool WindowOpenGLDisplayPlugin::hasFocus() const { } void WindowOpenGLDisplayPlugin::activate() { + _window = _container->getPrimaryWidget(); OpenGLDisplayPlugin::activate(); - _window = _container->getPrimarySurface(); - _window->makeCurrent(); - customizeContext(); - _window->doneCurrent(); } void WindowOpenGLDisplayPlugin::deactivate() { @@ -53,14 +41,3 @@ void WindowOpenGLDisplayPlugin::deactivate() { _window = nullptr; } -void WindowOpenGLDisplayPlugin::makeCurrent() { - _window->makeCurrent(); -} - -void WindowOpenGLDisplayPlugin::doneCurrent() { - _window->doneCurrent(); -} - -void WindowOpenGLDisplayPlugin::swapBuffers() { - _window->swapBuffers(); -} diff --git a/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.h index fc7691fc56..51e5d32503 100644 --- a/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.h @@ -9,21 +9,17 @@ #include "OpenGLDisplayPlugin.h" -class QGLWidget; +class QWidget; class WindowOpenGLDisplayPlugin : public OpenGLDisplayPlugin { public: - WindowOpenGLDisplayPlugin(); - virtual glm::uvec2 getRecommendedRenderSize() const override; - virtual glm::uvec2 getRecommendedUiSize() const override; virtual bool hasFocus() const override; virtual void activate() override; virtual void deactivate() override; protected: virtual glm::uvec2 getSurfaceSize() const override final; - virtual void makeCurrent() override; - virtual void doneCurrent() override; - virtual void swapBuffers() override; - QGLWidget* _window{ nullptr }; + virtual glm::uvec2 getSurfacePixels() const override final; + + QWidget* _window { nullptr }; }; diff --git a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp index bb39c7bb7a..68a711a847 100644 --- a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include @@ -153,7 +152,7 @@ glm::mat4 OpenVrDisplayPlugin::getEyeToHeadTransform(Eye eye) const { return _eyesData[eye]._eyeOffset; } -glm::mat4 OpenVrDisplayPlugin::getHeadPose() const { +glm::mat4 OpenVrDisplayPlugin::getHeadPose(uint32_t frameIndex) const { return _trackedDevicePoseMat4[0]; } @@ -161,26 +160,26 @@ void OpenVrDisplayPlugin::customizeContext() { WindowOpenGLDisplayPlugin::customizeContext(); } -void OpenVrDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { - // Flip y-axis since GL UV coords are backwards. - static vr::Compositor_TextureBounds leftBounds{ 0, 1, 0.5f, 0 }; - static vr::Compositor_TextureBounds rightBounds{ 0.5f, 1, 1, 0 }; - _compositor->Submit(vr::Eye_Left, (void*)finalTexture, &leftBounds); - _compositor->Submit(vr::Eye_Right, (void*)finalTexture, &rightBounds); - glFinish(); -} +//void OpenVrDisplayPlugin::display(uint32_t frameIndex, uint32_t finalTexture, const glm::uvec2& sceneSize) { +// // Flip y-axis since GL UV coords are backwards. +// static vr::Compositor_TextureBounds leftBounds{ 0, 1, 0.5f, 0 }; +// static vr::Compositor_TextureBounds rightBounds{ 0.5f, 1, 1, 0 }; +// _compositor->Submit(vr::Eye_Left, (void*)finalTexture, &leftBounds); +// _compositor->Submit(vr::Eye_Right, (void*)finalTexture, &rightBounds); +// glFinish(); +//} -void OpenVrDisplayPlugin::finishFrame() { -// swapBuffers(); - doneCurrent(); - _compositor->WaitGetPoses(_trackedDevicePose, vr::k_unMaxTrackedDeviceCount); - for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) { - _trackedDevicePoseMat4[i] = _sensorResetMat * toGlm(_trackedDevicePose[i].mDeviceToAbsoluteTracking); - } - openvr_for_each_eye([&](vr::Hmd_Eye eye) { - _eyesData[eye]._pose = _trackedDevicePoseMat4[0]; - }); -}; +//void OpenVrDisplayPlugin::finishFrame() { +//// swapBuffers(); +// doneCurrent(); +// _compositor->WaitGetPoses(_trackedDevicePose, vr::k_unMaxTrackedDeviceCount); +// for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) { +// _trackedDevicePoseMat4[i] = _sensorResetMat * toGlm(_trackedDevicePose[i].mDeviceToAbsoluteTracking); +// } +// openvr_for_each_eye([&](vr::Hmd_Eye eye) { +// _eyesData[eye]._pose = _trackedDevicePoseMat4[0]; +// }); +//}; #endif diff --git a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h index 15d37d9de8..c8887276b7 100644 --- a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h @@ -31,13 +31,11 @@ public: virtual void resetSensors() override; virtual glm::mat4 getEyeToHeadTransform(Eye eye) const override; - virtual glm::mat4 getHeadPose() const override; + virtual glm::mat4 getHeadPose(uint32_t frameIndex) const override; protected: - virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; +// virtual void display(uint32_t frameIndex, uint32_t finalTexture, const glm::uvec2& sceneSize) override; virtual void customizeContext() override; - // Do not perform swap in finish - virtual void finishFrame() override; private: vr::IVRSystem* _hmd { nullptr }; diff --git a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp index 6e14a158d4..ffaf005533 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp @@ -8,15 +8,6 @@ #include "InterleavedStereoDisplayPlugin.h" -#include -#include - -#include -#include -#include - -#include - static const char * INTERLEAVED_TEXTURED_VS = R"VS(#version 410 core #pragma line __LINE__ @@ -75,10 +66,10 @@ glm::uvec2 InterleavedStereoDisplayPlugin::getRecommendedRenderSize() const { return result; } -void InterleavedStereoDisplayPlugin::display( - GLuint finalTexture, const glm::uvec2& sceneSize) { +void InterleavedStereoDisplayPlugin::internalPresent() { using namespace oglplus; _program->Bind(); + auto sceneSize = getRecommendedRenderSize(); Uniform(*_program, "textureSize").SetValue(sceneSize); - WindowOpenGLDisplayPlugin::display(finalTexture, sceneSize); -} \ No newline at end of file + WindowOpenGLDisplayPlugin::internalPresent(); +} diff --git a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h index 3044d91247..7116363e44 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h @@ -19,7 +19,7 @@ public: virtual void customizeContext() override; virtual glm::uvec2 getRecommendedRenderSize() const override; - void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; + void internalPresent() override; private: static const QString NAME; diff --git a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp index 5ba113420d..12865cf4cd 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp @@ -7,17 +7,7 @@ // #include "SideBySideStereoDisplayPlugin.h" - -#include -#include -#include - -#include -#include -#include - -#include -#include +#include const QString SideBySideStereoDisplayPlugin::NAME("3D TV - Side by Side Stereo"); diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp index f7e71313df..a691f375eb 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp @@ -74,7 +74,7 @@ void StereoDisplayPlugin::activate() { if (screen == qApp->primaryScreen()) { checked = true; } - auto action = _container->addMenuItem(MENU_PATH(), name, + auto action = _container->addMenuItem(PluginType::DISPLAY_PLUGIN, MENU_PATH(), name, [this](bool clicked) { updateScreen(); }, true, checked, "Screens"); _screenActions[i] = action; } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 12e976d2bd..e3618d0e2a 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -408,8 +408,8 @@ int EntityTreeRenderer::getBoundaryLevelAdjust() const { } -void EntityTreeRenderer::processEraseMessage(NLPacket& packet, const SharedNodePointer& sourceNode) { - std::static_pointer_cast(_tree)->processEraseMessage(packet, sourceNode); +void EntityTreeRenderer::processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode) { + std::static_pointer_cast(_tree)->processEraseMessage(message, sourceNode); } Model* EntityTreeRenderer::allocateModel(const QString& url, const QString& collisionUrl) { diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 5cd86fba21..9980b9d8b6 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -49,7 +49,7 @@ public: EntityTreePointer getTree() { return std::static_pointer_cast(_tree); } - void processEraseMessage(NLPacket& packet, const SharedNodePointer& sourceNode); + void processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode); virtual void init(); diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp index 5d82311bcc..54fe491c46 100644 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp @@ -24,7 +24,9 @@ #include "../render-utils/simple_frag.h" EntityItemPointer RenderableBoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - return std::make_shared(entityID, properties); + EntityItemPointer entity{ new RenderableBoxEntityItem(entityID) }; + entity->setProperties(properties); + return entity; } void RenderableBoxEntityItem::setUserData(const QString& value) { @@ -39,6 +41,7 @@ void RenderableBoxEntityItem::render(RenderArgs* args) { Q_ASSERT(getType() == EntityTypes::Box); Q_ASSERT(args->_batch); + if (!_procedural) { _procedural.reset(new Procedural(this->getUserData())); _procedural->_vertexSource = simple_vert; @@ -62,4 +65,6 @@ void RenderableBoxEntityItem::render(RenderArgs* args) { } else { DependencyManager::get()->renderSolidCubeInstance(batch, getTransformToCenter(), cubeColor); } -}; + static const auto triCount = DependencyManager::get()->getCubeTriangleCount(); + args->_details._trianglesRendered += triCount; +} diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.h b/libraries/entities-renderer/src/RenderableBoxEntityItem.h index 838022c7d4..9addfd813a 100644 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderableBoxEntityItem.h @@ -20,10 +20,7 @@ class RenderableBoxEntityItem : public BoxEntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - - RenderableBoxEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : - BoxEntityItem(entityItemID, properties) - { } + RenderableBoxEntityItem(const EntityItemID& entityItemID) : BoxEntityItem(entityItemID) { } virtual void render(RenderArgs* args); virtual void setUserData(const QString& value); diff --git a/libraries/entities-renderer/src/RenderableLightEntityItem.cpp b/libraries/entities-renderer/src/RenderableLightEntityItem.cpp index b0cc0462c6..5b8891d1f2 100644 --- a/libraries/entities-renderer/src/RenderableLightEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableLightEntityItem.cpp @@ -20,7 +20,9 @@ #include "RenderableLightEntityItem.h" EntityItemPointer RenderableLightEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - return std::make_shared(entityID, properties); + EntityItemPointer entity{ new RenderableLightEntityItem(entityID) }; + entity->setProperties(properties); + return entity; } void RenderableLightEntityItem::render(RenderArgs* args) { diff --git a/libraries/entities-renderer/src/RenderableLightEntityItem.h b/libraries/entities-renderer/src/RenderableLightEntityItem.h index ecf24eaec7..aac1a4a998 100644 --- a/libraries/entities-renderer/src/RenderableLightEntityItem.h +++ b/libraries/entities-renderer/src/RenderableLightEntityItem.h @@ -18,10 +18,7 @@ class RenderableLightEntityItem : public LightEntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - - RenderableLightEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : - LightEntityItem(entityItemID, properties) - { } + RenderableLightEntityItem(const EntityItemID& entityItemID) : LightEntityItem(entityItemID) { } virtual void render(RenderArgs* args); virtual bool supportsDetailedRayIntersection() const { return true; } diff --git a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp index f39c31e22b..8eb3ea8754 100644 --- a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp @@ -20,7 +20,9 @@ #include "RenderableLineEntityItem.h" EntityItemPointer RenderableLineEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - return std::make_shared(entityID, properties); + EntityItemPointer entity{ new RenderableLineEntityItem(entityID) }; + entity->setProperties(properties); + return entity; } void RenderableLineEntityItem::updateGeometry() { diff --git a/libraries/entities-renderer/src/RenderableLineEntityItem.h b/libraries/entities-renderer/src/RenderableLineEntityItem.h index ba990046a0..9af8c0c8ba 100644 --- a/libraries/entities-renderer/src/RenderableLineEntityItem.h +++ b/libraries/entities-renderer/src/RenderableLineEntityItem.h @@ -19,9 +19,8 @@ class RenderableLineEntityItem : public LineEntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - - RenderableLineEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : - LineEntityItem(entityItemID, properties), + RenderableLineEntityItem(const EntityItemID& entityItemID) : + LineEntityItem(entityItemID), _lineVerticesID(GeometryCache::UNKNOWN_ID) { } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 782458894d..5b4704cabb 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -26,13 +26,14 @@ #include "RenderableEntityItem.h" EntityItemPointer RenderableModelEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - return std::make_shared(entityID, properties); + EntityItemPointer entity{ new RenderableModelEntityItem(entityID, properties.getDimensionsInitialized()) }; + entity->setProperties(properties); + return entity; } -RenderableModelEntityItem::RenderableModelEntityItem(const EntityItemID& entityItemID, - const EntityItemProperties& properties) : - ModelEntityItem(entityItemID, properties), - _dimensionsInitialized(properties.getDimensionsInitialized()) -{ + +RenderableModelEntityItem::RenderableModelEntityItem(const EntityItemID& entityItemID, bool dimensionsInitialized) : + ModelEntityItem(entityItemID), + _dimensionsInitialized(dimensionsInitialized) { } RenderableModelEntityItem::~RenderableModelEntityItem() { @@ -192,7 +193,7 @@ bool RenderableModelEntityItem::addToScene(EntityItemPointer self, std::shared_p if (_model) { render::Item::Status::Getters statusGetters; - makeEntityItemStatusGetters(shared_from_this(), statusGetters); + makeEntityItemStatusGetters(getThisPointer(), statusGetters); // note: we don't care if the model fails to add items, we always added our meta item and therefore we return // true so that the system knows our meta item is in the scene! @@ -238,7 +239,7 @@ void RenderableModelEntityItem::render(RenderArgs* args) { _model->removeFromScene(scene, pendingChanges); render::Item::Status::Getters statusGetters; - makeEntityItemStatusGetters(shared_from_this(), statusGetters); + makeEntityItemStatusGetters(getThisPointer(), statusGetters); _model->addToScene(scene, pendingChanges, statusGetters, _showCollisionHull); scene->enqueuePendingChanges(pendingChanges); @@ -563,3 +564,23 @@ bool RenderableModelEntityItem::contains(const glm::vec3& point) const { return false; } + +glm::quat RenderableModelEntityItem::getAbsoluteJointRotationInObjectFrame(int index) const { + if (_model) { + glm::quat result; + if (_model->getAbsoluteJointRotationInRigFrame(index, result)) { + return result; + } + } + return glm::quat(); +} + +glm::vec3 RenderableModelEntityItem::getAbsoluteJointTranslationInObjectFrame(int index) const { + if (_model) { + glm::vec3 result; + if (_model->getAbsoluteJointTranslationInRigFrame(index, result)) { + return result; + } + } + return glm::vec3(0.0f); +} diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index c4e36c240a..5c05dc5b44 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -24,48 +24,52 @@ class RenderableModelEntityItem : public ModelEntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - RenderableModelEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + RenderableModelEntityItem(const EntityItemID& entityItemID, bool dimensionsInitialized); virtual ~RenderableModelEntityItem(); virtual void setDimensions(const glm::vec3& value) override; - virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const; - virtual bool setProperties(const EntityItemProperties& properties); + virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; + virtual bool setProperties(const EntityItemProperties& properties) override; virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, - bool& somethingChanged); + bool& somethingChanged) override; - virtual void somethingChangedNotification() { + virtual void somethingChangedNotification() override { // FIX ME: this is overly aggressive. We only really need to simulate() if something about // the world space transform has changed and/or if some animation is occurring. _needsInitialSimulation = true; } virtual bool readyToAddToScene(RenderArgs* renderArgs = nullptr); - virtual bool addToScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges); - virtual void removeFromScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges); + virtual bool addToScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges) override; + virtual void removeFromScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges) override; - virtual void render(RenderArgs* args); - virtual bool supportsDetailedRayIntersection() const { return true; } + virtual void render(RenderArgs* args) override; + virtual bool supportsDetailedRayIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - void** intersectedObject, bool precisionPicking) const; + void** intersectedObject, bool precisionPicking) const override; Model* getModel(EntityTreeRenderer* renderer); - virtual bool needsToCallUpdate() const; - virtual void update(const quint64& now); + virtual bool needsToCallUpdate() const override; + virtual void update(const quint64& now) override; - virtual void setCompoundShapeURL(const QString& url); + virtual void setCompoundShapeURL(const QString& url) override; - bool isReadyToComputeShape(); - void computeShapeInfo(ShapeInfo& info); + virtual bool isReadyToComputeShape() override; + virtual void computeShapeInfo(ShapeInfo& info) override; - virtual bool contains(const glm::vec3& point) const; + virtual bool contains(const glm::vec3& point) const override; + + // these are in the frame of this object (model space) + virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; + virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; private: void remapTextures(); diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 05fca343fd..43c6373147 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -117,13 +117,15 @@ namespace render { -EntityItemPointer RenderableParticleEffectEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - return std::make_shared(entityID, properties); +EntityItemPointer RenderableParticleEffectEntityItem::factory(const EntityItemID& entityID, + const EntityItemProperties& properties) { + EntityItemPointer entity{ new RenderableParticleEffectEntityItem(entityID) }; + entity->setProperties(properties); + return entity; } -RenderableParticleEffectEntityItem::RenderableParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : - ParticleEffectEntityItem(entityItemID, properties) { - +RenderableParticleEffectEntityItem::RenderableParticleEffectEntityItem(const EntityItemID& entityItemID) : + ParticleEffectEntityItem(entityItemID) { // lazy creation of particle system pipeline if (!_untexturedPipeline && !_texturedPipeline) { createPipelines(); @@ -134,13 +136,14 @@ bool RenderableParticleEffectEntityItem::addToScene(EntityItemPointer self, render::ScenePointer scene, render::PendingChanges& pendingChanges) { - auto particlePayload = std::shared_ptr(new ParticlePayload(shared_from_this())); + auto particlePayload = + std::shared_ptr(new ParticlePayload(getThisPointer())); particlePayload->setPipeline(_untexturedPipeline); _renderItemId = scene->allocateID(); auto renderData = ParticlePayload::Pointer(particlePayload); auto renderPayload = render::PayloadPointer(new ParticlePayload::Payload(renderData)); render::Item::Status::Getters statusGetters; - makeEntityItemStatusGetters(shared_from_this(), statusGetters); + makeEntityItemStatusGetters(getThisPointer(), statusGetters); renderPayload->addStatusGetters(statusGetters); pendingChanges.resetItem(_renderItemId, renderPayload); _scene = scene; @@ -334,10 +337,10 @@ void RenderableParticleEffectEntityItem::createPipelines() { state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, destinationColorBlendArg, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - auto vertShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(untextured_particle_vert))); - auto fragShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(untextured_particle_frag))); - auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vertShader, fragShader)); - _untexturedPipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); + auto vertShader = gpu::Shader::createVertex(std::string(untextured_particle_vert)); + auto fragShader = gpu::Shader::createPixel(std::string(untextured_particle_frag)); + auto program = gpu::Shader::createProgram(vertShader, fragShader); + _untexturedPipeline = gpu::Pipeline::create(program, state); } if (!_texturedPipeline) { auto state = std::make_shared(); @@ -349,17 +352,16 @@ void RenderableParticleEffectEntityItem::createPipelines() { state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, destinationColorBlendArg, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - auto vertShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(textured_particle_vert))); + auto vertShader = gpu::Shader::createVertex(std::string(textured_particle_vert)); gpu::ShaderPointer fragShader; if (_additiveBlending) { - fragShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(textured_particle_frag))); + fragShader = gpu::Shader::createPixel(std::string(textured_particle_frag)); } else { //If we are sorting and have no additive blending, we want to discard pixels with low alpha to avoid inter-particle entity artifacts - fragShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(textured_particle_alpha_discard_frag))); + fragShader = gpu::Shader::createPixel(std::string(textured_particle_alpha_discard_frag)); } - auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vertShader, fragShader)); - _texturedPipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); - + auto program = gpu::Shader::createProgram(vertShader, fragShader); + _texturedPipeline = gpu::Pipeline::create(program, state); } } diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h index 678f7eb904..b024ddb991 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h @@ -19,7 +19,7 @@ class RenderableParticleEffectEntityItem : public ParticleEffectEntityItem { friend class ParticlePayload; public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - RenderableParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + RenderableParticleEffectEntityItem(const EntityItemID& entityItemID); virtual void update(const quint64& now) override; diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index 036d37a95b..256e36ebc3 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -23,16 +23,16 @@ #include "paintStroke_frag.h" - EntityItemPointer RenderablePolyLineEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - return EntityItemPointer(new RenderablePolyLineEntityItem(entityID, properties)); + EntityItemPointer entity{ new RenderablePolyLineEntityItem(entityID) }; + entity->setProperties(properties); + return entity; } -RenderablePolyLineEntityItem::RenderablePolyLineEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : -PolyLineEntityItem(entityItemID, properties) { +RenderablePolyLineEntityItem::RenderablePolyLineEntityItem(const EntityItemID& entityItemID) : + PolyLineEntityItem(entityItemID) { _numVertices = 0; _vertices = QVector(0.0f); - } gpu::PipelinePointer RenderablePolyLineEntityItem::_pipeline; @@ -50,9 +50,9 @@ void RenderablePolyLineEntityItem::createPipeline() { _format->setAttribute(gpu::Stream::COLOR, 0, gpu::Element::COLOR_RGBA_32, COLOR_OFFSET); _format->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), TEXTURE_OFFSET); - auto VS = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(paintStroke_vert))); - auto PS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(paintStroke_frag))); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(VS, PS)); + auto VS = gpu::Shader::createVertex(std::string(paintStroke_vert)); + auto PS = gpu::Shader::createPixel(std::string(paintStroke_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(VS, PS); gpu::Shader::BindingSet slotBindings; PAINTSTROKE_GPU_SLOT = 0; @@ -64,7 +64,7 @@ void RenderablePolyLineEntityItem::createPipeline() { state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); + _pipeline = gpu::Pipeline::create(program, state); } void RenderablePolyLineEntityItem::updateGeometry() { diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h index c49777cfa3..eca7c59fef 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h @@ -26,7 +26,7 @@ class RenderablePolyLineEntityItem : public PolyLineEntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); static void createPipeline(); - RenderablePolyLineEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + RenderablePolyLineEntityItem(const EntityItemID& entityItemID); virtual void render(RenderArgs* args); diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 16252eb453..10593a9e04 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -50,12 +50,13 @@ gpu::PipelinePointer RenderablePolyVoxEntityItem::_pipeline = nullptr; const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5; EntityItemPointer RenderablePolyVoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - return std::make_shared(entityID, properties); + EntityItemPointer entity{ new RenderablePolyVoxEntityItem(entityID) }; + entity->setProperties(properties); + return entity; } -RenderablePolyVoxEntityItem::RenderablePolyVoxEntityItem(const EntityItemID& entityItemID, - const EntityItemProperties& properties) : - PolyVoxEntityItem(entityItemID, properties), +RenderablePolyVoxEntityItem::RenderablePolyVoxEntityItem(const EntityItemID& entityItemID) : + PolyVoxEntityItem(entityItemID), _mesh(new model::Mesh()), _meshDirty(true), _xTexture(nullptr), @@ -477,8 +478,8 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { _meshLock.unlock(); if (!_pipeline) { - gpu::ShaderPointer vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(polyvox_vert))); - gpu::ShaderPointer pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(polyvox_frag))); + gpu::ShaderPointer vertexShader = gpu::Shader::createVertex(std::string(polyvox_vert)); + gpu::ShaderPointer pixelShader = gpu::Shader::createPixel(std::string(polyvox_frag)); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), MATERIAL_GPU_SLOT)); @@ -486,14 +487,14 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { slotBindings.insert(gpu::Shader::Binding(std::string("yMap"), 1)); slotBindings.insert(gpu::Shader::Binding(std::string("zMap"), 2)); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vertexShader, pixelShader)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vertexShader, pixelShader); gpu::Shader::makeProgram(*program, slotBindings); auto state = std::make_shared(); state->setCullMode(gpu::State::CULL_BACK); state->setDepthTest(true, true, gpu::LESS_EQUAL); - _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); + _pipeline = gpu::Pipeline::create(program, state); } gpu::Batch& batch = *args->_batch; @@ -546,12 +547,12 @@ bool RenderablePolyVoxEntityItem::addToScene(EntityItemPointer self, render::PendingChanges& pendingChanges) { _myItem = scene->allocateID(); - auto renderItem = std::make_shared(shared_from_this()); + auto renderItem = std::make_shared(getThisPointer()); auto renderData = PolyVoxPayload::Pointer(renderItem); auto renderPayload = std::make_shared(renderData); render::Item::Status::Getters statusGetters; - makeEntityItemStatusGetters(shared_from_this(), statusGetters); + makeEntityItemStatusGetters(getThisPointer(), statusGetters); renderPayload->addStatusGetters(statusGetters); pendingChanges.resetItem(_myItem, renderPayload); diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 9d0931a47e..0411945ede 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -44,8 +44,7 @@ namespace render { class RenderablePolyVoxEntityItem : public PolyVoxEntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - - RenderablePolyVoxEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + RenderablePolyVoxEntityItem(const EntityItemID& entityItemID); virtual ~RenderablePolyVoxEntityItem(); diff --git a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp index 0400ecb999..8768e41a07 100644 --- a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp @@ -29,7 +29,9 @@ static const float SPHERE_ENTITY_SCALE = 0.5f; EntityItemPointer RenderableSphereEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - return std::make_shared(entityID, properties); + EntityItemPointer entity{ new RenderableSphereEntityItem(entityID) }; + entity->setProperties(properties); + return entity; } void RenderableSphereEntityItem::setUserData(const QString& value) { @@ -69,4 +71,6 @@ void RenderableSphereEntityItem::render(RenderArgs* args) { batch.setModelTransform(Transform()); DependencyManager::get()->renderSolidSphereInstance(batch, modelTransform, sphereColor); } -}; + static const auto triCount = DependencyManager::get()->getSphereTriangleCount(); + args->_details._trianglesRendered += triCount; +} diff --git a/libraries/entities-renderer/src/RenderableSphereEntityItem.h b/libraries/entities-renderer/src/RenderableSphereEntityItem.h index 293ae79029..737bee3134 100644 --- a/libraries/entities-renderer/src/RenderableSphereEntityItem.h +++ b/libraries/entities-renderer/src/RenderableSphereEntityItem.h @@ -20,10 +20,7 @@ class RenderableSphereEntityItem : public SphereEntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - - RenderableSphereEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : - SphereEntityItem(entityItemID, properties) - { } + RenderableSphereEntityItem(const EntityItemID& entityItemID) : SphereEntityItem(entityItemID) { } virtual void render(RenderArgs* args); virtual void setUserData(const QString& value); diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index d87f89ba41..bdf3b5b97c 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -22,7 +22,9 @@ #include "GLMHelpers.h" EntityItemPointer RenderableTextEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - return std::make_shared(entityID, properties); + EntityItemPointer entity{ new RenderableTextEntityItem(entityID) }; + entity->setProperties(properties); + return entity; } void RenderableTextEntityItem::render(RenderArgs* args) { diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.h b/libraries/entities-renderer/src/RenderableTextEntityItem.h index de1d745875..149df946f7 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.h +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.h @@ -22,10 +22,7 @@ const int FIXED_FONT_POINT_SIZE = 40; class RenderableTextEntityItem : public TextEntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - - RenderableTextEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : - TextEntityItem(entityItemID, properties) - { } + RenderableTextEntityItem(const EntityItemID& entityItemID) : TextEntityItem(entityItemID) { } ~RenderableTextEntityItem() { delete _textRenderer; } virtual void render(RenderArgs* args); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 29fac6cd84..44a0740a4d 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -31,11 +31,13 @@ const float DPI = 30.47f; const float METERS_TO_INCHES = 39.3701f; EntityItemPointer RenderableWebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - return std::make_shared(entityID, properties); + EntityItemPointer entity{ new RenderableWebEntityItem(entityID) }; + entity->setProperties(properties); + return entity; } -RenderableWebEntityItem::RenderableWebEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : - WebEntityItem(entityItemID, properties) { +RenderableWebEntityItem::RenderableWebEntityItem(const EntityItemID& entityItemID) : + WebEntityItem(entityItemID) { qDebug() << "Created web entity " << getID(); } diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 63418a890f..2266401b5d 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -22,8 +22,7 @@ class QObject; class RenderableWebEntityItem : public WebEntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - - RenderableWebEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + RenderableWebEntityItem(const EntityItemID& entityItemID); ~RenderableWebEntityItem(); virtual void render(RenderArgs* args); diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index b7b91c9b3a..ca16b3aca1 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -26,7 +26,9 @@ static const float SPHERE_ENTITY_SCALE = 0.5f; EntityItemPointer RenderableZoneEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - return std::make_shared(entityID, properties); + EntityItemPointer entity{ new RenderableZoneEntityItem(entityID) }; + entity->setProperties(properties); + return entity; } template @@ -115,7 +117,7 @@ void RenderableZoneEntityItem::render(RenderArgs* args) { render::PendingChanges pendingChanges; _model->removeFromScene(scene, pendingChanges); render::Item::Status::Getters statusGetters; - makeEntityItemStatusGetters(shared_from_this(), statusGetters); + makeEntityItemStatusGetters(getThisPointer(), statusGetters); _model->addToScene(scene, pendingChanges, false); scene->enqueuePendingChanges(pendingChanges); @@ -209,7 +211,7 @@ bool RenderableZoneEntityItem::addToScene(EntityItemPointer self, std::shared_pt auto renderPayload = std::make_shared(renderData); render::Item::Status::Getters statusGetters; - makeEntityItemStatusGetters(shared_from_this(), statusGetters); + makeEntityItemStatusGetters(getThisPointer(), statusGetters); renderPayload->addStatusGetters(statusGetters); pendingChanges.resetItem(_myMetaItem, renderPayload); diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index 92de136df6..36555dbc45 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -21,10 +21,10 @@ class RenderableZoneEntityItem : public ZoneEntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - RenderableZoneEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : - ZoneEntityItem(entityItemID, properties), - _model(NULL), - _needsInitialSimulation(true) + RenderableZoneEntityItem(const EntityItemID& entityItemID) : + ZoneEntityItem(entityItemID), + _model(NULL), + _needsInitialSimulation(true) { } virtual bool setProperties(const EntityItemProperties& properties); diff --git a/libraries/entities/src/BoundingBoxRelatedProperties.cpp b/libraries/entities/src/BoundingBoxRelatedProperties.cpp new file mode 100644 index 0000000000..e9ee302300 --- /dev/null +++ b/libraries/entities/src/BoundingBoxRelatedProperties.cpp @@ -0,0 +1,83 @@ +// +// BoundingBoxRelatedProperties.cpp +// libraries/entities/src +// +// Created by Seth Alves on 2015-9-24 +// 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 "EntityItemProperties.h" +#include "BoundingBoxRelatedProperties.h" +#include "EntityTree.h" + +BoundingBoxRelatedProperties::BoundingBoxRelatedProperties(EntityItemPointer entity) : + position(entity->getPosition()), + rotation(entity->getRotation()), + registrationPoint(entity->getRegistrationPoint()), + dimensions(entity->getDimensions()), + parentID(entity->getParentID()) { +} + +BoundingBoxRelatedProperties::BoundingBoxRelatedProperties(EntityItemPointer entity, + const EntityItemProperties& propertiesWithUpdates) : + BoundingBoxRelatedProperties(entity) { + + if (propertiesWithUpdates.parentIDChanged()) { + parentID = propertiesWithUpdates.getParentID(); + } + + bool parentFound = false; + if (parentID != UNKNOWN_ENTITY_ID) { + EntityTreePointer tree = entity->getTree(); + EntityItemPointer parentZone = tree->findEntityByID(parentID); + if (parentZone) { + parentFound = true; + glm::vec3 localPosition = propertiesWithUpdates.containsPositionChange() ? + propertiesWithUpdates.getPosition() : + entity->getLocalPosition(); + + glm::quat localRotation = propertiesWithUpdates.rotationChanged() ? + propertiesWithUpdates.getRotation() : + entity->getLocalOrientation(); + + const Transform parentTransform = parentZone->getTransformToCenter(); + Transform parentDescaled(parentTransform.getRotation(), glm::vec3(1.0f), parentTransform.getTranslation()); + + Transform localTransform(localRotation, glm::vec3(1.0f), localPosition); + Transform result; + Transform::mult(result, parentDescaled, localTransform); + position = result.getTranslation(); + rotation = result.getRotation(); + } + } + + if (!parentFound) { + if (propertiesWithUpdates.containsPositionChange()) { + position = propertiesWithUpdates.getPosition(); + } + if (propertiesWithUpdates.rotationChanged()) { + rotation = propertiesWithUpdates.getRotation(); + } + } + + if (propertiesWithUpdates.registrationPointChanged()) { + registrationPoint = propertiesWithUpdates.getRegistrationPoint(); + } + + if (propertiesWithUpdates.dimensionsChanged()) { + dimensions = propertiesWithUpdates.getDimensions(); + } +} + +AACube BoundingBoxRelatedProperties::getMaximumAACube() const { + // see EntityItem::getMaximumAACube for comments which explain the following. + glm::vec3 scaledRegistrationPoint = (dimensions * registrationPoint); + glm::vec3 registrationRemainder = (dimensions * (glm::vec3(1.0f, 1.0f, 1.0f) - registrationPoint)); + glm::vec3 furthestExtentFromRegistration = glm::max(scaledRegistrationPoint, registrationRemainder); + float radius = glm::length(furthestExtentFromRegistration); + glm::vec3 minimumCorner = position - glm::vec3(radius, radius, radius); + return AACube(minimumCorner, radius * 2.0f); +} diff --git a/libraries/entities/src/BoundingBoxRelatedProperties.h b/libraries/entities/src/BoundingBoxRelatedProperties.h new file mode 100644 index 0000000000..811c885fd2 --- /dev/null +++ b/libraries/entities/src/BoundingBoxRelatedProperties.h @@ -0,0 +1,30 @@ +// +// BoundingBoxRelatedProperties.h +// libraries/entities/src +// +// Created by Seth Alves on 2015-9-24 +// 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 "EntityItem.h" + +#ifndef hifi_BoundingBoxRelatedProperties_h +#define hifi_BoundingBoxRelatedProperties_h + +class BoundingBoxRelatedProperties { + public: + BoundingBoxRelatedProperties(EntityItemPointer entity); + BoundingBoxRelatedProperties(EntityItemPointer entity, const EntityItemProperties& propertiesWithUpdates); + AACube getMaximumAACube() const; + + glm::vec3 position; + glm::quat rotation; + glm::vec3 registrationPoint; + glm::vec3 dimensions; + EntityItemID parentID; +}; + +#endif // hifi_BoundingBoxRelatedProperties_h diff --git a/libraries/entities/src/BoxEntityItem.cpp b/libraries/entities/src/BoxEntityItem.cpp index 70d5ba0c4f..061c5b3854 100644 --- a/libraries/entities/src/BoxEntityItem.cpp +++ b/libraries/entities/src/BoxEntityItem.cpp @@ -21,15 +21,13 @@ #include "EntityTreeElement.h" EntityItemPointer BoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - EntityItemPointer result { new BoxEntityItem(entityID, properties) }; - return result; + EntityItemPointer entity { new BoxEntityItem(entityID) }; + entity->setProperties(properties); + return entity; } -BoxEntityItem::BoxEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : - EntityItem(entityItemID) -{ +BoxEntityItem::BoxEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { _type = EntityTypes::Box; - setProperties(properties); } EntityItemProperties BoxEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { diff --git a/libraries/entities/src/BoxEntityItem.h b/libraries/entities/src/BoxEntityItem.h index 6c1b5b2312..351feb7e54 100644 --- a/libraries/entities/src/BoxEntityItem.h +++ b/libraries/entities/src/BoxEntityItem.h @@ -18,7 +18,7 @@ class BoxEntityItem : public EntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - BoxEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + BoxEntityItem(const EntityItemID& entityItemID); ALLOW_INSTANTIATION // This class can be instantiated diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index 05bb558767..91f21659ec 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -23,9 +23,9 @@ EntityEditPacketSender::EntityEditPacketSender() { packetReceiver.registerDirectListener(PacketType::EntityEditNack, this, "processEntityEditNackPacket"); } -void EntityEditPacketSender::processEntityEditNackPacket(QSharedPointer packet, SharedNodePointer sendingNode) { +void EntityEditPacketSender::processEntityEditNackPacket(QSharedPointer message, SharedNodePointer sendingNode) { if (_shouldProcessNack) { - processNackPacket(*packet, sendingNode); + processNackPacket(*message, sendingNode); } } diff --git a/libraries/entities/src/EntityEditPacketSender.h b/libraries/entities/src/EntityEditPacketSender.h index 0932ec2af1..ec90473643 100644 --- a/libraries/entities/src/EntityEditPacketSender.h +++ b/libraries/entities/src/EntityEditPacketSender.h @@ -35,7 +35,7 @@ public: virtual void adjustEditPacketForClockSkew(PacketType type, QByteArray& buffer, int clockSkew); public slots: - void processEntityEditNackPacket(QSharedPointer packet, SharedNodePointer sendingNode); + void processEntityEditNackPacket(QSharedPointer message, SharedNodePointer sendingNode); void toggleNackPackets() { _shouldProcessNack = !_shouldProcessNack; } private: diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 992b6a1bdd..9b31c2aa59 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -32,13 +32,12 @@ #include "EntityActionFactoryInterface.h" -bool EntityItem::_sendPhysicsUpdates = true; int EntityItem::_maxActionsDataSize = 800; quint64 EntityItem::_rememberDeletedActionTime = 20 * USECS_PER_SECOND; EntityItem::EntityItem(const EntityItemID& entityItemID) : + SpatiallyNestable(NestableTypes::Entity, entityItemID), _type(EntityTypes::Unknown), - _id(entityItemID), _lastSimulated(0), _lastUpdated(0), _lastEdited(0), @@ -46,7 +45,6 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) : _lastEditedFromRemoteInRemoteTime(0), _created(UNKNOWN_CREATED_TIME), _changedOnServer(0), - _transform(), _glowLevel(ENTITY_ITEM_DEFAULT_GLOW_LEVEL), _localRenderAlpha(ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA), _density(ENTITY_ITEM_DEFAULT_DENSITY), @@ -80,9 +78,7 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) : _simulated(false) { // explicitly set transform parts to set dirty flags used by batch rendering - _transform.setTranslation(ENTITY_ITEM_DEFAULT_POSITION); - _transform.setRotation(ENTITY_ITEM_DEFAULT_ROTATION); - _transform.setScale(ENTITY_ITEM_DEFAULT_DIMENSIONS); + setScale(ENTITY_ITEM_DEFAULT_DIMENSIONS); quint64 now = usecTimestampNow(); _lastSimulated = now; _lastUpdated = now; @@ -135,6 +131,8 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_HREF; requestedProperties += PROP_DESCRIPTION; requestedProperties += PROP_ACTION_DATA; + requestedProperties += PROP_PARENT_ID; + requestedProperties += PROP_PARENT_JOINT_INDEX; return requestedProperties; } @@ -241,8 +239,8 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet // PROP_CUSTOM_PROPERTIES_INCLUDED, APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, _simulationOwner.toByteArray()); - APPEND_ENTITY_PROPERTY(PROP_POSITION, getPosition()); - APPEND_ENTITY_PROPERTY(PROP_ROTATION, getRotation()); + APPEND_ENTITY_PROPERTY(PROP_POSITION, getLocalPosition()); + APPEND_ENTITY_PROPERTY(PROP_ROTATION, getLocalOrientation()); APPEND_ENTITY_PROPERTY(PROP_VELOCITY, getVelocity()); APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, getAngularVelocity()); APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, getAcceleration()); @@ -269,7 +267,8 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_HREF, getHref()); APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, getDescription()); APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, getActionData()); - + APPEND_ENTITY_PROPERTY(PROP_PARENT_ID, getParentID()); + APPEND_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, getParentJointIndex()); appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, @@ -312,6 +311,11 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet entityTreeElementExtraEncodeData->entities.insert(getEntityItemID(), propertiesDidntFit); } + // if any part of our entity was sent, call trackSend + if (appendState != OctreeElement::NONE) { + params.trackSend(getID(), getLastEdited()); + } + return appendState; } @@ -718,6 +722,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_DESCRIPTION, QString, setDescription); READ_ENTITY_PROPERTY(PROP_ACTION_DATA, QByteArray, setActionData); + READ_ENTITY_PROPERTY(PROP_PARENT_ID, QUuid, setParentID); + READ_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, quint16, setParentJointIndex); + bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData, somethingChanged); @@ -1040,9 +1047,9 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper properties._type = getType(); COPY_ENTITY_PROPERTY_TO_PROPERTIES(simulationOwner, getSimulationOwner); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(position, getPosition); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(position, getLocalPosition); COPY_ENTITY_PROPERTY_TO_PROPERTIES(dimensions, getDimensions); // NOTE: radius is obsolete - COPY_ENTITY_PROPERTY_TO_PROPERTIES(rotation, getRotation); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(rotation, getLocalOrientation); COPY_ENTITY_PROPERTY_TO_PROPERTIES(density, getDensity); COPY_ENTITY_PROPERTY_TO_PROPERTIES(velocity, getVelocity); COPY_ENTITY_PROPERTY_TO_PROPERTIES(gravity, getGravity); @@ -1070,6 +1077,8 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper COPY_ENTITY_PROPERTY_TO_PROPERTIES(href, getHref); COPY_ENTITY_PROPERTY_TO_PROPERTIES(description, getDescription); COPY_ENTITY_PROPERTY_TO_PROPERTIES(actionData, getActionData); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentID, getParentID); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentJointIndex, getParentJointIndex); properties._defaultSettings = false; @@ -1078,9 +1087,9 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper void EntityItem::getAllTerseUpdateProperties(EntityItemProperties& properties) const { // a TerseUpdate includes the transform and its derivatives - properties._position = getPosition(); + properties._position = getLocalPosition(); properties._velocity = _velocity; - properties._rotation = getRotation(); + properties._rotation = getLocalOrientation(); properties._angularVelocity = _angularVelocity; properties._acceleration = _acceleration; @@ -1132,6 +1141,8 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(href, setHref); SET_ENTITY_PROPERTY_FROM_PROPERTIES(description, setDescription); SET_ENTITY_PROPERTY_FROM_PROPERTIES(actionData, setActionData); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentID, setParentID); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentJointIndex, setParentJointIndex); if (somethingChanged) { uint64_t now = usecTimestampNow(); @@ -1185,7 +1196,7 @@ void EntityItem::setDimensions(const glm::vec3& value) { if (value.x <= 0.0f || value.y <= 0.0f || value.z <= 0.0f) { return; } - _transform.setScale(value); + setScale(value); requiresRecalcBoxes(); } @@ -1311,9 +1322,8 @@ void EntityItem::updatePosition(const glm::vec3& value) { if (shouldSuppressLocationEdits()) { return; } - if (getPosition() != value) { - setPosition(value); - _dirtyFlags |= Simulation::DIRTY_POSITION; + if (getLocalPosition() != value) { + setLocalPosition(value); } } @@ -1328,9 +1338,16 @@ void EntityItem::updateRotation(const glm::quat& rotation) { if (shouldSuppressLocationEdits()) { return; } - if (getRotation() != rotation) { - setRotation(rotation); + if (getLocalOrientation() != rotation) { + setLocalOrientation(rotation); _dirtyFlags |= Simulation::DIRTY_ROTATION; + forEachDescendant([&](SpatiallyNestablePointer object) { + if (object->getNestableType() == NestableTypes::Entity) { + EntityItemPointer entity = std::static_pointer_cast(object); + entity->_dirtyFlags |= Simulation::DIRTY_ROTATION; + entity->_dirtyFlags |= Simulation::DIRTY_POSITION; + } + }); } } @@ -1519,7 +1536,6 @@ bool EntityItem::addAction(EntitySimulation* simulation, EntityActionPointer act } bool EntityItem::addActionInternal(EntitySimulation* simulation, EntityActionPointer action) { - assertLocked(); assert(action); assert(simulation); auto actionOwnerEntity = action->getOwnerEntity().lock(); @@ -1575,7 +1591,6 @@ bool EntityItem::removeAction(EntitySimulation* simulation, const QUuid& actionI } bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulation* simulation) { - assertWriteLocked(); _previouslyDeletedActions.insert(actionID, usecTimestampNow()); if (_objectActions.contains(actionID)) { if (!simulation) { @@ -1621,7 +1636,6 @@ bool EntityItem::clearActions(EntitySimulation* simulation) { void EntityItem::deserializeActions() { - assertUnlocked(); withWriteLock([&] { deserializeActionsInternal(); }); @@ -1629,8 +1643,6 @@ void EntityItem::deserializeActions() { void EntityItem::deserializeActionsInternal() { - assertWriteLocked(); - quint64 now = usecTimestampNow(); if (!_element) { @@ -1672,7 +1684,7 @@ void EntityItem::deserializeActionsInternal() { action->locallyAddedButNotYetReceived = false; } else { auto actionFactory = DependencyManager::get(); - EntityItemPointer entity = shared_from_this(); + EntityItemPointer entity = getThisPointer(); EntityActionPointer action = actionFactory->factoryBA(entity, serializedAction); if (action) { entity->addActionInternal(simulation, action); @@ -1713,7 +1725,6 @@ void EntityItem::deserializeActionsInternal() { } void EntityItem::checkWaitingToRemove(EntitySimulation* simulation) { - assertLocked(); foreach(QUuid actionID, _actionsToRemove) { removeActionInternal(actionID, simulation); } @@ -1721,14 +1732,12 @@ void EntityItem::checkWaitingToRemove(EntitySimulation* simulation) { } void EntityItem::setActionData(QByteArray actionData) { - assertUnlocked(); withWriteLock([&] { setActionDataInternal(actionData); }); } void EntityItem::setActionDataInternal(QByteArray actionData) { - assertWriteLocked(); if (_allActionsDataCache != actionData) { _allActionsDataCache = actionData; deserializeActionsInternal(); @@ -1737,8 +1746,6 @@ void EntityItem::setActionDataInternal(QByteArray actionData) { } void EntityItem::serializeActions(bool& success, QByteArray& result) const { - assertLocked(); - if (_objectActions.size() == 0) { success = true; result.clear(); @@ -1781,7 +1788,6 @@ const QByteArray EntityItem::getActionDataInternal() const { const QByteArray EntityItem::getActionData() const { QByteArray result; - assertUnlocked(); if (_actionDataDirty) { withWriteLock([&] { @@ -1835,3 +1841,8 @@ QList EntityItem::getActionsOfType(EntityActionType typeToG return result; } + +void EntityItem::locationChanged() { + requiresRecalcBoxes(); + SpatiallyNestable::locationChanged(); // tell all the children, also +} diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index d80de4d427..ed4dd236d7 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -23,6 +23,7 @@ #include #include #include +#include #include "EntityItemID.h" #include "EntityItemPropertiesDefaults.h" @@ -48,25 +49,6 @@ namespace render { class PendingChanges; } -/* -// these thesholds determine what updates will be ignored (client and server) -const float IGNORE_POSITION_DELTA = 0.0001f; -const float IGNORE_DIMENSIONS_DELTA = 0.0005f; -const float IGNORE_ALIGNMENT_DOT = 0.99997f; -const float IGNORE_LINEAR_VELOCITY_DELTA = 0.001f; -const float IGNORE_DAMPING_DELTA = 0.001f; -const float IGNORE_GRAVITY_DELTA = 0.001f; -const float IGNORE_ANGULAR_VELOCITY_DELTA = 0.0002f; - -// these thresholds determine what updates will activate the physical object -const float ACTIVATION_POSITION_DELTA = 0.005f; -const float ACTIVATION_DIMENSIONS_DELTA = 0.005f; -const float ACTIVATION_ALIGNMENT_DOT = 0.99990f; -const float ACTIVATION_LINEAR_VELOCITY_DELTA = 0.01f; -const float ACTIVATION_GRAVITY_DELTA = 0.1f; -const float ACTIVATION_ANGULAR_VELOCITY_DELTA = 0.03f; -*/ - #define DONT_ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() = 0; #define ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() { }; @@ -74,31 +56,10 @@ const float ACTIVATION_ANGULAR_VELOCITY_DELTA = 0.03f; #define debugTimeOnly(T) qPrintable(QString("%1").arg(T, 16, 10)) #define debugTreeVector(V) V << "[" << V << " in meters ]" -//#if DEBUG -// #define assertLocked() assert(isLocked()) -//#else -// #define assertLocked() -//#endif -// -//#if DEBUG -// #define assertWriteLocked() assert(isWriteLocked()) -//#else -// #define assertWriteLocked() -//#endif -// -//#if DEBUG -// #define assertUnlocked() assert(isUnlocked()) -//#else -// #define assertUnlocked() -//#endif -#define assertLocked() -#define assertUnlocked() -#define assertWriteLocked() - /// EntityItem class this is the base class for all entity types. It handles the basic properties and functionality available /// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate /// one directly, instead you must only construct one of it's derived classes with additional features. -class EntityItem : public std::enable_shared_from_this, public ReadWriteLockable { +class EntityItem : public SpatiallyNestable, public ReadWriteLockable { // These two classes manage lists of EntityItem pointers and must be able to cleanup pointers when an EntityItem is deleted. // To make the cleanup robust each EntityItem has backpointers to its manager classes (which are only ever set/cleared by // the managers themselves, hence they are fiends) whose NULL status can be used to determine which managers still need to @@ -112,9 +73,8 @@ public: EntityItem(const EntityItemID& entityItemID); virtual ~EntityItem(); - // ID and EntityItemID related methods - const QUuid& getID() const { return _id; } - void setID(const QUuid& id) { _id = id; } + inline EntityItemPointer getThisPointer() { return std::static_pointer_cast(shared_from_this()); } + EntityItemID getEntityItemID() const { return EntityItemID(_id); } // methods for getting/setting all properties of an entity @@ -194,7 +154,7 @@ public: virtual bool supportsDetailedRayIntersection() const { return false; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, + bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, void** intersectedObject, bool precisionPicking) const { return true; } @@ -207,16 +167,6 @@ public: const Transform getTransformToCenter() const; void setTranformToCenter(const Transform& transform); - inline const Transform& getTransform() const { return _transform; } - inline void setTransform(const Transform& transform) { _transform = transform; requiresRecalcBoxes(); } - - /// Position in meters (-TREE_SCALE - TREE_SCALE) - inline const glm::vec3& getPosition() const { return _transform.getTranslation(); } - inline void setPosition(const glm::vec3& value) { _transform.setTranslation(value); requiresRecalcBoxes(); } - - inline const glm::quat& getRotation() const { return _transform.getRotation(); } - inline void setRotation(const glm::quat& rotation) { _transform.setRotation(rotation); requiresRecalcBoxes(); } - inline void requiresRecalcBoxes() { _recalcAABox = true; _recalcMinAACube = true; _recalcMaxAACube = true; } // Hyperlink related getters and setters @@ -227,7 +177,7 @@ public: void setDescription(QString value) { _description = value; } /// Dimensions in meters (0.0 - TREE_SCALE) - inline const glm::vec3& getDimensions() const { return _transform.getScale(); } + inline const glm::vec3 getDimensions() const { return getScale(); } virtual void setDimensions(const glm::vec3& value); float getGlowLevel() const { return _glowLevel; } @@ -356,6 +306,10 @@ public: /// return preferred shape type (actual physical shape may differ) virtual ShapeType getShapeType() const { return SHAPE_TYPE_NONE; } + // these are only needed because the names don't match + virtual const glm::quat getRotation() const { return getOrientation(); } + virtual void setRotation(glm::quat orientation) { setOrientation(orientation); } + // updateFoo() methods to be used when changes need to be accumulated in the _dirtyFlags void updatePosition(const glm::vec3& value); void updateDimensions(const glm::vec3& value); @@ -387,9 +341,6 @@ public: EntityTreePointer getTree() const; bool wantTerseEditLogging(); - static void setSendPhysicsUpdates(bool value) { _sendPhysicsUpdates = value; } - static bool getSendPhysicsUpdates() { return _sendPhysicsUpdates; } - glm::mat4 getEntityToWorldMatrix() const; glm::mat4 getWorldToEntityMatrix() const; glm::vec3 worldToEntity(const glm::vec3& point) const; @@ -427,14 +378,17 @@ public: QList getActionsOfType(EntityActionType typeToGet); + // these are in the frame of this object + virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override { return glm::quat(); } + virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override { return glm::vec3(0.0f); } + protected: const QByteArray getActionDataInternal() const; void setActionDataInternal(QByteArray actionData); - static bool _sendPhysicsUpdates; + virtual void locationChanged(); EntityTypes::EntityType _type; - QUuid _id; quint64 _lastSimulated; // last time this entity called simulate(), this includes velocity, angular velocity, // and physics changes quint64 _lastUpdated; // last time this entity called update(), this includes animations and non-physics changes @@ -446,7 +400,6 @@ protected: quint64 _created; quint64 _changedOnServer; - Transform _transform; mutable AABox _cachedAABox; mutable AACube _maxAACube; mutable AACube _minAACube; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 78a4f3e8b6..d040f785da 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -260,6 +260,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_X_P_NEIGHBOR_ID, xPNeighborID); CHECK_PROPERTY_CHANGE(PROP_Y_P_NEIGHBOR_ID, yPNeighborID); CHECK_PROPERTY_CHANGE(PROP_Z_P_NEIGHBOR_ID, zPNeighborID); + CHECK_PROPERTY_CHANGE(PROP_PARENT_ID, parentID); + CHECK_PROPERTY_CHANGE(PROP_PARENT_JOINT_INDEX, parentJointIndex); changedProperties += _animation.getChangedProperties(); changedProperties += _keyLight.getChangedProperties(); @@ -353,7 +355,8 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_START, alphaStart); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_FINISH, alphaFinish); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ADDITIVE_BLENDING, additiveBlending); - + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_POSITION, localPosition); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_ROTATION, localRotation); } // Models only @@ -468,6 +471,12 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(originalTextures, textureNamesList); // gettable, but not settable } + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_ID, parentID); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_JOINT_INDEX, parentJointIndex); + + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_POSITION, localPosition); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_ROTATION, localRotation); + // FIXME - I don't think these properties are supported any more //COPY_PROPERTY_TO_QSCRIPTVALUE(glowLevel); //COPY_PROPERTY_TO_QSCRIPTVALUE(localRenderAlpha); @@ -590,6 +599,12 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(yPNeighborID, EntityItemID, setYPNeighborID); COPY_PROPERTY_FROM_QSCRIPTVALUE(zPNeighborID, EntityItemID, setZPNeighborID); + COPY_PROPERTY_FROM_QSCRIPTVALUE(parentID, QUuid, setParentID); + COPY_PROPERTY_FROM_QSCRIPTVALUE(parentJointIndex, quint16, setParentJointIndex); + + COPY_PROPERTY_FROM_QSCRIPTVALUE(localPosition, glmVec3, setLocalPosition); + COPY_PROPERTY_FROM_QSCRIPTVALUE(localRotation, glmQuat, setLocalRotation); + _lastEdited = usecTimestampNow(); } @@ -906,7 +921,8 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_USER_DATA, properties.getUserData()); APPEND_ENTITY_PROPERTY(PROP_HREF, properties.getHref()); APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, properties.getDescription()); - + APPEND_ENTITY_PROPERTY(PROP_PARENT_ID, properties.getParentID()); + APPEND_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, properties.getParentJointIndex()); if (properties.getType() == EntityTypes::Web) { APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, properties.getSourceUrl()); @@ -1191,7 +1207,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_USER_DATA, QString, setUserData); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_HREF, QString, setHref); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DESCRIPTION, QString, setDescription); - + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARENT_ID, QUuid, setParentID); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARENT_JOINT_INDEX, quint16, setParentJointIndex); if (properties.getType() == EntityTypes::Web) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOURCE_URL, QString, setSourceUrl); @@ -1441,6 +1458,9 @@ void EntityItemProperties::markAllChanged() { _xPNeighborIDChanged = true; _yPNeighborIDChanged = true; _zPNeighborIDChanged = true; + + _parentIDChanged = true; + _parentJointIndexChanged = true; } /// The maximum bounding cube for the entity, independent of it's rotation. @@ -1478,7 +1498,7 @@ AABox EntityItemProperties::getAABox() const { glm::vec3 unrotatedMinRelativeToEntity = - (_dimensions * _registrationPoint); glm::vec3 unrotatedMaxRelativeToEntity = _dimensions * registrationRemainder; Extents unrotatedExtentsRelativeToRegistrationPoint = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity }; - Extents rotatedExtentsRelativeToRegistrationPoint = unrotatedExtentsRelativeToRegistrationPoint.getRotated(getRotation()); + Extents rotatedExtentsRelativeToRegistrationPoint = unrotatedExtentsRelativeToRegistrationPoint.getRotated(_rotation); // shift the extents to be relative to the position/registration point rotatedExtentsRelativeToRegistrationPoint.shiftBy(_position); @@ -1760,6 +1780,12 @@ QList EntityItemProperties::listChangedProperties() { if (zPNeighborIDChanged()) { out += "zPNeighborID"; } + if (parentIDChanged()) { + out += "parentID"; + } + if (parentJointIndexChanged()) { + out += "parentJointIndex"; + } getAnimation().listChangedProperties(out); getKeyLight().listChangedProperties(out); @@ -1769,3 +1795,7 @@ QList EntityItemProperties::listChangedProperties() { return out; } + +bool EntityItemProperties::parentDependentPropertyChanged() const { + return localPositionChanged() || positionChanged() || localRotationChanged() || rotationChanged(); +} diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 84a5aeca5d..c13519996a 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -83,6 +83,8 @@ public: { return (float)(usecTimestampNow() - getLastEdited()) / (float)USECS_PER_SECOND; } EntityPropertyFlags getChangedProperties() const; + bool parentDependentPropertyChanged() const; // was there a changed in a property that requires parent info to interpret? + AACube getMaximumAACube() const; AABox getAABox() const; @@ -189,6 +191,12 @@ public: DEFINE_PROPERTY_REF(PROP_X_P_NEIGHBOR_ID, XPNeighborID, xPNeighborID, EntityItemID, UNKNOWN_ENTITY_ID); DEFINE_PROPERTY_REF(PROP_Y_P_NEIGHBOR_ID, YPNeighborID, yPNeighborID, EntityItemID, UNKNOWN_ENTITY_ID); DEFINE_PROPERTY_REF(PROP_Z_P_NEIGHBOR_ID, ZPNeighborID, zPNeighborID, EntityItemID, UNKNOWN_ENTITY_ID); + DEFINE_PROPERTY_REF(PROP_PARENT_ID, ParentID, parentID, QUuid, UNKNOWN_ENTITY_ID); + DEFINE_PROPERTY_REF(PROP_PARENT_JOINT_INDEX, ParentJointIndex, parentJointIndex, quint16, 0); + + // 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); static QString getBackgroundModeString(BackgroundMode mode); @@ -387,6 +395,9 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, YPNeighborID, yPNeighborID, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, ZPNeighborID, zPNeighborID, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, ParentID, parentID, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, ParentJointIndex, parentJointIndex, ""); + properties.getAnimation().debugDump(); properties.getAtmosphere().debugDump(); properties.getSkybox().debugDump(); diff --git a/libraries/entities/src/EntityItemPropertiesMacros.h b/libraries/entities/src/EntityItemPropertiesMacros.h index b3299b6fe6..304e6f672c 100644 --- a/libraries/entities/src/EntityItemPropertiesMacros.h +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -104,6 +104,7 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const glm::vec3& v) { return vec3toScriptValue(e, v); } inline QScriptValue convertScriptValue(QScriptEngine* e, float v) { return QScriptValue(v); } inline QScriptValue convertScriptValue(QScriptEngine* e, int v) { return QScriptValue(v); } +inline QScriptValue convertScriptValue(QScriptEngine* e, quint16 v) { return QScriptValue(v); } inline QScriptValue convertScriptValue(QScriptEngine* e, quint32 v) { return QScriptValue(v); } inline QScriptValue convertScriptValue(QScriptEngine* e, quint64 v) { return QScriptValue((qsreal)v); } inline QScriptValue convertScriptValue(QScriptEngine* e, const QString& v) { return QScriptValue(v); } @@ -179,6 +180,7 @@ inline quint32 quint32_convertFromScriptValue(const QScriptValue& v, bool& isVal // Use QString::toUInt() so that isValid is set to false if the number is outside the quint32 range. return v.toString().toUInt(&isValid); } +inline quint16 quint16_convertFromScriptValue(const QScriptValue& v, bool& isValid) { return v.toVariant().toInt(&isValid); } inline uint16_t uint16_t_convertFromScriptValue(const QScriptValue& v, bool& isValid) { return v.toVariant().toInt(&isValid); } inline int int_convertFromScriptValue(const QScriptValue& v, bool& isValid) { return v.toVariant().toInt(&isValid); } inline bool bool_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = true; return v.toVariant().toBool(); } @@ -257,7 +259,7 @@ inline glmQuat glmQuat_convertFromScriptValue(const QScriptValue& v, bool& isVal } inline xColor xColor_convertFromScriptValue(const QScriptValue& v, bool& isValid) { - xColor newValue; + xColor newValue { 255, 255, 255 }; isValid = false; /// assume it can't be converted QScriptValue r = v.property("red"); QScriptValue g = v.property("green"); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index e5fa2983e2..3bca911a56 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -151,6 +151,12 @@ enum EntityPropertyList { PROP_ADDITIVE_BLENDING, + PROP_PARENT_ID, + PROP_PARENT_JOINT_INDEX, + + PROP_LOCAL_POSITION, // only used to convert values to and from scripts + PROP_LOCAL_ROTATION, // 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 bc57f2c72c..a0a6719521 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -64,8 +64,56 @@ void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) { } } +EntityItemProperties convertLocationToScriptSemantics(const EntityItemProperties& entitySideProperties) { + // 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. + EntityItemProperties scriptSideProperties = entitySideProperties; + scriptSideProperties.setLocalPosition(entitySideProperties.getPosition()); + scriptSideProperties.setLocalRotation(entitySideProperties.getRotation()); + + glm::vec3 worldPosition = SpatiallyNestable::localToWorld(entitySideProperties.getPosition(), + entitySideProperties.getParentID(), + entitySideProperties.getParentJointIndex()); + glm::quat worldRotation = SpatiallyNestable::localToWorld(entitySideProperties.getRotation(), + entitySideProperties.getParentID(), + entitySideProperties.getParentJointIndex()); + scriptSideProperties.setPosition(worldPosition); + scriptSideProperties.setRotation(worldRotation); + + return scriptSideProperties; +} + + +EntityItemProperties convertLocationFromScriptSemantics(const EntityItemProperties& scriptSideProperties) { + // 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; + + if (scriptSideProperties.localPositionChanged()) { + entitySideProperties.setPosition(scriptSideProperties.getLocalPosition()); + } else if (scriptSideProperties.positionChanged()) { + glm::vec3 localPosition = SpatiallyNestable::worldToLocal(entitySideProperties.getPosition(), + entitySideProperties.getParentID(), + entitySideProperties.getParentJointIndex()); + entitySideProperties.setPosition(localPosition); + } + + if (scriptSideProperties.localRotationChanged()) { + entitySideProperties.setRotation(scriptSideProperties.getLocalRotation()); + } else if (scriptSideProperties.rotationChanged()) { + glm::quat localRotation = SpatiallyNestable::worldToLocal(entitySideProperties.getRotation(), + entitySideProperties.getParentID(), + entitySideProperties.getParentJointIndex()); + entitySideProperties.setRotation(localRotation); + } + + return entitySideProperties; +} + + QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties) { - EntityItemProperties propertiesWithSimID = properties; + EntityItemProperties propertiesWithSimID = convertLocationFromScriptSemantics(properties); propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); EntityItemID id = EntityItemID(QUuid::createUuid()); @@ -111,6 +159,15 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit _entityTree->withReadLock([&] { EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(identity)); if (entity) { + if (desiredProperties.getHasProperty(PROP_POSITION) || + desiredProperties.getHasProperty(PROP_ROTATION) || + desiredProperties.getHasProperty(PROP_LOCAL_POSITION) || + desiredProperties.getHasProperty(PROP_LOCAL_ROTATION)) { + // 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); + } + results = entity->getProperties(desiredProperties); // TODO: improve sitting points and naturalDimensions in the future, @@ -130,10 +187,11 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit }); } - return results; + return convertLocationToScriptSemantics(results); } -QUuid EntityScriptingInterface::editEntity(QUuid id, EntityItemProperties properties) { +QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& scriptSideProperties) { + EntityItemProperties properties = scriptSideProperties; EntityItemID entityID(id); // If we have a local entity tree set, then also update it. if (!_entityTree) { @@ -143,10 +201,31 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, EntityItemProperties proper bool updatedEntity = false; _entityTree->withWriteLock([&] { + if (scriptSideProperties.parentDependentPropertyChanged() || + scriptSideProperties.parentIDChanged() || scriptSideProperties.parentJointIndexChanged()) { + // All of parentID, parentJointIndex, position, rotation are needed to make sense of any of them. + // If any of these changed, pull any missing properties from the entity. + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); + if (!entity) { + return; + } + if (!scriptSideProperties.parentIDChanged()) { + properties.setParentID(entity->getParentID()); + } + if (!scriptSideProperties.parentJointIndexChanged()) { + properties.setParentJointIndex(entity->getParentJointIndex()); + } + if (!scriptSideProperties.localPositionChanged() && !scriptSideProperties.positionChanged()) { + properties.setPosition(entity->getPosition()); + } + if (!scriptSideProperties.localRotationChanged() && !scriptSideProperties.rotationChanged()) { + properties.setRotation(entity->getOrientation()); + } + } + properties = convertLocationFromScriptSemantics(properties); updatedEntity = _entityTree->updateEntity(entityID, properties); }); - if (!updatedEntity) { return QUuid(); } @@ -167,8 +246,8 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, EntityItemProperties proper if (hasTerseUpdateChanges) { entity->getAllTerseUpdateProperties(properties); } - // TODO: if we knew that ONLY TerseUpdate properties have changed in properties AND the object - // is dynamic AND it is active in the physics simulation then we could chose to NOT queue an update + // TODO: if we knew that ONLY TerseUpdate properties have changed in properties AND the object + // is dynamic AND it is active in the physics simulation then we could chose to NOT queue an update // and instead let the physics simulation decide when to send a terse update. This would remove // the "slide-no-rotate" glitch (and typical a double-update) that we see during the "poke rolling // balls" test. However, even if we solve this problem we still need to provide a "slerp the visible @@ -333,15 +412,6 @@ bool EntityScriptingInterface::getDrawZoneBoundaries() const { return ZoneEntityItem::getDrawZoneBoundaries(); } -void EntityScriptingInterface::setSendPhysicsUpdates(bool value) { - EntityItem::setSendPhysicsUpdates(value); -} - -bool EntityScriptingInterface::getSendPhysicsUpdates() const { - return EntityItem::getSendPhysicsUpdates(); -} - - RayToEntityIntersectionResult::RayToEntityIntersectionResult() : intersects(false), accurate(true), // assume it's accurate @@ -548,16 +618,16 @@ bool EntityScriptingInterface::appendPoint(QUuid entityID, const glm::vec3& poin if (!entity) { qCDebug(entities) << "EntityScriptingInterface::setPoints no entity with ID" << entityID; } - + EntityTypes::EntityType entityType = entity->getType(); - + if (entityType == EntityTypes::Line) { return setPoints(entityID, [point](LineEntityItem& lineEntity) -> bool { return (LineEntityItem*)lineEntity.appendPoint(point); }); } - + return false; } @@ -684,82 +754,75 @@ QVariantMap EntityScriptingInterface::getActionArguments(const QUuid& entityID, return result; } -glm::vec3 EntityScriptingInterface::voxelCoordsToWorldCoords(const QUuid& entityID, glm::vec3 voxelCoords) { +EntityItemPointer EntityScriptingInterface::checkForTreeEntityAndTypeMatch(const QUuid& entityID, + EntityTypes::EntityType entityType) { if (!_entityTree) { - return glm::vec3(0.0f); + return EntityItemPointer(); } - + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); if (!entity) { - qCDebug(entities) << "EntityScriptingInterface::voxelCoordsToWorldCoords no entity with ID" << entityID; + qDebug() << "EntityScriptingInterface::checkForTreeEntityAndTypeMatch - no entity with ID" << entityID; + return entity; + } + + if (entityType != EntityTypes::Unknown && entity->getType() != entityType) { + return EntityItemPointer(); + } + + return entity; +} + +glm::vec3 EntityScriptingInterface::voxelCoordsToWorldCoords(const QUuid& entityID, glm::vec3 voxelCoords) { + if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::PolyVox)) { + auto polyVoxEntity = std::dynamic_pointer_cast(entity); + return polyVoxEntity->voxelCoordsToWorldCoords(voxelCoords); + } else { return glm::vec3(0.0f); } - - EntityTypes::EntityType entityType = entity->getType(); - if (entityType != EntityTypes::PolyVox) { - return glm::vec3(0.0f); - } - - auto polyVoxEntity = std::dynamic_pointer_cast(entity); - return polyVoxEntity->voxelCoordsToWorldCoords(voxelCoords); } glm::vec3 EntityScriptingInterface::worldCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 worldCoords) { - if (!_entityTree) { + if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::PolyVox)) { + auto polyVoxEntity = std::dynamic_pointer_cast(entity); + return polyVoxEntity->worldCoordsToVoxelCoords(worldCoords); + } else { return glm::vec3(0.0f); } - - EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); - if (!entity) { - qCDebug(entities) << "EntityScriptingInterface::worldCoordsToVoxelCoords no entity with ID" << entityID; - return glm::vec3(0.0f); - } - - EntityTypes::EntityType entityType = entity->getType(); - if (entityType != EntityTypes::PolyVox) { - return glm::vec3(0.0f); - } - - auto polyVoxEntity = std::dynamic_pointer_cast(entity); - return polyVoxEntity->worldCoordsToVoxelCoords(worldCoords); } glm::vec3 EntityScriptingInterface::voxelCoordsToLocalCoords(const QUuid& entityID, glm::vec3 voxelCoords) { - if (!_entityTree) { + if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::PolyVox)) { + auto polyVoxEntity = std::dynamic_pointer_cast(entity); + return polyVoxEntity->voxelCoordsToLocalCoords(voxelCoords); + } else { return glm::vec3(0.0f); } - - EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); - if (!entity) { - qCDebug(entities) << "EntityScriptingInterface::voxelCoordsToLocalCoords no entity with ID" << entityID; - return glm::vec3(0.0f); - } - - EntityTypes::EntityType entityType = entity->getType(); - if (entityType != EntityTypes::PolyVox) { - return glm::vec3(0.0f); - } - - auto polyVoxEntity = std::dynamic_pointer_cast(entity); - return polyVoxEntity->voxelCoordsToLocalCoords(voxelCoords); } glm::vec3 EntityScriptingInterface::localCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 localCoords) { - if (!_entityTree) { + if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::PolyVox)) { + auto polyVoxEntity = std::dynamic_pointer_cast(entity); + return polyVoxEntity->localCoordsToVoxelCoords(localCoords); + } else { return glm::vec3(0.0f); } - - EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); - if (!entity) { - qCDebug(entities) << "EntityScriptingInterface::localCoordsToVoxelCoords no entity with ID" << entityID; - return glm::vec3(0.0f); - } - - EntityTypes::EntityType entityType = entity->getType(); - if (entityType != EntityTypes::PolyVox) { - return glm::vec3(0.0f); - } - - auto polyVoxEntity = std::dynamic_pointer_cast(entity); - return polyVoxEntity->localCoordsToVoxelCoords(localCoords); +} + +glm::vec3 EntityScriptingInterface::getAbsoluteJointTranslationInObjectFrame(const QUuid& entityID, int jointIndex) { + if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::Model)) { + auto modelEntity = std::dynamic_pointer_cast(entity); + return modelEntity->getAbsoluteJointTranslationInObjectFrame(jointIndex); + } else { + return glm::vec3(0.0f); + } +} + +glm::quat EntityScriptingInterface::getAbsoluteJointRotationInObjectFrame(const QUuid& entityID, int jointIndex) { + if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::Model)) { + auto modelEntity = std::dynamic_pointer_cast(entity); + return modelEntity->getAbsoluteJointRotationInObjectFrame(jointIndex); + } else { + return glm::quat(); + } } diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 8a4414a596..f745b6b644 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -86,7 +86,7 @@ public slots: /// edits a model updating only the included properties, will return the identified EntityItemID in case of /// successful edit, if the input entityID is for an unknown model this function will have no effect - Q_INVOKABLE QUuid editEntity(QUuid entityID, EntityItemProperties properties); + Q_INVOKABLE QUuid editEntity(QUuid entityID, const EntityItemProperties& properties); /// deletes a model Q_INVOKABLE void deleteEntity(QUuid entityID); @@ -127,9 +127,6 @@ public slots: Q_INVOKABLE void setDrawZoneBoundaries(bool value); Q_INVOKABLE bool getDrawZoneBoundaries() const; - Q_INVOKABLE void setSendPhysicsUpdates(bool value); - Q_INVOKABLE bool getSendPhysicsUpdates() const; - Q_INVOKABLE bool setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value); Q_INVOKABLE bool setVoxel(QUuid entityID, const glm::vec3& position, int value); Q_INVOKABLE bool setAllVoxels(QUuid entityID, int value); @@ -152,6 +149,9 @@ public slots: Q_INVOKABLE glm::vec3 voxelCoordsToLocalCoords(const QUuid& entityID, glm::vec3 voxelCoords); Q_INVOKABLE glm::vec3 localCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 localCoords); + Q_INVOKABLE glm::vec3 getAbsoluteJointTranslationInObjectFrame(const QUuid& entityID, int jointIndex); + Q_INVOKABLE glm::quat getAbsoluteJointRotationInObjectFrame(const QUuid& entityID, int jointIndex); + signals: void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); @@ -182,6 +182,9 @@ private: bool setVoxels(QUuid entityID, std::function actor); bool setPoints(QUuid entityID, std::function actor); void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties); + + EntityItemPointer checkForTreeEntityAndTypeMatch(const QUuid& entityID, + EntityTypes::EntityType entityType = EntityTypes::Unknown); /// actually does the work of finding the ray intersection, can be called in locking mode or tryLock mode diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 9eac14e4b4..abad1e5cd1 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -24,6 +24,7 @@ #include "EntitiesLogging.h" #include "RecurseOctreeToMapOperator.h" #include "LogHandler.h" +#include "RemapIDOperator.h" static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50; @@ -44,12 +45,7 @@ void EntityTree::createRootElement() { } OctreeElementPointer EntityTree::createNewElement(unsigned char* octalCode) { - EntityTreeElementPointer newElement = EntityTreeElementPointer(new EntityTreeElement(octalCode), - // see comment int EntityTreeElement::createNewElement - [=](EntityTreeElement* dyingElement) { - EntityTreeElementPointer tmpSharedPointer(dyingElement); - dyingElement->notifyDeleteHooks(); - }); + auto newElement = EntityTreeElementPointer(new EntityTreeElement(octalCode)); newElement->setTree(std::static_pointer_cast(shared_from_this())); return std::static_pointer_cast(newElement); } @@ -145,8 +141,11 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI if (!wantsLocked) { EntityItemProperties tempProperties; tempProperties.setLocked(wantsLocked); - UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, tempProperties); + + BoundingBoxRelatedProperties newBBRelProperties(entity, tempProperties); + UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, newBBRelProperties); recurseTreeWithOperator(&theOperator); + entity->setProperties(tempProperties); _isDirty = true; } } @@ -211,8 +210,34 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI quint64 entityScriptTimestampBefore = entity->getScriptTimestamp(); QString collisionSoundURLBefore = entity->getCollisionSoundURL(); uint32_t preFlags = entity->getDirtyFlags(); - UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, properties); + + BoundingBoxRelatedProperties newBBRelProperties(entity, properties); + UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, newBBRelProperties); recurseTreeWithOperator(&theOperator); + entity->setProperties(properties); + + // if the entity has children, run UpdateEntityOperator on them. If the children have children, recurse + QQueue toProcess; + foreach (SpatiallyNestablePointer child, entity->getChildren()) { + if (child && child->getNestableType() == NestableTypes::Entity) { + toProcess.enqueue(child); + } + } + + while (!toProcess.empty()) { + EntityItemPointer childEntity = std::static_pointer_cast(toProcess.dequeue()); + BoundingBoxRelatedProperties newChildBBRelProperties(childEntity); + UpdateEntityOperator theChildOperator(getThisPointer(), + childEntity->getElement(), + childEntity, newChildBBRelProperties); + recurseTreeWithOperator(&theChildOperator); + foreach (SpatiallyNestablePointer childChild, childEntity->getChildren()) { + if (childChild && childChild->getNestableType() == NestableTypes::Entity) { + toProcess.enqueue(childChild); + } + } + } + _isDirty = true; uint32_t newFlags = entity->getDirtyFlags() & ~preFlags; @@ -716,9 +741,17 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList= 0) { + quint16 value = properties.getParentJointIndex(); + changedProperties[index] = QString("parentJointIndex:") + QString::number((int)value); + } + } } -int EntityTree::processEditPacketData(NLPacket& packet, const unsigned char* editData, int maxLength, +int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode) { if (!getIsServer()) { @@ -728,7 +761,7 @@ int EntityTree::processEditPacketData(NLPacket& packet, const unsigned char* edi int processedBytes = 0; // we handle these types of "edit" packets - switch (packet.getType()) { + switch (message.getType()) { case PacketType::EntityErase: { QByteArray dataByteArray = QByteArray::fromRawData(reinterpret_cast(editData), maxLength); processedBytes = processEraseMessageDetails(dataByteArray, senderNode); @@ -760,7 +793,7 @@ int EntityTree::processEditPacketData(NLPacket& packet, const unsigned char* edi startLookup = usecTimestampNow(); EntityItemPointer existingEntity = findEntityByEntityItemID(entityItemID); endLookup = usecTimestampNow(); - if (existingEntity && packet.getType() == PacketType::EntityEdit) { + if (existingEntity && message.getType() == PacketType::EntityEdit) { // if the EntityItem exists, then update it startLogging = usecTimestampNow(); if (wantEditLogging()) { @@ -780,7 +813,7 @@ int EntityTree::processEditPacketData(NLPacket& packet, const unsigned char* edi existingEntity->markAsChangedOnServer(); endUpdate = usecTimestampNow(); _totalUpdates++; - } else if (packet.getType() == PacketType::EntityAdd) { + } else if (message.getType() == PacketType::EntityAdd) { if (senderNode->getCanRez()) { // this is a new entity... assign a new entityID properties.setCreated(properties.getLastEdited()); @@ -813,7 +846,7 @@ int EntityTree::processEditPacketData(NLPacket& packet, const unsigned char* edi } else { static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("^Edit failed.*"); - qCDebug(entities) << "Edit failed. [" << packet.getType() <<"] " << + qCDebug(entities) << "Edit failed. [" << message.getType() <<"] " << "entity id:" << entityItemID << "existingEntity pointer:" << existingEntity.get(); } @@ -955,27 +988,27 @@ void EntityTree::forgetEntitiesDeletedBefore(quint64 sinceTime) { // TODO: consider consolidating processEraseMessageDetails() and processEraseMessage() -int EntityTree::processEraseMessage(NLPacket& packet, const SharedNodePointer& sourceNode) { +int EntityTree::processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode) { #ifdef EXTRA_ERASE_DEBUGGING qDebug() << "EntityTree::processEraseMessage()"; #endif withWriteLock([&] { - packet.seek(sizeof(OCTREE_PACKET_FLAGS) + sizeof(OCTREE_PACKET_SEQUENCE) + sizeof(OCTREE_PACKET_SENT_TIME)); + message.seek(sizeof(OCTREE_PACKET_FLAGS) + sizeof(OCTREE_PACKET_SEQUENCE) + sizeof(OCTREE_PACKET_SENT_TIME)); uint16_t numberOfIDs = 0; // placeholder for now - packet.readPrimitive(&numberOfIDs); + message.readPrimitive(&numberOfIDs); if (numberOfIDs > 0) { QSet entityItemIDsToDelete; for (size_t i = 0; i < numberOfIDs; i++) { - if (NUM_BYTES_RFC4122_UUID > packet.bytesLeftToRead()) { + if (NUM_BYTES_RFC4122_UUID > message.getBytesLeftToRead()) { qCDebug(entities) << "EntityTree::processEraseMessage().... bailing because not enough bytes in buffer"; break; // bail to prevent buffer overflow } - QUuid entityID = QUuid::fromRfc4122(packet.readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + QUuid entityID = QUuid::fromRfc4122(message.readWithoutCopy(NUM_BYTES_RFC4122_UUID)); #ifdef EXTRA_ERASE_DEBUGGING qDebug() << " ---- EntityTree::processEraseMessage() contained ID:" << entityID; #endif @@ -991,7 +1024,7 @@ int EntityTree::processEraseMessage(NLPacket& packet, const SharedNodePointer& s deleteEntities(entityItemIDsToDelete, true, true); } }); - return packet.pos(); + return message.getPosition(); } // This version skips over the header @@ -1162,6 +1195,11 @@ bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extra return true; } +void EntityTree::remapIDs() { + RemapIDOperator theOperator; + recurseTreeWithOperator(&theOperator); +} + bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues) { if (! entityDescription.contains("Entities")) { entityDescription["Entities"] = QVariantList(); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index d1e0462f64..064c106b91 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -78,8 +78,8 @@ public: { return thisVersion >= VERSION_ENTITIES_USE_METERS_AND_RADIANS; } virtual bool handlesEditPacketType(PacketType packetType) const; void fixupTerseEditLogging(EntityItemProperties& properties, QList& changedProperties); - virtual int processEditPacketData(NLPacket& packet, const unsigned char* editData, int maxLength, - const SharedNodePointer& senderNode); + virtual int processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, + const SharedNodePointer& senderNode) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& node, float& distance, BoxFace& face, glm::vec3& surfaceNormal, @@ -162,7 +162,7 @@ public: void forgetEntitiesDeletedBefore(quint64 sinceTime); - int processEraseMessage(NLPacket& packet, const SharedNodePointer& sourceNode); + int processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode); int processEraseMessageDetails(const QByteArray& buffer, const SharedNodePointer& sourceNode); EntityItemFBXService* getFBXService() const { return _fbxService; } @@ -196,6 +196,8 @@ public: bool wantTerseEditLogging() const { return _wantTerseEditLogging; } void setWantTerseEditLogging(bool value) { _wantTerseEditLogging = value; } + void remapIDs(); + bool writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues); bool readFromMap(QVariantMap& entityDescription); diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 02552ef488..ff2e97e8fe 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -28,29 +28,8 @@ EntityTreeElement::~EntityTreeElement() { _octreeMemoryUsage -= sizeof(EntityTreeElement); } -// This will be called primarily on addChildAt(), which means we're adding a child of our -// own type to our own tree. This means we should initialize that child with any tree and type -// specific settings that our children must have. OctreeElementPointer EntityTreeElement::createNewElement(unsigned char* octalCode) { - EntityTreeElementPointer newChild = - EntityTreeElementPointer(new EntityTreeElement(octalCode), - // This is a little bit horrible, but I haven't found a better way. The OctreeElement - // destructor used to call notifyDeleteHooks(), which calls zero or more of - // OctreeElementDeleteHook::elementDeleted - // which (now) expects an OctreeElementPointer argument. The destructor doesn't have - // access to the shared pointer (which has had its reference count drop to zero, - // or the destructor wouldn't have been called). The destructor also can't - // make a new shared pointer -- shared_from_this() is forbidden in a destructor, and - // using OctreeElementPointer(this) also fails. So, I've installed a custom deleter: - [=](EntityTreeElement* dyingElement) { - // make a new shared pointer with a reference count of 1 (and no custom deleter) - EntityTreeElementPointer tmpSharedPointer(dyingElement); - // call notifyDeleteHooks which will use shared_from_this() to get this same - // shared pointer, for use with the elementDeleted calls. - dyingElement->notifyDeleteHooks(); - // And now tmpSharedPointer's reference count drops to zero and the - // normal destructors are called. - }); + auto newChild = EntityTreeElementPointer(new EntityTreeElement(octalCode)); newChild->setTree(_myTree); return newChild; } @@ -174,7 +153,7 @@ void EntityTreeElement::updateEncodedData(int childIndex, AppendState childAppen -void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params, OctreeElementBag* bag) const { +void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) const { const bool wantDebug = false; if (wantDebug) { diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index 57e0bea696..d8a182156d 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -122,7 +122,7 @@ public: virtual bool shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const; virtual bool shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const; virtual void updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const; - virtual void elementEncodeComplete(EncodeBitstreamParams& params, OctreeElementBag* bag) const; + virtual void elementEncodeComplete(EncodeBitstreamParams& params) const; bool alreadyFullyEncoded(EncodeBitstreamParams& params) const; diff --git a/libraries/entities/src/EntityTreeHeadlessViewer.cpp b/libraries/entities/src/EntityTreeHeadlessViewer.cpp index bdb35f8547..149f92adad 100644 --- a/libraries/entities/src/EntityTreeHeadlessViewer.cpp +++ b/libraries/entities/src/EntityTreeHeadlessViewer.cpp @@ -39,6 +39,6 @@ void EntityTreeHeadlessViewer::update() { } } -void EntityTreeHeadlessViewer::processEraseMessage(NLPacket& packet, const SharedNodePointer& sourceNode) { - std::static_pointer_cast(_tree)->processEraseMessage(packet, sourceNode); +void EntityTreeHeadlessViewer::processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode) { + std::static_pointer_cast(_tree)->processEraseMessage(message, sourceNode); } diff --git a/libraries/entities/src/EntityTreeHeadlessViewer.h b/libraries/entities/src/EntityTreeHeadlessViewer.h index 9964c47b49..40e428b655 100644 --- a/libraries/entities/src/EntityTreeHeadlessViewer.h +++ b/libraries/entities/src/EntityTreeHeadlessViewer.h @@ -38,7 +38,7 @@ public: EntityTreePointer getTree() { return std::static_pointer_cast(_tree); } - void processEraseMessage(NLPacket& packet, const SharedNodePointer& sourceNode); + void processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode); virtual void init(); diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp index fd3f674c5e..ac56fc9c1f 100644 --- a/libraries/entities/src/LightEntityItem.cpp +++ b/libraries/entities/src/LightEntityItem.cpp @@ -24,23 +24,20 @@ bool LightEntityItem::_lightsArePickable = false; EntityItemPointer LightEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - EntityItemPointer result { new LightEntityItem(entityID, properties) }; - return result; + EntityItemPointer entity { new LightEntityItem(entityID) }; + entity->setProperties(properties); + return entity; } // our non-pure virtual subclass for now... -LightEntityItem::LightEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : - EntityItem(entityItemID) -{ +LightEntityItem::LightEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { _type = EntityTypes::Light; - + // default property values _color[RED_INDEX] = _color[GREEN_INDEX] = _color[BLUE_INDEX] = 0; _intensity = 1.0f; _exponent = 0.0f; _cutoff = PI; - - setProperties(properties); } void LightEntityItem::setDimensions(const glm::vec3& value) { diff --git a/libraries/entities/src/LightEntityItem.h b/libraries/entities/src/LightEntityItem.h index 9f8d340852..103c462809 100644 --- a/libraries/entities/src/LightEntityItem.h +++ b/libraries/entities/src/LightEntityItem.h @@ -18,7 +18,7 @@ class LightEntityItem : public EntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - LightEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + LightEntityItem(const EntityItemID& entityItemID); ALLOW_INSTANTIATION // This class can be instantiated diff --git a/libraries/entities/src/LineEntityItem.cpp b/libraries/entities/src/LineEntityItem.cpp index e03d3a7a96..d48780845f 100644 --- a/libraries/entities/src/LineEntityItem.cpp +++ b/libraries/entities/src/LineEntityItem.cpp @@ -26,20 +26,18 @@ const int LineEntityItem::MAX_POINTS_PER_LINE = 70; EntityItemPointer LineEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - EntityItemPointer result { new LineEntityItem(entityID, properties) }; - return result; + EntityItemPointer entity { new LineEntityItem(entityID) }; + entity->setProperties(properties); + return entity; } -LineEntityItem::LineEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : - EntityItem(entityItemID) , +LineEntityItem::LineEntityItem(const EntityItemID& entityItemID) : + EntityItem(entityItemID), _lineWidth(DEFAULT_LINE_WIDTH), _pointsChanged(true), _points(QVector(0)) { _type = EntityTypes::Line; - setProperties(properties); - - } EntityItemProperties LineEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { diff --git a/libraries/entities/src/LineEntityItem.h b/libraries/entities/src/LineEntityItem.h index b20587637f..4d63562cf7 100644 --- a/libraries/entities/src/LineEntityItem.h +++ b/libraries/entities/src/LineEntityItem.h @@ -18,7 +18,7 @@ class LineEntityItem : public EntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - LineEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + LineEntityItem(const EntityItemID& entityItemID); ALLOW_INSTANTIATION // This class can be instantiated diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index af9541ceca..b0b0e7c76a 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -25,17 +25,17 @@ const QString ModelEntityItem::DEFAULT_MODEL_URL = QString(""); const QString ModelEntityItem::DEFAULT_COMPOUND_SHAPE_URL = QString(""); EntityItemPointer ModelEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - return std::make_shared(entityID, properties); + EntityItemPointer entity { new ModelEntityItem(entityID) }; + entity->setProperties(properties); + return entity; } -ModelEntityItem::ModelEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : - EntityItem(entityItemID) +ModelEntityItem::ModelEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { _animationProperties.associateWithAnimationLoop(&_animationLoop); _animationLoop.setResetOnRunning(false); _type = EntityTypes::Model; - setProperties(properties); _jointMappingCompleted = false; _lastKnownCurrentFrame = -1; _color[0] = _color[1] = _color[2] = 0; diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index fb41ac4b77..cbd54f7168 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -21,7 +21,7 @@ class ModelEntityItem : public EntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - ModelEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + ModelEntityItem(const EntityItemID& entityItemID); ALLOW_INSTANTIATION // This class can be instantiated @@ -121,6 +121,9 @@ public: virtual bool shouldBePhysical() const; static void cleanupLoadedAnimations(); + + virtual glm::vec3 getJointPosition(int jointIndex) const { return glm::vec3(); } + virtual glm::quat getJointRotation(int jointIndex) const { return glm::quat(); } private: void setAnimationSettings(const QString& value); // only called for old bitstream format diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 06fcdb495c..33e7d1ca90 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -98,11 +98,13 @@ const bool ParticleEffectEntityItem::DEFAULT_ADDITIVE_BLENDING = false; EntityItemPointer ParticleEffectEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - return std::make_shared(entityID, properties); + EntityItemPointer entity { new ParticleEffectEntityItem(entityID) }; + entity->setProperties(properties); + return entity; } // our non-pure virtual subclass for now... -ParticleEffectEntityItem::ParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : +ParticleEffectEntityItem::ParticleEffectEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID), _lastSimulated(usecTimestampNow()), _particleLifetimes(DEFAULT_MAX_PARTICLES, 0.0f), @@ -128,7 +130,6 @@ ParticleEffectEntityItem::ParticleEffectEntityItem(const EntityItemID& entityIte _type = EntityTypes::ParticleEffect; setColor(DEFAULT_COLOR); - setProperties(properties); } ParticleEffectEntityItem::~ParticleEffectEntityItem() { diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index e3c5cd895a..d6dd257f7e 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -20,7 +20,7 @@ public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - ParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + ParticleEffectEntityItem(const EntityItemID& entityItemID); virtual ~ParticleEffectEntityItem(); ALLOW_INSTANTIATION // This class can be instantiated diff --git a/libraries/entities/src/PolyLineEntityItem.cpp b/libraries/entities/src/PolyLineEntityItem.cpp index 012ec3ca4a..e7281d8157 100644 --- a/libraries/entities/src/PolyLineEntityItem.cpp +++ b/libraries/entities/src/PolyLineEntityItem.cpp @@ -26,11 +26,12 @@ const int PolyLineEntityItem::MAX_POINTS_PER_LINE = 70; EntityItemPointer PolyLineEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - EntityItemPointer result{ new PolyLineEntityItem(entityID, properties) }; - return result; + EntityItemPointer entity{ new PolyLineEntityItem(entityID) }; + entity->setProperties(properties); + return entity; } -PolyLineEntityItem::PolyLineEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : +PolyLineEntityItem::PolyLineEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID), _lineWidth(DEFAULT_LINE_WIDTH), _pointsChanged(true), @@ -42,8 +43,6 @@ _strokeWidths(QVector(0.0f)), _textures("") { _type = EntityTypes::PolyLine; - _created = properties.getCreated(); - setProperties(properties); } EntityItemProperties PolyLineEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index 4da52f3f21..dfe062bbdb 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -18,7 +18,7 @@ class PolyLineEntityItem : public EntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - PolyLineEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + PolyLineEntityItem(const EntityItemID& entityItemID); ALLOW_INSTANTIATION // This class can be instantiated diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp index 403d0bb1bc..9b85938a78 100644 --- a/libraries/entities/src/PolyVoxEntityItem.cpp +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -32,7 +32,9 @@ const QString PolyVoxEntityItem::DEFAULT_Y_TEXTURE_URL = QString(""); const QString PolyVoxEntityItem::DEFAULT_Z_TEXTURE_URL = QString(""); EntityItemPointer PolyVoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - return std::make_shared(entityID, properties); + EntityItemPointer entity { new PolyVoxEntityItem(entityID) }; + entity->setProperties(properties); + return entity; } QByteArray PolyVoxEntityItem::makeEmptyVoxelData(quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize) { @@ -49,7 +51,7 @@ QByteArray PolyVoxEntityItem::makeEmptyVoxelData(quint16 voxelXSize, quint16 vox return newVoxelData; } -PolyVoxEntityItem::PolyVoxEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : +PolyVoxEntityItem::PolyVoxEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID), _voxelVolumeSize(PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE), _voxelData(PolyVoxEntityItem::DEFAULT_VOXEL_DATA), @@ -59,7 +61,6 @@ PolyVoxEntityItem::PolyVoxEntityItem(const EntityItemID& entityItemID, const Ent _yTextureURL(PolyVoxEntityItem::DEFAULT_Y_TEXTURE_URL), _zTextureURL(PolyVoxEntityItem::DEFAULT_Z_TEXTURE_URL) { _type = EntityTypes::PolyVox; - setProperties(properties); } void PolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 9070ad250f..13e541d298 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -18,7 +18,7 @@ class PolyVoxEntityItem : public EntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - PolyVoxEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + PolyVoxEntityItem(const EntityItemID& entityItemID); ALLOW_INSTANTIATION // This class can be instantiated diff --git a/libraries/entities/src/RemapIDOperator.cpp b/libraries/entities/src/RemapIDOperator.cpp new file mode 100644 index 0000000000..eee6e49a1c --- /dev/null +++ b/libraries/entities/src/RemapIDOperator.cpp @@ -0,0 +1,33 @@ +// +// RemapIDOperator.cpp +// libraries/entities/src +// +// Created by Seth Alves on 2015-12-6. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#include "EntityTree.h" +#include "RemapIDOperator.h" + +QUuid RemapIDOperator::remap(const QUuid& oldID) { + if (oldID.isNull()) { + return oldID; + } + if (!_oldToNew.contains(oldID)) { + _oldToNew[oldID] = QUuid::createUuid(); + } + return _oldToNew[oldID]; +} + +bool RemapIDOperator::postRecursion(OctreeElementPointer element) { + EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); + entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) { + entityItem->setID(remap(entityItem->getID())); + entityItem->setParentID(remap(entityItem->getParentID())); + }); + return true; +} diff --git a/libraries/entities/src/RemapIDOperator.h b/libraries/entities/src/RemapIDOperator.h new file mode 100644 index 0000000000..439aec28fc --- /dev/null +++ b/libraries/entities/src/RemapIDOperator.h @@ -0,0 +1,30 @@ +// +// RemapIDOperator.h +// libraries/entities/src +// +// Created by Seth Alves on 2015-12-6. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_RemapIDOperator_h +#define hifi_RemapIDOperator_h + +#include "Octree.h" + +// this will change all the IDs in an EntityTree. Parent/Child relationships are maintained. + +class RemapIDOperator : public RecurseOctreeOperator { +public: + RemapIDOperator() : RecurseOctreeOperator() {} + ~RemapIDOperator() {} + virtual bool preRecursion(OctreeElementPointer element) { return true; } + virtual bool postRecursion(OctreeElementPointer element); +private: + QUuid remap(const QUuid& oldID); + QHash _oldToNew; +}; + +#endif // hifi_RemapIDOperator_h diff --git a/libraries/entities/src/SphereEntityItem.cpp b/libraries/entities/src/SphereEntityItem.cpp index 26177f89de..841b70aa56 100644 --- a/libraries/entities/src/SphereEntityItem.cpp +++ b/libraries/entities/src/SphereEntityItem.cpp @@ -24,16 +24,14 @@ #include "SphereEntityItem.h" EntityItemPointer SphereEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - EntityItemPointer result { new SphereEntityItem(entityID, properties) }; - return result; + EntityItemPointer entity { new SphereEntityItem(entityID) }; + entity->setProperties(properties); + return entity; } // our non-pure virtual subclass for now... -SphereEntityItem::SphereEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : - EntityItem(entityItemID) -{ +SphereEntityItem::SphereEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { _type = EntityTypes::Sphere; - setProperties(properties); _volumeMultiplier *= PI / 6.0f; } diff --git a/libraries/entities/src/SphereEntityItem.h b/libraries/entities/src/SphereEntityItem.h index e1e31d4839..941d5a167c 100644 --- a/libraries/entities/src/SphereEntityItem.h +++ b/libraries/entities/src/SphereEntityItem.h @@ -18,7 +18,7 @@ class SphereEntityItem : public EntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - SphereEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + SphereEntityItem(const EntityItemID& entityItemID); ALLOW_INSTANTIATION // This class can be instantiated diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 49d3c13d80..893329d1ce 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -30,14 +30,13 @@ const xColor TextEntityItem::DEFAULT_BACKGROUND_COLOR = { 0, 0, 0}; const bool TextEntityItem::DEFAULT_FACE_CAMERA = false; EntityItemPointer TextEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - return std::make_shared(entityID, properties); + EntityItemPointer entity { new TextEntityItem(entityID) }; + entity->setProperties(properties); + return entity; } -TextEntityItem::TextEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : - EntityItem(entityItemID) -{ +TextEntityItem::TextEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { _type = EntityTypes::Text; - setProperties(properties); } const float TEXT_ENTITY_ITEM_FIXED_DEPTH = 0.01f; diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index d205e9d01e..1caceee085 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -18,7 +18,7 @@ class TextEntityItem : public EntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - TextEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + TextEntityItem(const EntityItemID& entityItemID); ALLOW_INSTANTIATION // This class can be instantiated diff --git a/libraries/entities/src/UpdateEntityOperator.cpp b/libraries/entities/src/UpdateEntityOperator.cpp index 617663f48a..4acc386333 100644 --- a/libraries/entities/src/UpdateEntityOperator.cpp +++ b/libraries/entities/src/UpdateEntityOperator.cpp @@ -14,12 +14,12 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTreePointer tree, EntityTreeElementPointer containingElement, EntityItemPointer existingEntity, - const EntityItemProperties& properties) : + const BoundingBoxRelatedProperties& newProperties) : _tree(tree), _existingEntity(existingEntity), _containingElement(containingElement), _containingElementCube(containingElement->getAACube()), - _properties(properties), + _newProperties(newProperties), _entityItemID(existingEntity->getEntityItemID()), _foundOld(false), _foundNew(false), @@ -44,83 +44,32 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTreePointer tree, _oldEntityCube = _existingEntity->getMaximumAACube(); _oldEntityBox = _oldEntityCube.clamp((float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); // clamp to domain bounds - // If the old properties doesn't contain the properties required to calculate a bounding box, - // get them from the existing entity. Registration point is required to correctly calculate - // the bounding box. - if (!_properties.registrationPointChanged()) { - _properties.setRegistrationPoint(_existingEntity->getRegistrationPoint()); - } - - // If the new properties has position OR dimension changes, but not both, we need to - // get the old property value and set it in our properties in order for our bounds - // calculations to work. - if (_properties.containsPositionChange() && !_properties.containsDimensionsChange()) { - glm::vec3 oldDimensions= _existingEntity->getDimensions(); - _properties.setDimensions(oldDimensions); - - if (_wantDebug) { - qCDebug(entities) << " ** setting properties dimensions - had position change, no dimension change **"; - } - - } - if (!_properties.containsPositionChange() && _properties.containsDimensionsChange()) { - glm::vec3 oldPosition= _existingEntity->getPosition(); - _properties.setPosition(oldPosition); - - if (_wantDebug) { - qCDebug(entities) << " ** setting properties position - had dimensions change, no position change **"; - } - } - - // If our new properties don't have bounds details (no change to position, etc) or if this containing element would - // be the best fit for our new properties, then just do the new portion of the store pass, since the change path will + // If our new properties don't have bounds details (no change to position, etc) or if this containing element would + // be the best fit for our new properties, then just do the new portion of the store pass, since the change path will // be the same for both parts of the update - bool oldElementBestFit = _containingElement->bestFitBounds(_properties); - - // if we don't have bounds properties, then use our old clamped box to determine best fit - if (!_properties.containsBoundsProperties()) { - oldElementBestFit = _containingElement->bestFitBounds(_oldEntityBox); + bool oldElementBestFit = _containingElement->bestFitBounds(newProperties.getMaximumAACube()); - if (_wantDebug) { - qCDebug(entities) << " ** old Element best fit - no dimensions change, no position change **"; - } - - } - // For some reason we've seen a case where the original containing element isn't a best fit for the old properties // in this case we want to move it, even if the properties haven't changed. - if (!_properties.containsBoundsProperties() && !oldElementBestFit) { + if (!oldElementBestFit) { _newEntityCube = _oldEntityCube; _removeOld = true; // our properties are going to move us, so remember this for later processing if (_wantDebug) { qCDebug(entities) << " **** UNUSUAL CASE **** no changes, but not best fit... consider it a move.... **"; } - - - } else if (!_properties.containsBoundsProperties() || oldElementBestFit) { + } else { _foundOld = true; _newEntityCube = _oldEntityCube; _dontMove = true; if (_wantDebug) { - qCDebug(entities) << " **** TYPICAL NO MOVE CASE ****"; - qCDebug(entities) << " _properties.containsBoundsProperties():" << _properties.containsBoundsProperties(); - qCDebug(entities) << " oldElementBestFit:" << oldElementBestFit; - } - - } else { - _newEntityCube = _properties.getMaximumAACube(); - _removeOld = true; // our properties are going to move us, so remember this for later processing - - if (_wantDebug) { - qCDebug(entities) << " **** TYPICAL MOVE CASE ****"; + qCDebug(entities) << " **** TYPICAL NO MOVE CASE **** oldElementBestFit:" << oldElementBestFit; } } _newEntityBox = _newEntityCube.clamp((float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); // clamp to domain bounds - if (_wantDebug) { qCDebug(entities) << " _entityItemID:" << _entityItemID; qCDebug(entities) << " _containingElementCube:" << _containingElementCube; @@ -176,7 +125,7 @@ bool UpdateEntityOperator::subTreeContainsNewEntity(OctreeElementPointer element bool UpdateEntityOperator::preRecursion(OctreeElementPointer element) { EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); - + // In Pre-recursion, we're generally deciding whether or not we want to recurse this // path of the tree. For this operation, we want to recurse the branch of the tree if // and of the following are true: @@ -185,7 +134,7 @@ bool UpdateEntityOperator::preRecursion(OctreeElementPointer element) { // // Note: it's often the case that the branch in question contains both the old entity // and the new entity. - + bool keepSearching = false; // assume we don't need to search any more bool subtreeContainsOld = subTreeContainsOldEntity(element); @@ -257,7 +206,8 @@ bool UpdateEntityOperator::preRecursion(OctreeElementPointer element) { qCDebug(entities) << " NEW TREE CASE...."; qCDebug(entities) << " entityTreeElement=" << entityTreeElement.get(); qCDebug(entities) << " _containingElement=" << _containingElement.get(); - qCDebug(entities) << " entityTreeElement->bestFitBounds(_newEntityBox)=" << entityTreeElement->bestFitBounds(_newEntityBox); + qCDebug(entities) << " entityTreeElement->bestFitBounds(_newEntityBox)=" + << entityTreeElement->bestFitBounds(_newEntityBox); } // If this element is the best fit for the new entity properties, then add/or update it @@ -270,16 +220,9 @@ bool UpdateEntityOperator::preRecursion(OctreeElementPointer element) { EntityTreeElementPointer oldElement = _existingEntity->getElement(); // if we are the existing containing element, then we can just do the update of the entity properties if (entityTreeElement == oldElement) { - if (_wantDebug) { qCDebug(entities) << " *** This is the same OLD ELEMENT ***"; } - - // set the entity properties and mark our element as changed. - _existingEntity->setProperties(_properties); - if (_wantDebug) { - qCDebug(entities) << " *** set properties ***"; - } } else { // otherwise, this is an add case. if (oldElement) { @@ -290,11 +233,6 @@ bool UpdateEntityOperator::preRecursion(OctreeElementPointer element) { } entityTreeElement->addEntityItem(_existingEntity); _tree->setContainingElement(_entityItemID, entityTreeElement); - - _existingEntity->setProperties(_properties); // still need to update the properties! - if (_wantDebug) { - qCDebug(entities) << " *** ADDING ENTITY to ELEMENT and MAP and SETTING PROPERTIES ***"; - } } _foundNew = true; // we found the new element _removeOld = false; // and it has already been removed from the old @@ -308,7 +246,6 @@ bool UpdateEntityOperator::preRecursion(OctreeElementPointer element) { qCDebug(entities) << "--------------------------------------------------"; } - return keepSearching; // if we haven't yet found it, keep looking } @@ -329,9 +266,9 @@ bool UpdateEntityOperator::postRecursion(OctreeElementPointer element) { } // It's not OK to prune if we have the potential of deleting the original containig element. - // because if we prune the containing element then new might end up reallocating the same memory later + // because if we prune the containing element then new might end up reallocating the same memory later // and that will confuse our logic. - // + // // it's ok to prune if: // 1) we're not removing the old // 2) we are removing the old, but this subtree doesn't contain the old @@ -340,17 +277,17 @@ bool UpdateEntityOperator::postRecursion(OctreeElementPointer element) { EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); entityTreeElement->pruneChildren(); // take this opportunity to prune any empty leaves } - + return keepSearching; // if we haven't yet found it, keep looking } -OctreeElementPointer UpdateEntityOperator::possiblyCreateChildAt(OctreeElementPointer element, int childIndex) { +OctreeElementPointer UpdateEntityOperator::possiblyCreateChildAt(OctreeElementPointer element, int childIndex) { // If we're getting called, it's because there was no child element at this index while recursing. // We only care if this happens while still searching for the new entity location. - // Check to see if + // Check to see if if (!_foundNew) { float childElementScale = element->getScale() / 2.0f; // all of our children will be half our scale - + // Note: because the entity's bounds might have been clamped to the domain. We want to check if the // bounds of the clamped box would fit in our child elements. It may be the case that the actual // bounds of the element would hang outside of the child elements cells. @@ -365,5 +302,5 @@ OctreeElementPointer UpdateEntityOperator::possiblyCreateChildAt(OctreeElementPo } } } - return NULL; + return NULL; } diff --git a/libraries/entities/src/UpdateEntityOperator.h b/libraries/entities/src/UpdateEntityOperator.h index 46322997f7..aac442d415 100644 --- a/libraries/entities/src/UpdateEntityOperator.h +++ b/libraries/entities/src/UpdateEntityOperator.h @@ -12,6 +12,7 @@ #ifndef hifi_UpdateEntityOperator_h #define hifi_UpdateEntityOperator_h +#include "BoundingBoxRelatedProperties.h" #include "EntitiesLogging.h" #include "EntityItem.h" #include "EntityItemProperties.h" @@ -21,7 +22,8 @@ class UpdateEntityOperator : public RecurseOctreeOperator { public: UpdateEntityOperator(EntityTreePointer tree, EntityTreeElementPointer containingElement, - EntityItemPointer existingEntity, const EntityItemProperties& properties); + EntityItemPointer existingEntity, const BoundingBoxRelatedProperties& newProperties); + ~UpdateEntityOperator(); virtual bool preRecursion(OctreeElementPointer element); @@ -32,7 +34,7 @@ private: EntityItemPointer _existingEntity; EntityTreeElementPointer _containingElement; AACube _containingElementCube; // we temporarily store our cube here in case we need to delete the containing element - EntityItemProperties _properties; + BoundingBoxRelatedProperties _newProperties; EntityItemID _entityItemID; bool _foundOld; bool _foundNew; diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index 56f8357c8f..5f113f1de4 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -23,14 +23,13 @@ const QString WebEntityItem::DEFAULT_SOURCE_URL("http://www.google.com"); EntityItemPointer WebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - return std::make_shared(entityID, properties); + EntityItemPointer entity { new WebEntityItem(entityID) }; + entity->setProperties(properties); + return entity; } -WebEntityItem::WebEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : - EntityItem(entityItemID) -{ +WebEntityItem::WebEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { _type = EntityTypes::Web; - setProperties(properties); } const float WEB_ENTITY_ITEM_FIXED_DEPTH = 0.01f; diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index 425d89de76..8e9d924cde 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -17,7 +17,7 @@ public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - WebEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + WebEntityItem(const EntityItemID& entityItemID); ALLOW_INSTANTIATION // This class can be instantiated diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 2d0e534fa7..41f722fe36 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -27,20 +27,18 @@ const ShapeType ZoneEntityItem::DEFAULT_SHAPE_TYPE = SHAPE_TYPE_BOX; const QString ZoneEntityItem::DEFAULT_COMPOUND_SHAPE_URL = ""; EntityItemPointer ZoneEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - return std::make_shared(entityID, properties); + EntityItemPointer entity { new ZoneEntityItem(entityID) }; + entity->setProperties(properties); + return entity; } -ZoneEntityItem::ZoneEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : - EntityItem(entityItemID) -{ +ZoneEntityItem::ZoneEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { _type = EntityTypes::Zone; _shapeType = DEFAULT_SHAPE_TYPE; _compoundShapeURL = DEFAULT_COMPOUND_SHAPE_URL; _backgroundMode = BACKGROUND_MODE_INHERIT; - - setProperties(properties); } diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index c076e003e4..bf323248c0 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -25,7 +25,7 @@ class ZoneEntityItem : public EntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - ZoneEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + ZoneEntityItem(const EntityItemID& entityItemID); ALLOW_INSTANTIATION // This class can be instantiated diff --git a/libraries/gl/src/gl/GLEscrow.h b/libraries/gl/src/gl/GLEscrow.h index 54b124ae3c..d860f1239c 100644 --- a/libraries/gl/src/gl/GLEscrow.h +++ b/libraries/gl/src/gl/GLEscrow.h @@ -76,7 +76,7 @@ public: } }; - using Mutex = std::mutex; + using Mutex = std::recursive_mutex; using Lock = std::unique_lock; using Recycler = std::function; // deque gives us random access, double ended push & pop and size, all in constant time @@ -87,6 +87,11 @@ public: _recycler = recycler; } + size_t depth() { + Lock lock(_mutex); + return _submits.size(); + } + // Submit a new resource from the producer context // returns the number of prior submissions that were // never consumed before becoming available. @@ -124,7 +129,22 @@ public: } return result; } - + + // Returns the next available resource provided by the submitter, + // or if none is available (which could mean either the submission + // list is empty or that the first item on the list isn't yet signaled + // Also releases any previous texture held by the caller + T fetchAndRelease(T oldValue) { + T result = fetch(); + if (!result) { + return oldValue; + } + if (oldValue) { + release(oldValue); + } + return result; + } + // If fetch returns a non-zero value, it's the responsibility of the // client to release it at some point void release(T t, GLsync readSync = 0) { @@ -157,7 +177,12 @@ private: pop(_releases); } - trash.swap(_trash); + { + // FIXME I don't think this lock should be necessary, only the submitting thread + // touches the trash + Lock lock(_mutex); + trash.swap(_trash); + } } // FIXME maybe doing a timing on the deleters and warn if it's taking excessive time? @@ -175,6 +200,7 @@ private: // May be called on any thread, but must be inside a locked section void pop(Deque& deque) { + Lock lock(_mutex); auto& item = deque.front(); _trash.push_front(item); deque.pop_front(); diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index f67f9e9120..6ad7f816b8 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -1,15 +1,37 @@ #include "GLHelpers.h" +#include -QSurfaceFormat getDefaultOpenGlSurfaceFormat() { - QSurfaceFormat format; - // Qt Quick may need a depth and stencil buffer. Always make sure these are available. - format.setDepthBufferSize(DEFAULT_GL_DEPTH_BUFFER_BITS); - format.setStencilBufferSize(DEFAULT_GL_STENCIL_BUFFER_BITS); - format.setVersion(4, 1); +#include +#include + +const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { + static QSurfaceFormat format; + static std::once_flag once; + std::call_once(once, [] { + // Qt Quick may need a depth and stencil buffer. Always make sure these are available. + format.setDepthBufferSize(DEFAULT_GL_DEPTH_BUFFER_BITS); + format.setStencilBufferSize(DEFAULT_GL_STENCIL_BUFFER_BITS); + format.setVersion(4, 1); #ifdef DEBUG - format.setOption(QSurfaceFormat::DebugContext); + format.setOption(QSurfaceFormat::DebugContext); #endif - format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); - return format; + format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); + }); + return format; +} + +const QGLFormat& getDefaultGLFormat() { + // Specify an OpenGL 3.3 format using the Core profile. + // That is, no old-school fixed pipeline functionality + static QGLFormat glFormat; + static std::once_flag once; + std::call_once(once, [] { + glFormat.setVersion(4, 1); + glFormat.setProfile(QGLFormat::CoreProfile); // Requires >=Qt-4.8.0 + glFormat.setSampleBuffers(false); + glFormat.setDepth(false); + glFormat.setStencil(false); + }); + return glFormat; } diff --git a/libraries/gl/src/gl/GLHelpers.h b/libraries/gl/src/gl/GLHelpers.h index dc9f0f3140..335272d991 100644 --- a/libraries/gl/src/gl/GLHelpers.h +++ b/libraries/gl/src/gl/GLHelpers.h @@ -10,14 +10,15 @@ #ifndef hifi_GLHelpers_h #define hifi_GLHelpers_h -#include - // 16 bits of depth precision #define DEFAULT_GL_DEPTH_BUFFER_BITS 16 // 8 bits of stencil buffer (typically you really only need 1 bit for functionality // but GL implementations usually just come with buffer sizes in multiples of 8) #define DEFAULT_GL_STENCIL_BUFFER_BITS 8 -QSurfaceFormat getDefaultOpenGlSurfaceFormat(); +class QSurfaceFormat; +class QGLFormat; +const QSurfaceFormat& getDefaultOpenGLSurfaceFormat(); +const QGLFormat& getDefaultGLFormat(); #endif diff --git a/libraries/gl/src/gl/GLWidget.cpp b/libraries/gl/src/gl/GLWidget.cpp new file mode 100644 index 0000000000..c67dec1e51 --- /dev/null +++ b/libraries/gl/src/gl/GLWidget.cpp @@ -0,0 +1,129 @@ +// +// +// Created by Bradley Austin Davis on 2015/12/03 +// Derived from interface/src/GLCanvas.cpp created by Stephen Birarda on 8/14/13. +// Copyright 2013-2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include + +#include "GLWidget.h" + +#include + +#include +#include +#include + +#include +#include +#include + + +#include "GLHelpers.h" + + +GLWidget::GLWidget() : QGLWidget(getDefaultGLFormat()) { +#ifdef Q_OS_LINUX + // Cause GLWidget::eventFilter to be called. + // It wouldn't hurt to do this on Mac and PC too; but apparently it's only needed on linux. + qApp->installEventFilter(this); +#endif +} + +int GLWidget::getDeviceWidth() const { + return width() * (windowHandle() ? (float)windowHandle()->devicePixelRatio() : 1.0f); +} + +int GLWidget::getDeviceHeight() const { + return height() * (windowHandle() ? (float)windowHandle()->devicePixelRatio() : 1.0f); +} + +void GLWidget::initializeGL() { + setAttribute(Qt::WA_AcceptTouchEvents); + setAcceptDrops(true); + // Note, we *DO NOT* want Qt to automatically swap buffers for us. This results in the "ringing" bug mentioned in WL#19514 when we're throttling the framerate. + setAutoBufferSwap(false); + + // TODO: write the proper code for linux + makeCurrent(); +#if defined(Q_OS_WIN) + _vsyncSupported = context()->contextHandle()->hasExtension("WGL_EXT_swap_control");; +#endif +} + +void GLWidget::paintEvent(QPaintEvent* event) { + QWidget::paintEvent(event); +} + +void GLWidget::resizeEvent(QResizeEvent* event) { + QWidget::resizeEvent(event); +} + +bool GLWidget::event(QEvent* event) { + switch (event->type()) { + case QEvent::MouseMove: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::FocusIn: + case QEvent::FocusOut: + case QEvent::Resize: + case QEvent::TouchBegin: + case QEvent::TouchEnd: + case QEvent::TouchUpdate: + case QEvent::Wheel: + case QEvent::DragEnter: + case QEvent::Drop: + if (QCoreApplication::sendEvent(QCoreApplication::instance(), event)) { + return true; + } + break; + + default: + break; + } + return QGLWidget::event(event); +} + + +// Pressing Alt (and Meta) key alone activates the menubar because its style inherits the +// SHMenuBarAltKeyNavigation from QWindowsStyle. This makes it impossible for a scripts to +// receive keyPress events for the Alt (and Meta) key in a reliable manner. +// +// This filter catches events before QMenuBar can steal the keyboard focus. +// The idea was borrowed from +// http://www.archivum.info/qt-interest@trolltech.com/2006-09/00053/Re-(Qt4)-Alt-key-focus-QMenuBar-(solved).html + +bool GLWidget::eventFilter(QObject*, QEvent* event) { + switch (event->type()) { + case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::ShortcutOverride: + { + QKeyEvent* keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Alt || keyEvent->key() == Qt::Key_Meta) { + if (event->type() == QEvent::KeyPress) { + keyPressEvent(keyEvent); + } else if (event->type() == QEvent::KeyRelease) { + keyReleaseEvent(keyEvent); + } else { + QGLWidget::event(event); + } + return true; + } + } + default: + break; + } + return false; +} + +bool GLWidget::isVsyncSupported() const { + return _vsyncSupported; +} + diff --git a/libraries/gl/src/gl/GLWidget.h b/libraries/gl/src/gl/GLWidget.h new file mode 100644 index 0000000000..5b391aa6cd --- /dev/null +++ b/libraries/gl/src/gl/GLWidget.h @@ -0,0 +1,40 @@ +// +// Created by Bradley Austin Davis on 2015/12/03 +// Derived from interface/src/GLCanvas.h created by Stephen Birarda on 8/14/13. +// Copyright 2013-2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_GLWidget_h +#define hifi_GLWidget_h + +#include + +/// customized canvas that simply forwards requests/events to the singleton application +class GLWidget : public QGLWidget { + Q_OBJECT + +public: + GLWidget(); + int getDeviceWidth() const; + int getDeviceHeight() const; + QSize getDeviceSize() const { return QSize(getDeviceWidth(), getDeviceHeight()); } + bool isVsyncSupported() const; + virtual void initializeGL() override; + +protected: + virtual bool event(QEvent* event) override; + virtual void paintEvent(QPaintEvent* event) override; + virtual void resizeEvent(QResizeEvent* event) override; + +private slots: + virtual bool eventFilter(QObject*, QEvent* event) override; + +private: + bool _vsyncSupported { false }; +}; + + +#endif // hifi_GLCanvas_h diff --git a/libraries/gl/src/gl/GlWindow.cpp b/libraries/gl/src/gl/GLWindow.cpp similarity index 69% rename from libraries/gl/src/gl/GlWindow.cpp rename to libraries/gl/src/gl/GLWindow.cpp index 40a5bedf7e..78c7666d25 100644 --- a/libraries/gl/src/gl/GlWindow.cpp +++ b/libraries/gl/src/gl/GLWindow.cpp @@ -6,17 +6,18 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "GlWindow.h" +#include "GLWindow.h" #include #include #include "GLHelpers.h" -GlWindow::GlWindow(QOpenGLContext* shareContext) : GlWindow(getDefaultOpenGlSurfaceFormat(), shareContext) { +void GLWindow::createContext(QOpenGLContext* shareContext) { + createContext(getDefaultOpenGLSurfaceFormat(), shareContext); } -GlWindow::GlWindow(const QSurfaceFormat& format, QOpenGLContext* shareContext) { +void GLWindow::createContext(const QSurfaceFormat& format, QOpenGLContext* shareContext) { setSurfaceType(QSurface::OpenGLSurface); setFormat(format); _context = new QOpenGLContext; @@ -27,13 +28,15 @@ GlWindow::GlWindow(const QSurfaceFormat& format, QOpenGLContext* shareContext) { _context->create(); } -GlWindow::~GlWindow() { - _context->doneCurrent(); - _context->deleteLater(); - _context = nullptr; +GLWindow::~GLWindow() { + if (_context) { + _context->doneCurrent(); + _context->deleteLater(); + _context = nullptr; + } } -bool GlWindow::makeCurrent() { +bool GLWindow::makeCurrent() { bool makeCurrentResult = _context->makeCurrent(this); Q_ASSERT(makeCurrentResult); @@ -49,11 +52,16 @@ bool GlWindow::makeCurrent() { return makeCurrentResult; } -void GlWindow::doneCurrent() { +void GLWindow::doneCurrent() { _context->doneCurrent(); } -void GlWindow::swapBuffers() { +void GLWindow::swapBuffers() { _context->swapBuffers(this); } +QOpenGLContext* GLWindow::context() const { + return _context; +} + + diff --git a/libraries/gl/src/gl/GlWindow.h b/libraries/gl/src/gl/GLWindow.h similarity index 63% rename from libraries/gl/src/gl/GlWindow.h rename to libraries/gl/src/gl/GLWindow.h index 4956177725..bccfcd7320 100644 --- a/libraries/gl/src/gl/GlWindow.h +++ b/libraries/gl/src/gl/GLWindow.h @@ -7,8 +7,8 @@ // #pragma once -#ifndef hifi_GlWindow_h -#define hifi_GlWindow_h +#ifndef hifi_GLWindow_h +#define hifi_GLWindow_h #include #include @@ -16,14 +16,15 @@ class QOpenGLContext; class QOpenGLDebugLogger; -class GlWindow : public QWindow { +class GLWindow : public QWindow { public: - GlWindow(QOpenGLContext* shareContext = nullptr); - GlWindow(const QSurfaceFormat& format, QOpenGLContext* shareContext = nullptr); - virtual ~GlWindow(); + virtual ~GLWindow(); + void createContext(QOpenGLContext* shareContext = nullptr); + void createContext(const QSurfaceFormat& format, QOpenGLContext* shareContext = nullptr); bool makeCurrent(); void doneCurrent(); void swapBuffers(); + QOpenGLContext* context() const; private: std::once_flag _reportOnce; QOpenGLContext* _context{ nullptr }; diff --git a/libraries/gl/src/gl/OffscreenGlCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp similarity index 84% rename from libraries/gl/src/gl/OffscreenGlCanvas.cpp rename to libraries/gl/src/gl/OffscreenGLCanvas.cpp index e5c1ee4c4a..640c8ed5f5 100644 --- a/libraries/gl/src/gl/OffscreenGlCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -1,5 +1,5 @@ // -// OffscreenGlCanvas.cpp +// OffscreenGLCanvas.cpp // interface/src/renderer // // Created by Bradley Austin Davis on 2014/04/09. @@ -10,7 +10,7 @@ // -#include "OffscreenGlCanvas.h" +#include "OffscreenGLCanvas.h" #include #include @@ -18,10 +18,10 @@ #include "GLHelpers.h" -OffscreenGlCanvas::OffscreenGlCanvas() : _context(new QOpenGLContext), _offscreenSurface(new QOffscreenSurface){ +OffscreenGLCanvas::OffscreenGLCanvas() : _context(new QOpenGLContext), _offscreenSurface(new QOffscreenSurface){ } -OffscreenGlCanvas::~OffscreenGlCanvas() { +OffscreenGLCanvas::~OffscreenGLCanvas() { #ifdef DEBUG if (_logger) { makeCurrent(); @@ -32,12 +32,12 @@ OffscreenGlCanvas::~OffscreenGlCanvas() { _context->doneCurrent(); } -void OffscreenGlCanvas::create(QOpenGLContext* sharedContext) { +void OffscreenGLCanvas::create(QOpenGLContext* sharedContext) { if (nullptr != sharedContext) { sharedContext->doneCurrent(); _context->setShareContext(sharedContext); } - _context->setFormat(getDefaultOpenGlSurfaceFormat()); + _context->setFormat(getDefaultOpenGLSurfaceFormat()); _context->create(); _offscreenSurface->setFormat(_context->format()); @@ -45,7 +45,7 @@ void OffscreenGlCanvas::create(QOpenGLContext* sharedContext) { } -bool OffscreenGlCanvas::makeCurrent() { +bool OffscreenGLCanvas::makeCurrent() { bool result = _context->makeCurrent(_offscreenSurface); Q_ASSERT(result); @@ -72,7 +72,7 @@ bool OffscreenGlCanvas::makeCurrent() { return result; } -void OffscreenGlCanvas::doneCurrent() { +void OffscreenGLCanvas::doneCurrent() { _context->doneCurrent(); } diff --git a/libraries/gl/src/gl/OffscreenGlCanvas.h b/libraries/gl/src/gl/OffscreenGLCanvas.h similarity index 77% rename from libraries/gl/src/gl/OffscreenGlCanvas.h rename to libraries/gl/src/gl/OffscreenGLCanvas.h index 94014adf98..e278f550f0 100644 --- a/libraries/gl/src/gl/OffscreenGlCanvas.h +++ b/libraries/gl/src/gl/OffscreenGLCanvas.h @@ -1,5 +1,5 @@ // -// OffscreenGlCanvas.h +// OffscreenGLCanvas.h // interface/src/renderer // // Created by Bradley Austin Davis on 2014/04/09. @@ -9,8 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #pragma once -#ifndef hifi_OffscreenGlCanvas_h -#define hifi_OffscreenGlCanvas_h +#ifndef hifi_OffscreenGLCanvas_h +#define hifi_OffscreenGLCanvas_h #include #include @@ -19,10 +19,10 @@ class QOpenGLContext; class QOffscreenSurface; class QOpenGLDebugLogger; -class OffscreenGlCanvas : public QObject { +class OffscreenGLCanvas : public QObject { public: - OffscreenGlCanvas(); - ~OffscreenGlCanvas(); + OffscreenGLCanvas(); + ~OffscreenGLCanvas(); void create(QOpenGLContext* sharedContext = nullptr); bool makeCurrent(); void doneCurrent(); @@ -40,4 +40,4 @@ protected: }; -#endif // hifi_OffscreenGlCanvas_h +#endif // hifi_OffscreenGLCanvas_h diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index e8a950a16b..482eb1b36f 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -23,7 +23,7 @@ #include #include "GLEscrow.h" -#include "OffscreenGlCanvas.h" +#include "OffscreenGLCanvas.h" // FIXME move to threaded rendering with Qt 5.5 //#define QML_THREADED @@ -64,12 +64,12 @@ static const QEvent::Type STOP = QEvent::Type(QEvent::User + 4); static const QEvent::Type UPDATE = QEvent::Type(QEvent::User + 5); #endif -class OffscreenQmlRenderer : public OffscreenGlCanvas { +class OffscreenQmlRenderer : public OffscreenGLCanvas { friend class OffscreenQmlSurface; public: OffscreenQmlRenderer(OffscreenQmlSurface* surface, QOpenGLContext* shareContext) : _surface(surface) { - OffscreenGlCanvas::create(shareContext); + OffscreenGLCanvas::create(shareContext); #ifdef QML_THREADED // Qt 5.5 _renderControl->prepareThread(_renderThread); @@ -337,6 +337,9 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { // a timer with a small interval is used to get better performance. _updateTimer.setInterval(MIN_TIMER_MS); connect(&_updateTimer, &QTimer::timeout, this, &OffscreenQmlSurface::updateQuick); + QObject::connect(qApp, &QCoreApplication::aboutToQuit, [this]{ + disconnect(&_updateTimer, &QTimer::timeout, this, &OffscreenQmlSurface::updateQuick); + }); _updateTimer.start(); _qmlComponent = new QQmlComponent(_qmlEngine); diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index 2e3ea7fc36..80b3a4f158 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -80,9 +80,9 @@ void Batch::clear() { _framebuffers.clear(); } -uint32 Batch::cacheData(uint32 size, const void* data) { - uint32 offset = _data.size(); - uint32 numBytes = size; +size_t Batch::cacheData(size_t size, const void* data) { + size_t offset = _data.size(); + size_t numBytes = size; _data.resize(offset + numBytes); memcpy(_data.data() + offset, data, size); diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index 8397f92da6..da1f13151e 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -334,7 +334,7 @@ public: NUM_COMMANDS, }; typedef std::vector Commands; - typedef std::vector CommandOffsets; + typedef std::vector CommandOffsets; const Commands& getCommands() const { return _commands; } const CommandOffsets& getCommandOffsets() const { return _commandOffsets; } @@ -342,11 +342,17 @@ public: class Param { public: union { +#if (QT_POINTER_SIZE == 8) + size_t _size; +#endif int32 _int; uint32 _uint; - float _float; - char _chars[4]; + float _float; + char _chars[sizeof(size_t)]; }; +#if (QT_POINTER_SIZE == 8) + Param(size_t val) : _size(val) {} +#endif Param(int32 val) : _int(val) {} Param(uint32 val) : _uint(val) {} Param(float val) : _float(val) {} @@ -370,8 +376,8 @@ public: std::vector< Cache > _items; size_t size() const { return _items.size(); } - uint32 cache(const Data& data) { - uint32 offset = _items.size(); + size_t cache(const Data& data) { + size_t offset = _items.size(); _items.push_back(Cache(data)); return offset; } @@ -403,8 +409,8 @@ public: // FOr example Mat4s are going there typedef unsigned char Byte; typedef std::vector Bytes; - uint32 cacheData(uint32 size, const void* data); - Byte* editData(uint32 offset) { + size_t cacheData(size_t size, const void* data); + Byte* editData(size_t offset) { if (offset >= _data.size()) { return 0; } diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h index ff0fd9faea..3022f47b51 100644 --- a/libraries/gpu/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -33,7 +33,7 @@ typedef char int8; typedef unsigned char Byte; -typedef uint32 Offset; +typedef size_t Offset; typedef glm::mat4 Mat4; typedef glm::mat3 Mat3; diff --git a/libraries/gpu/src/gpu/GLBackend.cpp b/libraries/gpu/src/gpu/GLBackend.cpp index e49a3ba6c0..d4f3c5c4b3 100644 --- a/libraries/gpu/src/gpu/GLBackend.cpp +++ b/libraries/gpu/src/gpu/GLBackend.cpp @@ -304,7 +304,7 @@ void GLBackend::syncCache() { glEnable(GL_LINE_SMOOTH); } -void GLBackend::do_draw(Batch& batch, uint32 paramOffset) { +void GLBackend::do_draw(Batch& batch, size_t paramOffset) { updateInput(); updateTransform(); updatePipeline(); @@ -317,7 +317,7 @@ void GLBackend::do_draw(Batch& batch, uint32 paramOffset) { (void) CHECK_GL_ERROR(); } -void GLBackend::do_drawIndexed(Batch& batch, uint32 paramOffset) { +void GLBackend::do_drawIndexed(Batch& batch, size_t paramOffset) { updateInput(); updateTransform(); updatePipeline(); @@ -336,7 +336,7 @@ void GLBackend::do_drawIndexed(Batch& batch, uint32 paramOffset) { (void) CHECK_GL_ERROR(); } -void GLBackend::do_drawInstanced(Batch& batch, uint32 paramOffset) { +void GLBackend::do_drawInstanced(Batch& batch, size_t paramOffset) { updateInput(); updateTransform(); updatePipeline(); @@ -351,7 +351,7 @@ void GLBackend::do_drawInstanced(Batch& batch, uint32 paramOffset) { (void) CHECK_GL_ERROR(); } -void GLBackend::do_drawIndexedInstanced(Batch& batch, uint32 paramOffset) { +void GLBackend::do_drawIndexedInstanced(Batch& batch, size_t paramOffset) { updateInput(); updateTransform(); updatePipeline(); @@ -378,7 +378,7 @@ void GLBackend::do_drawIndexedInstanced(Batch& batch, uint32 paramOffset) { } -void GLBackend::do_multiDrawIndirect(Batch& batch, uint32 paramOffset) { +void GLBackend::do_multiDrawIndirect(Batch& batch, size_t paramOffset) { #if (GPU_INPUT_PROFILE == GPU_CORE_43) updateInput(); updateTransform(); @@ -387,7 +387,7 @@ void GLBackend::do_multiDrawIndirect(Batch& batch, uint32 paramOffset) { uint commandCount = batch._params[paramOffset + 0]._uint; GLenum mode = _primitiveToGLmode[(Primitive)batch._params[paramOffset + 1]._uint]; - glMultiDrawArraysIndirect(mode, reinterpret_cast(_input._indirectBufferOffset), commandCount, _input._indirectBufferStride); + glMultiDrawArraysIndirect(mode, reinterpret_cast(_input._indirectBufferOffset), commandCount, (GLsizei)_input._indirectBufferStride); #else // FIXME implement the slow path #endif @@ -395,7 +395,7 @@ void GLBackend::do_multiDrawIndirect(Batch& batch, uint32 paramOffset) { } -void GLBackend::do_multiDrawIndexedIndirect(Batch& batch, uint32 paramOffset) { +void GLBackend::do_multiDrawIndexedIndirect(Batch& batch, size_t paramOffset) { #if (GPU_INPUT_PROFILE == GPU_CORE_43) updateInput(); updateTransform(); @@ -405,7 +405,7 @@ void GLBackend::do_multiDrawIndexedIndirect(Batch& batch, uint32 paramOffset) { GLenum mode = _primitiveToGLmode[(Primitive)batch._params[paramOffset + 1]._uint]; GLenum indexType = _elementTypeToGLType[_input._indexBufferType]; - glMultiDrawElementsIndirect(mode, indexType, reinterpret_cast(_input._indirectBufferOffset), commandCount, _input._indirectBufferStride); + glMultiDrawElementsIndirect(mode, indexType, reinterpret_cast(_input._indirectBufferOffset), commandCount, (GLsizei)_input._indirectBufferStride); #else // FIXME implement the slow path #endif @@ -413,11 +413,11 @@ void GLBackend::do_multiDrawIndexedIndirect(Batch& batch, uint32 paramOffset) { } -void GLBackend::do_resetStages(Batch& batch, uint32 paramOffset) { +void GLBackend::do_resetStages(Batch& batch, size_t paramOffset) { resetStages(); } -void GLBackend::do_runLambda(Batch& batch, uint32 paramOffset) { +void GLBackend::do_runLambda(Batch& batch, size_t paramOffset) { std::function f = batch._lambdas.get(batch._params[paramOffset]._uint); f(); } @@ -455,7 +455,7 @@ void Batch::_glActiveBindTexture(GLenum unit, GLenum target, GLuint texture) { DO_IT_NOW(_glActiveBindTexture, 3); } -void GLBackend::do_glActiveBindTexture(Batch& batch, uint32 paramOffset) { +void GLBackend::do_glActiveBindTexture(Batch& batch, size_t paramOffset) { glActiveTexture(batch._params[paramOffset + 2]._uint); glBindTexture( batch._params[paramOffset + 1]._uint, @@ -474,7 +474,7 @@ void Batch::_glUniform1i(GLint location, GLint v0) { DO_IT_NOW(_glUniform1i, 1); } -void GLBackend::do_glUniform1i(Batch& batch, uint32 paramOffset) { +void GLBackend::do_glUniform1i(Batch& batch, size_t paramOffset) { if (_pipeline._program == 0) { // We should call updatePipeline() to bind the program but we are not doing that // because these uniform setters are deprecated and we don;t want to create side effect @@ -497,7 +497,7 @@ void Batch::_glUniform1f(GLint location, GLfloat v0) { DO_IT_NOW(_glUniform1f, 1); } -void GLBackend::do_glUniform1f(Batch& batch, uint32 paramOffset) { +void GLBackend::do_glUniform1f(Batch& batch, size_t paramOffset) { if (_pipeline._program == 0) { // We should call updatePipeline() to bind the program but we are not doing that // because these uniform setters are deprecated and we don;t want to create side effect @@ -521,7 +521,7 @@ void Batch::_glUniform2f(GLint location, GLfloat v0, GLfloat v1) { DO_IT_NOW(_glUniform2f, 1); } -void GLBackend::do_glUniform2f(Batch& batch, uint32 paramOffset) { +void GLBackend::do_glUniform2f(Batch& batch, size_t paramOffset) { if (_pipeline._program == 0) { // We should call updatePipeline() to bind the program but we are not doing that // because these uniform setters are deprecated and we don;t want to create side effect @@ -546,7 +546,7 @@ void Batch::_glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2) { DO_IT_NOW(_glUniform3f, 1); } -void GLBackend::do_glUniform3f(Batch& batch, uint32 paramOffset) { +void GLBackend::do_glUniform3f(Batch& batch, size_t paramOffset) { if (_pipeline._program == 0) { // We should call updatePipeline() to bind the program but we are not doing that // because these uniform setters are deprecated and we don;t want to create side effect @@ -575,7 +575,7 @@ void Batch::_glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLf } -void GLBackend::do_glUniform4f(Batch& batch, uint32 paramOffset) { +void GLBackend::do_glUniform4f(Batch& batch, size_t paramOffset) { if (_pipeline._program == 0) { // We should call updatePipeline() to bind the program but we are not doing that // because these uniform setters are deprecated and we don;t want to create side effect @@ -601,7 +601,7 @@ void Batch::_glUniform3fv(GLint location, GLsizei count, const GLfloat* value) { DO_IT_NOW(_glUniform3fv, 3); } -void GLBackend::do_glUniform3fv(Batch& batch, uint32 paramOffset) { +void GLBackend::do_glUniform3fv(Batch& batch, size_t paramOffset) { if (_pipeline._program == 0) { // We should call updatePipeline() to bind the program but we are not doing that // because these uniform setters are deprecated and we don;t want to create side effect @@ -627,7 +627,7 @@ void Batch::_glUniform4fv(GLint location, GLsizei count, const GLfloat* value) { DO_IT_NOW(_glUniform4fv, 3); } -void GLBackend::do_glUniform4fv(Batch& batch, uint32 paramOffset) { +void GLBackend::do_glUniform4fv(Batch& batch, size_t paramOffset) { if (_pipeline._program == 0) { // We should call updatePipeline() to bind the program but we are not doing that // because these uniform setters are deprecated and we don;t want to create side effect @@ -653,7 +653,7 @@ void Batch::_glUniform4iv(GLint location, GLsizei count, const GLint* value) { DO_IT_NOW(_glUniform4iv, 3); } -void GLBackend::do_glUniform4iv(Batch& batch, uint32 paramOffset) { +void GLBackend::do_glUniform4iv(Batch& batch, size_t paramOffset) { if (_pipeline._program == 0) { // We should call updatePipeline() to bind the program but we are not doing that // because these uniform setters are deprecated and we don;t want to create side effect @@ -679,7 +679,7 @@ void Batch::_glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpo DO_IT_NOW(_glUniformMatrix4fv, 4); } -void GLBackend::do_glUniformMatrix4fv(Batch& batch, uint32 paramOffset) { +void GLBackend::do_glUniformMatrix4fv(Batch& batch, size_t paramOffset) { if (_pipeline._program == 0) { // We should call updatePipeline() to bind the program but we are not doing that // because these uniform setters are deprecated and we don;t want to create side effect @@ -704,7 +704,7 @@ void Batch::_glColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) DO_IT_NOW(_glColor4f, 4); } -void GLBackend::do_glColor4f(Batch& batch, uint32 paramOffset) { +void GLBackend::do_glColor4f(Batch& batch, size_t paramOffset) { glm::vec4 newColor( batch._params[paramOffset + 3]._float, @@ -720,14 +720,14 @@ void GLBackend::do_glColor4f(Batch& batch, uint32 paramOffset) { } -void GLBackend::do_pushProfileRange(Batch& batch, uint32 paramOffset) { +void GLBackend::do_pushProfileRange(Batch& batch, size_t paramOffset) { #if defined(NSIGHT_FOUND) auto name = batch._profileRanges.get(batch._params[paramOffset]._uint); nvtxRangePush(name.c_str()); #endif } -void GLBackend::do_popProfileRange(Batch& batch, uint32 paramOffset) { +void GLBackend::do_popProfileRange(Batch& batch, size_t paramOffset) { #if defined(NSIGHT_FOUND) nvtxRangePop(); #endif diff --git a/libraries/gpu/src/gpu/GLBackend.h b/libraries/gpu/src/gpu/GLBackend.h index 9f1e17205c..f44fbe6c0d 100644 --- a/libraries/gpu/src/gpu/GLBackend.h +++ b/libraries/gpu/src/gpu/GLBackend.h @@ -195,17 +195,17 @@ public: static const int MAX_NUM_ATTRIBUTES = Stream::NUM_INPUT_SLOTS; static const int MAX_NUM_INPUT_BUFFERS = 16; - uint32 getNumInputBuffers() const { return _input._invalidBuffers.size(); } + size_t getNumInputBuffers() const { return _input._invalidBuffers.size(); } // this is the maximum per shader stage on the low end apple // TODO make it platform dependant at init time static const int MAX_NUM_UNIFORM_BUFFERS = 12; - uint32 getMaxNumUniformBuffers() const { return MAX_NUM_UNIFORM_BUFFERS; } + size_t getMaxNumUniformBuffers() const { return MAX_NUM_UNIFORM_BUFFERS; } // this is the maximum per shader stage on the low end apple // TODO make it platform dependant at init time static const int MAX_NUM_RESOURCE_TEXTURES = 16; - uint32 getMaxNumResourceTextures() const { return MAX_NUM_RESOURCE_TEXTURES; } + size_t getMaxNumResourceTextures() const { return MAX_NUM_RESOURCE_TEXTURES; } // The State setters called by the GLState::Commands when a new state is assigned void do_setStateFillMode(int32 mode); @@ -248,18 +248,18 @@ protected: Stats _stats; // Draw Stage - void do_draw(Batch& batch, uint32 paramOffset); - void do_drawIndexed(Batch& batch, uint32 paramOffset); - void do_drawInstanced(Batch& batch, uint32 paramOffset); - void do_drawIndexedInstanced(Batch& batch, uint32 paramOffset); - void do_multiDrawIndirect(Batch& batch, uint32 paramOffset); - void do_multiDrawIndexedIndirect(Batch& batch, uint32 paramOffset); + void do_draw(Batch& batch, size_t paramOffset); + void do_drawIndexed(Batch& batch, size_t paramOffset); + void do_drawInstanced(Batch& batch, size_t paramOffset); + void do_drawIndexedInstanced(Batch& batch, size_t paramOffset); + void do_multiDrawIndirect(Batch& batch, size_t paramOffset); + void do_multiDrawIndexedIndirect(Batch& batch, size_t paramOffset); // Input Stage - void do_setInputFormat(Batch& batch, uint32 paramOffset); - void do_setInputBuffer(Batch& batch, uint32 paramOffset); - void do_setIndexBuffer(Batch& batch, uint32 paramOffset); - void do_setIndirectBuffer(Batch& batch, uint32 paramOffset); + void do_setInputFormat(Batch& batch, size_t paramOffset); + void do_setInputBuffer(Batch& batch, size_t paramOffset); + void do_setIndexBuffer(Batch& batch, size_t paramOffset); + void do_setIndirectBuffer(Batch& batch, size_t paramOffset); void initInput(); void killInput(); @@ -310,11 +310,11 @@ protected: } _input; // Transform Stage - void do_setModelTransform(Batch& batch, uint32 paramOffset); - void do_setViewTransform(Batch& batch, uint32 paramOffset); - void do_setProjectionTransform(Batch& batch, uint32 paramOffset); - void do_setViewportTransform(Batch& batch, uint32 paramOffset); - void do_setDepthRangeTransform(Batch& batch, uint32 paramOffset); + void do_setModelTransform(Batch& batch, size_t paramOffset); + void do_setViewTransform(Batch& batch, size_t paramOffset); + void do_setProjectionTransform(Batch& batch, size_t paramOffset); + void do_setViewportTransform(Batch& batch, size_t paramOffset); + void do_setDepthRangeTransform(Batch& batch, size_t paramOffset); void initTransform(); void killTransform(); @@ -362,7 +362,7 @@ protected: // Uniform Stage - void do_setUniformBuffer(Batch& batch, uint32 paramOffset); + void do_setUniformBuffer(Batch& batch, size_t paramOffset); void releaseUniformBuffer(uint32_t slot); void resetUniformStage(); @@ -375,7 +375,7 @@ protected: } _uniform; // Resource Stage - void do_setResourceTexture(Batch& batch, uint32 paramOffset); + void do_setResourceTexture(Batch& batch, size_t paramOffset); void releaseResourceTexture(uint32_t slot); void resetResourceStage(); @@ -390,9 +390,9 @@ protected: size_t _commandIndex{ 0 }; // Pipeline Stage - void do_setPipeline(Batch& batch, uint32 paramOffset); - void do_setStateBlendFactor(Batch& batch, uint32 paramOffset); - void do_setStateScissorRect(Batch& batch, uint32 paramOffset); + void do_setPipeline(Batch& batch, size_t paramOffset); + void do_setStateBlendFactor(Batch& batch, size_t paramOffset); + void do_setStateScissorRect(Batch& batch, size_t paramOffset); // Standard update pipeline check that the current Program and current State or good to go for a void updatePipeline(); @@ -429,9 +429,9 @@ protected: } _pipeline; // Output stage - void do_setFramebuffer(Batch& batch, uint32 paramOffset); - void do_clearFramebuffer(Batch& batch, uint32 paramOffset); - void do_blit(Batch& batch, uint32 paramOffset); + void do_setFramebuffer(Batch& batch, size_t paramOffset); + void do_clearFramebuffer(Batch& batch, size_t paramOffset); + void do_blit(Batch& batch, size_t paramOffset); // Synchronize the state cache of this Backend with the actual real state of the GL Context void syncOutputStateCache(); @@ -446,9 +446,9 @@ protected: } _output; // Query section - void do_beginQuery(Batch& batch, uint32 paramOffset); - void do_endQuery(Batch& batch, uint32 paramOffset); - void do_getQuery(Batch& batch, uint32 paramOffset); + void do_beginQuery(Batch& batch, size_t paramOffset); + void do_endQuery(Batch& batch, size_t paramOffset); + void do_getQuery(Batch& batch, size_t paramOffset); void resetQueryStage(); struct QueryStageState { @@ -456,33 +456,33 @@ protected: }; // Reset stages - void do_resetStages(Batch& batch, uint32 paramOffset); + void do_resetStages(Batch& batch, size_t paramOffset); - void do_runLambda(Batch& batch, uint32 paramOffset); + void do_runLambda(Batch& batch, size_t paramOffset); void resetStages(); // TODO: As long as we have gl calls explicitely issued from interface // code, we need to be able to record and batch these calls. THe long // term strategy is to get rid of any GL calls in favor of the HIFI GPU API - void do_glActiveBindTexture(Batch& batch, uint32 paramOffset); + void do_glActiveBindTexture(Batch& batch, size_t paramOffset); - void do_glUniform1i(Batch& batch, uint32 paramOffset); - void do_glUniform1f(Batch& batch, uint32 paramOffset); - void do_glUniform2f(Batch& batch, uint32 paramOffset); - void do_glUniform3f(Batch& batch, uint32 paramOffset); - void do_glUniform4f(Batch& batch, uint32 paramOffset); - void do_glUniform3fv(Batch& batch, uint32 paramOffset); - void do_glUniform4fv(Batch& batch, uint32 paramOffset); - void do_glUniform4iv(Batch& batch, uint32 paramOffset); - void do_glUniformMatrix4fv(Batch& batch, uint32 paramOffset); + void do_glUniform1i(Batch& batch, size_t paramOffset); + void do_glUniform1f(Batch& batch, size_t paramOffset); + void do_glUniform2f(Batch& batch, size_t paramOffset); + void do_glUniform3f(Batch& batch, size_t paramOffset); + void do_glUniform4f(Batch& batch, size_t paramOffset); + void do_glUniform3fv(Batch& batch, size_t paramOffset); + void do_glUniform4fv(Batch& batch, size_t paramOffset); + void do_glUniform4iv(Batch& batch, size_t paramOffset); + void do_glUniformMatrix4fv(Batch& batch, size_t paramOffset); - void do_glColor4f(Batch& batch, uint32 paramOffset); + void do_glColor4f(Batch& batch, size_t paramOffset); - void do_pushProfileRange(Batch& batch, uint32 paramOffset); - void do_popProfileRange(Batch& batch, uint32 paramOffset); + void do_pushProfileRange(Batch& batch, size_t paramOffset); + void do_popProfileRange(Batch& batch, size_t paramOffset); - typedef void (GLBackend::*CommandCall)(Batch&, uint32); + typedef void (GLBackend::*CommandCall)(Batch&, size_t); static CommandCall _commandCalls[Batch::NUM_COMMANDS]; }; diff --git a/libraries/gpu/src/gpu/GLBackendBuffer.cpp b/libraries/gpu/src/gpu/GLBackendBuffer.cpp index 3eeedc5dc3..49aeeca38e 100755 --- a/libraries/gpu/src/gpu/GLBackendBuffer.cpp +++ b/libraries/gpu/src/gpu/GLBackendBuffer.cpp @@ -46,7 +46,7 @@ GLBackend::GLBuffer* GLBackend::syncGPUObject(const Buffer& buffer) { glBufferData(GL_ARRAY_BUFFER, buffer.getSysmem().getSize(), buffer.getSysmem().readData(), GL_DYNAMIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); object->_stamp = buffer.getSysmem().getStamp(); - object->_size = buffer.getSysmem().getSize(); + object->_size = (GLuint)buffer.getSysmem().getSize(); //} (void) CHECK_GL_ERROR(); diff --git a/libraries/gpu/src/gpu/GLBackendInput.cpp b/libraries/gpu/src/gpu/GLBackendInput.cpp index 5cdcf0adc6..75f4be3cbe 100755 --- a/libraries/gpu/src/gpu/GLBackendInput.cpp +++ b/libraries/gpu/src/gpu/GLBackendInput.cpp @@ -12,7 +12,7 @@ using namespace gpu; -void GLBackend::do_setInputFormat(Batch& batch, uint32 paramOffset) { +void GLBackend::do_setInputFormat(Batch& batch, size_t paramOffset) { Stream::FormatPointer format = batch._streamFormats.get(batch._params[paramOffset]._uint); if (format != _input._format) { @@ -21,7 +21,7 @@ void GLBackend::do_setInputFormat(Batch& batch, uint32 paramOffset) { } } -void GLBackend::do_setInputBuffer(Batch& batch, uint32 paramOffset) { +void GLBackend::do_setInputBuffer(Batch& batch, size_t paramOffset) { Offset stride = batch._params[paramOffset + 0]._uint; Offset offset = batch._params[paramOffset + 1]._uint; BufferPointer buffer = batch._buffers.get(batch._params[paramOffset + 2]._uint); @@ -232,14 +232,14 @@ void GLBackend::updateInput() { GLenum type = _elementTypeToGLType[attrib._element.getType()]; // GLenum perLocationStride = strides[bufferNum]; GLenum perLocationStride = attrib._element.getLocationSize(); - GLuint stride = strides[bufferNum]; - GLuint pointer = attrib._offset + offsets[bufferNum]; + GLuint stride = (GLuint)strides[bufferNum]; + GLuint pointer = (GLuint)(attrib._offset + offsets[bufferNum]); GLboolean isNormalized = attrib._element.isNormalized(); for (size_t locNum = 0; locNum < locationCount; ++locNum) { - glVertexAttribPointer(slot + locNum, count, type, isNormalized, stride, - reinterpret_cast(pointer + perLocationStride * locNum)); - glVertexAttribDivisor(slot + locNum, attrib._frequency); + glVertexAttribPointer(slot + (GLuint)locNum, count, type, isNormalized, stride, + reinterpret_cast(pointer + perLocationStride * (GLuint)locNum)); + glVertexAttribDivisor(slot + (GLuint)locNum, attrib._frequency); } // TODO: Support properly the IAttrib version @@ -287,7 +287,7 @@ void GLBackend::resetInputStage() { } -void GLBackend::do_setIndexBuffer(Batch& batch, uint32 paramOffset) { +void GLBackend::do_setIndexBuffer(Batch& batch, size_t paramOffset) { _input._indexBufferType = (Type)batch._params[paramOffset + 2]._uint; _input._indexBufferOffset = batch._params[paramOffset + 0]._uint; @@ -304,7 +304,7 @@ void GLBackend::do_setIndexBuffer(Batch& batch, uint32 paramOffset) { (void) CHECK_GL_ERROR(); } -void GLBackend::do_setIndirectBuffer(Batch& batch, uint32 paramOffset) { +void GLBackend::do_setIndirectBuffer(Batch& batch, size_t paramOffset) { _input._indirectBufferOffset = batch._params[paramOffset + 1]._uint; _input._indirectBufferStride = batch._params[paramOffset + 2]._uint; diff --git a/libraries/gpu/src/gpu/GLBackendOutput.cpp b/libraries/gpu/src/gpu/GLBackendOutput.cpp index 2bcd7e31d8..3ae8ee5435 100755 --- a/libraries/gpu/src/gpu/GLBackendOutput.cpp +++ b/libraries/gpu/src/gpu/GLBackendOutput.cpp @@ -196,7 +196,7 @@ void GLBackend::resetOutputStage() { } } -void GLBackend::do_setFramebuffer(Batch& batch, uint32 paramOffset) { +void GLBackend::do_setFramebuffer(Batch& batch, size_t paramOffset) { auto framebuffer = batch._framebuffers.get(batch._params[paramOffset]._uint); if (_output._framebuffer != framebuffer) { auto newFBO = getFramebufferID(framebuffer); @@ -208,7 +208,7 @@ void GLBackend::do_setFramebuffer(Batch& batch, uint32 paramOffset) { } } -void GLBackend::do_clearFramebuffer(Batch& batch, uint32 paramOffset) { +void GLBackend::do_clearFramebuffer(Batch& batch, size_t paramOffset) { if (_stereo._enable && !_pipeline._stateCache.scissorEnable) { qWarning("Clear without scissor in stereo mode"); } @@ -298,7 +298,7 @@ void GLBackend::do_clearFramebuffer(Batch& batch, uint32 paramOffset) { (void) CHECK_GL_ERROR(); } -void GLBackend::do_blit(Batch& batch, uint32 paramOffset) { +void GLBackend::do_blit(Batch& batch, size_t paramOffset) { auto srcframebuffer = batch._framebuffers.get(batch._params[paramOffset]._uint); Vec4i srcvp; for (size_t i = 0; i < 4; ++i) { diff --git a/libraries/gpu/src/gpu/GLBackendPipeline.cpp b/libraries/gpu/src/gpu/GLBackendPipeline.cpp index 8dd5242c3a..8601c7512b 100755 --- a/libraries/gpu/src/gpu/GLBackendPipeline.cpp +++ b/libraries/gpu/src/gpu/GLBackendPipeline.cpp @@ -57,7 +57,7 @@ GLBackend::GLPipeline* GLBackend::syncGPUObject(const Pipeline& pipeline) { return object; } -void GLBackend::do_setPipeline(Batch& batch, uint32 paramOffset) { +void GLBackend::do_setPipeline(Batch& batch, size_t paramOffset) { PipelinePointer pipeline = batch._pipelines.get(batch._params[paramOffset + 0]._uint); if (_pipeline._pipeline == pipeline) { @@ -168,7 +168,7 @@ void GLBackend::resetUniformStage() { } } -void GLBackend::do_setUniformBuffer(Batch& batch, uint32 paramOffset) { +void GLBackend::do_setUniformBuffer(Batch& batch, size_t paramOffset) { GLuint slot = batch._params[paramOffset + 3]._uint; BufferPointer uniformBuffer = batch._buffers.get(batch._params[paramOffset + 2]._uint); GLintptr rangeStart = batch._params[paramOffset + 1]._uint; @@ -237,7 +237,7 @@ void GLBackend::resetResourceStage() { } } -void GLBackend::do_setResourceTexture(Batch& batch, uint32 paramOffset) { +void GLBackend::do_setResourceTexture(Batch& batch, size_t paramOffset) { GLuint slot = batch._params[paramOffset + 1]._uint; TexturePointer resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint); diff --git a/libraries/gpu/src/gpu/GLBackendQuery.cpp b/libraries/gpu/src/gpu/GLBackendQuery.cpp index 5772a09943..0a76d38963 100644 --- a/libraries/gpu/src/gpu/GLBackendQuery.cpp +++ b/libraries/gpu/src/gpu/GLBackendQuery.cpp @@ -60,7 +60,7 @@ GLuint GLBackend::getQueryID(const QueryPointer& query) { } } -void GLBackend::do_beginQuery(Batch& batch, uint32 paramOffset) { +void GLBackend::do_beginQuery(Batch& batch, size_t paramOffset) { auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { @@ -74,7 +74,7 @@ void GLBackend::do_beginQuery(Batch& batch, uint32 paramOffset) { } } -void GLBackend::do_endQuery(Batch& batch, uint32 paramOffset) { +void GLBackend::do_endQuery(Batch& batch, size_t paramOffset) { auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { @@ -88,7 +88,7 @@ void GLBackend::do_endQuery(Batch& batch, uint32 paramOffset) { } } -void GLBackend::do_getQuery(Batch& batch, uint32 paramOffset) { +void GLBackend::do_getQuery(Batch& batch, size_t paramOffset) { auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { diff --git a/libraries/gpu/src/gpu/GLBackendShared.h b/libraries/gpu/src/gpu/GLBackendShared.h index 21bd10a33a..59da9ab9e9 100644 --- a/libraries/gpu/src/gpu/GLBackendShared.h +++ b/libraries/gpu/src/gpu/GLBackendShared.h @@ -11,6 +11,8 @@ #ifndef hifi_gpu_GLBackend_Shared_h #define hifi_gpu_GLBackend_Shared_h +#include + #include #include "GPULogging.h" diff --git a/libraries/gpu/src/gpu/GLBackendState.cpp b/libraries/gpu/src/gpu/GLBackendState.cpp index 895d0a0027..64bd87c876 100644 --- a/libraries/gpu/src/gpu/GLBackendState.cpp +++ b/libraries/gpu/src/gpu/GLBackendState.cpp @@ -763,7 +763,7 @@ void GLBackend::do_setStateColorWriteMask(uint32 mask) { } -void GLBackend::do_setStateBlendFactor(Batch& batch, uint32 paramOffset) { +void GLBackend::do_setStateBlendFactor(Batch& batch, size_t paramOffset) { Vec4 factor(batch._params[paramOffset + 0]._float, batch._params[paramOffset + 1]._float, @@ -774,7 +774,7 @@ void GLBackend::do_setStateBlendFactor(Batch& batch, uint32 paramOffset) { (void) CHECK_GL_ERROR(); } -void GLBackend::do_setStateScissorRect(Batch& batch, uint32 paramOffset) { +void GLBackend::do_setStateScissorRect(Batch& batch, size_t paramOffset) { Vec4i rect; memcpy(&rect, batch.editData(batch._params[paramOffset]._uint), sizeof(Vec4i)); diff --git a/libraries/gpu/src/gpu/GLBackendTransform.cpp b/libraries/gpu/src/gpu/GLBackendTransform.cpp index 963cab778f..686b52296f 100755 --- a/libraries/gpu/src/gpu/GLBackendTransform.cpp +++ b/libraries/gpu/src/gpu/GLBackendTransform.cpp @@ -15,22 +15,22 @@ using namespace gpu; // Transform Stage -void GLBackend::do_setModelTransform(Batch& batch, uint32 paramOffset) { +void GLBackend::do_setModelTransform(Batch& batch, size_t paramOffset) { _transform._model = batch._transforms.get(batch._params[paramOffset]._uint); _transform._invalidModel = true; } -void GLBackend::do_setViewTransform(Batch& batch, uint32 paramOffset) { +void GLBackend::do_setViewTransform(Batch& batch, size_t paramOffset) { _transform._view = batch._transforms.get(batch._params[paramOffset]._uint); _transform._invalidView = true; } -void GLBackend::do_setProjectionTransform(Batch& batch, uint32 paramOffset) { +void GLBackend::do_setProjectionTransform(Batch& batch, size_t paramOffset) { memcpy(&_transform._projection, batch.editData(batch._params[paramOffset]._uint), sizeof(Mat4)); _transform._invalidProj = true; } -void GLBackend::do_setViewportTransform(Batch& batch, uint32 paramOffset) { +void GLBackend::do_setViewportTransform(Batch& batch, size_t paramOffset) { memcpy(&_transform._viewport, batch.editData(batch._params[paramOffset]._uint), sizeof(Vec4i)); ivec4& vp = _transform._viewport; @@ -49,7 +49,7 @@ void GLBackend::do_setViewportTransform(Batch& batch, uint32 paramOffset) { _transform._invalidViewport = true; } -void GLBackend::do_setDepthRangeTransform(Batch& batch, uint32 paramOffset) { +void GLBackend::do_setDepthRangeTransform(Batch& batch, size_t paramOffset) { Vec2 depthRange(batch._params[paramOffset + 0]._float, batch._params[paramOffset + 1]._float); @@ -142,7 +142,8 @@ void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const Stereo } void GLBackend::TransformStageState::transfer() const { - static QByteArray bufferData; + // FIXME not thread safe + static std::vector bufferData; if (!_cameras.empty()) { glBindBuffer(GL_UNIFORM_BUFFER, _cameraBuffer); bufferData.resize(_cameraUboSize * _cameras.size()); @@ -168,22 +169,23 @@ void GLBackend::TransformStageState::transfer() const { } void GLBackend::TransformStageState::update(size_t commandIndex, const StereoState& stereo) const { - int offset = -1; + static const size_t INVALID_OFFSET = (size_t)-1; + size_t offset = INVALID_OFFSET; while ((_objectsItr != _objectOffsets.end()) && (commandIndex >= (*_objectsItr).first)) { offset = (*_objectsItr).second; ++_objectsItr; } - if (offset >= 0) { + if (offset != INVALID_OFFSET) { glBindBufferRange(GL_UNIFORM_BUFFER, TRANSFORM_OBJECT_SLOT, _objectBuffer, offset, sizeof(Backend::TransformObject)); } - offset = -1; + offset = INVALID_OFFSET; while ((_camerasItr != _cameraOffsets.end()) && (commandIndex >= (*_camerasItr).first)) { offset = (*_camerasItr).second; ++_camerasItr; } - if (offset >= 0) { + if (offset != INVALID_OFFSET) { // We include both camera offsets for stereo if (stereo._enable && stereo._pass) { offset += _cameraUboSize; diff --git a/libraries/gpu/src/gpu/Pipeline.cpp b/libraries/gpu/src/gpu/Pipeline.cpp index 501cb3fea4..e5d991f181 100755 --- a/libraries/gpu/src/gpu/Pipeline.cpp +++ b/libraries/gpu/src/gpu/Pipeline.cpp @@ -15,9 +15,7 @@ using namespace gpu; -Pipeline::Pipeline(): - _program(), - _state() +Pipeline::Pipeline() { } @@ -25,8 +23,8 @@ Pipeline::~Pipeline() { } -Pipeline* Pipeline::create(const ShaderPointer& program, const StatePointer& state) { - Pipeline* pipeline = new Pipeline(); +Pipeline::Pointer Pipeline::create(const ShaderPointer& program, const StatePointer& state) { + auto pipeline = Pointer(new Pipeline()); pipeline->_program = program; pipeline->_state = state; diff --git a/libraries/gpu/src/gpu/Pipeline.h b/libraries/gpu/src/gpu/Pipeline.h index 73e9a29913..adc65a0c66 100755 --- a/libraries/gpu/src/gpu/Pipeline.h +++ b/libraries/gpu/src/gpu/Pipeline.h @@ -22,7 +22,9 @@ namespace gpu { class Pipeline { public: - static Pipeline* create(const ShaderPointer& program, const StatePointer& state); + using Pointer = std::shared_ptr< Pipeline >; + + static Pointer create(const ShaderPointer& program, const StatePointer& state); ~Pipeline(); const ShaderPointer& getProgram() const { return _program; } @@ -44,7 +46,7 @@ protected: friend class Backend; }; -typedef std::shared_ptr< Pipeline > PipelinePointer; +typedef Pipeline::Pointer PipelinePointer; typedef std::vector< PipelinePointer > Pipelines; }; diff --git a/libraries/gpu/src/gpu/Resource.h b/libraries/gpu/src/gpu/Resource.h index 8d53d6e2e7..794ee680f4 100644 --- a/libraries/gpu/src/gpu/Resource.h +++ b/libraries/gpu/src/gpu/Resource.h @@ -26,9 +26,9 @@ namespace gpu { class Resource { public: - typedef unsigned int Size; + typedef size_t Size; - static const Size NOT_ALLOCATED = -1; + static const Size NOT_ALLOCATED = (Size)-1; // The size in bytes of data stored in the resource virtual Size getSize() const = 0; diff --git a/libraries/gpu/src/gpu/Shader.cpp b/libraries/gpu/src/gpu/Shader.cpp index 60bcbdaed0..ddb3a0d755 100755 --- a/libraries/gpu/src/gpu/Shader.cpp +++ b/libraries/gpu/src/gpu/Shader.cpp @@ -36,24 +36,20 @@ Shader::~Shader() { } -Shader* Shader::createVertex(const Source& source) { - Shader* shader = new Shader(VERTEX, source); - return shader; +Shader::Pointer Shader::createVertex(const Source& source) { + return Pointer(new Shader(VERTEX, source)); } -Shader* Shader::createPixel(const Source& source) { - Shader* shader = new Shader(PIXEL, source); - return shader; +Shader::Pointer Shader::createPixel(const Source& source) { + return Pointer(new Shader(PIXEL, source)); } -Shader* Shader::createProgram(Pointer& vertexShader, Pointer& pixelShader) { - if (vertexShader && vertexShader->getType() == VERTEX) { - if (pixelShader && pixelShader->getType() == PIXEL) { - Shader* shader = new Shader(PROGRAM, vertexShader, pixelShader); - return shader; - } +Shader::Pointer Shader::createProgram(Pointer& vertexShader, Pointer& pixelShader) { + if (vertexShader && vertexShader->getType() == VERTEX && + pixelShader && pixelShader->getType() == PIXEL) { + return Pointer(new Shader(PROGRAM, vertexShader, pixelShader)); } - return nullptr; + return Pointer(); } void Shader::defineSlots(const SlotSet& uniforms, const SlotSet& buffers, const SlotSet& textures, const SlotSet& samplers, const SlotSet& inputs, const SlotSet& outputs) { diff --git a/libraries/gpu/src/gpu/Shader.h b/libraries/gpu/src/gpu/Shader.h index 55812c6166..bceb00c71e 100755 --- a/libraries/gpu/src/gpu/Shader.h +++ b/libraries/gpu/src/gpu/Shader.h @@ -108,10 +108,10 @@ public: PROGRAM, }; - static Shader* createVertex(const Source& source); - static Shader* createPixel(const Source& source); + static Pointer createVertex(const Source& source); + static Pointer createPixel(const Source& source); - static Shader* createProgram(Pointer& vertexShader, Pointer& pixelShader); + static Pointer createProgram(Pointer& vertexShader, Pointer& pixelShader); ~Shader(); diff --git a/libraries/gpu/src/gpu/StandardShaderLib.cpp b/libraries/gpu/src/gpu/StandardShaderLib.cpp index 864bff08c9..81500347fd 100755 --- a/libraries/gpu/src/gpu/StandardShaderLib.cpp +++ b/libraries/gpu/src/gpu/StandardShaderLib.cpp @@ -39,7 +39,7 @@ ShaderPointer StandardShaderLib::getProgram(GetShader getVS, GetShader getPS) { } else { auto vs = (getVS)(); auto ps = (getPS)(); - auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); + auto program = gpu::Shader::createProgram(vs, ps); if (program) { // Program created, let's try to make it if (gpu::Shader::makeProgram((*program))) { @@ -59,42 +59,42 @@ ShaderPointer StandardShaderLib::getProgram(GetShader getVS, GetShader getPS) { ShaderPointer StandardShaderLib::getDrawUnitQuadTexcoordVS() { if (!_drawUnitQuadTexcoordVS) { - _drawUnitQuadTexcoordVS = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(DrawUnitQuadTexcoord_vert))); + _drawUnitQuadTexcoordVS = gpu::Shader::createVertex(std::string(DrawUnitQuadTexcoord_vert)); } return _drawUnitQuadTexcoordVS; } ShaderPointer StandardShaderLib::getDrawTransformUnitQuadVS() { if (!_drawTransformUnitQuadVS) { - _drawTransformUnitQuadVS = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(DrawTransformUnitQuad_vert))); + _drawTransformUnitQuadVS = gpu::Shader::createVertex(std::string(DrawTransformUnitQuad_vert)); } return _drawTransformUnitQuadVS; } ShaderPointer StandardShaderLib::getDrawTexcoordRectTransformUnitQuadVS() { if (!_drawTexcoordRectTransformUnitQuadVS) { - _drawTexcoordRectTransformUnitQuadVS = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(DrawTexcoordRectTransformUnitQuad_vert))); + _drawTexcoordRectTransformUnitQuadVS = gpu::Shader::createVertex(std::string(DrawTexcoordRectTransformUnitQuad_vert)); } return _drawTexcoordRectTransformUnitQuadVS; } ShaderPointer StandardShaderLib::getDrawViewportQuadTransformTexcoordVS() { if (!_drawViewportQuadTransformTexcoordVS) { - _drawViewportQuadTransformTexcoordVS = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(DrawViewportQuadTransformTexcoord_vert))); + _drawViewportQuadTransformTexcoordVS = gpu::Shader::createVertex(std::string(DrawViewportQuadTransformTexcoord_vert)); } return _drawViewportQuadTransformTexcoordVS; } ShaderPointer StandardShaderLib::getDrawTexturePS() { if (!_drawTexturePS) { - _drawTexturePS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(DrawTexture_frag))); + _drawTexturePS = gpu::Shader::createPixel(std::string(DrawTexture_frag)); } return _drawTexturePS; } ShaderPointer StandardShaderLib::getDrawTextureOpaquePS() { if (!_drawTextureOpaquePS) { - _drawTextureOpaquePS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(DrawTextureOpaque_frag))); + _drawTextureOpaquePS = gpu::Shader::createPixel(std::string(DrawTextureOpaque_frag)); } return _drawTextureOpaquePS; } @@ -103,7 +103,7 @@ ShaderPointer StandardShaderLib::getDrawTextureOpaquePS() { ShaderPointer StandardShaderLib::getDrawColoredTexturePS() { if (!_drawColoredTexturePS) { - _drawColoredTexturePS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(DrawColoredTexture_frag))); + _drawColoredTexturePS = gpu::Shader::createPixel(std::string(DrawColoredTexture_frag)); } return _drawColoredTexturePS; } diff --git a/libraries/gpu/src/gpu/Stream.h b/libraries/gpu/src/gpu/Stream.h index 492af5f62a..4fff3b651d 100644 --- a/libraries/gpu/src/gpu/Stream.h +++ b/libraries/gpu/src/gpu/Stream.h @@ -93,19 +93,14 @@ public: }; typedef std::map< Slot, ChannelInfo > ChannelMap; - Format() : - _attributes(), - _elementTotalSize(0) {} - ~Format() {} - - uint32 getNumAttributes() const { return _attributes.size(); } + size_t getNumAttributes() const { return _attributes.size(); } const AttributeMap& getAttributes() const { return _attributes; } - uint8 getNumChannels() const { return _channels.size(); } + size_t getNumChannels() const { return _channels.size(); } const ChannelMap& getChannels() const { return _channels; } Offset getChannelStride(Slot channel) const { return _channels.at(channel)._stride; } - uint32 getElementTotalSize() const { return _elementTotalSize; } + size_t getElementTotalSize() const { return _elementTotalSize; } bool setAttribute(Slot slot, Slot channel, Element element, Offset offset = 0, Frequency frequency = PER_VERTEX); bool setAttribute(Slot slot, Frequency frequency = PER_VERTEX); @@ -115,7 +110,7 @@ public: protected: AttributeMap _attributes; ChannelMap _channels; - uint32 _elementTotalSize; + uint32 _elementTotalSize { 0 }; void evaluateCache(); }; @@ -140,7 +135,7 @@ public: const Buffers& getBuffers() const { return _buffers; } const Offsets& getOffsets() const { return _offsets; } const Strides& getStrides() const { return _strides; } - uint32 getNumBuffers() const { return _buffers.size(); } + size_t getNumBuffers() const { return _buffers.size(); } BufferStream makeRangedStream(uint32 offset, uint32 count = -1) const; diff --git a/libraries/input-plugins/src/input-plugins/SixenseManager.cpp b/libraries/input-plugins/src/input-plugins/SixenseManager.cpp index 18fdc9ddad..5dd0248224 100644 --- a/libraries/input-plugins/src/input-plugins/SixenseManager.cpp +++ b/libraries/input-plugins/src/input-plugins/SixenseManager.cpp @@ -68,7 +68,7 @@ void SixenseManager::activate() { #ifdef HAVE_SIXENSE _container->addMenu(MENU_PATH); - _container->addMenuItem(MENU_PATH, TOGGLE_SMOOTH, + _container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, TOGGLE_SMOOTH, [this] (bool clicked) { setSixenseFilter(clicked); }, true, true); diff --git a/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp b/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp index ec0c35cc96..5315152cea 100644 --- a/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp +++ b/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp @@ -35,16 +35,19 @@ vr::IVRSystem* acquireOpenVrSystem(); void releaseOpenVrSystem(); -const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches -const QString CONTROLLER_MODEL_STRING = "vr_controller_05_wireless_b"; +static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches +static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, + CONTROLLER_LENGTH_OFFSET / 2.0f, + CONTROLLER_LENGTH_OFFSET * 2.0f); +static const QString CONTROLLER_MODEL_STRING = "vr_controller_05_wireless_b"; + +static const QString MENU_PARENT = "Avatar"; +static const QString MENU_NAME = "Vive Controllers"; +static const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; +static const QString RENDER_CONTROLLERS = "Render Hand Controllers"; const QString ViveControllerManager::NAME = "OpenVR"; -const QString MENU_PARENT = "Avatar"; -const QString MENU_NAME = "Vive Controllers"; -const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; -const QString RENDER_CONTROLLERS = "Render Hand Controllers"; - bool ViveControllerManager::isSupported() const { #ifdef Q_OS_WIN auto hmd = acquireOpenVrSystem(); @@ -60,7 +63,7 @@ void ViveControllerManager::activate() { InputPlugin::activate(); #ifdef Q_OS_WIN _container->addMenu(MENU_PATH); - _container->addMenuItem(MENU_PATH, RENDER_CONTROLLERS, + _container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, RENDER_CONTROLLERS, [this] (bool clicked) { this->setRenderControllers(clicked); }, true, true); @@ -320,14 +323,11 @@ void ViveControllerManager::InputDevice::handleButtonEvent(uint32_t button, bool } void ViveControllerManager::InputDevice::handlePoseEvent(const mat4& mat, bool left) { - glm::vec3 position = extractTranslation(mat); - glm::quat rotation = glm::quat_cast(mat); - // When the sensor-to-world rotation is identity the coordinate axes look like this: // // user // forward - // z + // -z // | // y| user // y o----x right @@ -372,17 +372,27 @@ void ViveControllerManager::InputDevice::handlePoseEvent(const mat4& mat, bool l // Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX) // // Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX) - - const glm::quat quarterX = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f)); - const glm::quat yFlip = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)); - float sign = left ? -1.0f : 1.0f; - const glm::quat signedQuaterZ = glm::angleAxis(sign * PI / 2.0f, glm::vec3(0.0f, 0.0f, 1.0f)); - const glm::quat eighthX = glm::angleAxis(PI / 4.0f, glm::vec3(1.0f, 0.0f, 0.0f)); - const glm::quat offset = glm::inverse(signedQuaterZ * eighthX); - rotation = rotation * offset * yFlip * quarterX; - position += rotation * glm::vec3(0, 0, -CONTROLLER_LENGTH_OFFSET); + static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y); + static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X); + static const glm::quat viveToHand = yFlip * quarterX; + static const glm::quat leftQuaterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z); + static const glm::quat rightQuaterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z); + static const glm::quat eighthX = glm::angleAxis(PI / 4.0f, Vectors::UNIT_X); + + static const glm::quat leftRotationOffset = glm::inverse(leftQuaterZ * eighthX) * viveToHand; + static const glm::quat rightRotationOffset = glm::inverse(rightQuaterZ * eighthX) * viveToHand; + + static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; + static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; + + glm::vec3 position = extractTranslation(mat); + glm::quat rotation = glm::quat_cast(mat); + + position += rotation * (left ? leftTranslationOffset : rightTranslationOffset); + rotation = rotation * (left ? leftRotationOffset : rightRotationOffset); + _poseStateMap[left ? controller::LEFT_HAND : controller::RIGHT_HAND] = controller::Pose(position, rotation); } diff --git a/libraries/model/src/model/Skybox.cpp b/libraries/model/src/model/Skybox.cpp index 21b40a54c8..3e445933fa 100755 --- a/libraries/model/src/model/Skybox.cpp +++ b/libraries/model/src/model/Skybox.cpp @@ -88,9 +88,9 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky } { - auto skyVS = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(Skybox_vert))); - auto skyFS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(Skybox_frag))); - auto skyShader = gpu::ShaderPointer(gpu::Shader::createProgram(skyVS, skyFS)); + auto skyVS = gpu::Shader::createVertex(std::string(Skybox_vert)); + auto skyFS = gpu::Shader::createPixel(std::string(Skybox_frag)); + auto skyShader = gpu::Shader::createProgram(skyVS, skyFS); gpu::Shader::BindingSet bindings; bindings.insert(gpu::Shader::Binding(std::string("cubeMap"), SKYBOX_SKYMAP_SLOT)); @@ -102,7 +102,7 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky auto skyState = std::make_shared(); skyState->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); - thePipeline = gpu::PipelinePointer(gpu::Pipeline::create(skyShader, skyState)); + thePipeline = gpu::Pipeline::create(skyShader, skyState); } }); diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 83d91b32d3..4e7e7f24ba 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -40,7 +40,7 @@ AssetClient::AssetClient() { auto nodeList = DependencyManager::get(); auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::AssetGetInfoReply, this, "handleAssetGetInfoReply"); - packetReceiver.registerMessageListener(PacketType::AssetGetReply, this, "handleAssetGetReply"); + packetReceiver.registerListener(PacketType::AssetGetReply, this, "handleAssetGetReply", true); packetReceiver.registerListener(PacketType::AssetUploadReply, this, "handleAssetUploadReply"); connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &AssetClient::handleNodeKilled); @@ -125,7 +125,7 @@ AssetUpload* AssetClient::createUpload(const QByteArray& data, const QString& ex } bool AssetClient::getAsset(const QString& hash, const QString& extension, DataOffset start, DataOffset end, - ReceivedAssetCallback callback) { + ReceivedAssetCallback callback, ProgressCallback progressCallback) { if (hash.length() != SHA256_HASH_HEX_LENGTH) { qCWarning(asset_client) << "Invalid hash size"; return false; @@ -156,7 +156,7 @@ bool AssetClient::getAsset(const QString& hash, const QString& extension, DataOf nodeList->sendPacket(std::move(packet), *assetServer); - _pendingRequests[assetServer][messageID] = callback; + _pendingRequests[assetServer][messageID] = { callback, progressCallback }; return true; } @@ -189,18 +189,18 @@ bool AssetClient::getAssetInfo(const QString& hash, const QString& extension, Ge return false; } -void AssetClient::handleAssetGetInfoReply(QSharedPointer packet, SharedNodePointer senderNode) { +void AssetClient::handleAssetGetInfoReply(QSharedPointer message, SharedNodePointer senderNode) { MessageID messageID; - packet->readPrimitive(&messageID); - auto assetHash = packet->read(SHA256_HASH_LENGTH); + message->readPrimitive(&messageID); + auto assetHash = message->read(SHA256_HASH_LENGTH); AssetServerError error; - packet->readPrimitive(&error); + message->readPrimitive(&error); AssetInfo info { assetHash.toHex(), 0 }; if (error == AssetServerError::NoError) { - packet->readPrimitive(&info.size); + message->readPrimitive(&info.size); } // Check if we have any pending requests for this node @@ -223,25 +223,19 @@ void AssetClient::handleAssetGetInfoReply(QSharedPointer packet, Share } } -void AssetClient::handleAssetGetReply(QSharedPointer packetList, SharedNodePointer senderNode) { - QByteArray data = packetList->getMessage(); - QBuffer packet { &data }; - packet.open(QIODevice::ReadOnly); - - auto assetHash = packet.read(SHA256_HASH_LENGTH); +void AssetClient::handleAssetGetReply(QSharedPointer message, SharedNodePointer senderNode) { + auto assetHash = message->read(SHA256_HASH_LENGTH); qCDebug(asset_client) << "Got reply for asset: " << assetHash.toHex(); MessageID messageID; - packet.read(reinterpret_cast(&messageID), sizeof(messageID)); + message->readHeadPrimitive(&messageID); AssetServerError error; - packet.read(reinterpret_cast(&error), sizeof(AssetServerError)); - QByteArray assetData; + message->readHeadPrimitive(&error); + DataOffset length = 0; if (!error) { - DataOffset length; - packet.read(reinterpret_cast(&length), sizeof(DataOffset)); - data = packet.read(length); + message->readHeadPrimitive(&length); } else { qCWarning(asset_client) << "Failure getting asset: " << error; } @@ -256,8 +250,22 @@ void AssetClient::handleAssetGetReply(QSharedPointer packetList, S // Check if we have this pending request auto requestIt = messageCallbackMap.find(messageID); if (requestIt != messageCallbackMap.end()) { - auto callback = requestIt->second; - callback(true, error, data); + auto& callbacks = requestIt->second; + + if (message->isComplete()) { + callbacks.completeCallback(true, error, message->readAll()); + } else { + connect(message.data(), &ReceivedMessage::progress, this, [this, length, message, callbacks]() { + callbacks.progressCallback(message->getSize(), length); + }); + connect(message.data(), &ReceivedMessage::completed, this, [this, message, error, callbacks]() { + if (message->failed()) { + callbacks.completeCallback(false, AssetServerError::NoError, QByteArray()); + } else { + callbacks.completeCallback(true, error, message->readAll()); + } + }); + } messageCallbackMap.erase(requestIt); } @@ -292,19 +300,19 @@ bool AssetClient::uploadAsset(const QByteArray& data, const QString& extension, return false; } -void AssetClient::handleAssetUploadReply(QSharedPointer packet, SharedNodePointer senderNode) { +void AssetClient::handleAssetUploadReply(QSharedPointer message, SharedNodePointer senderNode) { MessageID messageID; - packet->readPrimitive(&messageID); + message->readPrimitive(&messageID); AssetServerError error; - packet->readPrimitive(&error); + message->readPrimitive(&error); QString hashString; if (error) { qCWarning(asset_client) << "Error uploading file to asset server"; } else { - auto hash = packet->read(SHA256_HASH_LENGTH); + auto hash = message->read(SHA256_HASH_LENGTH); hashString = hash.toHex(); qCDebug(asset_client) << "Successfully uploaded asset to asset-server - SHA256 hash is " << hashString; @@ -339,7 +347,7 @@ void AssetClient::handleNodeKilled(SharedNodePointer node) { auto messageMapIt = _pendingRequests.find(node); if (messageMapIt != _pendingRequests.end()) { for (const auto& value : messageMapIt->second) { - value.second(false, AssetServerError::NoError, QByteArray()); + value.second.completeCallback(false, AssetServerError::NoError, QByteArray()); } messageMapIt->second.clear(); } diff --git a/libraries/networking/src/AssetClient.h b/libraries/networking/src/AssetClient.h index 0616317eec..58790ef926 100644 --- a/libraries/networking/src/AssetClient.h +++ b/libraries/networking/src/AssetClient.h @@ -22,6 +22,7 @@ #include "LimitedNodeList.h" #include "NLPacket.h" #include "Node.h" +#include "ReceivedMessage.h" #include "ResourceCache.h" class AssetRequest; @@ -35,7 +36,7 @@ struct AssetInfo { using ReceivedAssetCallback = std::function; using GetInfoCallback = std::function; using UploadResultCallback = std::function; - +using ProgressCallback = std::function; class AssetClient : public QObject, public Dependency { @@ -50,19 +51,25 @@ public: Q_INVOKABLE AssetUpload* createUpload(const QByteArray& data, const QString& extension); private slots: - void handleAssetGetInfoReply(QSharedPointer packet, SharedNodePointer senderNode); - void handleAssetGetReply(QSharedPointer packetList, SharedNodePointer senderNode); - void handleAssetUploadReply(QSharedPointer packet, SharedNodePointer senderNode); + void handleAssetGetInfoReply(QSharedPointer message, SharedNodePointer senderNode); + void handleAssetGetReply(QSharedPointer message, SharedNodePointer senderNode); + void handleAssetUploadReply(QSharedPointer message, SharedNodePointer senderNode); void handleNodeKilled(SharedNodePointer node); private: bool getAssetInfo(const QString& hash, const QString& extension, GetInfoCallback callback); - bool getAsset(const QString& hash, const QString& extension, DataOffset start, DataOffset end, ReceivedAssetCallback callback); + bool getAsset(const QString& hash, const QString& extension, DataOffset start, DataOffset end, + ReceivedAssetCallback callback, ProgressCallback progressCallback); bool uploadAsset(const QByteArray& data, const QString& extension, UploadResultCallback callback); + struct GetAssetCallbacks { + ReceivedAssetCallback completeCallback; + ProgressCallback progressCallback; + }; + static MessageID _currentID; - std::unordered_map> _pendingRequests; + std::unordered_map> _pendingRequests; std::unordered_map> _pendingInfoRequests; std::unordered_map> _pendingUploads; diff --git a/libraries/networking/src/AssetRequest.cpp b/libraries/networking/src/AssetRequest.cpp index 7e647e9142..19f91349fb 100644 --- a/libraries/networking/src/AssetRequest.cpp +++ b/libraries/networking/src/AssetRequest.cpp @@ -124,6 +124,8 @@ void AssetRequest::start() { _state = Finished; emit finished(this); + }, [this](qint64 totalReceived, qint64 total) { + emit progress(totalReceived, total); }); }); } diff --git a/libraries/networking/src/AssetResourceRequest.cpp b/libraries/networking/src/AssetResourceRequest.cpp index eba9e45e5c..dac3329153 100644 --- a/libraries/networking/src/AssetResourceRequest.cpp +++ b/libraries/networking/src/AssetResourceRequest.cpp @@ -46,7 +46,7 @@ void AssetResourceRequest::doSend() { } connect(_assetRequest, &AssetRequest::progress, this, &AssetResourceRequest::progress); - connect(_assetRequest, &AssetRequest::finished, [this](AssetRequest* req) { + connect(_assetRequest, &AssetRequest::finished, this, [this](AssetRequest* req) { Q_ASSERT(_state == InProgress); Q_ASSERT(req == _assetRequest); Q_ASSERT(req->getState() == AssetRequest::Finished); diff --git a/libraries/networking/src/AssetUpload.cpp b/libraries/networking/src/AssetUpload.cpp index e6f467e717..2d6f0c0e97 100644 --- a/libraries/networking/src/AssetUpload.cpp +++ b/libraries/networking/src/AssetUpload.cpp @@ -63,6 +63,7 @@ void AssetUpload::start() { // file opened, read the data and grab the extension _extension = QFileInfo(_filename).suffix(); + _extension = _extension.toLower(); _data = file.readAll(); } else { diff --git a/libraries/networking/src/Assignment.cpp b/libraries/networking/src/Assignment.cpp index 5fdedeafb9..4c72a16f1d 100644 --- a/libraries/networking/src/Assignment.cpp +++ b/libraries/networking/src/Assignment.cpp @@ -68,20 +68,20 @@ Assignment::Assignment(Assignment::Command command, Assignment::Type type, const } } -Assignment::Assignment(NLPacket& packet) : +Assignment::Assignment(ReceivedMessage& message) : _pool(), _location(GlobalLocation), _payload(), _walletUUID(), _nodeVersion() { - if (packet.getType() == PacketType::RequestAssignment) { + if (message.getType() == PacketType::RequestAssignment) { _command = Assignment::RequestCommand; - } else if (packet.getType() == PacketType::CreateAssignment) { + } else if (message.getType() == PacketType::CreateAssignment) { _command = Assignment::CreateCommand; } - QDataStream packetStream(&packet); + QDataStream packetStream(message.getMessage()); packetStream >> *this; } diff --git a/libraries/networking/src/Assignment.h b/libraries/networking/src/Assignment.h index 9639411eec..399deaa314 100644 --- a/libraries/networking/src/Assignment.h +++ b/libraries/networking/src/Assignment.h @@ -14,6 +14,8 @@ #include +#include "ReceivedMessage.h" + #include "NodeList.h" const int MAX_PAYLOAD_BYTES = 1024; @@ -61,7 +63,7 @@ public: /// Constructs an Assignment from a network packet /// \param packet the packet to un-pack the assignment from - Assignment(NLPacket& packet); + Assignment(ReceivedMessage& packet); void setUUID(const QUuid& uuid) { _uuid = uuid; } const QUuid& getUUID() const { return _uuid; } diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 681f971ef5..db775983e1 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -94,7 +94,7 @@ void DomainHandler::softReset() { clearSettings(); // cancel the failure timeout for any pending requests for settings - QMetaObject::invokeMethod(&_settingsTimer, "stop", Qt::AutoConnection); + QMetaObject::invokeMethod(&_settingsTimer, "stop"); } void DomainHandler::hardReset() { @@ -276,7 +276,7 @@ void DomainHandler::requestDomainSettings() { } } -void DomainHandler::processSettingsPacketList(QSharedPointer packetList) { +void DomainHandler::processSettingsPacketList(QSharedPointer packetList) { // stop our settings timer since we successfully requested the settings we need _settingsTimer.stop(); @@ -291,8 +291,8 @@ void DomainHandler::processSettingsPacketList(QSharedPointer packe emit settingsReceived(_settingsObject); } -void DomainHandler::processICEPingReplyPacket(QSharedPointer packet) { - const HifiSockAddr& senderSockAddr = packet->getSenderSockAddr(); +void DomainHandler::processICEPingReplyPacket(QSharedPointer message) { + const HifiSockAddr& senderSockAddr = message->getSenderSockAddr(); qCDebug(networking) << "Received reply from domain-server on" << senderSockAddr; if (getIP().isNull()) { @@ -309,10 +309,10 @@ void DomainHandler::processICEPingReplyPacket(QSharedPointer packet) { } } -void DomainHandler::processDTLSRequirementPacket(QSharedPointer dtlsRequirementPacket) { +void DomainHandler::processDTLSRequirementPacket(QSharedPointer message) { // figure out the port that the DS wants us to use for us to talk to them with DTLS unsigned short dtlsPort; - dtlsRequirementPacket->readPrimitive(&dtlsPort); + message->readPrimitive(&dtlsPort); qCDebug(networking) << "domain-server DTLS port changed to" << dtlsPort << "- Enabling DTLS."; @@ -321,14 +321,14 @@ void DomainHandler::processDTLSRequirementPacket(QSharedPointer dtlsRe // initializeDTLSSession(); } -void DomainHandler::processICEResponsePacket(QSharedPointer icePacket) { +void DomainHandler::processICEResponsePacket(QSharedPointer message) { if (_icePeer.hasSockets()) { qDebug() << "Received an ICE peer packet for domain-server but we already have sockets. Not processing."; // bail on processing this packet if our ice peer doesn't have sockets return; } - QDataStream iceResponseStream(icePacket.data()); + QDataStream iceResponseStream(message->getMessage()); iceResponseStream >> _icePeer; diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 25d1c910cd..f60ac2fbe6 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -24,6 +24,7 @@ #include "NLPacket.h" #include "NLPacketList.h" #include "Node.h" +#include "ReceivedMessage.h" const unsigned short DEFAULT_DOMAIN_SERVER_PORT = 40102; const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT = 40103; @@ -87,10 +88,10 @@ public slots: void setHostnameAndPort(const QString& hostname, quint16 port = DEFAULT_DOMAIN_SERVER_PORT); void setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id); - void processSettingsPacketList(QSharedPointer packetList); - void processICEPingReplyPacket(QSharedPointer packet); - void processDTLSRequirementPacket(QSharedPointer dtlsRequirementPacket); - void processICEResponsePacket(QSharedPointer icePacket); + void processSettingsPacketList(QSharedPointer packetList); + void processICEPingReplyPacket(QSharedPointer message); + void processDTLSRequirementPacket(QSharedPointer dtlsRequirementPacket); + void processICEResponsePacket(QSharedPointer icePacket); private slots: void completedHostnameLookup(const QHostInfo& hostInfo); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index e717856ca2..4419ba882a 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -60,10 +60,6 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short if (firstCall) { NodeType::init(); - // register the SharedNodePointer meta-type for signals/slots - qRegisterMetaType>(); - qRegisterMetaType(); - firstCall = false; } @@ -99,9 +95,14 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short _packetReceiver->handleVerifiedPacket(std::move(packet)); } ); - _nodeSocket.setPacketListHandler( - [this](std::unique_ptr packetList) { - _packetReceiver->handleVerifiedPacketList(std::move(packetList)); + _nodeSocket.setMessageHandler( + [this](std::unique_ptr packet) { + _packetReceiver->handleVerifiedMessagePacket(std::move(packet)); + } + ); + _nodeSocket.setMessageFailureHandler( + [this](HifiSockAddr from, udt::Packet::MessageNumber messageNumber) { + _packetReceiver->handleMessageFailure(from, messageNumber); } ); @@ -411,7 +412,7 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const Node& return sendPacket(std::move(packet), destinationSockAddr, destinationNode.getConnectionSecret()); } -int LimitedNodeList::updateNodeWithDataFromPacket(QSharedPointer packet, SharedNodePointer sendingNode) { +int LimitedNodeList::updateNodeWithDataFromPacket(QSharedPointer message, SharedNodePointer sendingNode) { QMutexLocker locker(&sendingNode->getMutex()); NodeData* linkedData = sendingNode->getLinkedData(); @@ -421,7 +422,7 @@ int LimitedNodeList::updateNodeWithDataFromPacket(QSharedPointer packe if (linkedData) { QMutexLocker linkedDataLocker(&linkedData->getMutex()); - return linkedData->parseData(*packet); + return linkedData->parseData(*message); } return 0; @@ -435,17 +436,23 @@ SharedNodePointer LimitedNodeList::nodeWithUUID(const QUuid& nodeUUID) { } void LimitedNodeList::eraseAllNodes() { - qCDebug(networking) << "Clearing the NodeList. Deleting all nodes in list."; - QSet killedNodes; - eachNode([&killedNodes](const SharedNodePointer& node){ - killedNodes.insert(node); - }); { - // iterate the current nodes, emit that they are dying and remove them from the hash + // iterate the current nodes - grab them so we can emit that they are dying + // and then remove them from the hash QWriteLocker writeLocker(&_nodeMutex); - _nodeHash.clear(); + + if (_nodeHash.size() > 0) { + qCDebug(networking) << "LimitedNodeList::eraseAllNodes() removing all nodes from NodeList."; + + auto it = _nodeHash.begin(); + + while (it != _nodeHash.end()) { + killedNodes.insert(it->second); + it = _nodeHash.unsafe_erase(it); + } + } } foreach(const SharedNodePointer& killedNode, killedNodes) { @@ -481,9 +488,9 @@ bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID) { return false; } -void LimitedNodeList::processKillNode(NLPacket& packet) { +void LimitedNodeList::processKillNode(ReceivedMessage& message) { // read the node id - QUuid nodeUUID = QUuid::fromRfc4122(packet.readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + QUuid nodeUUID = QUuid::fromRfc4122(message.readWithoutCopy(NUM_BYTES_RFC4122_UUID)); // kill the node with this UUID, if it exists killNodeWithUUID(nodeUUID); @@ -556,11 +563,11 @@ std::unique_ptr LimitedNodeList::constructPingPacket(PingType_t pingTy return pingPacket; } -std::unique_ptr LimitedNodeList::constructPingReplyPacket(NLPacket& pingPacket) { +std::unique_ptr LimitedNodeList::constructPingReplyPacket(ReceivedMessage& message) { PingType_t typeFromOriginalPing; quint64 timeFromOriginalPing; - pingPacket.readPrimitive(&typeFromOriginalPing); - pingPacket.readPrimitive(&timeFromOriginalPing); + message.readPrimitive(&typeFromOriginalPing); + message.readPrimitive(&timeFromOriginalPing); int packetSize = sizeof(PingType_t) + sizeof(quint64) + sizeof(quint64); auto replyPacket = NLPacket::create(PacketType::PingReply, packetSize); @@ -581,11 +588,11 @@ std::unique_ptr LimitedNodeList::constructICEPingPacket(PingType_t pin return icePingPacket; } -std::unique_ptr LimitedNodeList::constructICEPingReplyPacket(NLPacket& pingPacket, const QUuid& iceID) { +std::unique_ptr LimitedNodeList::constructICEPingReplyPacket(ReceivedMessage& message, const QUuid& iceID) { // pull out the ping type so we can reply back with that PingType_t pingType; - memcpy(&pingType, pingPacket.getPayload() + NUM_BYTES_RFC4122_UUID, sizeof(PingType_t)); + memcpy(&pingType, message.getRawMessage() + NUM_BYTES_RFC4122_UUID, sizeof(PingType_t)); int packetSize = NUM_BYTES_RFC4122_UUID + sizeof(PingType_t); auto icePingReplyPacket = NLPacket::create(PacketType::ICEPingReply, packetSize); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 8daeb3d256..7f95291e5b 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -37,8 +37,9 @@ #include "DomainHandler.h" #include "Node.h" #include "NLPacket.h" -#include "PacketReceiver.h" #include "NLPacketList.h" +#include "PacketReceiver.h" +#include "ReceivedMessage.h" #include "udt/PacketHeaders.h" #include "udt/Socket.h" #include "UUIDHasher.h" @@ -144,9 +145,9 @@ public: const HifiSockAddr& getLocalSockAddr() const { return _localSockAddr; } const HifiSockAddr& getSTUNSockAddr() const { return _stunSockAddr; } - void processKillNode(NLPacket& packet); + void processKillNode(ReceivedMessage& message); - int updateNodeWithDataFromPacket(QSharedPointer packet, SharedNodePointer matchingNode); + int updateNodeWithDataFromPacket(QSharedPointer packet, SharedNodePointer matchingNode); unsigned int broadcastToNodes(std::unique_ptr packet, const NodeSet& destinationNodeTypes); SharedNodePointer soloNodeOfType(NodeType_t nodeType); @@ -155,10 +156,10 @@ public: void resetPacketStats(); std::unique_ptr constructPingPacket(PingType_t pingType = PingType::Agnostic); - std::unique_ptr constructPingReplyPacket(NLPacket& pingPacket); + std::unique_ptr constructPingReplyPacket(ReceivedMessage& message); std::unique_ptr constructICEPingPacket(PingType_t pingType, const QUuid& iceID); - std::unique_ptr constructICEPingReplyPacket(NLPacket& pingPacket, const QUuid& iceID); + std::unique_ptr constructICEPingReplyPacket(ReceivedMessage& message, const QUuid& iceID); void sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr); void sendPeerQueryToIceServer(const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerID); diff --git a/libraries/networking/src/MessagesClient.cpp b/libraries/networking/src/MessagesClient.cpp index ef8ecb534c..3a03027da0 100644 --- a/libraries/networking/src/MessagesClient.cpp +++ b/libraries/networking/src/MessagesClient.cpp @@ -26,7 +26,7 @@ MessagesClient::MessagesClient() { }); auto nodeList = DependencyManager::get(); auto& packetReceiver = nodeList->getPacketReceiver(); - packetReceiver.registerMessageListener(PacketType::MessagesData, this, "handleMessagesPacket"); + packetReceiver.registerListener(PacketType::MessagesData, this, "handleMessagesPacket"); connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &MessagesClient::handleNodeActivated); } @@ -36,22 +36,18 @@ void MessagesClient::init() { } } -void MessagesClient::decodeMessagesPacket(QSharedPointer packetList, QString& channel, QString& message, QUuid& senderID) { - QByteArray packetData = packetList->getMessage(); - QBuffer packet{ &packetData }; - packet.open(QIODevice::ReadOnly); - +void MessagesClient::decodeMessagesPacket(QSharedPointer receivedMessage, QString& channel, QString& message, QUuid& senderID) { quint16 channelLength; - packet.read(reinterpret_cast(&channelLength), sizeof(channelLength)); - auto channelData = packet.read(channelLength); + receivedMessage->readPrimitive(&channelLength); + auto channelData = receivedMessage->read(channelLength); channel = QString::fromUtf8(channelData); quint16 messageLength; - packet.read(reinterpret_cast(&messageLength), sizeof(messageLength)); - auto messageData = packet.read(messageLength); + receivedMessage->readPrimitive(&messageLength); + auto messageData = receivedMessage->read(messageLength); message = QString::fromUtf8(messageData); - QByteArray bytesSenderID = packet.read(NUM_BYTES_RFC4122_UUID); + QByteArray bytesSenderID = receivedMessage->read(NUM_BYTES_RFC4122_UUID); if (bytesSenderID.length() == NUM_BYTES_RFC4122_UUID) { senderID = QUuid::fromRfc4122(bytesSenderID); } else { @@ -79,10 +75,10 @@ std::unique_ptr MessagesClient::encodeMessagesPacket(QString chann } -void MessagesClient::handleMessagesPacket(QSharedPointer packetList, SharedNodePointer senderNode) { +void MessagesClient::handleMessagesPacket(QSharedPointer receivedMessage, SharedNodePointer senderNode) { QString channel, message; QUuid senderID; - decodeMessagesPacket(packetList, channel, message, senderID); + decodeMessagesPacket(receivedMessage, channel, message, senderID); emit messageReceived(channel, message, senderID); } diff --git a/libraries/networking/src/MessagesClient.h b/libraries/networking/src/MessagesClient.h index d5d94f62c4..ff7874fd8e 100644 --- a/libraries/networking/src/MessagesClient.h +++ b/libraries/networking/src/MessagesClient.h @@ -20,6 +20,7 @@ #include "LimitedNodeList.h" #include "NLPacket.h" #include "Node.h" +#include "ReceivedMessage.h" class MessagesClient : public QObject, public Dependency { Q_OBJECT @@ -32,7 +33,7 @@ public: Q_INVOKABLE void subscribe(QString channel); Q_INVOKABLE void unsubscribe(QString channel); - static void decodeMessagesPacket(QSharedPointer packetList, QString& channel, QString& message, QUuid& senderID); + static void decodeMessagesPacket(QSharedPointer receivedMessage, QString& channel, QString& message, QUuid& senderID); static std::unique_ptr encodeMessagesPacket(QString channel, QString message, QUuid senderID); @@ -40,7 +41,7 @@ signals: void messageReceived(QString channel, QString message, QUuid senderUUID); private slots: - void handleMessagesPacket(QSharedPointer packetList, SharedNodePointer senderNode); + void handleMessagesPacket(QSharedPointer receivedMessage, SharedNodePointer senderNode); void handleNodeActivated(SharedNodePointer node); protected: diff --git a/libraries/networking/src/NLPacketList.cpp b/libraries/networking/src/NLPacketList.cpp index 318fb037a1..c6bea33d86 100644 --- a/libraries/networking/src/NLPacketList.cpp +++ b/libraries/networking/src/NLPacketList.cpp @@ -44,6 +44,7 @@ NLPacketList::NLPacketList(PacketList&& other) : PacketList(std::move(other)) { auto nlPacket = static_cast(_packets.front().get()); _sourceID = nlPacket->getSourceID(); _packetType = nlPacket->getType(); + _packetVersion = nlPacket->getVersion(); } } diff --git a/libraries/networking/src/NLPacketList.h b/libraries/networking/src/NLPacketList.h index c705e6ece3..250d15dbb9 100644 --- a/libraries/networking/src/NLPacketList.h +++ b/libraries/networking/src/NLPacketList.h @@ -21,20 +21,25 @@ public: static std::unique_ptr create(PacketType packetType, QByteArray extendedHeader = QByteArray(), bool isReliable = false, bool isOrdered = false); - static std::unique_ptr fromPacketList(std::unique_ptr); + static std::unique_ptr fromPacketList(std::unique_ptr); + PacketVersion getVersion() const { return _packetVersion; } const QUuid& getSourceID() const { return _sourceID; } private: NLPacketList(PacketType packetType, QByteArray extendedHeader = QByteArray(), bool isReliable = false, bool isOrdered = false); - NLPacketList(PacketList&& packetList); + NLPacketList(udt::PacketList&& packetList); NLPacketList(const NLPacketList& other) = delete; NLPacketList& operator=(const NLPacketList& other) = delete; virtual std::unique_ptr createPacket(); + + PacketVersion _packetVersion; QUuid _sourceID; }; +Q_DECLARE_METATYPE(QSharedPointer) + #endif // hifi_PacketList_h diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index 918bd5b972..b28d0a6cb1 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -22,6 +22,10 @@ const QString UNKNOWN_NodeType_t_NAME = "Unknown"; +static int NodePtrMetaTypeId = qRegisterMetaType("Node*"); +static int sharedPtrNodeMetaTypeId = qRegisterMetaType>("QSharedPointer"); +static int sharedNodePtrMetaTypeId = qRegisterMetaType("SharedNodePointer"); + namespace NodeType { QHash TypeNameHash; } diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 2b35516787..865e885add 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -85,6 +85,8 @@ private: bool _canRez; }; +Q_DECLARE_METATYPE(Node*) + typedef QSharedPointer SharedNodePointer; Q_DECLARE_METATYPE(SharedNodePointer) diff --git a/libraries/networking/src/NodeData.h b/libraries/networking/src/NodeData.h index 78ef446938..7a214cd240 100644 --- a/libraries/networking/src/NodeData.h +++ b/libraries/networking/src/NodeData.h @@ -17,6 +17,7 @@ #include #include "NLPacket.h" +#include "ReceivedMessage.h" class Node; @@ -25,7 +26,7 @@ class NodeData : public QObject { public: NodeData(); virtual ~NodeData() = 0; - virtual int parseData(NLPacket& packet) { return 0; } + virtual int parseData(ReceivedMessage& message) { return 0; } QMutex& getMutex() { return _mutex; } diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 925c64c77a..e591ee8a31 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -40,7 +40,8 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned _nodeTypesOfInterest(), _domainHandler(this), _numNoReplyDomainCheckIns(0), - _assignmentServerSocket() + _assignmentServerSocket(), + _keepAlivePingTimer(this) { setCustomDeleter([](Dependency* dependency){ static_cast(dependency)->deleteLater(); @@ -93,7 +94,7 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned _keepAlivePingTimer.setInterval(KEEPALIVE_PING_INTERVAL_MS); connect(&_keepAlivePingTimer, &QTimer::timeout, this, &NodeList::sendKeepAlivePings); connect(&_domainHandler, SIGNAL(connectedToDomain(QString)), &_keepAlivePingTimer, SLOT(start())); - connect(&_domainHandler, &DomainHandler::disconnectedFromDomain, &_keepAlivePingTimer, &QTimer::stop); + connect(&_domainHandler, &DomainHandler::disconnectedFromDomain, this, &NodeList::stopKeepalivePingTimer); // we definitely want STUN to update our public socket, so call the LNL to kick that off startSTUNPublicSocketUpdate(); @@ -105,7 +106,7 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned packetReceiver.registerListener(PacketType::ICEPing, this, "processICEPingPacket"); packetReceiver.registerListener(PacketType::DomainServerAddedNode, this, "processDomainServerAddedNode"); packetReceiver.registerListener(PacketType::DomainServerConnectionToken, this, "processDomainServerConnectionTokenPacket"); - packetReceiver.registerMessageListener(PacketType::DomainSettings, &_domainHandler, "processSettingsPacketList"); + packetReceiver.registerListener(PacketType::DomainSettings, &_domainHandler, "processSettingsPacketList"); packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_domainHandler, "processICEResponsePacket"); packetReceiver.registerListener(PacketType::DomainServerRequireDTLS, &_domainHandler, "processDTLSRequirementPacket"); packetReceiver.registerListener(PacketType::ICEPingReply, &_domainHandler, "processICEPingReplyPacket"); @@ -129,16 +130,16 @@ qint64 NodeList::sendStatsToDomainServer(const QJsonObject& statsObject) { return sendStats(statsObject, _domainHandler.getSockAddr()); } -void NodeList::timePingReply(QSharedPointer packet, const SharedNodePointer& sendingNode) { +void NodeList::timePingReply(ReceivedMessage& message, const SharedNodePointer& sendingNode) { PingType_t pingType; quint64 ourOriginalTime, othersReplyTime; - packet->seek(0); + message.seek(0); - packet->readPrimitive(&pingType); - packet->readPrimitive(&ourOriginalTime); - packet->readPrimitive(&othersReplyTime); + message.readPrimitive(&pingType); + message.readPrimitive(&ourOriginalTime); + message.readPrimitive(&othersReplyTime); quint64 now = usecTimestampNow(); int pingTime = now - ourOriginalTime; @@ -167,11 +168,11 @@ void NodeList::timePingReply(QSharedPointer packet, const SharedNodePo } } -void NodeList::processPingPacket(QSharedPointer packet, SharedNodePointer sendingNode) { +void NodeList::processPingPacket(QSharedPointer message, SharedNodePointer sendingNode) { // send back a reply - auto replyPacket = constructPingReplyPacket(*packet); - const HifiSockAddr& senderSockAddr = packet->getSenderSockAddr(); + auto replyPacket = constructPingReplyPacket(*message); + const HifiSockAddr& senderSockAddr = message->getSenderSockAddr(); sendPacket(std::move(replyPacket), *sendingNode, senderSockAddr); // If we don't have a symmetric socket for this node and this socket doesn't match @@ -184,21 +185,26 @@ void NodeList::processPingPacket(QSharedPointer packet, SharedNodePoin } } -void NodeList::processPingReplyPacket(QSharedPointer packet, SharedNodePointer sendingNode) { +void NodeList::processPingReplyPacket(QSharedPointer message, SharedNodePointer sendingNode) { // activate the appropriate socket for this node, if not yet updated - activateSocketFromNodeCommunication(packet, sendingNode); + activateSocketFromNodeCommunication(*message, sendingNode); // set the ping time for this node for stat collection - timePingReply(packet, sendingNode); + timePingReply(*message, sendingNode); } -void NodeList::processICEPingPacket(QSharedPointer packet) { +void NodeList::processICEPingPacket(QSharedPointer message) { // send back a reply - auto replyPacket = constructICEPingReplyPacket(*packet, _domainHandler.getICEClientID()); - sendPacket(std::move(replyPacket), packet->getSenderSockAddr()); + auto replyPacket = constructICEPingReplyPacket(*message, _domainHandler.getICEClientID()); + sendPacket(std::move(replyPacket), message->getSenderSockAddr()); } void NodeList::reset() { + if (thread() != QThread::currentThread()) { + QMetaObject::invokeMethod(this, "reset", Qt::BlockingQueuedConnection); + return; + } + LimitedNodeList::reset(); _numNoReplyDomainCheckIns = 0; @@ -376,34 +382,34 @@ void NodeList::sendDSPathQuery(const QString& newPath) { } } -void NodeList::processDomainServerPathResponse(QSharedPointer packet) { +void NodeList::processDomainServerPathResponse(QSharedPointer message) { // This is a response to a path query we theoretically made. // In the future we may want to check that this was actually from our DS and for a query we actually made. // figure out how many bytes the path query is quint16 numPathBytes; - packet->readPrimitive(&numPathBytes); + message->readPrimitive(&numPathBytes); // pull the path from the packet - if (packet->bytesLeftToRead() < numPathBytes) { + if (message->getBytesLeftToRead() < numPathBytes) { qCDebug(networking) << "Could not read query path from DomainServerPathQueryResponse. Bailing."; return; } - QString pathQuery = QString::fromUtf8(packet->getPayload() + packet->pos(), numPathBytes); - packet->seek(packet->pos() + numPathBytes); + QString pathQuery = QString::fromUtf8(message->getRawMessage() + message->getPosition(), numPathBytes); + message->seek(message->getPosition() + numPathBytes); // figure out how many bytes the viewpoint is quint16 numViewpointBytes; - packet->readPrimitive(&numViewpointBytes); + message->readPrimitive(&numViewpointBytes); - if (packet->bytesLeftToRead() < numViewpointBytes) { + if (message->getBytesLeftToRead() < numViewpointBytes) { qCDebug(networking) << "Could not read resulting viewpoint from DomainServerPathQueryReponse. Bailing"; return; } // pull the viewpoint from the packet - QString viewpoint = QString::fromUtf8(packet->getPayload() + packet->pos(), numViewpointBytes); + QString viewpoint = QString::fromUtf8(message->getRawMessage() + message->getPosition(), numViewpointBytes); // Hand it off to the AddressManager so it can handle it as a relative viewpoint if (DependencyManager::get()->goToViewpointForPath(viewpoint, pathQuery)) { @@ -465,17 +471,17 @@ void NodeList::pingPunchForDomainServer() { } } -void NodeList::processDomainServerConnectionTokenPacket(QSharedPointer packet) { +void NodeList::processDomainServerConnectionTokenPacket(QSharedPointer message) { if (_domainHandler.getSockAddr().isNull()) { // refuse to process this packet if we aren't currently connected to the DS return; } // read in the connection token from the packet, then send domain-server checkin - _domainHandler.setConnectionToken(QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID))); + _domainHandler.setConnectionToken(QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID))); sendDomainServerCheckIn(); } -void NodeList::processDomainServerList(QSharedPointer packet) { +void NodeList::processDomainServerList(QSharedPointer message) { if (_domainHandler.getSockAddr().isNull()) { // refuse to process this packet if we aren't currently connected to the DS return; @@ -486,7 +492,7 @@ void NodeList::processDomainServerList(QSharedPointer packet) { DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSList); - QDataStream packetStream(packet.data()); + QDataStream packetStream(message->getMessage()); // grab the domain's ID from the beginning of the packet QUuid domainUUID; @@ -512,22 +518,22 @@ void NodeList::processDomainServerList(QSharedPointer packet) { setThisNodeCanRez((bool) thisNodeCanRez); // pull each node in the packet - while (packetStream.device()->pos() < packet->getPayloadSize()) { + while (packetStream.device()->pos() < message->getSize()) { parseNodeFromPacketStream(packetStream); } } -void NodeList::processDomainServerAddedNode(QSharedPointer packet) { +void NodeList::processDomainServerAddedNode(QSharedPointer message) { // setup a QDataStream - QDataStream packetStream(packet.data()); + QDataStream packetStream(message->getMessage()); // use our shared method to pull out the new node parseNodeFromPacketStream(packetStream); } -void NodeList::processDomainServerRemovedNode(QSharedPointer packet) { +void NodeList::processDomainServerRemovedNode(QSharedPointer message) { // read the UUID from the packet, remove it if it exists - QUuid nodeUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + QUuid nodeUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); qDebug() << "Received packet from domain-server to remove node with UUID" << uuidStringWithoutCurlyBraces(nodeUUID); killNodeWithUUID(nodeUUID); } @@ -618,9 +624,9 @@ void NodeList::handleNodePingTimeout() { } } -void NodeList::activateSocketFromNodeCommunication(QSharedPointer packet, const SharedNodePointer& sendingNode) { +void NodeList::activateSocketFromNodeCommunication(ReceivedMessage& message, const SharedNodePointer& sendingNode) { // deconstruct this ping packet to see if it is a public or local reply - QDataStream packetStream(packet.data()); + QDataStream packetStream(message.getMessage()); quint8 pingType; packetStream >> pingType; @@ -640,6 +646,12 @@ void NodeList::activateSocketFromNodeCommunication(QSharedPointer pack } } +void NodeList::stopKeepalivePingTimer() { + if (_keepAlivePingTimer.isActive()) { + _keepAlivePingTimer.stop(); + } +} + void NodeList::sendKeepAlivePings() { eachMatchingNode([this](const SharedNodePointer& node)->bool { return _nodeTypesOfInterest.contains(node->getType()); diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 02f49d2918..171ea0c6a7 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -39,6 +39,7 @@ const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 5; using NodePacketPair = std::pair>; using NodeSharedPacketPair = std::pair>; +using NodeSharedReceivedMessagePair = std::pair>; class Application; class Assignment; @@ -62,8 +63,6 @@ public: void addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes); void resetNodeInterestSet() { _nodeTypesOfInterest.clear(); } - void processReceivedPacket(std::unique_ptr, HifiSockAddr senderSockAddr); - void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; } void sendAssignment(Assignment& assignment); @@ -74,21 +73,22 @@ public slots: void sendDomainServerCheckIn(); void handleDSPathQuery(const QString& newPath); - void processDomainServerList(QSharedPointer packet); - void processDomainServerAddedNode(QSharedPointer packet); - void processDomainServerRemovedNode(QSharedPointer packet); - void processDomainServerPathResponse(QSharedPointer packet); + void processDomainServerList(QSharedPointer message); + void processDomainServerAddedNode(QSharedPointer message); + void processDomainServerRemovedNode(QSharedPointer message); + void processDomainServerPathResponse(QSharedPointer message); - void processDomainServerConnectionTokenPacket(QSharedPointer packet); + void processDomainServerConnectionTokenPacket(QSharedPointer message); - void processPingPacket(QSharedPointer packet, SharedNodePointer sendingNode); - void processPingReplyPacket(QSharedPointer packet, SharedNodePointer sendingNode); + void processPingPacket(QSharedPointer message, SharedNodePointer sendingNode); + void processPingReplyPacket(QSharedPointer message, SharedNodePointer sendingNode); + + void processICEPingPacket(QSharedPointer message); - void processICEPingPacket(QSharedPointer packet); - signals: void limitOfSilentDomainCheckInsReached(); private slots: + void stopKeepalivePingTimer(); void sendPendingDSPathQuery(); void handleICEConnectionToDomainServer(); @@ -106,8 +106,8 @@ private: void processDomainServerAuthRequest(const QByteArray& packet); void requestAuthForDomainServer(); - void activateSocketFromNodeCommunication(QSharedPointer packet, const SharedNodePointer& sendingNode); - void timePingReply(QSharedPointer packet, const SharedNodePointer& sendingNode); + void activateSocketFromNodeCommunication(ReceivedMessage& message, const SharedNodePointer& sendingNode); + void timePingReply(ReceivedMessage& message, const SharedNodePointer& sendingNode); void sendDSPathQuery(const QString& newPath); diff --git a/libraries/networking/src/PacketReceiver.cpp b/libraries/networking/src/PacketReceiver.cpp index 07f25fee5f..530efc5fb3 100644 --- a/libraries/networking/src/PacketReceiver.cpp +++ b/libraries/networking/src/PacketReceiver.cpp @@ -19,10 +19,10 @@ #include "NodeList.h" #include "SharedUtil.h" -Q_DECLARE_METATYPE(QSharedPointer); PacketReceiver::PacketReceiver(QObject* parent) : QObject(parent) { qRegisterMetaType>(); qRegisterMetaType>(); + qRegisterMetaType>(); } bool PacketReceiver::registerListenerForTypes(PacketTypeList types, QObject* listener, const char* slot) { @@ -94,42 +94,19 @@ void PacketReceiver::registerDirectListenerForTypes(PacketTypeList types, } } -bool PacketReceiver::registerMessageListener(PacketType type, QObject* listener, const char* slot) { - Q_ASSERT_X(listener, "PacketReceiver::registerMessageListener", "No object to register"); - Q_ASSERT_X(slot, "PacketReceiver::registerMessageListener", "No slot to register"); - - QMetaMethod matchingMethod = matchingMethodForListener(type, listener, slot); - - if (matchingMethod.isValid()) { - QMutexLocker locker(&_packetListenerLock); - - if (_packetListListenerMap.contains(type)) { - qCWarning(networking) << "Registering a packet listener for packet type" << type - << "that will remove a previously registered listener"; - } - - // add the mapping - _packetListListenerMap[type] = ObjectMethodPair(QPointer(listener), matchingMethod); - - qCDebug(networking) << "Registering a packet listener for packet list type" << type; - - return true; - } else { - qCWarning(networking) << "FAILED to Register a packet listener for packet list type" << type; - return false; - } -} - -bool PacketReceiver::registerListener(PacketType type, QObject* listener, const char* slot) { +bool PacketReceiver::registerListener(PacketType type, QObject* listener, const char* slot, + bool deliverPending) { Q_ASSERT_X(listener, "PacketReceiver::registerListener", "No object to register"); Q_ASSERT_X(slot, "PacketReceiver::registerListener", "No slot to register"); QMetaMethod matchingMethod = matchingMethodForListener(type, listener, slot); if (matchingMethod.isValid()) { - registerVerifiedListener(type, listener, matchingMethod); + qCDebug(networking) << "Registering a packet listener for packet list type" << type; + registerVerifiedListener(type, listener, matchingMethod, deliverPending); return true; } else { + qCWarning(networking) << "FAILED to Register a packet listener for packet list type" << type; return false; } } @@ -139,29 +116,21 @@ QMetaMethod PacketReceiver::matchingMethodForListener(PacketType type, QObject* Q_ASSERT_X(slot, "PacketReceiver::matchingMethodForListener", "No slot to call"); // normalize the slot with the expected parameters - static const QString SIGNATURE_TEMPLATE("%1(%2)"); - static const QString NON_SOURCED_PACKET_LISTENER_PARAMETERS = "QSharedPointer"; - static const QString NON_SOURCED_PACKETLIST_LISTENER_PARAMETERS = "QSharedPointer"; + static const QString NON_SOURCED_MESSAGE_LISTENER_PARAMETERS = "QSharedPointer"; QSet possibleSignatures { - SIGNATURE_TEMPLATE.arg(slot, NON_SOURCED_PACKET_LISTENER_PARAMETERS), - SIGNATURE_TEMPLATE.arg(slot, NON_SOURCED_PACKETLIST_LISTENER_PARAMETERS) + SIGNATURE_TEMPLATE.arg(slot, NON_SOURCED_MESSAGE_LISTENER_PARAMETERS) }; if (!NON_SOURCED_PACKETS.contains(type)) { - static const QString SOURCED_PACKET_LISTENER_PARAMETERS = "QSharedPointer,QSharedPointer"; - static const QString TYPEDEF_SOURCED_PACKET_LISTENER_PARAMETERS = "QSharedPointer,SharedNodePointer"; - static const QString SOURCED_PACKETLIST_LISTENER_PARAMETERS = "QSharedPointer,QSharedPointer"; - static const QString TYPEDEF_SOURCED_PACKETLIST_LISTENER_PARAMETERS = "QSharedPointer,SharedNodePointer"; + static const QString SOURCED_MESSAGE_LISTENER_PARAMETERS = "QSharedPointer,QSharedPointer"; + static const QString TYPEDEF_SOURCED_MESSAGE_LISTENER_PARAMETERS = "QSharedPointer,SharedNodePointer"; - // a sourced packet must take the shared pointer to the packet but optionally could include + // a sourced packet must take the shared pointer to the ReceivedMessage but optionally could include // a shared pointer to the node - - possibleSignatures << SIGNATURE_TEMPLATE.arg(slot, TYPEDEF_SOURCED_PACKET_LISTENER_PARAMETERS); - possibleSignatures << SIGNATURE_TEMPLATE.arg(slot, SOURCED_PACKET_LISTENER_PARAMETERS); - possibleSignatures << SIGNATURE_TEMPLATE.arg(slot, TYPEDEF_SOURCED_PACKETLIST_LISTENER_PARAMETERS); - possibleSignatures << SIGNATURE_TEMPLATE.arg(slot, SOURCED_PACKETLIST_LISTENER_PARAMETERS); + possibleSignatures << SIGNATURE_TEMPLATE.arg(slot, TYPEDEF_SOURCED_MESSAGE_LISTENER_PARAMETERS); + possibleSignatures << SIGNATURE_TEMPLATE.arg(slot, SOURCED_MESSAGE_LISTENER_PARAMETERS); } int methodIndex = -1; @@ -196,17 +165,17 @@ QMetaMethod PacketReceiver::matchingMethodForListener(PacketType type, QObject* } } -void PacketReceiver::registerVerifiedListener(PacketType type, QObject* object, const QMetaMethod& slot) { +void PacketReceiver::registerVerifiedListener(PacketType type, QObject* object, const QMetaMethod& slot, bool deliverPending) { Q_ASSERT_X(object, "PacketReceiver::registerVerifiedListener", "No object to register"); QMutexLocker locker(&_packetListenerLock); - if (_packetListenerMap.contains(type)) { + if (_messageListenerMap.contains(type)) { qCWarning(networking) << "Registering a packet listener for packet type" << type << "that will remove a previously registered listener"; } // add the mapping - _packetListenerMap[type] = ObjectMethodPair(QPointer(object), slot); + _messageListenerMap[type] = { QPointer(object), slot, deliverPending }; } void PacketReceiver::unregisterListener(QObject* listener) { @@ -215,156 +184,23 @@ void PacketReceiver::unregisterListener(QObject* listener) { { QMutexLocker packetListenerLocker(&_packetListenerLock); - // TODO: replace the two while loops below with a replace_if on the vector (once we move to Message everywhere) + // clear any registrations for this listener in _messageListenerMap + auto it = _messageListenerMap.begin(); - // clear any registrations for this listener in _packetListenerMap - auto it = _packetListenerMap.begin(); - - while (it != _packetListenerMap.end()) { - if (it.value().first == listener) { - it = _packetListenerMap.erase(it); + while (it != _messageListenerMap.end()) { + if (it.value().object == listener) { + it = _messageListenerMap.erase(it); } else { ++it; } } - - // clear any registrations for this listener in _packetListListener - auto listIt = _packetListListenerMap.begin(); - - while (listIt != _packetListListenerMap.end()) { - if (listIt.value().first == listener) { - listIt = _packetListListenerMap.erase(listIt); - } else { - ++listIt; - } - } } QMutexLocker directConnectSetLocker(&_directConnectSetMutex); _directlyConnectedObjects.remove(listener); } -void PacketReceiver::handleVerifiedPacketList(std::unique_ptr packetList) { - // if we're supposed to drop this packet then break out here - if (_shouldDropPackets) { - return; - } - - // setup an NLPacketList from the PacketList we were passed - auto nlPacketList = NLPacketList::fromPacketList(std::move(packetList)); - - auto nodeList = DependencyManager::get(); - - _inPacketCount += nlPacketList->getNumPackets(); - _inByteCount += nlPacketList->getDataSize(); - - SharedNodePointer matchingNode; - - if (!nlPacketList->getSourceID().isNull()) { - matchingNode = nodeList->nodeWithUUID(nlPacketList->getSourceID()); - } - - QMutexLocker packetListenerLocker(&_packetListenerLock); - - bool listenerIsDead = false; - - auto it = _packetListListenerMap.find(nlPacketList->getType()); - - if (it != _packetListListenerMap.end() && it->second.isValid()) { - - auto listener = it.value(); - - if (listener.first) { - - bool success = false; - - Qt::ConnectionType connectionType; - // check if this is a directly connected listener - { - QMutexLocker directConnectLocker(&_directConnectSetMutex); - - connectionType = _directlyConnectedObjects.contains(listener.first) ? Qt::DirectConnection : Qt::AutoConnection; - } - - PacketType packetType = nlPacketList->getType(); - - if (matchingNode) { - emit dataReceived(matchingNode->getType(), nlPacketList->getDataSize()); - QMetaMethod metaMethod = listener.second; - - static const QByteArray QSHAREDPOINTER_NODE_NORMALIZED = QMetaObject::normalizedType("QSharedPointer"); - static const QByteArray SHARED_NODE_NORMALIZED = QMetaObject::normalizedType("SharedNodePointer"); - - // one final check on the QPointer before we go to invoke - if (listener.first) { - if (metaMethod.parameterTypes().contains(SHARED_NODE_NORMALIZED)) { - success = metaMethod.invoke(listener.first, - connectionType, - Q_ARG(QSharedPointer, - QSharedPointer(nlPacketList.release())), - Q_ARG(SharedNodePointer, matchingNode)); - - } else if (metaMethod.parameterTypes().contains(QSHAREDPOINTER_NODE_NORMALIZED)) { - success = metaMethod.invoke(listener.first, - connectionType, - Q_ARG(QSharedPointer, - QSharedPointer(nlPacketList.release())), - Q_ARG(QSharedPointer, matchingNode)); - - } else { - success = metaMethod.invoke(listener.first, - connectionType, - Q_ARG(QSharedPointer, - QSharedPointer(nlPacketList.release()))); - } - } else { - listenerIsDead = true; - } - } else { - emit dataReceived(NodeType::Unassigned, nlPacketList->getDataSize()); - - // one final check on the QPointer before we invoke - if (listener.first) { - success = listener.second.invoke(listener.first, - Q_ARG(QSharedPointer, - QSharedPointer(nlPacketList.release()))); - } else { - listenerIsDead = true; - } - - } - - if (!success) { - qCDebug(networking).nospace() << "Error delivering packet " << packetType << " to listener " - << listener.first << "::" << qPrintable(listener.second.methodSignature()); - } - - } else { - listenerIsDead = true; - } - - if (listenerIsDead) { - qCDebug(networking).nospace() << "Listener for packet " << nlPacketList->getType() - << " has been destroyed. Removing from listener map."; - it = _packetListListenerMap.erase(it); - - // if it exists, remove the listener from _directlyConnectedObjects - { - QMutexLocker directConnectLocker(&_directConnectSetMutex); - _directlyConnectedObjects.remove(listener.first); - } - } - - } else if (it == _packetListListenerMap.end()) { - qCWarning(networking) << "No listener found for packet list type" << nlPacketList->getType(); - - // insert a dummy listener so we don't print this again - _packetListListenerMap.insert(nlPacketList->getType(), { nullptr, QMetaMethod() }); - } -} - void PacketReceiver::handleVerifiedPacket(std::unique_ptr packet) { - // if we're supposed to drop this packet then break out here if (_shouldDropPackets) { return; @@ -374,80 +210,128 @@ void PacketReceiver::handleVerifiedPacket(std::unique_ptr packet) { // setup an NLPacket from the packet we were passed auto nlPacket = NLPacket::fromBase(std::move(packet)); - - _inPacketCount++; - _inByteCount += nlPacket->getDataSize(); + auto receivedMessage = QSharedPointer::create(*nlPacket); + + _inPacketCount += 1; + _inByteCount += nlPacket->size(); + + handleVerifiedMessage(receivedMessage, true); +} + +void PacketReceiver::handleVerifiedMessagePacket(std::unique_ptr packet) { + auto nlPacket = NLPacket::fromBase(std::move(packet)); + + _inPacketCount += 1; + _inByteCount += nlPacket->size(); + + auto key = std::pair(nlPacket->getSenderSockAddr(), nlPacket->getMessageNumber()); + auto it = _pendingMessages.find(key); + QSharedPointer message; + + if (it == _pendingMessages.end()) { + // Create message + message = QSharedPointer::create(*nlPacket); + if (!message->isComplete()) { + _pendingMessages[key] = message; + } + handleVerifiedMessage(message, true); + } else { + message = it->second; + message->appendPacket(*nlPacket); + + if (message->isComplete()) { + _pendingMessages.erase(it); + handleVerifiedMessage(message, false); + } + } +} + +void PacketReceiver::handleMessageFailure(HifiSockAddr from, udt::Packet::MessageNumber messageNumber) { + auto key = std::pair(from, messageNumber); + auto it = _pendingMessages.find(key); + if (it != _pendingMessages.end()) { + auto message = it->second; + message->setFailed(); + _pendingMessages.erase(it); + } +} + +void PacketReceiver::handleVerifiedMessage(QSharedPointer receivedMessage, bool justReceived) { + auto nodeList = DependencyManager::get(); SharedNodePointer matchingNode; - if (!nlPacket->getSourceID().isNull()) { - matchingNode = nodeList->nodeWithUUID(nlPacket->getSourceID()); + if (!receivedMessage->getSourceID().isNull()) { + matchingNode = nodeList->nodeWithUUID(receivedMessage->getSourceID()); } QMutexLocker packetListenerLocker(&_packetListenerLock); bool listenerIsDead = false; - auto it = _packetListenerMap.find(nlPacket->getType()); - - if (it != _packetListenerMap.end() && it->second.isValid()) { - + auto it = _messageListenerMap.find(receivedMessage->getType()); + + if (it != _messageListenerMap.end() && it->method.isValid()) { + auto listener = it.value(); - - if (listener.first) { + + if ((listener.deliverPending && !justReceived) || (!listener.deliverPending && !receivedMessage->isComplete())) { + return; + } + + if (listener.object) { bool success = false; - + + Qt::ConnectionType connectionType; // check if this is a directly connected listener - QMutexLocker directConnectSetLocker(&_directConnectSetMutex); - Qt::ConnectionType connectionType = - _directlyConnectedObjects.contains(listener.first) ? Qt::DirectConnection : Qt::AutoConnection; - directConnectSetLocker.unlock(); + { + QMutexLocker directConnectLocker(&_directConnectSetMutex); + + connectionType = _directlyConnectedObjects.contains(listener.object) ? Qt::DirectConnection : Qt::AutoConnection; + } - PacketType packetType = nlPacket->getType(); + PacketType packetType = receivedMessage->getType(); if (matchingNode) { - emit dataReceived(matchingNode->getType(), nlPacket->getDataSize()); - matchingNode->recordBytesReceived(nlPacket->getDataSize()); - - QMetaMethod metaMethod = listener.second; + emit dataReceived(matchingNode->getType(), receivedMessage->getSize()); + matchingNode->recordBytesReceived(receivedMessage->getSize()); + + QMetaMethod metaMethod = listener.method; static const QByteArray QSHAREDPOINTER_NODE_NORMALIZED = QMetaObject::normalizedType("QSharedPointer"); static const QByteArray SHARED_NODE_NORMALIZED = QMetaObject::normalizedType("SharedNodePointer"); // one final check on the QPointer before we go to invoke - if (listener.first) { + if (listener.object) { if (metaMethod.parameterTypes().contains(SHARED_NODE_NORMALIZED)) { - success = metaMethod.invoke(listener.first, + success = metaMethod.invoke(listener.object, connectionType, - Q_ARG(QSharedPointer, - QSharedPointer(nlPacket.release())), + Q_ARG(QSharedPointer, receivedMessage), Q_ARG(SharedNodePointer, matchingNode)); } else if (metaMethod.parameterTypes().contains(QSHAREDPOINTER_NODE_NORMALIZED)) { - success = metaMethod.invoke(listener.first, + success = metaMethod.invoke(listener.object, connectionType, - Q_ARG(QSharedPointer, - QSharedPointer(nlPacket.release())), + Q_ARG(QSharedPointer, receivedMessage), Q_ARG(QSharedPointer, matchingNode)); } else { - success = metaMethod.invoke(listener.first, + success = metaMethod.invoke(listener.object, connectionType, - Q_ARG(QSharedPointer, - QSharedPointer(nlPacket.release()))); + Q_ARG(QSharedPointer, receivedMessage)); } } else { listenerIsDead = true; } } else { - emit dataReceived(NodeType::Unassigned, nlPacket->getDataSize()); + // qDebug() << "Got verified unsourced packet list: " << QString(nlPacketList->getMessage()); + emit dataReceived(NodeType::Unassigned, receivedMessage->getSize()); // one final check on the QPointer before we invoke - if (listener.first) { - success = listener.second.invoke(listener.first, - Q_ARG(QSharedPointer, - QSharedPointer(nlPacket.release()))); + if (listener.object) { + success = listener.method.invoke(listener.object, + Q_ARG(QSharedPointer, receivedMessage)); } else { listenerIsDead = true; } @@ -456,7 +340,7 @@ void PacketReceiver::handleVerifiedPacket(std::unique_ptr packet) { if (!success) { qCDebug(networking).nospace() << "Error delivering packet " << packetType << " to listener " - << listener.first << "::" << qPrintable(listener.second.methodSignature()); + << listener.object << "::" << qPrintable(listener.method.methodSignature()); } } else { @@ -464,19 +348,20 @@ void PacketReceiver::handleVerifiedPacket(std::unique_ptr packet) { } if (listenerIsDead) { - qCDebug(networking).nospace() << "Listener for packet " << nlPacket->getType() + qCDebug(networking).nospace() << "Listener for packet " << receivedMessage->getType() << " has been destroyed. Removing from listener map."; - it = _packetListenerMap.erase(it); + it = _messageListenerMap.erase(it); // if it exists, remove the listener from _directlyConnectedObjects - QMutexLocker locker(&_directConnectSetMutex); - _directlyConnectedObjects.remove(listener.first); + { + QMutexLocker directConnectLocker(&_directConnectSetMutex); + _directlyConnectedObjects.remove(listener.object); + } } - - } else if (it == _packetListenerMap.end()) { - qCWarning(networking) << "No listener found for packet type" << nlPacket->getType(); + } else if (it == _messageListenerMap.end()) { + qCWarning(networking) << "No listener found for packet type" << receivedMessage->getType(); // insert a dummy listener so we don't print this again - _packetListenerMap.insert(nlPacket->getType(), { nullptr, QMetaMethod() }); + _messageListenerMap.insert(receivedMessage->getType(), { nullptr, QMetaMethod(), false }); } } diff --git a/libraries/networking/src/PacketReceiver.h b/libraries/networking/src/PacketReceiver.h index 1c6f9e73d2..ff4ab3e15c 100644 --- a/libraries/networking/src/PacketReceiver.h +++ b/libraries/networking/src/PacketReceiver.h @@ -14,6 +14,7 @@ #define hifi_PacketReceiver_h #include +#include #include #include @@ -24,11 +25,21 @@ #include "NLPacket.h" #include "NLPacketList.h" +#include "ReceivedMessage.h" #include "udt/PacketHeaders.h" class EntityEditPacketSender; class OctreePacketProcessor; +namespace std { + template <> + struct hash> { + size_t operator()(const std::pair& pair) const { + return hash()(pair.first) ^ hash()(pair.second); + } + }; +} + class PacketReceiver : public QObject { Q_OBJECT public: @@ -46,37 +57,46 @@ public: void resetCounters() { _inPacketCount = 0; _inByteCount = 0; } + // If deliverPending is false, ReceivedMessage will only be delivered once all packets for the message have + // been received. If deliverPending is true, ReceivedMessage will be delivered as soon as the first packet + // for the message is received. + bool registerListener(PacketType type, QObject* listener, const char* slot, bool deliverPending = false); bool registerListenerForTypes(PacketTypeList types, QObject* listener, const char* slot); - bool registerMessageListener(PacketType type, QObject* listener, const char* slot); - bool registerListener(PacketType type, QObject* listener, const char* slot); void unregisterListener(QObject* listener); void handleVerifiedPacket(std::unique_ptr packet); - void handleVerifiedPacketList(std::unique_ptr packetList); + void handleVerifiedMessagePacket(std::unique_ptr message); + void handleMessageFailure(HifiSockAddr from, udt::Packet::MessageNumber messageNumber); signals: void dataReceived(quint8 channelType, int bytes); private: + struct Listener { + QPointer object; + QMetaMethod method; + bool deliverPending; + }; + + void handleVerifiedMessage(QSharedPointer message, bool justReceived); + // these are brutal hacks for now - ideally GenericThread / ReceivedPacketProcessor // should be changed to have a true event loop and be able to handle our QMetaMethod::invoke void registerDirectListenerForTypes(PacketTypeList types, QObject* listener, const char* slot); void registerDirectListener(PacketType type, QObject* listener, const char* slot); QMetaMethod matchingMethodForListener(PacketType type, QObject* object, const char* slot) const; - void registerVerifiedListener(PacketType type, QObject* listener, const QMetaMethod& slot); - - using ObjectMethodPair = std::pair, QMetaMethod>; + void registerVerifiedListener(PacketType type, QObject* listener, const QMetaMethod& slot, bool deliverPending = false); QMutex _packetListenerLock; - // TODO: replace the two following hashes with an std::vector once we switch Packet/PacketList to Message - QHash _packetListenerMap; - QHash _packetListListenerMap; + QHash _messageListenerMap; int _inPacketCount = 0; int _inByteCount = 0; bool _shouldDropPackets = false; QMutex _directConnectSetMutex; QSet _directlyConnectedObjects; + + std::unordered_map, QSharedPointer> _pendingMessages; friend class EntityEditPacketSender; friend class OctreePacketProcessor; diff --git a/libraries/networking/src/ReceivedMessage.cpp b/libraries/networking/src/ReceivedMessage.cpp new file mode 100644 index 0000000000..651b856923 --- /dev/null +++ b/libraries/networking/src/ReceivedMessage.cpp @@ -0,0 +1,119 @@ +// +// ReceivedMessage.cpp +// libraries/networking/src +// +// Created by Ryan Huffman on 2015/09/17 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#include "ReceivedMessage.h" + +#include "QSharedPointer" + +static int receivedMessageMetaTypeId = qRegisterMetaType("ReceivedMessage*"); +static int sharedPtrReceivedMessageMetaTypeId = qRegisterMetaType>("QSharedPointer"); + +static const int HEAD_DATA_SIZE = 512; + +ReceivedMessage::ReceivedMessage(const NLPacketList& packetList) + : _data(packetList.getMessage()), + _headData(_data.mid(0, HEAD_DATA_SIZE)), + _numPackets(packetList.getNumPackets()), + _sourceID(packetList.getSourceID()), + _packetType(packetList.getType()), + _packetVersion(packetList.getVersion()), + _senderSockAddr(packetList.getSenderSockAddr()), + _isComplete(true) +{ +} + +ReceivedMessage::ReceivedMessage(NLPacket& packet) + : _data(packet.readAll()), + _headData(_data.mid(0, HEAD_DATA_SIZE)), + _numPackets(1), + _sourceID(packet.getSourceID()), + _packetType(packet.getType()), + _packetVersion(packet.getVersion()), + _senderSockAddr(packet.getSenderSockAddr()), + _isComplete(packet.getPacketPosition() == NLPacket::ONLY) +{ +} + +void ReceivedMessage::setFailed() { + _failed = true; + _isComplete = true; + emit completed(); +} + +void ReceivedMessage::appendPacket(NLPacket& packet) { + Q_ASSERT_X(!_isComplete, "ReceivedMessage::appendPacket", + "We should not be appending to a complete message"); + + // Limit progress signal to every X packets + const int EMIT_PROGRESS_EVERY_X_PACKETS = 100; + + ++_numPackets; + + _data.append(packet.getPayload(), packet.getPayloadSize()); + + if (_numPackets % EMIT_PROGRESS_EVERY_X_PACKETS == 0) { + emit progress(); + } + + if (packet.getPacketPosition() == NLPacket::PacketPosition::LAST) { + _isComplete = true; + emit completed(); + } +} + +qint64 ReceivedMessage::peek(char* data, qint64 size) { + memcpy(data, _data.constData() + _position, size); + return size; +} + +qint64 ReceivedMessage::read(char* data, qint64 size) { + memcpy(data, _data.constData() + _position, size); + _position += size; + return size; +} + +qint64 ReceivedMessage::readHead(char* data, qint64 size) { + memcpy(data, _headData.constData() + _position, size); + _position += size; + return size; +} + +QByteArray ReceivedMessage::peek(qint64 size) { + return _data.mid(_position, size); +} + +QByteArray ReceivedMessage::read(qint64 size) { + auto data = _data.mid(_position, size); + _position += size; + return data; +} + +QByteArray ReceivedMessage::readHead(qint64 size) { + auto data = _headData.mid(_position, size); + _position += size; + return data; +} + +QByteArray ReceivedMessage::readAll() { + return read(getBytesLeftToRead()); +} + +QByteArray ReceivedMessage::readWithoutCopy(qint64 size) { + QByteArray data { QByteArray::fromRawData(_data.constData() + _position, size) }; + _position += size; + return data; +} + +void ReceivedMessage::onComplete() { + _isComplete = true; + emit completed(); +} diff --git a/libraries/networking/src/ReceivedMessage.h b/libraries/networking/src/ReceivedMessage.h new file mode 100644 index 0000000000..1f797f1262 --- /dev/null +++ b/libraries/networking/src/ReceivedMessage.h @@ -0,0 +1,117 @@ +// +// ReceivedMessage.h +// libraries/networking/src +// +// Created by Ryan Huffman on 2015/09/15 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ReceivedMessage_h +#define hifi_ReceivedMessage_h + +#include +#include + +#include + +#include "NLPacketList.h" + +class ReceivedMessage : public QObject { + Q_OBJECT +public: + ReceivedMessage(const NLPacketList& packetList); + ReceivedMessage(NLPacket& packet); + + QByteArray getMessage() const { return _data; } + const char* getRawMessage() const { return _data.constData(); } + + PacketType getType() const { return _packetType; } + PacketVersion getVersion() const { return _packetVersion; } + + void setFailed(); + + void appendPacket(NLPacket& packet); + + bool failed() const { return _failed; } + bool isComplete() const { return _isComplete; } + + const QUuid& getSourceID() const { return _sourceID; } + const HifiSockAddr& getSenderSockAddr() { return _senderSockAddr; } + + qint64 getPosition() const { return _position; } + + // Get the number of packets that were used to send this message + qint64 getNumPackets() const { return _numPackets; } + + qint64 getSize() const { return _data.size(); } + + qint64 getBytesLeftToRead() const { return _data.size() - _position; } + + void seek(qint64 position) { _position = position; } + + qint64 peek(char* data, qint64 size); + qint64 read(char* data, qint64 size); + + // Temporary functionality for reading in the first HEAD_DATA_SIZE bytes of the message + // safely across threads. + qint64 readHead(char* data, qint64 size); + + QByteArray peek(qint64 size); + QByteArray read(qint64 size); + QByteArray readAll(); + + QByteArray readHead(qint64 size); + + // This will return a QByteArray referencing the underlying data _without_ refcounting that data. + // Be careful when using this method, only use it when the lifetime of the returned QByteArray will not + // exceed that of the ReceivedMessage. + QByteArray readWithoutCopy(qint64 size); + + template qint64 peekPrimitive(T* data); + template qint64 readPrimitive(T* data); + + template qint64 readHeadPrimitive(T* data); + +signals: + void progress(); + void completed(); + +private slots: + void onComplete(); + +private: + QByteArray _data; + QByteArray _headData; + + std::atomic _size { true }; + std::atomic _position { 0 }; + std::atomic _numPackets { 0 }; + + QUuid _sourceID; + PacketType _packetType; + PacketVersion _packetVersion; + HifiSockAddr _senderSockAddr; + + std::atomic _isComplete { true }; + std::atomic _failed { false }; +}; + +Q_DECLARE_METATYPE(ReceivedMessage*) +Q_DECLARE_METATYPE(QSharedPointer) + +template qint64 ReceivedMessage::peekPrimitive(T* data) { + return peek(reinterpret_cast(data), sizeof(T)); +} + +template qint64 ReceivedMessage::readPrimitive(T* data) { + return read(reinterpret_cast(data), sizeof(T)); +} + +template qint64 ReceivedMessage::readHeadPrimitive(T* data) { + return readHead(reinterpret_cast(data), sizeof(T)); +} + +#endif diff --git a/libraries/networking/src/ReceivedPacketProcessor.cpp b/libraries/networking/src/ReceivedPacketProcessor.cpp index 1afb84624f..c18d4ed1e8 100644 --- a/libraries/networking/src/ReceivedPacketProcessor.cpp +++ b/libraries/networking/src/ReceivedPacketProcessor.cpp @@ -24,9 +24,9 @@ void ReceivedPacketProcessor::terminating() { _hasPackets.wakeAll(); } -void ReceivedPacketProcessor::queueReceivedPacket(QSharedPointer packet, SharedNodePointer sendingNode) { +void ReceivedPacketProcessor::queueReceivedPacket(QSharedPointer message, SharedNodePointer sendingNode) { lock(); - _packets.push_back({ sendingNode, packet }); + _packets.push_back({ sendingNode, message }); _nodePacketCounts[sendingNode->getUUID()]++; _lastWindowIncomingPackets++; unlock(); @@ -66,7 +66,7 @@ bool ReceivedPacketProcessor::process() { } lock(); - std::list currentPackets; + std::list currentPackets; currentPackets.swap(_packets); unlock(); diff --git a/libraries/networking/src/ReceivedPacketProcessor.h b/libraries/networking/src/ReceivedPacketProcessor.h index db75c9c4d1..3a0e93ee09 100644 --- a/libraries/networking/src/ReceivedPacketProcessor.h +++ b/libraries/networking/src/ReceivedPacketProcessor.h @@ -23,7 +23,7 @@ public: ReceivedPacketProcessor(); /// Add packet from network receive thread to the processing queue. - void queueReceivedPacket(QSharedPointer packet, SharedNodePointer sendingNode); + void queueReceivedPacket(QSharedPointer message, SharedNodePointer sendingNode); /// Are there received packets waiting to be processed bool hasPacketsToProcess() const { return _packets.size() > 0; } @@ -58,7 +58,7 @@ protected: /// Callback for processing of recieved packets. Implement this to process the incoming packets. /// \param SharedNodePointer& sendingNode the node that sent this packet /// \param QByteArray& the packet to be processed - virtual void processPacket(QSharedPointer packet, SharedNodePointer sendingNode) = 0; + virtual void processPacket(QSharedPointer message, SharedNodePointer sendingNode) = 0; /// Implements generic processing behavior for this thread. virtual bool process(); @@ -76,7 +76,7 @@ protected: virtual void postProcess() { } protected: - std::list _packets; + std::list _packets; QHash _nodePacketCounts; QWaitCondition _hasPackets; diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index 5433ed0ece..9aeddbc99c 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -18,8 +18,8 @@ #include "ThreadedAssignment.h" -ThreadedAssignment::ThreadedAssignment(NLPacket& packet) : - Assignment(packet), +ThreadedAssignment::ThreadedAssignment(ReceivedMessage& message) : + Assignment(message), _isFinished(false) { diff --git a/libraries/networking/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h index 33dca969df..87d503d2bf 100644 --- a/libraries/networking/src/ThreadedAssignment.h +++ b/libraries/networking/src/ThreadedAssignment.h @@ -14,12 +14,14 @@ #include +#include "ReceivedMessage.h" + #include "Assignment.h" class ThreadedAssignment : public Assignment { Q_OBJECT public: - ThreadedAssignment(NLPacket& packet); + ThreadedAssignment(ReceivedMessage& message); ~ThreadedAssignment() { stop(); } void setFinished(bool isFinished); diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index 639b2990ec..e63d44e72f 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -49,6 +49,11 @@ Connection::Connection(Socket* parentSocket, HifiSockAddr destination, std::uniq Connection::~Connection() { stopSendQueue(); + + // Fail any pending received messages + for (auto& pendingMessage : _pendingReceivedMessages) { + _parentSocket->messageFailed(this, pendingMessage.first); + } } void Connection::stopSendQueue() { @@ -130,19 +135,17 @@ void Connection::queueReceivedMessagePacket(std::unique_ptr packet) { Q_ASSERT(packet->isPartOfMessage()); auto messageNumber = packet->getMessageNumber(); - PendingReceivedMessage& pendingMessage = _pendingReceivedMessages[messageNumber]; + auto& pendingMessage = _pendingReceivedMessages[messageNumber]; pendingMessage.enqueuePacket(std::move(packet)); - if (pendingMessage.isComplete()) { - // All messages have been received, create PacketList - auto packetList = PacketList::fromReceivedPackets(std::move(pendingMessage._packets)); - - _pendingReceivedMessages.erase(messageNumber); + while (pendingMessage.hasAvailablePackets()) { + auto packet = pendingMessage.removeNextPacket(); + _parentSocket->messageReceived(std::move(packet)); + } - if (_parentSocket) { - _parentSocket->messageReceived(std::move(packetList)); - } + if (pendingMessage.isComplete()) { + _pendingReceivedMessages.erase(messageNumber); } } @@ -807,6 +810,9 @@ void Connection::resetReceiveState() { _receivedControlProbeTail = false; // clear any pending received messages + for (auto& pendingMessage : _pendingReceivedMessages) { + _parentSocket->messageFailed(this, pendingMessage.first); + } _pendingReceivedMessages.clear(); } @@ -877,3 +883,18 @@ void PendingReceivedMessage::enqueuePacket(std::unique_ptr packet) { _packets.insert(it.base(), std::move(packet)); } + +bool PendingReceivedMessage::hasAvailablePackets() const { + return _packets.size() > 0 + && _nextPartNumber == _packets.front()->getMessagePartNumber(); +} + +std::unique_ptr PendingReceivedMessage::removeNextPacket() { + if (hasAvailablePackets()) { + _nextPartNumber++; + auto p = std::move(_packets.front()); + _packets.pop_front(); + return p; + } + return std::unique_ptr(); +} diff --git a/libraries/networking/src/udt/Connection.h b/libraries/networking/src/udt/Connection.h index 7f9978c326..f8c4acd7d9 100644 --- a/libraries/networking/src/udt/Connection.h +++ b/libraries/networking/src/udt/Connection.h @@ -38,11 +38,14 @@ class PendingReceivedMessage { public: void enqueuePacket(std::unique_ptr packet); bool isComplete() const { return _hasLastPacket && _numPackets == _packets.size(); } + bool hasAvailablePackets() const; + std::unique_ptr removeNextPacket(); std::list> _packets; private: bool _hasLastPacket { false }; + Packet::MessagePartNumber _nextPartNumber = 0; unsigned int _numPackets { 0 }; }; @@ -71,6 +74,8 @@ public: bool isActive() const { return _isActive; } + HifiSockAddr getDestination() const { return _destination; } + signals: void packetSent(); void connectionInactive(const HifiSockAddr& sockAddr); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 2f685b7e08..93fbe7acdc 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -41,9 +41,10 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityAdd: case PacketType::EntityEdit: case PacketType::EntityData: - return VERSION_ENTITIES_POLYLINE_TEXTURE; + return VERSION_ENTITIES_HAVE_PARENTS; case PacketType::AvatarData: case PacketType::BulkAvatarData: + return 17; default: return 17; } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 23df89b3d7..cd7b9a113c 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -160,5 +160,6 @@ const PacketVersion VERSION_ENTITIES_KEYLIGHT_PROPERTIES_GROUP = 47; const PacketVersion VERSION_ENTITIES_KEYLIGHT_PROPERTIES_GROUP_BIS = 48; const PacketVersion VERSION_ENTITIES_PARTICLES_ADDITIVE_BLENDING = 49; const PacketVersion VERSION_ENTITIES_POLYLINE_TEXTURE = 50; +const PacketVersion VERSION_ENTITIES_HAVE_PARENTS = 51; #endif // hifi_PacketHeaders_h diff --git a/libraries/networking/src/udt/PacketList.cpp b/libraries/networking/src/udt/PacketList.cpp index 332603a8b9..e4212ccb88 100644 --- a/libraries/networking/src/udt/PacketList.cpp +++ b/libraries/networking/src/udt/PacketList.cpp @@ -50,6 +50,10 @@ PacketList::PacketList(PacketList&& other) : { } +HifiSockAddr PacketList::getSenderSockAddr() const { + return _packets.size() > 0 ? _packets.front()->getSenderSockAddr() : HifiSockAddr(); +} + void PacketList::startSegment() { _segmentStartIndex = _currentPacket ? _currentPacket->pos() : _extendedHeader.size(); } @@ -117,7 +121,7 @@ void PacketList::closeCurrentPacket(bool shouldSendEmpty) { } } -QByteArray PacketList::getMessage() { +QByteArray PacketList::getMessage() const { size_t sizeBytes = 0; for (const auto& packet : _packets) { diff --git a/libraries/networking/src/udt/PacketList.h b/libraries/networking/src/udt/PacketList.h index a6bee9f5eb..2c6c14f42f 100644 --- a/libraries/networking/src/udt/PacketList.h +++ b/libraries/networking/src/udt/PacketList.h @@ -42,12 +42,14 @@ public: int getNumPackets() const { return _packets.size() + (_currentPacket ? 1 : 0); } size_t getDataSize() const; size_t getMessageSize() const; - QByteArray getMessage(); + QByteArray getMessage() const; QByteArray getExtendedHeader() const { return _extendedHeader; } void startSegment(); void endSegment(); + + HifiSockAddr getSenderSockAddr() const; void closeCurrentPacket(bool shouldSendEmpty = false); diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 6d4a834879..11f1b043c8 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -20,6 +20,7 @@ #include "ControlPacket.h" #include "Packet.h" #include "../NLPacket.h" +#include "../NLPacketList.h" #include "PacketList.h" using namespace udt; @@ -123,8 +124,9 @@ qint64 Socket::writePacketList(std::unique_ptr packetList, const Hif // because Qt can't invoke with the unique_ptr we have to release it here and re-construct in writeReliablePacketList if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "writeReliablePacketList", Qt::QueuedConnection, - Q_ARG(PacketList*, packetList.release()), + auto ptr = packetList.release(); + QMetaObject::invokeMethod(this, "writeReliablePacketList", Qt::AutoConnection, + Q_ARG(PacketList*, ptr), Q_ARG(HifiSockAddr, sockAddr)); } else { writeReliablePacketList(packetList.release(), sockAddr); @@ -194,10 +196,12 @@ void Socket::clearConnections() { QMetaObject::invokeMethod(this, "clearConnections", Qt::BlockingQueuedConnection); return; } - - // clear all of the current connections in the socket - qDebug() << "Clearing all remaining connections in Socket."; - _connectionsHash.clear(); + + if (_connectionsHash.size() > 0) { + // clear all of the current connections in the socket + qDebug() << "Clearing all remaining connections in Socket."; + _connectionsHash.clear(); + } } void Socket::cleanupConnection(HifiSockAddr sockAddr) { @@ -210,9 +214,15 @@ void Socket::cleanupConnection(HifiSockAddr sockAddr) { } } -void Socket::messageReceived(std::unique_ptr packetList) { - if (_packetListHandler) { - _packetListHandler(std::move(packetList)); +void Socket::messageReceived(std::unique_ptr packet) { + if (_messageHandler) { + _messageHandler(std::move(packet)); + } +} + +void Socket::messageFailed(Connection* connection, Packet::MessageNumber messageNumber) { + if (_messageFailureHandler) { + _messageFailureHandler(connection->getDestination(), messageNumber); } } diff --git a/libraries/networking/src/udt/Socket.h b/libraries/networking/src/udt/Socket.h index 68fcb483b0..88db8e3d86 100644 --- a/libraries/networking/src/udt/Socket.h +++ b/libraries/networking/src/udt/Socket.h @@ -32,7 +32,6 @@ class UDTTest; namespace udt { class BasePacket; -class ControlSender; class Packet; class PacketList; class SequenceNumber; @@ -41,7 +40,8 @@ using PacketFilterOperator = std::function; using BasePacketHandler = std::function)>; using PacketHandler = std::function)>; -using PacketListHandler = std::function)>; +using MessageHandler = std::function)>; +using MessageFailureHandler = std::function; class Socket : public QObject { Q_OBJECT @@ -65,14 +65,16 @@ public: void setPacketFilterOperator(PacketFilterOperator filterOperator) { _packetFilterOperator = filterOperator; } void setPacketHandler(PacketHandler handler) { _packetHandler = handler; } - void setPacketListHandler(PacketListHandler handler) { _packetListHandler = handler; } + void setMessageHandler(MessageHandler handler) { _messageHandler = handler; } + void setMessageFailureHandler(MessageFailureHandler handler) { _messageFailureHandler = handler; } void addUnfilteredHandler(const HifiSockAddr& senderSockAddr, BasePacketHandler handler) { _unfilteredHandlers[senderSockAddr] = handler; } void setCongestionControlFactory(std::unique_ptr ccFactory); - void messageReceived(std::unique_ptr packetList); + void messageReceived(std::unique_ptr packet); + void messageFailed(Connection* connection, Packet::MessageNumber messageNumber); StatsVector sampleStatsForAllConnections(); @@ -100,7 +102,8 @@ private: QUdpSocket _udpSocket { this }; PacketFilterOperator _packetFilterOperator; PacketHandler _packetHandler; - PacketListHandler _packetListHandler; + MessageHandler _messageHandler; + MessageFailureHandler _messageFailureHandler; std::unordered_map _unfilteredHandlers; std::unordered_map _unreliableSequenceNumbers; diff --git a/libraries/octree/src/CoverageMap.cpp b/libraries/octree/src/CoverageMap.cpp deleted file mode 100644 index 626d4bcf1a..0000000000 --- a/libraries/octree/src/CoverageMap.cpp +++ /dev/null @@ -1,542 +0,0 @@ -// -// CoverageMap.cpp -// libraries/octree/src -// -// Created by Brad Hefta-Gaub on 06/11/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 "OctreeLogging.h" -#include "CoverageMap.h" - -int CoverageMap::_mapCount = 0; -int CoverageMap::_checkMapRootCalls = 0; -int CoverageMap::_notAllInView = 0; -bool CoverageMap::wantDebugging = false; - -const int MAX_POLYGONS_PER_REGION = 50; - -const BoundingBox CoverageMap::ROOT_BOUNDING_BOX = BoundingBox(glm::vec2(-1.0f,-1.0f), glm::vec2(2.0f,2.0f)); - -// Coverage Map's polygon coordinates are from -1 to 1 in the following mapping to screen space. -// -// (0,0) (windowWidth, 0) -// -1,1 1,1 -// +-----------------------+ -// | | | -// | | | -// | -1,0 | | -// |-----------+-----------| -// | 0,0 | -// | | | -// | | | -// | | | -// +-----------------------+ -// -1,-1 1,-1 -// (0,windowHeight) (windowWidth,windowHeight) -// - -// Choosing a minimum sized polygon. Since we know a typical window is approximately 1500 pixels wide -// then a pixel on our screen will be ~ 2.0/1500 or 0.0013 "units" wide, similarly pixels are typically -// about that tall as well. If we say that polygons should be at least 10x10 pixels to be considered "big enough" -// then we can calculate a reasonable polygon area -const int TYPICAL_SCREEN_WIDTH_IN_PIXELS = 1500; -const int MINIMUM_POLYGON_AREA_SIDE_IN_PIXELS = 10; -const float TYPICAL_SCREEN_PIXEL_WIDTH = (2.0f / TYPICAL_SCREEN_WIDTH_IN_PIXELS); -const float CoverageMap::MINIMUM_POLYGON_AREA_TO_STORE = (TYPICAL_SCREEN_PIXEL_WIDTH * MINIMUM_POLYGON_AREA_SIDE_IN_PIXELS) * - (TYPICAL_SCREEN_PIXEL_WIDTH * MINIMUM_POLYGON_AREA_SIDE_IN_PIXELS); - -CoverageMap::CoverageMap(BoundingBox boundingBox, bool isRoot, bool managePolygons) : - _isRoot(isRoot), - _myBoundingBox(boundingBox), - _managePolygons(managePolygons), - _topHalf (boundingBox.topHalf() , false, managePolygons, TOP_HALF ), - _bottomHalf (boundingBox.bottomHalf(), false, managePolygons, BOTTOM_HALF ), - _leftHalf (boundingBox.leftHalf() , false, managePolygons, LEFT_HALF ), - _rightHalf (boundingBox.rightHalf() , false, managePolygons, RIGHT_HALF ), - _remainder (boundingBox, isRoot, managePolygons, REMAINDER ) -{ - _mapCount++; - init(); -}; - -CoverageMap::~CoverageMap() { - erase(); -}; - -void CoverageMap::printStats() { - qCDebug(octree, "CoverageMap::printStats()..."); - qCDebug(octree, "MINIMUM_POLYGON_AREA_TO_STORE=%f", (double)MINIMUM_POLYGON_AREA_TO_STORE); - qCDebug(octree, "_mapCount=%d",_mapCount); - qCDebug(octree, "_checkMapRootCalls=%d",_checkMapRootCalls); - qCDebug(octree, "_notAllInView=%d",_notAllInView); - qCDebug(octree, "_maxPolygonsUsed=%d",CoverageRegion::_maxPolygonsUsed); - qCDebug(octree, "_totalPolygons=%d",CoverageRegion::_totalPolygons); - qCDebug(octree, "_occlusionTests=%d",CoverageRegion::_occlusionTests); - qCDebug(octree, "_regionSkips=%d",CoverageRegion::_regionSkips); - qCDebug(octree, "_tooSmallSkips=%d",CoverageRegion::_tooSmallSkips); - qCDebug(octree, "_regionFullSkips=%d",CoverageRegion::_regionFullSkips); - qCDebug(octree, "_outOfOrderPolygon=%d",CoverageRegion::_outOfOrderPolygon); - qCDebug(octree, "_clippedPolygons=%d",CoverageRegion::_clippedPolygons); -} - -void CoverageMap::erase() { - // tell our regions to erase() - _topHalf.erase(); - _bottomHalf.erase(); - _leftHalf.erase(); - _rightHalf.erase(); - _remainder.erase(); - - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - if (_childMaps[i]) { - delete _childMaps[i]; - _childMaps[i] = NULL; - } - } - - if (_isRoot && wantDebugging) { - qCDebug(octree, "CoverageMap last to be deleted..."); - printStats(); - - CoverageRegion::_maxPolygonsUsed = 0; - CoverageRegion::_totalPolygons = 0; - CoverageRegion::_occlusionTests = 0; - CoverageRegion::_regionSkips = 0; - CoverageRegion::_tooSmallSkips = 0; - CoverageRegion::_regionFullSkips = 0; - CoverageRegion::_outOfOrderPolygon = 0; - CoverageRegion::_clippedPolygons = 0; - _mapCount = 0; - _checkMapRootCalls = 0; - _notAllInView = 0; - } -} - -void CoverageMap::init() { - memset(_childMaps,0,sizeof(_childMaps)); -} - -// 0 = bottom, right -// 1 = bottom, left -// 2 = top, right -// 3 = top, left -BoundingBox CoverageMap::getChildBoundingBox(int childIndex) { - const int LEFT_BIT = 1; - const int TOP_BIT = 2; - // initialize to our corner, and half our size - BoundingBox result(_myBoundingBox.corner,_myBoundingBox.size/2.0f); - // if our "left" bit is set, then add size.x to the corner - if ((childIndex & LEFT_BIT) == LEFT_BIT) { - result.corner.x += result.size.x; - } - // if our "top" bit is set, then add size.y to the corner - if ((childIndex & TOP_BIT) == TOP_BIT) { - result.corner.y += result.size.y; - } - return result; -} - -int CoverageMap::getPolygonCount() const { - return (_topHalf.getPolygonCount() + - _bottomHalf.getPolygonCount() + - _leftHalf.getPolygonCount() + - _rightHalf.getPolygonCount() + - _remainder.getPolygonCount()); -} - -OctreeProjectedPolygon* CoverageMap::getPolygon(int index) const { - int base = 0; - if ((index - base) < _topHalf.getPolygonCount()) { - return _topHalf.getPolygon((index - base)); - } - base += _topHalf.getPolygonCount(); - - if ((index - base) < _bottomHalf.getPolygonCount()) { - return _bottomHalf.getPolygon((index - base)); - } - base += _bottomHalf.getPolygonCount(); - - if ((index - base) < _leftHalf.getPolygonCount()) { - return _leftHalf.getPolygon((index - base)); - } - base += _leftHalf.getPolygonCount(); - - if ((index - base) < _rightHalf.getPolygonCount()) { - return _rightHalf.getPolygon((index - base)); - } - base += _rightHalf.getPolygonCount(); - - if ((index - base) < _remainder.getPolygonCount()) { - return _remainder.getPolygon((index - base)); - } - return NULL; -} - - - -// possible results = STORED/NOT_STORED, OCCLUDED, DOESNT_FIT -CoverageMapStorageResult CoverageMap::checkMap(OctreeProjectedPolygon* polygon, bool storeIt) { - - if (_isRoot) { - _checkMapRootCalls++; - } - - // short circuit: we don't handle polygons that aren't all in view, so, if the polygon in question is - // not in view, then we just discard it with a DOESNT_FIT, this saves us time checking values later. - if (!polygon->getAllInView()) { - _notAllInView++; - return DOESNT_FIT; - } - - BoundingBox polygonBox(polygon->getBoundingBox()); - if (_isRoot || _myBoundingBox.contains(polygonBox)) { - - CoverageMapStorageResult result = NOT_STORED; - CoverageRegion* storeIn = &_remainder; - - // Check each half of the box independently - const bool useRegions = true; // for now we will continue to use regions - if (useRegions) { - if (_topHalf.contains(polygonBox)) { - result = _topHalf.checkRegion(polygon, polygonBox, storeIt); - storeIn = &_topHalf; - } else if (_bottomHalf.contains(polygonBox)) { - result = _bottomHalf.checkRegion(polygon, polygonBox, storeIt); - storeIn = &_bottomHalf; - } else if (_leftHalf.contains(polygonBox)) { - result = _leftHalf.checkRegion(polygon, polygonBox, storeIt); - storeIn = &_leftHalf; - } else if (_rightHalf.contains(polygonBox)) { - result = _rightHalf.checkRegion(polygon, polygonBox, storeIt); - storeIn = &_rightHalf; - } - } - - // if we got this far, there are one of two possibilities, either a polygon doesn't fit - // in one of the halves, or it did fit, but it wasn't occluded by anything only in that - // half. In either of these cases, we want to check our remainder region to see if its - // occluded by anything there - if (!(result == STORED || result == OCCLUDED)) { - result = _remainder.checkRegion(polygon, polygonBox, storeIt); - } - - // It's possible that this first set of checks might have resulted in an out of order polygon - // in which case we just return.. - if (result == STORED || result == OCCLUDED) { - - /* - if (result == STORED) - qCDebug(octree, "CoverageMap2::checkMap()... STORED\n"); - else - qCDebug(octree, "CoverageMap2::checkMap()... OCCLUDED\n"); - */ - - return result; - } - - // if we made it here, then it means the polygon being stored is not occluded - // at this level of the quad tree, so we can continue to insert it into the map. - // First we check to see if it fits in any of our sub maps - const bool useChildMaps = true; // for now we will continue to use child maps - if (useChildMaps) { - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - BoundingBox childMapBoundingBox = getChildBoundingBox(i); - if (childMapBoundingBox.contains(polygon->getBoundingBox())) { - // if no child map exists yet, then create it - if (!_childMaps[i]) { - _childMaps[i] = new CoverageMap(childMapBoundingBox, NOT_ROOT, _managePolygons); - } - result = _childMaps[i]->checkMap(polygon, storeIt); - - /* - switch (result) { - case STORED: - qCDebug(octree, "checkMap() = STORED\n"); - break; - case NOT_STORED: - qCDebug(octree, "checkMap() = NOT_STORED\n"); - break; - case OCCLUDED: - qCDebug(octree, "checkMap() = OCCLUDED\n"); - break; - default: - qCDebug(octree, "checkMap() = ????? \n"); - break; - } - */ - - return result; - } - } - } - // if we got this far, then the polygon is in our bounding box, but doesn't fit in - // any of our child bounding boxes, so we should add it here. - if (storeIt) { - if (polygon->getBoundingBox().area() > CoverageMap::MINIMUM_POLYGON_AREA_TO_STORE) { - if (storeIn->getPolygonCount() < MAX_POLYGONS_PER_REGION) { - storeIn->storeInArray(polygon); - return STORED; - } else { - CoverageRegion::_regionFullSkips++; - return NOT_STORED; - } - } else { - CoverageRegion::_tooSmallSkips++; - return NOT_STORED; - } - } else { - return NOT_STORED; - } - } - return DOESNT_FIT; -} - - -CoverageRegion::CoverageRegion(BoundingBox boundingBox, bool isRoot, bool managePolygons, RegionName regionName) : - _isRoot(isRoot), - _myBoundingBox(boundingBox), - _managePolygons(managePolygons), - _regionName(regionName) -{ - init(); -}; - -CoverageRegion::~CoverageRegion() { - erase(); -}; - -void CoverageRegion::init() { - _polygonCount = 0; - _polygonArraySize = 0; - _polygons = NULL; - _polygonDistances = NULL; - _polygonSizes = NULL; -} - - -void CoverageRegion::erase() { - -/** - if (_polygonCount) { - qCDebug(octree, "CoverageRegion::erase()...\n"); - qCDebug(octree, "_polygonCount=%d\n",_polygonCount); - _myBoundingBox.printDebugDetails(getRegionName()); - //for (int i = 0; i < _polygonCount; i++) { - // qCDebug(octree, "_polygons[%d]=",i); - // _polygons[i]->getBoundingBox().printDebugDetails(); - //} - } -**/ - // If we're in charge of managing the polygons, then clean them up first - if (_polygons && _managePolygons) { - for (int i = 0; i < _polygonCount; i++) { - delete _polygons[i]; - _polygons[i] = NULL; // do we need to do this? - } - } - - // Now, clean up our local storage - _polygonCount = 0; - _polygonArraySize = 0; - if (_polygons) { - delete[] _polygons; - _polygons = NULL; - } - if (_polygonDistances) { - delete[] _polygonDistances; - _polygonDistances = NULL; - } - if (_polygonSizes) { - delete[] _polygonSizes; - _polygonSizes = NULL; - } -} - -void CoverageRegion::growPolygonArray() { - OctreeProjectedPolygon** newPolygons = new OctreeProjectedPolygon*[_polygonArraySize + DEFAULT_GROW_SIZE]; - float* newDistances = new float[_polygonArraySize + DEFAULT_GROW_SIZE]; - float* newSizes = new float[_polygonArraySize + DEFAULT_GROW_SIZE]; - - - if (_polygons) { - memcpy(newPolygons, _polygons, sizeof(OctreeProjectedPolygon*) * _polygonCount); - delete[] _polygons; - memcpy(newDistances, _polygonDistances, sizeof(float) * _polygonCount); - delete[] _polygonDistances; - memcpy(newSizes, _polygonSizes, sizeof(float) * _polygonCount); - delete[] _polygonSizes; - } - _polygons = newPolygons; - _polygonDistances = newDistances; - _polygonSizes = newSizes; - _polygonArraySize = _polygonArraySize + DEFAULT_GROW_SIZE; -} - -const char* CoverageRegion::getRegionName() const { - switch (_regionName) { - case TOP_HALF: - return "TOP_HALF"; - case BOTTOM_HALF: - return "BOTTOM_HALF"; - case LEFT_HALF: - return "LEFT_HALF"; - case RIGHT_HALF: - return "RIGHT_HALF"; - default: - case REMAINDER: - return "REMAINDER"; - } - return "REMAINDER"; -} - -int CoverageRegion::_maxPolygonsUsed = 0; -int CoverageRegion::_totalPolygons = 0; -int CoverageRegion::_occlusionTests = 0; -int CoverageRegion::_regionSkips = 0; -int CoverageRegion::_tooSmallSkips = 0; -int CoverageRegion::_regionFullSkips = 0; -int CoverageRegion::_outOfOrderPolygon = 0; -int CoverageRegion::_clippedPolygons = 0; - - -bool CoverageRegion::mergeItemsInArray(OctreeProjectedPolygon* seed, bool seedInArray) { - for (int i = 0; i < _polygonCount; i++) { - OctreeProjectedPolygon* otherPolygon = _polygons[i]; - if (otherPolygon->canMerge(*seed)) { - otherPolygon->merge(*seed); - - if (seedInArray) { - int* IGNORED_ADDRESS = NULL; - // remove this otherOtherPolygon for our polygon array - _polygonCount = removeFromSortedArrays((void*)seed, - (void**)_polygons, _polygonDistances, IGNORED_ADDRESS, - _polygonCount, _polygonArraySize); - _totalPolygons--; - } - - // clean up - if (_managePolygons) { - delete seed; - } - - // Now run again using our newly merged polygon as the seed - mergeItemsInArray(otherPolygon, true); - - return true; - } - } - return false; -} - -// just handles storage in the array, doesn't test for occlusion or -// determining if this is the correct map to store in! -void CoverageRegion::storeInArray(OctreeProjectedPolygon* polygon) { - - _currentCoveredBounds.explandToInclude(polygon->getBoundingBox()); - - - // Before we actually store this polygon in the array, check to see if this polygon can be merged to any of the existing - // polygons already in our array. - if (mergeItemsInArray(polygon, false)) { - return; // exit early - } - - // only after we attempt to merge! - _totalPolygons++; - - if (_polygonArraySize < _polygonCount + 1) { - growPolygonArray(); - } - - // As an experiment we're going to see if we get an improvement by storing the polygons in coverage area sorted order - // this means the bigger polygons are earlier in the array. We should have a higher probability of being occluded earlier - // in the list. We still check to see if the polygon is "in front" of the target polygon before we test occlusion. Since - // sometimes things come out of order. - const bool SORT_BY_SIZE = false; - const int IGNORED = 0; - int* IGNORED_ADDRESS = NULL; - if (SORT_BY_SIZE) { - // This old code assumes that polygons will always be added in z-buffer order, but that doesn't seem to - // be a good assumption. So instead, we will need to sort this by distance. Use a binary search to find the - // insertion point in this array, and shift the array accordingly - float area = polygon->getBoundingBox().area(); - float reverseArea = 4.0f - area; - _polygonCount = insertIntoSortedArrays((void*)polygon, reverseArea, IGNORED, - (void**)_polygons, _polygonSizes, IGNORED_ADDRESS, - _polygonCount, _polygonArraySize); - } else { - _polygonCount = insertIntoSortedArrays((void*)polygon, polygon->getDistance(), IGNORED, - (void**)_polygons, _polygonDistances, IGNORED_ADDRESS, - _polygonCount, _polygonArraySize); - } - - // Debugging and Optimization Tuning code. - if (_polygonCount > _maxPolygonsUsed) { - _maxPolygonsUsed = _polygonCount; - } -} - - - -CoverageMapStorageResult CoverageRegion::checkRegion(OctreeProjectedPolygon* polygon, const BoundingBox& polygonBox, bool storeIt) { - - CoverageMapStorageResult result = DOESNT_FIT; - - if (_isRoot || _myBoundingBox.contains(polygonBox)) { - result = NOT_STORED; // if we got here, then we DO fit... - - // only actually check the polygons if this polygon is in the covered bounds for this region - if (!_currentCoveredBounds.contains(polygonBox)) { - _regionSkips += _polygonCount; - } else { - // check to make sure this polygon isn't occluded by something at this level - for (int i = 0; i < _polygonCount; i++) { - OctreeProjectedPolygon* polygonAtThisLevel = _polygons[i]; - - // Check to make sure that the polygon in question is "behind" the polygon in the list - // otherwise, we don't need to test it's occlusion (although, it means we've potentially - // added an item previously that may be occluded??? Is that possible? Maybe not, because two - // voxels can't have the exact same outline. So one occludes the other, they can't both occlude - // each other. - - _occlusionTests++; - if (polygonAtThisLevel->occludes(*polygon)) { - // if the polygonAtThisLevel is actually behind the one we're inserting, then we don't - // want to report our inserted one as occluded, but we do want to add our inserted one. - if (polygonAtThisLevel->getDistance() >= polygon->getDistance()) { - _outOfOrderPolygon++; - if (storeIt) { - if (polygon->getBoundingBox().area() > CoverageMap::MINIMUM_POLYGON_AREA_TO_STORE) { - if (getPolygonCount() < MAX_POLYGONS_PER_REGION) { - storeInArray(polygon); - return STORED; - } else { - CoverageRegion::_regionFullSkips++; - return NOT_STORED; - } - } else { - _tooSmallSkips++; - return NOT_STORED; - } - } else { - return NOT_STORED; - } - } - // this polygon is occluded by a closer polygon, so don't store it, and let the caller know - return OCCLUDED; - } - } - } - } - return result; -} diff --git a/libraries/octree/src/CoverageMap.h b/libraries/octree/src/CoverageMap.h deleted file mode 100644 index bff6bb1078..0000000000 --- a/libraries/octree/src/CoverageMap.h +++ /dev/null @@ -1,120 +0,0 @@ -// -// CoverageMap.h -// libraries/octree/src -// -// Created by Brad Hefta-Gaub on 06/11/13. -// Copyright 2013 High Fidelity, Inc. -// -// 2D CoverageMap Quad tree for storage of OctreeProjectedPolygons -// -// 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_CoverageMap_h -#define hifi_CoverageMap_h - -#include -#include "OctreeProjectedPolygon.h" - -typedef enum {STORED, OCCLUDED, DOESNT_FIT, NOT_STORED} CoverageMapStorageResult; -typedef enum {TOP_HALF, BOTTOM_HALF, LEFT_HALF, RIGHT_HALF, REMAINDER} RegionName; - -class CoverageRegion { - -public: - - CoverageRegion(BoundingBox boundingBox, bool isRoot, bool managePolygons = true, RegionName regionName = REMAINDER); - ~CoverageRegion(); - - CoverageMapStorageResult checkRegion(OctreeProjectedPolygon* polygon, const BoundingBox& polygonBox, bool storeIt); - void storeInArray(OctreeProjectedPolygon* polygon); - - bool contains(const BoundingBox& box) const { return _myBoundingBox.contains(box); }; - void erase(); // erase the coverage region - - static int _maxPolygonsUsed; - static int _totalPolygons; - static int _occlusionTests; - static int _regionSkips; - static int _tooSmallSkips; - static int _regionFullSkips; - static int _outOfOrderPolygon; - static int _clippedPolygons; - - - const char* getRegionName() const; - - int getPolygonCount() const { return _polygonCount; }; - OctreeProjectedPolygon* getPolygon(int index) const { return _polygons[index]; }; - -private: - void init(); - - bool _isRoot; // is this map the root, if so, it never returns DOESNT_FIT - BoundingBox _myBoundingBox; - BoundingBox _currentCoveredBounds; // area in this region currently covered by some polygon - bool _managePolygons; // will the coverage map delete the polygons on destruct - RegionName _regionName; - int _polygonCount; // how many polygons at this level - int _polygonArraySize; // how much room is there to store polygons at this level - OctreeProjectedPolygon** _polygons; - - // we will use one or the other of these depending on settings in the code. - float* _polygonDistances; - float* _polygonSizes; - void growPolygonArray(); - static const int DEFAULT_GROW_SIZE = 100; - - bool mergeItemsInArray(OctreeProjectedPolygon* seed, bool seedInArray); - -}; - -class CoverageMap { - -public: - static const int NUMBER_OF_CHILDREN = 4; - static const bool NOT_ROOT=false; - static const bool IS_ROOT=true; - static const BoundingBox ROOT_BOUNDING_BOX; - static const float MINIMUM_POLYGON_AREA_TO_STORE; - - CoverageMap(BoundingBox boundingBox = ROOT_BOUNDING_BOX, bool isRoot = IS_ROOT, bool managePolygons = true); - ~CoverageMap(); - - CoverageMapStorageResult checkMap(OctreeProjectedPolygon* polygon, bool storeIt = true); - - BoundingBox getChildBoundingBox(int childIndex); - - void erase(); // erase the coverage map - void printStats(); - - static bool wantDebugging; - - int getPolygonCount() const; - OctreeProjectedPolygon* getPolygon(int index) const; - CoverageMap* getChild(int childIndex) const { return _childMaps[childIndex]; }; - -private: - void init(); - - bool _isRoot; // is this map the root, if so, it never returns DOESNT_FIT - BoundingBox _myBoundingBox; - CoverageMap* _childMaps[NUMBER_OF_CHILDREN]; - bool _managePolygons; // will the coverage map delete the polygons on destruct - - // We divide the map into 5 regions representing each possible half of the map, and the whole map - // this allows us to keep the list of polygons shorter - CoverageRegion _topHalf; - CoverageRegion _bottomHalf; - CoverageRegion _leftHalf; - CoverageRegion _rightHalf; - CoverageRegion _remainder; - - static int _mapCount; - static int _checkMapRootCalls; - static int _notAllInView; -}; - - -#endif // hifi_CoverageMap_h diff --git a/libraries/octree/src/CoverageMapV2.cpp b/libraries/octree/src/CoverageMapV2.cpp deleted file mode 100644 index 8467ea1ee9..0000000000 --- a/libraries/octree/src/CoverageMapV2.cpp +++ /dev/null @@ -1,251 +0,0 @@ -// -// CoverageMapV2.cpp -// libraries/octree/src -// -// Created by Brad Hefta-Gaub on 06/11/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 "OctreeLogging.h" -#include "CoverageMapV2.h" - -int CoverageMapV2::_mapCount = 0; -int CoverageMapV2::_checkMapRootCalls = 0; -int CoverageMapV2::_notAllInView = 0; -bool CoverageMapV2::wantDebugging = false; - -const BoundingBox CoverageMapV2::ROOT_BOUNDING_BOX = BoundingBox(glm::vec2(-1.0f,-1.0f), glm::vec2(2.0f,2.0f)); - -// Coverage Map's polygon coordinates are from -1 to 1 in the following mapping to screen space. -// -// (0,0) (windowWidth, 0) -// -1,1 1,1 -// +-----------------------+ -// | | | -// | | | -// | -1,0 | | -// |-----------+-----------| -// | 0,0 | -// | | | -// | | | -// | | | -// +-----------------------+ -// -1,-1 1,-1 -// (0,windowHeight) (windowWidth,windowHeight) -// - -// Choosing a minimum sized polygon. Since we know a typical window is approximately 1500 pixels wide -// then a pixel on our screen will be ~ 2.0/1500 or 0.0013 "units" wide, similarly pixels are typically -// about that tall as well. If we say that polygons should be at least 10x10 pixels to be considered "big enough" -// then we can calculate a reasonable polygon area -const int TYPICAL_SCREEN_WIDTH_IN_PIXELS = 1500; -const int MINIMUM_POLYGON_AREA_SIDE_IN_PIXELS = 10; -const float TYPICAL_SCREEN_PIXEL_WIDTH = (2.0f / TYPICAL_SCREEN_WIDTH_IN_PIXELS); -const float CoverageMapV2::MINIMUM_POLYGON_AREA_TO_STORE = (TYPICAL_SCREEN_PIXEL_WIDTH * MINIMUM_POLYGON_AREA_SIDE_IN_PIXELS) * - (TYPICAL_SCREEN_PIXEL_WIDTH * MINIMUM_POLYGON_AREA_SIDE_IN_PIXELS); -const float CoverageMapV2::NOT_COVERED = FLT_MAX; -const float CoverageMapV2::MINIMUM_OCCLUSION_CHECK_AREA = MINIMUM_POLYGON_AREA_TO_STORE/10.0f; // one quarter the size of poly - - -CoverageMapV2::CoverageMapV2(BoundingBox boundingBox, bool isRoot, bool isCovered, float coverageDistance) : - _isRoot(isRoot), - _myBoundingBox(boundingBox), - _isCovered(isCovered), - _coveredDistance(coverageDistance) -{ - _mapCount++; - init(); -}; - -CoverageMapV2::~CoverageMapV2() { - erase(); -}; - -void CoverageMapV2::erase() { - - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - if (_childMaps[i]) { - delete _childMaps[i]; - _childMaps[i] = NULL; - } - } - - if (_isRoot && wantDebugging) { - qCDebug(octree, "CoverageMapV2 last to be deleted..."); - qCDebug(octree, "MINIMUM_POLYGON_AREA_TO_STORE=%f", (double)MINIMUM_POLYGON_AREA_TO_STORE); - qCDebug(octree, "_mapCount=%d",_mapCount); - qCDebug(octree, "_checkMapRootCalls=%d",_checkMapRootCalls); - qCDebug(octree, "_notAllInView=%d",_notAllInView); - _mapCount = 0; - _checkMapRootCalls = 0; - _notAllInView = 0; - } -} - -void CoverageMapV2::init() { - memset(_childMaps,0,sizeof(_childMaps)); -} - -// 0 = bottom, left -// 1 = bottom, right -// 2 = top, left -// 3 = top, right -BoundingBox CoverageMapV2::getChildBoundingBox(int childIndex) { - const int RIGHT_BIT = 1; - const int TOP_BIT = 2; - // initialize to our corner, and half our size - BoundingBox result(_myBoundingBox.corner,_myBoundingBox.size/2.0f); - // if our "right" bit is set, then add size.x to the corner - if ((childIndex & RIGHT_BIT) == RIGHT_BIT) { - result.corner.x += result.size.x; - } - // if our "top" bit is set, then add size.y to the corner - if ((childIndex & TOP_BIT) == TOP_BIT) { - result.corner.y += result.size.y; - } - return result; -} - -// possible results = STORED/NOT_STORED, OCCLUDED, DOESNT_FIT -CoverageMapV2StorageResult CoverageMapV2::checkMap(const OctreeProjectedPolygon* polygon, bool storeIt) { - assert(_isRoot); // you can only call this on the root map!!! - _checkMapRootCalls++; - - // short circuit: if we're the root node (only case we're here), and we're covered, and this polygon is deeper than our - // covered depth, then this polygon is occluded! - if (_isCovered && _coveredDistance < polygon->getDistance()) { - return V2_OCCLUDED; - } - - // short circuit: we don't handle polygons that aren't all in view, so, if the polygon in question is - // not in view, then we just discard it with a DOESNT_FIT, this saves us time checking values later. - if (!polygon->getAllInView()) { - _notAllInView++; - return V2_DOESNT_FIT; - } - - // Here's where we recursively check the polygon against the coverage map. We need to maintain two pieces of state. - // The first state is: have we seen at least one "fully occluded" map items. If we haven't then we don't track the covered - // state of the polygon. - // The second piece of state is: Are all of our "fully occluded" map items "covered". If even one of these occluded map - // items is not covered, then our polygon is not covered. - bool seenOccludedMapNodes = false; - bool allOccludedMapNodesCovered = false; - - recurseMap(polygon, storeIt, seenOccludedMapNodes, allOccludedMapNodesCovered); - - // Ok, no matter how we were called, if all our occluded map nodes are covered, then we know this polygon - // is occluded, otherwise, we will report back to the caller about whether or not we stored the polygon - if (allOccludedMapNodesCovered) { - return V2_OCCLUDED; - } - if (storeIt) { - return V2_STORED; // otherwise report that we STORED it - } - return V2_NOT_STORED; // unless we weren't asked to store it, then we didn't -} - -void CoverageMapV2::recurseMap(const OctreeProjectedPolygon* polygon, bool storeIt, - bool& seenOccludedMapNodes, bool& allOccludedMapNodesCovered) { - - // if we are really small, then we act like we don't intersect, this allows us to stop - // recusing as we get to the smalles edge of the polygon - if (_myBoundingBox.area() < MINIMUM_OCCLUSION_CHECK_AREA) { - return; // stop recursion, we're done! - } - - // Determine if this map node intersects the polygon and/or is fully covered by the polygon - // There are a couple special cases: If we're the root, we are assumed to intersect with all - // polygons. Also, any map node that is fully occluded also intersects. - bool nodeIsCoveredByPolygon = polygon->occludes(_myBoundingBox); - bool nodeIsIntersectedByPolygon = nodeIsCoveredByPolygon || _isRoot || polygon->intersects(_myBoundingBox); - - // If we don't intersect, then we can just return, we're done recursing - if (!nodeIsIntersectedByPolygon) { - return; // stop recursion, we're done! - } - - // At this point, we know our node intersects with the polygon. If this node is covered, then we want to treat it - // as if the node was fully covered, because this allows us to short circuit further recursion... - if (_isCovered && _coveredDistance < polygon->getDistance()) { - nodeIsCoveredByPolygon = true; // fake it till you make it - } - - // If this node in the map is fully covered by our polygon, then we don't need to recurse any further, but - // we do need to do some bookkeeping. - if (nodeIsCoveredByPolygon) { - // If this is the very first fully covered node we've seen, then we're initialize our allOccludedMapNodesCovered - // to be our current covered state. This has the following effect: if this node isn't already covered, then by - // definition, we know that at least one node for this polygon isn't covered, and therefore we aren't fully covered. - if (!seenOccludedMapNodes) { - allOccludedMapNodesCovered = (_isCovered && _coveredDistance < polygon->getDistance()); - // We need to mark that we've seen at least one node of our polygon! ;) - seenOccludedMapNodes = true; - } else { - // If this is our second or later node of our polygon, then we need to track our allOccludedMapNodesCovered state - allOccludedMapNodesCovered = allOccludedMapNodesCovered && - (_isCovered && _coveredDistance < polygon->getDistance()); - } - - // if we're in store mode then we want to record that this node is covered. - if (storeIt) { - _isCovered = true; - // store the minimum distance of our previous known distance, or our current polygon's distance. This is because - // we know that we're at least covered at this distance, but if we had previously identified that we're covered - // at a shallower distance, then we want to maintain that distance - _coveredDistance = std::min(polygon->getDistance(), _coveredDistance); - - // Note: this might be a good chance to delete child maps, but we're not going to do that at this point because - // we're trying to maintain the known distances in the lower portion of the tree. - } - - // and since this node of the quad map is covered, we can safely stop recursion. because we know all smaller map - // nodes will also be covered. - return; - } - - // If we got here, then it means we know that this node is not fully covered by the polygon, but it does intersect - // with the polygon. - - // Another case is that we aren't yet marked as covered, and so we should recurse and process smaller quad tree nodes. - // Note: we use this to determine if we can collapse the child quad trees and mark this node as covered - bool allChildrenOccluded = true; - float maxChildCoveredDepth = NOT_COVERED; - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - BoundingBox childMapBoundingBox = getChildBoundingBox(i); - // if no child map exists yet, then create it - if (!_childMaps[i]) { - // children get created with the coverage state of their parent. - _childMaps[i] = new CoverageMapV2(childMapBoundingBox, NOT_ROOT, _isCovered, _coveredDistance); - } - - _childMaps[i]->recurseMap(polygon, storeIt, seenOccludedMapNodes, allOccludedMapNodesCovered); - - // if so far, all of our children are covered, then record our furthest coverage distance - if (allChildrenOccluded && _childMaps[i]->_isCovered) { - maxChildCoveredDepth = std::max(maxChildCoveredDepth, _childMaps[i]->_coveredDistance); - } else { - // otherwise, at least one of our children is not covered, so not all are covered - allChildrenOccluded = false; - } - } - // if all the children are covered, this makes our quad tree "shallower" because it records that - // entire quad is covered, it uses the "furthest" z-order so that if a shalower polygon comes through - // we won't assume its occluded - if (allChildrenOccluded && storeIt) { - _isCovered = true; - _coveredDistance = maxChildCoveredDepth; - } - - // normal exit case... return... -} diff --git a/libraries/octree/src/CoverageMapV2.h b/libraries/octree/src/CoverageMapV2.h deleted file mode 100644 index fc9a3ea70e..0000000000 --- a/libraries/octree/src/CoverageMapV2.h +++ /dev/null @@ -1,72 +0,0 @@ -// -// CoverageMapV2.h -// libraries/octree/src -// -// Created by Brad Hefta-Gaub on 06/11/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_CoverageMapV2_h -#define hifi_CoverageMapV2_h - -#include - -#include "OctreeProjectedPolygon.h" - -typedef enum { - V2_DOESNT_FIT, V2_STORED, V2_NOT_STORED, - V2_INTERSECT, V2_NO_INTERSECT, - V2_OCCLUDED, V2_NOT_OCCLUDED -} CoverageMapV2StorageResult; - -class CoverageMapV2 { - -public: - static const int NUMBER_OF_CHILDREN = 4; - static const bool NOT_ROOT = false; - static const bool IS_ROOT = true; - static const BoundingBox ROOT_BOUNDING_BOX; - static const float MINIMUM_POLYGON_AREA_TO_STORE; - static const float NOT_COVERED; - static const float MINIMUM_OCCLUSION_CHECK_AREA; - static bool wantDebugging; - - CoverageMapV2(BoundingBox boundingBox = ROOT_BOUNDING_BOX, bool isRoot = IS_ROOT, - bool isCovered = false, float coverageDistance = NOT_COVERED); - ~CoverageMapV2(); - - CoverageMapV2StorageResult checkMap(const OctreeProjectedPolygon* polygon, bool storeIt = true); - - BoundingBox getChildBoundingBox(int childIndex); - const BoundingBox& getBoundingBox() const { return _myBoundingBox; }; - CoverageMapV2* getChild(int childIndex) const { return _childMaps[childIndex]; }; - bool isCovered() const { return _isCovered; }; - - void erase(); // erase the coverage map - - void render(); - - -private: - void recurseMap(const OctreeProjectedPolygon* polygon, bool storeIt, - bool& seenOccludedMapNodes, bool& allOccludedMapNodesCovered); - - void init(); - - bool _isRoot; - BoundingBox _myBoundingBox; - CoverageMapV2* _childMaps[NUMBER_OF_CHILDREN]; - - bool _isCovered; - float _coveredDistance; - - static int _mapCount; - static int _checkMapRootCalls; - static int _notAllInView; -}; - - -#endif // hifi_CoverageMapV2_h diff --git a/libraries/octree/src/JurisdictionListener.cpp b/libraries/octree/src/JurisdictionListener.cpp index 4979972c8a..dbbd146f4e 100644 --- a/libraries/octree/src/JurisdictionListener.cpp +++ b/libraries/octree/src/JurisdictionListener.cpp @@ -58,11 +58,11 @@ bool JurisdictionListener::queueJurisdictionRequest() { return isStillRunning(); } -void JurisdictionListener::processPacket(QSharedPointer packet, SharedNodePointer sendingNode) { - if (packet->getType() == PacketType::Jurisdiction) { +void JurisdictionListener::processPacket(QSharedPointer message, SharedNodePointer sendingNode) { + if (message->getType() == PacketType::Jurisdiction) { JurisdictionMap map; - map.unpackFromPacket(*packet); - _jurisdictions[packet->getSourceID()] = map; + map.unpackFromPacket(*message); + _jurisdictions[message->getSourceID()] = map; } } diff --git a/libraries/octree/src/JurisdictionListener.h b/libraries/octree/src/JurisdictionListener.h index 02fce896f5..0c713ca27f 100644 --- a/libraries/octree/src/JurisdictionListener.h +++ b/libraries/octree/src/JurisdictionListener.h @@ -47,7 +47,7 @@ public slots: protected: /// Callback for processing of received packets. Will process any queued PacketType::_JURISDICTION and update the /// jurisdiction map member variable - virtual void processPacket(QSharedPointer packet, SharedNodePointer sendingNode); + virtual void processPacket(QSharedPointer messsage, SharedNodePointer sendingNode) override; private: NodeToJurisdictionMap _jurisdictions; diff --git a/libraries/octree/src/JurisdictionMap.cpp b/libraries/octree/src/JurisdictionMap.cpp index 255652a3cc..d473b3973e 100644 --- a/libraries/octree/src/JurisdictionMap.cpp +++ b/libraries/octree/src/JurisdictionMap.cpp @@ -298,28 +298,28 @@ std::unique_ptr JurisdictionMap::packIntoPacket() { return packet; } -int JurisdictionMap::unpackFromPacket(NLPacket& packet) { +int JurisdictionMap::unpackFromPacket(ReceivedMessage& message) { clear(); // read the root jurisdiction int bytes = 0; - packet.readPrimitive(&bytes); + message.readPrimitive(&bytes); - if (bytes > 0 && bytes <= packet.bytesLeftToRead()) { + if (bytes > 0 && bytes <= message.getBytesLeftToRead()) { _rootOctalCode = new unsigned char[bytes]; - packet.read(reinterpret_cast(_rootOctalCode), bytes); + message.read(reinterpret_cast(_rootOctalCode), bytes); // if and only if there's a root jurisdiction, also include the end nodes int endNodeCount = 0; - packet.readPrimitive(&endNodeCount); + message.readPrimitive(&endNodeCount); for (int i = 0; i < endNodeCount; i++) { int bytes = 0; - packet.readPrimitive(&bytes); + message.readPrimitive(&bytes); - if (bytes <= packet.bytesLeftToRead()) { + if (bytes <= message.getBytesLeftToRead()) { unsigned char* endNodeCode = new unsigned char[bytes]; - packet.read(reinterpret_cast(endNodeCode), bytes); + message.read(reinterpret_cast(endNodeCode), bytes); // if the endNodeCode was 0 length then don't add it if (bytes > 0) { @@ -329,5 +329,5 @@ int JurisdictionMap::unpackFromPacket(NLPacket& packet) { } } - return packet.pos(); // excludes header + return message.getPosition(); // excludes header } diff --git a/libraries/octree/src/JurisdictionMap.h b/libraries/octree/src/JurisdictionMap.h index 7cf7ebe6a9..3f415efba8 100644 --- a/libraries/octree/src/JurisdictionMap.h +++ b/libraries/octree/src/JurisdictionMap.h @@ -56,7 +56,7 @@ public: void copyContents(unsigned char* rootCodeIn, const std::vector& endNodesIn); - int unpackFromPacket(NLPacket& packet); + 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 diff --git a/libraries/octree/src/JurisdictionSender.cpp b/libraries/octree/src/JurisdictionSender.cpp index e408982eed..ed3d59cebc 100644 --- a/libraries/octree/src/JurisdictionSender.cpp +++ b/libraries/octree/src/JurisdictionSender.cpp @@ -28,8 +28,8 @@ JurisdictionSender::JurisdictionSender(JurisdictionMap* map, NodeType_t type) : JurisdictionSender::~JurisdictionSender() { } -void JurisdictionSender::processPacket(QSharedPointer packet, SharedNodePointer sendingNode) { - if (packet->getType() == PacketType::JurisdictionRequest) { +void JurisdictionSender::processPacket(QSharedPointer message, SharedNodePointer sendingNode) { + if (message->getType() == PacketType::JurisdictionRequest) { lockRequestingNodes(); _nodesRequestingJurisdictions.push(sendingNode->getUUID()); unlockRequestingNodes(); diff --git a/libraries/octree/src/JurisdictionSender.h b/libraries/octree/src/JurisdictionSender.h index dd0ff44a11..a0a3b5b4dd 100644 --- a/libraries/octree/src/JurisdictionSender.h +++ b/libraries/octree/src/JurisdictionSender.h @@ -38,7 +38,7 @@ public: void setNodeType(NodeType_t type) { _nodeType = type; } protected: - virtual void processPacket(QSharedPointer packet, SharedNodePointer sendingNode); + virtual void processPacket(QSharedPointer message, SharedNodePointer sendingNode) override; /// Locks all the resources of the thread. void lockRequestingNodes() { _requestingNodeMutex.lock(); } diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 6f73be360f..aacb57f31d 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -41,7 +41,6 @@ #include #include -#include "CoverageMap.h" #include "OctreeConstants.h" #include "OctreeElementBag.h" #include "Octree.h" @@ -951,9 +950,9 @@ int Octree::encodeTreeBitstream(OctreeElementPointer element, // if childBytesWritten == 1 then something went wrong... that's not possible assert(childBytesWritten != 1); - // if includeColor and childBytesWritten == 2, then it can only mean that the lower level trees don't exist or for some + // if childBytesWritten == 2, then it can only mean that the lower level trees don't exist or for some // reason couldn't be written... so reset them here... This isn't true for the non-color included case - if (suppressEmptySubtrees() && params.includeColor && childBytesWritten == 2) { + if (suppressEmptySubtrees() && childBytesWritten == 2) { childBytesWritten = 0; //params.stopReason = EncodeBitstreamParams::UNKNOWN; // possibly should be DIDNT_FIT... } @@ -1103,31 +1102,6 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, params.stopReason = EncodeBitstreamParams::NO_CHANGE; return bytesAtThisLevel; } - - // If the user also asked for occlusion culling, check if this element is occluded, but only if it's not a leaf. - // leaf occlusion is handled down below when we check child nodes - if (params.wantOcclusionCulling && !element->isLeaf()) { - OctreeProjectedPolygon* voxelPolygon = - new OctreeProjectedPolygon(params.viewFrustum->getProjectedPolygon(element->getAACube())); - - // In order to check occlusion culling, the shadow has to be "all in view" otherwise, we will ignore occlusion - // culling and proceed as normal - if (voxelPolygon->getAllInView()) { - CoverageMapStorageResult result = params.map->checkMap(voxelPolygon, false); - delete voxelPolygon; // cleanup - if (result == OCCLUDED) { - if (params.stats) { - params.stats->skippedOccluded(element); - } - params.stopReason = EncodeBitstreamParams::OCCLUDED; - return bytesAtThisLevel; - } - } else { - // If this shadow wasn't "all in view" then we ignored it for occlusion culling, but - // we do need to clean up memory and proceed as normal... - delete voxelPolygon; - } - } } bool keepDiggingDeeper = true; // Assuming we're in view we have a great work ethic, we're always ready for more! @@ -1190,20 +1164,10 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, } } - if (params.wantOcclusionCulling) { - if (childElement) { - float distance = params.viewFrustum ? childElement->distanceToCamera(*params.viewFrustum) : 0; - - currentCount = insertOctreeElementIntoSortedArrays(childElement, distance, i, - sortedChildren, (float*)&distancesToChildren, - (int*)&indexOfChildren, currentCount, NUMBER_OF_CHILDREN); - } - } else { - sortedChildren[i] = childElement; - indexOfChildren[i] = i; - distancesToChildren[i] = 0.0f; - currentCount++; - } + sortedChildren[i] = childElement; + indexOfChildren[i] = i; + distancesToChildren[i] = 0.0f; + currentCount++; // track stats // must check childElement here, because it could be we got here with no childElement @@ -1255,36 +1219,6 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, bool childIsOccluded = false; // assume it's not occluded - // If the user also asked for occlusion culling, check if this element is occluded - if (params.wantOcclusionCulling && childElement->isLeaf()) { - // Don't check occlusion here, just add them to our distance ordered array... - - // FIXME params.ViewFrustum is used here, but later it is checked against nullptr. - OctreeProjectedPolygon* voxelPolygon = new OctreeProjectedPolygon( - params.viewFrustum->getProjectedPolygon(childElement->getAACube())); - - // In order to check occlusion culling, the shadow has to be "all in view" otherwise, we ignore occlusion - // culling and proceed as normal - if (voxelPolygon->getAllInView()) { - CoverageMapStorageResult result = params.map->checkMap(voxelPolygon, true); - - // In all cases where the shadow wasn't stored, we need to free our own memory. - // In the case where it is stored, the CoverageMap will free memory for us later. - if (result != STORED) { - delete voxelPolygon; - } - - // If while attempting to add this voxel's shadow, we determined it was occluded, then - // we don't need to process it further and we can exit early. - if (result == OCCLUDED) { - childIsOccluded = true; - } - } else { - delete voxelPolygon; - } - } // wants occlusion culling & isLeaf() - - bool shouldRender = !params.viewFrustum ? true : childElement->calculateShouldRender(params.viewFrustum, @@ -1359,7 +1293,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, // NOW might be a good time to give our tree subclass and this element a chance to set up and check any extra encode data element->initializeExtraEncodeData(params); - // write the child element data... NOTE: includeColor means include element data + // write the child element data... // NOTE: the format of the bitstream is generally this: // [octalcode] // [bitmask for existence of child data] @@ -1369,65 +1303,63 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, // N x [ ... tree for children ...] // // This section of the code, is writing the "N x [child data]" portion of this bitstream - if (params.includeColor) { - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - if (oneAtBit(childrenDataBits, i)) { - OctreeElementPointer childElement = element->getChildAtIndex(i); + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + if (oneAtBit(childrenDataBits, i)) { + OctreeElementPointer childElement = element->getChildAtIndex(i); - // the childrenDataBits were set up by the in view/LOD logic, it may contain children that we've already - // processed and sent the data bits for. Let our tree subclass determine if it really wants to send the - // data for this child at this point - if (childElement && element->shouldIncludeChildData(i, params)) { + // the childrenDataBits were set up by the in view/LOD logic, it may contain children that we've already + // processed and sent the data bits for. Let our tree subclass determine if it really wants to send the + // data for this child at this point + if (childElement && element->shouldIncludeChildData(i, params)) { - int bytesBeforeChild = packetData->getUncompressedSize(); + int bytesBeforeChild = packetData->getUncompressedSize(); - // a childElement may "partially" write it's data. for example, the model server where the entire - // contents of the element may be larger than can fit in a single MTU/packetData. In this case, - // we want to allow the appendElementData() to respond that it produced partial data, which should be - // written, but that the childElement needs to be reprocessed in an additional pass or passes - // to be completed. - LevelDetails childDataLevelKey = packetData->startLevel(); + // a childElement may "partially" write it's data. for example, the model server where the entire + // contents of the element may be larger than can fit in a single MTU/packetData. In this case, + // we want to allow the appendElementData() to respond that it produced partial data, which should be + // written, but that the childElement needs to be reprocessed in an additional pass or passes + // to be completed. + LevelDetails childDataLevelKey = packetData->startLevel(); - OctreeElement::AppendState childAppendState = childElement->appendElementData(packetData, params); + OctreeElement::AppendState childAppendState = childElement->appendElementData(packetData, params); - // allow our tree subclass to do any additional bookkeeping it needs to do with encoded data state - element->updateEncodedData(i, childAppendState, params); + // allow our tree subclass to do any additional bookkeeping it needs to do with encoded data state + element->updateEncodedData(i, childAppendState, params); - // Continue this level so long as some part of this child element was appended. - bool childFit = (childAppendState != OctreeElement::NONE); + // Continue this level so long as some part of this child element was appended. + bool childFit = (childAppendState != OctreeElement::NONE); - // some datatypes (like Voxels) assume that all child data will fit, if it doesn't fit - // the data type wants to bail on this element level completely - if (!childFit && mustIncludeAllChildData()) { - continueThisLevel = false; - break; - } + // some datatypes (like Voxels) assume that all child data will fit, if it doesn't fit + // the data type wants to bail on this element level completely + if (!childFit && mustIncludeAllChildData()) { + continueThisLevel = false; + break; + } - // If the child was partially or fully appended, then mark the actualChildrenDataBits as including - // this child data - if (childFit) { - actualChildrenDataBits += (1 << (7 - i)); - continueThisLevel = packetData->endLevel(childDataLevelKey); - } else { - packetData->discardLevel(childDataLevelKey); - elementAppendState = OctreeElement::PARTIAL; - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - } + // If the child was partially or fully appended, then mark the actualChildrenDataBits as including + // this child data + if (childFit) { + actualChildrenDataBits += (1 << (7 - i)); + continueThisLevel = packetData->endLevel(childDataLevelKey); + } else { + packetData->discardLevel(childDataLevelKey); + elementAppendState = OctreeElement::PARTIAL; + params.stopReason = EncodeBitstreamParams::DIDNT_FIT; + } - // If this child was partially appended, then consider this element to be partially appended - if (childAppendState == OctreeElement::PARTIAL) { - elementAppendState = OctreeElement::PARTIAL; - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - } + // If this child was partially appended, then consider this element to be partially appended + if (childAppendState == OctreeElement::PARTIAL) { + elementAppendState = OctreeElement::PARTIAL; + params.stopReason = EncodeBitstreamParams::DIDNT_FIT; + } - int bytesAfterChild = packetData->getUncompressedSize(); + int bytesAfterChild = packetData->getUncompressedSize(); - bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child + bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child - // don't need to check childElement here, because we can't get here with no childElement - if (params.stats && (childAppendState != OctreeElement::NONE)) { - params.stats->colorSent(childElement); - } + // don't need to check childElement here, because we can't get here with no childElement + if (params.stats && (childAppendState != OctreeElement::NONE)) { + params.stats->colorSent(childElement); } } } @@ -1510,10 +1442,6 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, // final packet in standard order. So what we're going to do is keep track of how big each subtree was in bytes, // and then later reshuffle these sections of our output buffer back into normal order. This allows us to make // a single recursive pass in distance sorted order, but retain standard order in our encoded packet - int recursiveSliceSizes[NUMBER_OF_CHILDREN]; - const unsigned char* recursiveSliceStarts[NUMBER_OF_CHILDREN]; - int firstRecursiveSliceOffset = packetData->getUncompressedByteOffset(); - int allSlicesSize = 0; // for each child element in Distance sorted order..., check to see if they exist, are colored, and in view, and if so // add them to our distance ordered array of children @@ -1524,8 +1452,6 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, if (oneAtBit(childrenExistInPacketBits, originalIndex)) { int thisLevel = currentEncodeLevel; - // remember this for reshuffling - recursiveSliceStarts[originalIndex] = packetData->getUncompressedData() + packetData->getUncompressedSize(); int childTreeBytesOut = 0; @@ -1546,10 +1472,6 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, } } - // remember this for reshuffling - recursiveSliceSizes[originalIndex] = childTreeBytesOut; - allSlicesSize += childTreeBytesOut; - // if the child wrote 0 bytes, it means that nothing below exists or was in view, or we ran out of space, // basically, the children below don't contain any info. @@ -1566,17 +1488,10 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, // so, if the child returns 2 bytes out, we can actually consider that an empty tree also!! // // we can make this act like no bytes out, by just resetting the bytes out in this case - if (suppressEmptySubtrees() && params.includeColor && !params.includeExistsBits && childTreeBytesOut == 2) { + if (suppressEmptySubtrees() && !params.includeExistsBits && childTreeBytesOut == 2) { childTreeBytesOut = 0; // this is the degenerate case of a tree with no colors and no child trees } - // We used to try to collapse trees that didn't contain any data, but this does appear to create a problem - // in detecting element deletion. So, I've commented this out but left it in here as a warning to anyone else - // about not attempting to add this optimization back in, without solving the element deletion case. - // We need to send these bitMasks in case the exists in tree bitmask is indicating the deletion of a tree - //if (params.includeColor && params.includeExistsBits && childTreeBytesOut == 3) { - // childTreeBytesOut = 0; // this is the degenerate case of a tree with no colors and no child trees - //} bytesAtThisLevel += childTreeBytesOut; @@ -1596,7 +1511,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, // If this is the last of the child exists bits, then we're actually be rolling out the entire tree if (params.stats && childrenExistInPacketBits == 0) { - params.stats->childBitsRemoved(params.includeExistsBits, params.includeColor); + params.stats->childBitsRemoved(params.includeExistsBits); } if (!continueThisLevel) { @@ -1613,33 +1528,6 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, } // end if (childTreeBytesOut == 0) } // end if (oneAtBit(childrenExistInPacketBits, originalIndex)) } // end for - - // reshuffle here... - if (continueThisLevel && params.wantOcclusionCulling) { - unsigned char tempReshuffleBuffer[MAX_OCTREE_UNCOMRESSED_PACKET_SIZE]; - - unsigned char* tempBufferTo = &tempReshuffleBuffer[0]; // this is our temporary destination - - // iterate through our childrenExistInPacketBits, these will be the sections of the packet that we copied subTree - // details into. Unfortunately, they're in distance sorted order, not original index order. we need to put them - // back into original distance order - for (int originalIndex = 0; originalIndex < NUMBER_OF_CHILDREN; originalIndex++) { - if (oneAtBit(childrenExistInPacketBits, originalIndex)) { - int thisSliceSize = recursiveSliceSizes[originalIndex]; - const unsigned char* thisSliceStarts = recursiveSliceStarts[originalIndex]; - - memcpy(tempBufferTo, thisSliceStarts, thisSliceSize); - tempBufferTo += thisSliceSize; - } - } - - // now that all slices are back in the correct order, copy them to the correct output buffer - continueThisLevel = packetData->updatePriorBytes(firstRecursiveSliceOffset, &tempReshuffleBuffer[0], allSlicesSize); - if (!continueThisLevel) { - qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to update recursive slice!!!"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - } } // end keepDiggingDeeper // If we made it this far, then we've written all of our child data... if this element is the root @@ -1736,7 +1624,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, // If our element is completed let the element know so it can do any cleanup it of extra wants if (elementAppendState == OctreeElement::COMPLETED) { - element->elementEncodeComplete(params, &bag); + element->elementEncodeComplete(params); } return bytesAtThisLevel; @@ -1918,7 +1806,7 @@ bool Octree::readSVOFromStream(unsigned long streamLength, QDataStream& inputStr unsigned char* dataAt = entireFileDataSection; - ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, NULL, 0, + ReadBitstreamToTreeParams args(NO_EXISTS_BITS, NULL, 0, SharedNodePointer(), wantImportProgress, gotVersion); readBitstreamToTree(dataAt, dataLength, args); @@ -1957,7 +1845,7 @@ bool Octree::readSVOFromStream(unsigned long streamLength, QDataStream& inputStr unsigned char* dataAt = fileChunk; unsigned long dataLength = chunkLength; - ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, NULL, 0, + ReadBitstreamToTreeParams args(NO_EXISTS_BITS, NULL, 0, SharedNodePointer(), wantImportProgress, gotVersion); readBitstreamToTree(dataAt, dataLength, args); @@ -2104,10 +1992,8 @@ void Octree::writeToSVOFile(const char* fileName, OctreeElementPointer element) int bytesWritten = 0; bool lastPacketWritten = false; - while (!elementBag.isEmpty()) { - OctreeElementPointer subTree = elementBag.extract(); - - EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS); + while (OctreeElementPointer subTree = elementBag.extract()) { + EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, NO_EXISTS_BITS); withReadLock([&] { params.extraEncodeData = &extraEncodeData; bytesWritten = encodeTreeBitstream(subTree, &packetData, elementBag, params); diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 5ebb991d49..dc0294e1de 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -18,16 +18,6 @@ #include #include -class CoverageMap; -class ReadBitstreamToTreeParams; -class Octree; -class OctreeElement; -class OctreeElementBag; -class OctreePacketData; -class Shape; -typedef std::shared_ptr OctreePointer; - - #include #include @@ -38,6 +28,12 @@ typedef std::shared_ptr OctreePointer; #include "OctreePacketData.h" #include "OctreeSceneStats.h" +class ReadBitstreamToTreeParams; +class Octree; +class OctreeElement; +class OctreePacketData; +class Shape; +using OctreePointer = std::shared_ptr; extern QVector PERSIST_EXTENSIONS; @@ -56,12 +52,8 @@ typedef QHash CubeList; const bool NO_EXISTS_BITS = false; const bool WANT_EXISTS_BITS = true; -const bool NO_COLOR = false; -const bool WANT_COLOR = true; const bool COLLAPSE_EMPTY_TREE = true; const bool DONT_COLLAPSE = false; -const bool NO_OCCLUSION_CULLING = false; -const bool WANT_OCCLUSION_CULLING = true; const int DONT_CHOP = 0; const int NO_BOUNDARY_ADJUST = 0; @@ -78,18 +70,15 @@ public: int maxEncodeLevel; int maxLevelReached; const ViewFrustum* viewFrustum; - bool includeColor; bool includeExistsBits; int chopLevels; bool deltaViewFrustum; const ViewFrustum* lastViewFrustum; - bool wantOcclusionCulling; int boundaryLevelAdjust; float octreeElementSizeScale; quint64 lastViewFrustumSent; bool forceSendScene; OctreeSceneStats* stats; - CoverageMap* map; JurisdictionMap* jurisdictionMap; OctreeElementExtraEncodeData* extraEncodeData; @@ -111,13 +100,10 @@ public: EncodeBitstreamParams( int maxEncodeLevel = INT_MAX, const ViewFrustum* viewFrustum = IGNORE_VIEW_FRUSTUM, - bool includeColor = WANT_COLOR, bool includeExistsBits = WANT_EXISTS_BITS, int chopLevels = 0, bool deltaViewFrustum = false, const ViewFrustum* lastViewFrustum = IGNORE_VIEW_FRUSTUM, - bool wantOcclusionCulling = NO_OCCLUSION_CULLING, - CoverageMap* map = IGNORE_COVERAGE_MAP, int boundaryLevelAdjust = NO_BOUNDARY_ADJUST, float octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE, quint64 lastViewFrustumSent = IGNORE_LAST_SENT, @@ -128,18 +114,15 @@ public: maxEncodeLevel(maxEncodeLevel), maxLevelReached(0), viewFrustum(viewFrustum), - includeColor(includeColor), includeExistsBits(includeExistsBits), chopLevels(chopLevels), deltaViewFrustum(deltaViewFrustum), lastViewFrustum(lastViewFrustum), - wantOcclusionCulling(wantOcclusionCulling), boundaryLevelAdjust(boundaryLevelAdjust), octreeElementSizeScale(octreeElementSizeScale), lastViewFrustumSent(lastViewFrustumSent), forceSendScene(forceSendScene), stats(stats), - map(map), jurisdictionMap(jurisdictionMap), extraEncodeData(extraEncodeData), stopReason(UNKNOWN) @@ -179,6 +162,8 @@ public: case OCCLUDED: return QString("OCCLUDED"); break; } } + + std::function trackSend { [](const QUuid&, quint64){} }; }; class ReadElementBufferToTreeArgs { @@ -191,7 +176,6 @@ public: class ReadBitstreamToTreeParams { public: - bool includeColor; bool includeExistsBits; OctreeElementPointer destinationElement; QUuid sourceUUID; @@ -202,14 +186,12 @@ public: int entitiesPerPacket = 0; ReadBitstreamToTreeParams( - bool includeColor = WANT_COLOR, bool includeExistsBits = WANT_EXISTS_BITS, OctreeElementPointer destinationElement = NULL, QUuid sourceUUID = QUuid(), SharedNodePointer sourceNode = SharedNodePointer(), bool wantImportProgress = false, PacketVersion bitstreamVersion = 0) : - includeColor(includeColor), includeExistsBits(includeExistsBits), destinationElement(destinationElement), sourceUUID(sourceUUID), @@ -236,7 +218,7 @@ public: return thisVersion == versionForPacketType(expectedDataPacketType()); } virtual PacketVersion expectedVersion() const { return versionForPacketType(expectedDataPacketType()); } virtual bool handlesEditPacketType(PacketType packetType) const { return false; } - virtual int processEditPacketData(NLPacket& packet, const unsigned char* editData, int maxLength, + virtual int processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, const SharedNodePointer& sourceNode) { return 0; } virtual bool recurseChildrenWithData() const { return true; } @@ -316,7 +298,7 @@ public: Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL); // Note: this assumes the fileFormat is the HIO individual voxels code files - void loadOctreeFile(const char* fileName, bool wantColorRandomizer); + void loadOctreeFile(const char* fileName); // Octree exporters void writeToFile(const char* filename, OctreeElementPointer element = NULL, QString persistAsFileType = "svo"); diff --git a/libraries/octree/src/OctreeEditPacketSender.cpp b/libraries/octree/src/OctreeEditPacketSender.cpp index 495effc825..df53a0f873 100644 --- a/libraries/octree/src/OctreeEditPacketSender.cpp +++ b/libraries/octree/src/OctreeEditPacketSender.cpp @@ -339,7 +339,7 @@ bool OctreeEditPacketSender::process() { return PacketSender::process(); } -void OctreeEditPacketSender::processNackPacket(NLPacket& packet, SharedNodePointer sendingNode) { +void OctreeEditPacketSender::processNackPacket(ReceivedMessage& message, SharedNodePointer sendingNode) { // parse sending node from packet, retrieve packet history for that node // if packet history doesn't exist for the sender node (somehow), bail @@ -350,9 +350,9 @@ void OctreeEditPacketSender::processNackPacket(NLPacket& packet, SharedNodePoint const SentPacketHistory& sentPacketHistory = _sentPacketHistories[sendingNode->getUUID()]; // read sequence numbers and queue packets for resend - while (packet.bytesLeftToRead() > 0) { + while (message.getBytesLeftToRead() > 0) { unsigned short int sequenceNumber; - packet.readPrimitive(&sequenceNumber); + message.readPrimitive(&sequenceNumber); // retrieve packet from history const NLPacket* packet = sentPacketHistory.getPacket(sequenceNumber); diff --git a/libraries/octree/src/OctreeEditPacketSender.h b/libraries/octree/src/OctreeEditPacketSender.h index d8761e6161..12cb03fcd8 100644 --- a/libraries/octree/src/OctreeEditPacketSender.h +++ b/libraries/octree/src/OctreeEditPacketSender.h @@ -77,7 +77,7 @@ public: virtual char getMyNodeType() const = 0; virtual void adjustEditPacketForClockSkew(PacketType type, QByteArray& buffer, int clockSkew) { } - void processNackPacket(NLPacket& packet, SharedNodePointer sendingNode); + void processNackPacket(ReceivedMessage& message, SharedNodePointer sendingNode); public slots: void nodeKilled(SharedNodePointer node); diff --git a/libraries/octree/src/OctreeElement.cpp b/libraries/octree/src/OctreeElement.cpp index 5f03627f1a..f16e1dc88d 100644 --- a/libraries/octree/src/OctreeElement.cpp +++ b/libraries/octree/src/OctreeElement.cpp @@ -95,10 +95,6 @@ void OctreeElement::init(unsigned char * octalCode) { } OctreeElement::~OctreeElement() { - // We can't call notifyDeleteHooks from here: - // notifyDeleteHooks(); - // see comment in EntityTreeElement::createNewElement. - assert(_deleteHooksNotified); _voxelNodeCount--; if (isLeaf()) { _voxelNodeLeafCount--; @@ -115,7 +111,6 @@ OctreeElement::~OctreeElement() { void OctreeElement::markWithChangedTime() { _lastChanged = usecTimestampNow(); - notifyUpdateHooks(); // if the node has changed, notify our hooks } // This method is called by Octree when the subtree below this node @@ -522,57 +517,6 @@ float OctreeElement::distanceToPoint(const glm::vec3& point) const { return distance; } -QReadWriteLock OctreeElement::_deleteHooksLock; -std::vector OctreeElement::_deleteHooks; - -void OctreeElement::addDeleteHook(OctreeElementDeleteHook* hook) { - _deleteHooksLock.lockForWrite(); - _deleteHooks.push_back(hook); - _deleteHooksLock.unlock(); -} - -void OctreeElement::removeDeleteHook(OctreeElementDeleteHook* hook) { - _deleteHooksLock.lockForWrite(); - for (unsigned int i = 0; i < _deleteHooks.size(); i++) { - if (_deleteHooks[i] == hook) { - _deleteHooks.erase(_deleteHooks.begin() + i); - break; - } - } - _deleteHooksLock.unlock(); -} - -void OctreeElement::notifyDeleteHooks() { - _deleteHooksLock.lockForRead(); - for (unsigned int i = 0; i < _deleteHooks.size(); i++) { - _deleteHooks[i]->elementDeleted(shared_from_this()); - } - _deleteHooksLock.unlock(); - _deleteHooksNotified = true; -} - -std::vector OctreeElement::_updateHooks; - -void OctreeElement::addUpdateHook(OctreeElementUpdateHook* hook) { - _updateHooks.push_back(hook); -} - -void OctreeElement::removeUpdateHook(OctreeElementUpdateHook* hook) { - for (unsigned int i = 0; i < _updateHooks.size(); i++) { - if (_updateHooks[i] == hook) { - _updateHooks.erase(_updateHooks.begin() + i); - return; - } - } -} - -void OctreeElement::notifyUpdateHooks() { - for (unsigned int i = 0; i < _updateHooks.size(); i++) { - _updateHooks[i]->elementUpdated(shared_from_this()); - } -} - - bool OctreeElement::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const { // center and radius are in meters, so we have to scale the _cube into world-frame diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h index d705b64acd..3c25ec0850 100644 --- a/libraries/octree/src/OctreeElement.h +++ b/libraries/octree/src/OctreeElement.h @@ -32,28 +32,15 @@ using AtomicUIntStat = std::atomic; class EncodeBitstreamParams; class Octree; class OctreeElement; -class OctreeElementBag; -class OctreeElementDeleteHook; class OctreePacketData; class ReadBitstreamToTreeParams; class Shape; class VoxelSystem; -typedef std::shared_ptr OctreeElementPointer; -typedef std::shared_ptr ConstOctreeElementPointer; -typedef std::shared_ptr OctreePointer; - -// Callers who want delete hook callbacks should implement this class -class OctreeElementDeleteHook { -public: - virtual void elementDeleted(OctreeElementPointer element) = 0; -}; - -// Callers who want update hook callbacks should implement this class -class OctreeElementUpdateHook { -public: - virtual void elementUpdated(OctreeElementPointer element) = 0; -}; +using OctreeElementPointer = std::shared_ptr; +using OctreeElementWeakPointer = std::weak_ptr; +using ConstOctreeElementPointer = std::shared_ptr; +using OctreePointer = std::shared_ptr; class OctreeElement: public std::enable_shared_from_this { @@ -103,7 +90,7 @@ public: virtual bool shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const { return true; } virtual void updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const { } - virtual void elementEncodeComplete(EncodeBitstreamParams& params, OctreeElementBag* bag) const { } + virtual void elementEncodeComplete(EncodeBitstreamParams& params) const { } /// Override to serialize the state of this element. This is used for persistance and for transmission across the network. virtual AppendState appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const @@ -181,12 +168,6 @@ public: bool matchesSourceUUID(const QUuid& sourceUUID) const; static uint16_t getSourceNodeUUIDKey(const QUuid& sourceUUID); - static void addDeleteHook(OctreeElementDeleteHook* hook); - static void removeDeleteHook(OctreeElementDeleteHook* hook); - - static void addUpdateHook(OctreeElementUpdateHook* hook); - static void removeUpdateHook(OctreeElementUpdateHook* hook); - static void resetPopulationStatistics(); static unsigned long getNodeCount() { return _voxelNodeCount; } static unsigned long getInternalNodeCount() { return _voxelNodeCount - _voxelNodeLeafCount; } @@ -244,8 +225,6 @@ protected: void setChildAtIndex(int childIndex, OctreeElementPointer child); void calculateAACube(); - void notifyDeleteHooks(); - void notifyUpdateHooks(); AACube _cube; /// Client and server, axis aligned box for bounds of this voxel, 48 bytes @@ -287,14 +266,6 @@ protected: _unknownBufferIndex : 1, _childrenExternal : 1; /// Client only, is this voxel's VBO buffer the unknown buffer index, 1 bit - bool _deleteHooksNotified = false; - - static QReadWriteLock _deleteHooksLock; - static std::vector _deleteHooks; - - //static QReadWriteLock _updateHooksLock; - static std::vector _updateHooks; - static AtomicUIntStat _voxelNodeCount; static AtomicUIntStat _voxelNodeLeafCount; diff --git a/libraries/octree/src/OctreeElementBag.cpp b/libraries/octree/src/OctreeElementBag.cpp index 5af63c7bb1..4634c05a06 100644 --- a/libraries/octree/src/OctreeElementBag.cpp +++ b/libraries/octree/src/OctreeElementBag.cpp @@ -12,54 +12,30 @@ #include "OctreeElementBag.h" #include -OctreeElementBag::OctreeElementBag() : - _bagElements() -{ - OctreeElement::addDeleteHook(this); - _hooked = true; -} - -OctreeElementBag::~OctreeElementBag() { - unhookNotifications(); - deleteAll(); -} - -void OctreeElementBag::unhookNotifications() { - if (_hooked) { - OctreeElement::removeDeleteHook(this); - _hooked = false; - } -} - -void OctreeElementBag::elementDeleted(OctreeElementPointer element) { - remove(element); // note: remove can safely handle nodes that aren't in it, so we don't need to check contains() -} - - void OctreeElementBag::deleteAll() { - _bagElements.clear(); + _bagElements = Bag(); } +bool OctreeElementBag::isEmpty() { + // Pop all expired front elements + while (!_bagElements.empty() && _bagElements.front().expired()) { + _bagElements.pop(); + } + + return _bagElements.empty(); +} void OctreeElementBag::insert(OctreeElementPointer element) { - _bagElements.insert(element); + _bagElements.push(element); } OctreeElementPointer OctreeElementBag::extract() { - OctreeElementPointer result = NULL; + OctreeElementPointer result; - if (_bagElements.size() > 0) { - QSet::iterator front = _bagElements.begin(); - result = *front; - _bagElements.erase(front); + // Find the first element still alive + while (!result && !_bagElements.empty()) { + result = _bagElements.front().lock(); // Grab head's shared_ptr + _bagElements.pop(); } return result; } - -bool OctreeElementBag::contains(OctreeElementPointer element) { - return _bagElements.contains(element); -} - -void OctreeElementBag::remove(OctreeElementPointer element) { - _bagElements.remove(element); -} diff --git a/libraries/octree/src/OctreeElementBag.h b/libraries/octree/src/OctreeElementBag.h index 8ef01b44a2..02423b640c 100644 --- a/libraries/octree/src/OctreeElementBag.h +++ b/libraries/octree/src/OctreeElementBag.h @@ -16,31 +16,24 @@ #ifndef hifi_OctreeElementBag_h #define hifi_OctreeElementBag_h +#include + #include "OctreeElement.h" -class OctreeElementBag : public OctreeElementDeleteHook { - +class OctreeElementBag { + using Bag = std::queue; + public: - OctreeElementBag(); - ~OctreeElementBag(); - void insert(OctreeElementPointer element); // put a element into the bag OctreeElementPointer extract(); // pull a element out of the bag (could come in any order) - bool contains(OctreeElementPointer element); // is this element in the bag? - void remove(OctreeElementPointer element); // remove a specific element from the bag - bool isEmpty() const { return _bagElements.isEmpty(); } - int count() const { return _bagElements.size(); } - + bool isEmpty(); + void deleteAll(); - virtual void elementDeleted(OctreeElementPointer element); - - void unhookNotifications(); private: - QSet _bagElements; - bool _hooked; + Bag _bagElements; }; -typedef QMap OctreeElementExtraEncodeData; +using OctreeElementExtraEncodeData = QMap; #endif // hifi_OctreeElementBag_h diff --git a/libraries/octree/src/OctreeHeadlessViewer.cpp b/libraries/octree/src/OctreeHeadlessViewer.cpp index 88a77a4c53..32da638f96 100644 --- a/libraries/octree/src/OctreeHeadlessViewer.cpp +++ b/libraries/octree/src/OctreeHeadlessViewer.cpp @@ -49,13 +49,6 @@ void OctreeHeadlessViewer::queryOctree() { qCDebug(octree) << "---------------"; } - // These will be the same for all servers, so we can set them up once and then reuse for each server we send to. - _octreeQuery.setWantLowResMoving(true); - _octreeQuery.setWantColor(true); - _octreeQuery.setWantDelta(true); - _octreeQuery.setWantOcclusionCulling(false); - _octreeQuery.setWantCompression(true); // TODO: should be on by default - _octreeQuery.setCameraPosition(_viewFrustum.getPosition()); _octreeQuery.setCameraOrientation(_viewFrustum.getOrientation()); _octreeQuery.setCameraFov(_viewFrustum.getFieldOfView()); @@ -231,10 +224,10 @@ void OctreeHeadlessViewer::queryOctree() { } -int OctreeHeadlessViewer::parseOctreeStats(QSharedPointer packet, SharedNodePointer sourceNode) { +int OctreeHeadlessViewer::parseOctreeStats(QSharedPointer message, SharedNodePointer sourceNode) { OctreeSceneStats temp; - int statsMessageLength = temp.unpackFromPacket(*packet); + int statsMessageLength = temp.unpackFromPacket(*message); // TODO: actually do something with these stats, like expose them to JS... diff --git a/libraries/octree/src/OctreeHeadlessViewer.h b/libraries/octree/src/OctreeHeadlessViewer.h index cebfa5a928..82282dafaa 100644 --- a/libraries/octree/src/OctreeHeadlessViewer.h +++ b/libraries/octree/src/OctreeHeadlessViewer.h @@ -37,7 +37,7 @@ public: void setJurisdictionListener(JurisdictionListener* jurisdictionListener) { _jurisdictionListener = jurisdictionListener; } - static int parseOctreeStats(QSharedPointer packet, SharedNodePointer sourceNode); + static int parseOctreeStats(QSharedPointer message, SharedNodePointer sourceNode); static void trackIncomingOctreePacket(const QByteArray& packet, const SharedNodePointer& sendingNode, bool wasStatsPacket); public slots: diff --git a/libraries/octree/src/OctreeQuery.cpp b/libraries/octree/src/OctreeQuery.cpp index e8beb0404c..07bca14844 100644 --- a/libraries/octree/src/OctreeQuery.cpp +++ b/libraries/octree/src/OctreeQuery.cpp @@ -40,11 +40,15 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) { // bitMask of less than byte wide items unsigned char bitItems = 0; - if (_wantLowResMoving) { setAtBit(bitItems, WANT_LOW_RES_MOVING_BIT); } - if (_wantColor) { setAtBit(bitItems, WANT_COLOR_AT_BIT); } - if (_wantDelta) { setAtBit(bitItems, WANT_DELTA_AT_BIT); } - if (_wantOcclusionCulling) { setAtBit(bitItems, WANT_OCCLUSION_CULLING_BIT); } - if (_wantCompression) { setAtBit(bitItems, WANT_COMPRESSION); } + + // NOTE: we need to keep these here for new clients to talk to old servers. After we know that the clients and + // servers and clients have all been updated we could remove these bits. New servers will always force these + // features on old clients even if they don't ask for them. (which old clients will properly handle). New clients + // will always ask for these so that old servers will use these features. + setAtBit(bitItems, WANT_LOW_RES_MOVING_BIT); + setAtBit(bitItems, WANT_COLOR_AT_BIT); + setAtBit(bitItems, WANT_DELTA_AT_BIT); + setAtBit(bitItems, WANT_COMPRESSION); *destinationBuffer++ = bitItems; @@ -64,9 +68,9 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) { } // called on the other nodes - assigns it to my views of the others -int OctreeQuery::parseData(NLPacket& packet) { +int OctreeQuery::parseData(ReceivedMessage& message) { - const unsigned char* startPosition = reinterpret_cast(packet.getPayload()); + const unsigned char* startPosition = reinterpret_cast(message.getRawMessage()); const unsigned char* sourceBuffer = startPosition; // camera details @@ -80,14 +84,13 @@ int OctreeQuery::parseData(NLPacket& packet) { memcpy(&_cameraEyeOffsetPosition, sourceBuffer, sizeof(_cameraEyeOffsetPosition)); sourceBuffer += sizeof(_cameraEyeOffsetPosition); - // voxel sending features... + // optional feature flags unsigned char bitItems = 0; bitItems = (unsigned char)*sourceBuffer++; - _wantLowResMoving = oneAtBit(bitItems, WANT_LOW_RES_MOVING_BIT); - _wantColor = oneAtBit(bitItems, WANT_COLOR_AT_BIT); - _wantDelta = oneAtBit(bitItems, WANT_DELTA_AT_BIT); - _wantOcclusionCulling = oneAtBit(bitItems, WANT_OCCLUSION_CULLING_BIT); - _wantCompression = oneAtBit(bitItems, WANT_COMPRESSION); + + // NOTE: we used to use these bits to set feature request items if we need to extend the protocol with optional features + // do it here with... wantFeature= oneAtBit(bitItems, WANT_FEATURE_BIT); + Q_UNUSED(bitItems); // desired Max Octree PPS memcpy(&_maxQueryPPS, sourceBuffer, sizeof(_maxQueryPPS)); diff --git a/libraries/octree/src/OctreeQuery.h b/libraries/octree/src/OctreeQuery.h index 86474ffc02..fec1ac0c2a 100644 --- a/libraries/octree/src/OctreeQuery.h +++ b/libraries/octree/src/OctreeQuery.h @@ -37,7 +37,7 @@ typedef unsigned long long quint64; const int WANT_LOW_RES_MOVING_BIT = 0; const int WANT_COLOR_AT_BIT = 1; const int WANT_DELTA_AT_BIT = 2; -const int WANT_OCCLUSION_CULLING_BIT = 3; +const int UNUSED_BIT_3 = 3; // unused... available for new feature const int WANT_COMPRESSION = 4; // 5th bit class OctreeQuery : public NodeData { @@ -48,7 +48,7 @@ public: virtual ~OctreeQuery() {} int getBroadcastData(unsigned char* destinationBuffer); - int parseData(NLPacket& packet); + int parseData(ReceivedMessage& message) override; // getters for camera details const glm::vec3& getCameraPosition() const { return _cameraPosition; } @@ -71,21 +71,11 @@ public: void setCameraEyeOffsetPosition(const glm::vec3& eyeOffsetPosition) { _cameraEyeOffsetPosition = eyeOffsetPosition; } // related to Octree Sending strategies - bool getWantColor() const { return _wantColor; } - bool getWantDelta() const { return _wantDelta; } - bool getWantLowResMoving() const { return _wantLowResMoving; } - bool getWantOcclusionCulling() const { return _wantOcclusionCulling; } - bool getWantCompression() const { return _wantCompression; } int getMaxQueryPacketsPerSecond() const { return _maxQueryPPS; } float getOctreeSizeScale() const { return _octreeElementSizeScale; } int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; } public slots: - void setWantLowResMoving(bool wantLowResMoving) { _wantLowResMoving = wantLowResMoving; } - void setWantColor(bool wantColor) { _wantColor = wantColor; } - void setWantDelta(bool wantDelta) { _wantDelta = wantDelta; } - void setWantOcclusionCulling(bool wantOcclusionCulling) { _wantOcclusionCulling = wantOcclusionCulling; } - void setWantCompression(bool wantCompression) { _wantCompression = wantCompression; } void setMaxQueryPacketsPerSecond(int maxQueryPPS) { _maxQueryPPS = maxQueryPPS; } void setOctreeSizeScale(float octreeSizeScale) { _octreeElementSizeScale = octreeSizeScale; } void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; } @@ -101,11 +91,6 @@ protected: glm::vec3 _cameraEyeOffsetPosition = glm::vec3(0.0f); // octree server sending items - bool _wantColor = true; - bool _wantDelta = true; - bool _wantLowResMoving = true; - bool _wantOcclusionCulling = false; - bool _wantCompression = false; int _maxQueryPPS = DEFAULT_MAX_OCTREE_PPS; float _octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE; /// used for LOD calculations int _boundaryLevelAdjust = 0; /// used for LOD calculations diff --git a/libraries/octree/src/OctreeRenderer.cpp b/libraries/octree/src/OctreeRenderer.cpp index b7be4cf3e7..324a8ca20f 100644 --- a/libraries/octree/src/OctreeRenderer.cpp +++ b/libraries/octree/src/OctreeRenderer.cpp @@ -41,7 +41,7 @@ void OctreeRenderer::setTree(OctreePointer newTree) { _tree = newTree; } -void OctreeRenderer::processDatagram(NLPacket& packet, SharedNodePointer sourceNode) { +void OctreeRenderer::processDatagram(ReceivedMessage& message, SharedNodePointer sourceNode) { bool extraDebugging = false; if (extraDebugging) { @@ -56,19 +56,19 @@ void OctreeRenderer::processDatagram(NLPacket& packet, SharedNodePointer sourceN bool showTimingDetails = false; // Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showTimingDetails, "OctreeRenderer::processDatagram()", showTimingDetails); - if (packet.getType() == getExpectedPacketType()) { + if (message.getType() == getExpectedPacketType()) { PerformanceWarning warn(showTimingDetails, "OctreeRenderer::processDatagram expected PacketType", showTimingDetails); // if we are getting inbound packets, then our tree is also viewing, and we should remember that fact. _tree->setIsViewing(true); OCTREE_PACKET_FLAGS flags; - packet.readPrimitive(&flags); + message.readPrimitive(&flags); OCTREE_PACKET_SEQUENCE sequence; - packet.readPrimitive(&sequence); + message.readPrimitive(&sequence); OCTREE_PACKET_SENT_TIME sentAt; - packet.readPrimitive(&sentAt); + message.readPrimitive(&sentAt); bool packetIsColored = oneAtBit(flags, PACKET_IS_COLOR_BIT); bool packetIsCompressed = oneAtBit(flags, PACKET_IS_COMPRESSED_BIT); @@ -83,7 +83,7 @@ void OctreeRenderer::processDatagram(NLPacket& packet, SharedNodePointer sourceN qCDebug(octree, "OctreeRenderer::processDatagram() ... Got Packet Section" " color:%s compressed:%s sequence: %u flight:%d usec size:%lld data:%lld", debug::valueOf(packetIsColored), debug::valueOf(packetIsCompressed), - sequence, flightTime, packet.getDataSize(), packet.bytesLeftToRead()); + sequence, flightTime, message.getSize(), message.getBytesLeftToRead()); } _packetsInLastWindow++; @@ -95,28 +95,28 @@ void OctreeRenderer::processDatagram(NLPacket& packet, SharedNodePointer sourceN quint64 totalUncompress = 0; quint64 totalReadBitsteam = 0; - const QUuid& sourceUUID = packet.getSourceID(); + const QUuid& sourceUUID = message.getSourceID(); int subsection = 1; bool error = false; - while (packet.bytesLeftToRead() > 0 && !error) { + while (message.getBytesLeftToRead() > 0 && !error) { if (packetIsCompressed) { - if (packet.bytesLeftToRead() > (qint64) sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE)) { - packet.readPrimitive(§ionLength); + if (message.getBytesLeftToRead() > (qint64) sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE)) { + message.readPrimitive(§ionLength); } else { sectionLength = 0; error = true; } } else { - sectionLength = packet.bytesLeftToRead(); + sectionLength = message.getBytesLeftToRead(); } if (sectionLength) { // ask the VoxelTree to read the bitstream into the tree - ReadBitstreamToTreeParams args(packetIsColored ? WANT_COLOR : NO_COLOR, WANT_EXISTS_BITS, NULL, - sourceUUID, sourceNode, false, packet.getVersion()); + ReadBitstreamToTreeParams args(WANT_EXISTS_BITS, NULL, + sourceUUID, sourceNode, false, message.getVersion()); quint64 startUncompress, startLock = usecTimestampNow(); quint64 startReadBitsteam, endReadBitsteam; // FIXME STUTTER - there may be an opportunity to bump this lock outside of the @@ -125,14 +125,14 @@ void OctreeRenderer::processDatagram(NLPacket& packet, SharedNodePointer sourceN startUncompress = usecTimestampNow(); OctreePacketData packetData(packetIsCompressed); - packetData.loadFinalizedContent(reinterpret_cast(packet.getPayload() + packet.pos()), + packetData.loadFinalizedContent(reinterpret_cast(message.getRawMessage() + message.getPosition()), sectionLength); if (extraDebugging) { qCDebug(octree, "OctreeRenderer::processDatagram() ... Got Packet Section" " color:%s compressed:%s sequence: %u flight:%d usec size:%lld data:%lld" " subsection:%d sectionLength:%d uncompressed:%d", debug::valueOf(packetIsColored), debug::valueOf(packetIsCompressed), - sequence, flightTime, packet.getDataSize(), packet.bytesLeftToRead(), subsection, sectionLength, + sequence, flightTime, message.getSize(), message.getBytesLeftToRead(), subsection, sectionLength, packetData.getUncompressedSize()); } @@ -148,7 +148,7 @@ void OctreeRenderer::processDatagram(NLPacket& packet, SharedNodePointer sourceN }); // seek forwards in packet - packet.seek(packet.pos() + sectionLength); + message.seek(message.getPosition() + sectionLength); elementsPerPacket += args.elementsPerPacket; entitiesPerPacket += args.entitiesPerPacket; diff --git a/libraries/octree/src/OctreeRenderer.h b/libraries/octree/src/OctreeRenderer.h index e2d97f0484..b5126ab6e6 100644 --- a/libraries/octree/src/OctreeRenderer.h +++ b/libraries/octree/src/OctreeRenderer.h @@ -45,7 +45,7 @@ public: virtual void setTree(OctreePointer newTree); /// process incoming data - virtual void processDatagram(NLPacket& packet, SharedNodePointer sourceNode); + virtual void processDatagram(ReceivedMessage& message, SharedNodePointer sourceNode); /// initialize and GPU/rendering related resources virtual void init(); diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp index 22352fbe3b..822fbb2c50 100644 --- a/libraries/octree/src/OctreeSceneStats.cpp +++ b/libraries/octree/src/OctreeSceneStats.cpp @@ -371,14 +371,12 @@ void OctreeSceneStats::existsInPacketBitsWritten() { _existsInPacketBitsWritten++; } -void OctreeSceneStats::childBitsRemoved(bool includesExistsBits, bool includesColors) { +void OctreeSceneStats::childBitsRemoved(bool includesExistsBits) { _existsInPacketBitsWritten--; if (includesExistsBits) { _existsBitsWritten--; } - if (includesColors) { - _colorBitsWritten--; - } + _colorBitsWritten--; _treesRemoved++; } @@ -443,7 +441,7 @@ int OctreeSceneStats::packIntoPacket() { return _statsPacket->getPayloadSize(); } -int OctreeSceneStats::unpackFromPacket(NLPacket& packet) { +int OctreeSceneStats::unpackFromPacket(ReceivedMessage& packet) { packet.readPrimitive(&_start); packet.readPrimitive(&_end); packet.readPrimitive(&_elapsed); @@ -550,7 +548,7 @@ int OctreeSceneStats::unpackFromPacket(NLPacket& packet) { float calculatedBPV = total == 0 ? 0 : (_bytes * 8) / total; _bitsPerOctreeAverage.updateAverage(calculatedBPV); - return packet.pos(); // excludes header! + return packet.getPosition(); // excludes header! } @@ -748,17 +746,17 @@ const char* OctreeSceneStats::getItemValue(Item item) { return _itemValueBuffer; } -void OctreeSceneStats::trackIncomingOctreePacket(NLPacket& packet, bool wasStatsPacket, int nodeClockSkewUsec) { +void OctreeSceneStats::trackIncomingOctreePacket(ReceivedMessage& message, bool wasStatsPacket, int nodeClockSkewUsec) { const bool wantExtraDebugging = false; // skip past the flags - packet.seek(sizeof(OCTREE_PACKET_FLAGS)); + message.seek(sizeof(OCTREE_PACKET_FLAGS)); OCTREE_PACKET_SEQUENCE sequence; - packet.readPrimitive(&sequence); + message.readPrimitive(&sequence); OCTREE_PACKET_SENT_TIME sentAt; - packet.readPrimitive(&sentAt); + message.readPrimitive(&sentAt); //bool packetIsColored = oneAtBit(flags, PACKET_IS_COLOR_BIT); //bool packetIsCompressed = oneAtBit(flags, PACKET_IS_COMPRESSED_BIT); @@ -790,8 +788,8 @@ void OctreeSceneStats::trackIncomingOctreePacket(NLPacket& packet, bool wasStats // track packets here... _incomingPacket++; - _incomingBytes += packet.getDataSize(); + _incomingBytes += message.getSize(); if (!wasStatsPacket) { - _incomingWastedBytes += (udt::MAX_PACKET_SIZE - packet.getDataSize()); + _incomingWastedBytes += (udt::MAX_PACKET_SIZE - message.getSize()); } } diff --git a/libraries/octree/src/OctreeSceneStats.h b/libraries/octree/src/OctreeSceneStats.h index bdb4ef206a..ced81277e4 100644 --- a/libraries/octree/src/OctreeSceneStats.h +++ b/libraries/octree/src/OctreeSceneStats.h @@ -89,13 +89,13 @@ public: void existsInPacketBitsWritten(); /// Fix up tracking statistics in case where bitmasks were removed for some reason - void childBitsRemoved(bool includesExistsBits, bool includesColors); + void childBitsRemoved(bool includesExistsBits); /// Pack the details of the statistics into a buffer for sending as a network packet int packIntoPacket(); /// Unpack the details of the statistics from a network packet - int unpackFromPacket(NLPacket& packet); + int unpackFromPacket(ReceivedMessage& packet); /// Indicates that a scene has been completed and the statistics are ready to be sent bool isReadyToSend() const { return _isReadyToSend; } @@ -161,7 +161,7 @@ public: quint64 getLastFullTotalBytes() const { return _lastFullTotalBytes; } // Used in client implementations to track individual octree packets - void trackIncomingOctreePacket(NLPacket& packet, bool wasStatsPacket, int nodeClockSkewUsec); + void trackIncomingOctreePacket(ReceivedMessage& message, bool wasStatsPacket, int nodeClockSkewUsec); quint32 getIncomingPackets() const { return _incomingPacket; } quint64 getIncomingBytes() const { return _incomingBytes; } diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 34439e57ce..f86369a514 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -455,8 +455,9 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q EntityItemProperties properties; // explicitly set the properties that changed so that they will be packed - properties.setPosition(_serverPosition); - properties.setRotation(_serverRotation); + properties.setPosition(_entity->getLocalPosition()); + properties.setRotation(_entity->getLocalOrientation()); + properties.setVelocity(_serverVelocity); properties.setAcceleration(_serverAcceleration); properties.setAngularVelocity(_serverAngularVelocity); @@ -492,20 +493,14 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q _nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS; } - if (EntityItem::getSendPhysicsUpdates()) { - EntityItemID id(_entity->getID()); - EntityEditPacketSender* entityPacketSender = static_cast(packetSender); - #ifdef WANT_DEBUG - qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()..."; - #endif + EntityItemID id(_entity->getID()); + EntityEditPacketSender* entityPacketSender = static_cast(packetSender); + #ifdef WANT_DEBUG + qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()..."; + #endif - entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, id, properties); - _entity->setLastBroadcast(usecTimestampNow()); - } else { - #ifdef WANT_DEBUG - qCDebug(physics) << "EntityMotionState::sendUpdate()... NOT sending update as requested."; - #endif - } + entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, id, properties); + _entity->setLastBroadcast(usecTimestampNow()); _lastStep = step; } diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index aa9f7852c8..7389d18143 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -140,6 +140,7 @@ void ObjectMotionState::updateCCDConfiguration() { // TODO: Ideally the swept sphere radius would be contained by the object. Using the bounding sphere // radius works well for spherical objects, but may cause issues with other shapes. For arbitrary // objects we may want to consider a different approach, such as grouping rigid bodies together. + _body->setCcdSweptSphereRadius(radius); } else { // Disable CCD diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 5b00391f09..83afbc9402 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -14,6 +14,7 @@ #include #include +class QImage; #include #include @@ -65,29 +66,15 @@ public: // processing messages in the middle of submitFrame virtual void stop() = 0; - /** - * Called by the application before the frame rendering. Can be used for - * render timing related calls (for instance, the Oculus begin frame timing - * call) - */ - virtual void preRender() = 0; - /** - * Called by the application immediately before calling the display function. - * For OpenGL based plugins, this is the best place to put activate the output - * OpenGL context - */ - virtual void preDisplay() = 0; - /** * Sends the scene texture to the display plugin. */ - virtual void display(uint32_t sceneTexture, const glm::uvec2& sceneSize) = 0; + virtual void submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) = 0; /** - * Called by the application immeidately after display. For OpenGL based - * displays, this is the best place to put the buffer swap - */ - virtual void finishFrame() = 0; + * Sends the scene texture to the display plugin. + */ + virtual void submitOverlayTexture(uint32_t overlayTexture, const glm::uvec2& overlaySize) = 0; // Does the rendering surface have current focus? virtual bool hasFocus() const = 0; @@ -110,18 +97,21 @@ public: return baseProjection; } + // Fetch the most recently displayed image as a QImage + virtual QImage getScreenshot() const = 0; + // HMD specific methods // TODO move these into another class? virtual glm::mat4 getEyeToHeadTransform(Eye eye) const { static const glm::mat4 transform; return transform; } - virtual glm::mat4 getHeadPose() const { + virtual glm::mat4 getHeadPose(uint32_t frameIndex) const { static const glm::mat4 pose; return pose; } // Needed for timewarp style features - virtual void setEyeRenderPose(Eye eye, const glm::mat4& pose) { + virtual void setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) { // NOOP } @@ -129,8 +119,8 @@ public: virtual void abandonCalibration() {} virtual void resetSensors() {} - virtual float devicePixelRatio() { return 1.0; } - + virtual float devicePixelRatio() { return 1.0f; } + virtual float presentRate() { return -1.0f; } static const QString& MENU_PATH(); signals: diff --git a/libraries/plugins/src/plugins/Forward.h b/libraries/plugins/src/plugins/Forward.h index 8d8259ba4f..036b42f7d7 100644 --- a/libraries/plugins/src/plugins/Forward.h +++ b/libraries/plugins/src/plugins/Forward.h @@ -10,6 +10,11 @@ #include #include +enum class PluginType { + DISPLAY_PLUGIN, + INPUT_PLUGIN, +}; + class DisplayPlugin; class InputPlugin; class Plugin; diff --git a/libraries/plugins/src/plugins/PluginContainer.h b/libraries/plugins/src/plugins/PluginContainer.h index f013bfe3bf..19859fd98b 100644 --- a/libraries/plugins/src/plugins/PluginContainer.h +++ b/libraries/plugins/src/plugins/PluginContainer.h @@ -8,11 +8,19 @@ #pragma once #include +#include #include +#include +#include + +#include "Forward.h" class QAction; -class QGLWidget; +class GLWidget; class QScreen; +class QOpenGLContext; +class QWindow; + class DisplayPlugin; class PluginContainer { @@ -22,7 +30,7 @@ public: virtual ~PluginContainer(); virtual void addMenu(const QString& menuName) = 0; virtual void removeMenu(const QString& menuName) = 0; - virtual QAction* addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") = 0; + virtual QAction* addMenuItem(PluginType pluginType, const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") = 0; virtual void removeMenuItem(const QString& menuName, const QString& menuItem) = 0; virtual bool isOptionChecked(const QString& name) = 0; virtual void setIsOptionChecked(const QString& path, bool checked) = 0; @@ -30,7 +38,25 @@ public: virtual void unsetFullscreen(const QScreen* avoidScreen = nullptr) = 0; virtual void showDisplayPluginsTools() = 0; virtual void requestReset() = 0; - virtual QGLWidget* getPrimarySurface() = 0; + virtual bool makeRenderingContextCurrent() = 0; + virtual void releaseSceneTexture(uint32_t texture) = 0; + virtual void releaseOverlayTexture(uint32_t texture) = 0; + virtual GLWidget* getPrimaryWidget() = 0; + virtual QWindow* getPrimaryWindow() = 0; + virtual QOpenGLContext* getPrimaryContext() = 0; virtual bool isForeground() = 0; virtual const DisplayPlugin* getActiveDisplayPlugin() const = 0; + + QVector>& currentDisplayActions() { + return _currentDisplayPluginActions; + } + + QVector>& currentInputActions() { + return _currentInputPluginActions; + } + +protected: + QVector> _currentDisplayPluginActions; + QVector> _currentInputPluginActions; + }; diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index a0020d21f3..69a433d5e1 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -170,7 +170,7 @@ void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm if (!_pipeline || _pipelineDirty) { _pipelineDirty = true; if (!_vertexShader) { - _vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(_vertexSource)); + _vertexShader = gpu::Shader::createVertex(_vertexSource); } // Build the fragment shader @@ -193,8 +193,8 @@ void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm fragmentShaderSource.replace(replaceIndex, PROCEDURAL_BLOCK.size(), _shaderSource.toLocal8Bit().data()); } //qDebug() << "FragmentShader:\n" << fragmentShaderSource.c_str(); - _fragmentShader = gpu::ShaderPointer(gpu::Shader::createPixel(fragmentShaderSource)); - _shader = gpu::ShaderPointer(gpu::Shader::createProgram(_vertexShader, _fragmentShader)); + _fragmentShader = gpu::Shader::createPixel(fragmentShaderSource); + _shader = gpu::Shader::createProgram(_vertexShader, _fragmentShader); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("iChannel0"), 0)); @@ -203,7 +203,7 @@ void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm slotBindings.insert(gpu::Shader::Binding(std::string("iChannel3"), 3)); gpu::Shader::makeProgram(*_shader, slotBindings); - _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(_shader, _state)); + _pipeline = gpu::Pipeline::create(_shader, _state); for (size_t i = 0; i < NUM_STANDARD_UNIFORMS; ++i) { const std::string& name = STANDARD_UNIFORM_NAMES[i]; _standardUniformSlots[i] = _shader->getUniforms().findLocation(name); diff --git a/libraries/recording/src/recording/impl/ArrayClip.h b/libraries/recording/src/recording/impl/ArrayClip.h index 10b3580228..165842d24a 100644 --- a/libraries/recording/src/recording/impl/ArrayClip.h +++ b/libraries/recording/src/recording/impl/ArrayClip.h @@ -32,7 +32,7 @@ public: return _frames.size(); } - Clip::Pointer duplicate() const { + virtual Clip::Pointer duplicate() const override { auto result = newClip(); Locker lock(_mutex); for (size_t i = 0; i < _frames.size(); ++i) { @@ -41,7 +41,7 @@ public: return result; } - virtual void seekFrameTime(Frame::Time offset) { + virtual void seekFrameTime(Frame::Time offset) override { Locker lock(_mutex); auto itr = std::lower_bound(_frames.begin(), _frames.end(), offset, [](const T& a, Frame::Time b)->bool { diff --git a/libraries/recording/src/recording/impl/PointerClip.h b/libraries/recording/src/recording/impl/PointerClip.h index f5c0dd6bc4..48d4e4cbc8 100644 --- a/libraries/recording/src/recording/impl/PointerClip.h +++ b/libraries/recording/src/recording/impl/PointerClip.h @@ -45,7 +45,7 @@ public: // FIXME move to frame? static const qint64 MINIMUM_FRAME_SIZE = sizeof(FrameType) + sizeof(Frame::Time) + sizeof(FrameSize); protected: - void reset(); + void reset() override; virtual FrameConstPointer readFrame(size_t index) const override; QJsonDocument _header; uchar* _data { nullptr }; diff --git a/libraries/render-utils/src/AbstractViewStateInterface.h b/libraries/render-utils/src/AbstractViewStateInterface.h index b65289933c..39da33ee8f 100644 --- a/libraries/render-utils/src/AbstractViewStateInterface.h +++ b/libraries/render-utils/src/AbstractViewStateInterface.h @@ -45,7 +45,7 @@ public: virtual int getBoundaryLevelAdjust() const = 0; virtual PickRay computePickRay(float x, float y) const = 0; - virtual const glm::vec3& getAvatarPosition() const = 0; + virtual glm::vec3 getAvatarPosition() const = 0; virtual void postLambdaEvent(std::function f) = 0; virtual qreal getDevicePixelRatio() = 0; diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.cpp b/libraries/render-utils/src/AmbientOcclusionEffect.cpp index 456a6430a7..e54f55fc94 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.cpp +++ b/libraries/render-utils/src/AmbientOcclusionEffect.cpp @@ -37,9 +37,9 @@ AmbientOcclusion::AmbientOcclusion() { const gpu::PipelinePointer& AmbientOcclusion::getOcclusionPipeline() { if (!_occlusionPipeline) { - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(ambient_occlusion_vert))); - auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(ambient_occlusion_frag))); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); + auto vs = gpu::Shader::createVertex(std::string(ambient_occlusion_vert)); + auto ps = gpu::Shader::createPixel(std::string(ambient_occlusion_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("depthTexture"), 0)); @@ -78,16 +78,16 @@ const gpu::PipelinePointer& AmbientOcclusion::getOcclusionPipeline() { _occlusionTexture = gpu::TexturePointer(gpu::Texture::create2D(format, width, height, defaultSampler)); // Good to go add the brand new pipeline - _occlusionPipeline.reset(gpu::Pipeline::create(program, state)); + _occlusionPipeline = gpu::Pipeline::create(program, state); } return _occlusionPipeline; } const gpu::PipelinePointer& AmbientOcclusion::getVBlurPipeline() { if (!_vBlurPipeline) { - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(gaussian_blur_vertical_vert))); - auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(gaussian_blur_frag))); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); + auto vs = gpu::Shader::createVertex(std::string(gaussian_blur_vertical_vert)); + auto ps = gpu::Shader::createPixel(std::string(gaussian_blur_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; gpu::Shader::makeProgram(*program, slotBindings); @@ -111,16 +111,16 @@ const gpu::PipelinePointer& AmbientOcclusion::getVBlurPipeline() { _vBlurTexture = gpu::TexturePointer(gpu::Texture::create2D(format, width, height, defaultSampler)); // Good to go add the brand new pipeline - _vBlurPipeline.reset(gpu::Pipeline::create(program, state)); + _vBlurPipeline = gpu::Pipeline::create(program, state); } return _vBlurPipeline; } const gpu::PipelinePointer& AmbientOcclusion::getHBlurPipeline() { if (!_hBlurPipeline) { - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(gaussian_blur_horizontal_vert))); - auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(gaussian_blur_frag))); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); + auto vs = gpu::Shader::createVertex(std::string(gaussian_blur_horizontal_vert)); + auto ps = gpu::Shader::createPixel(std::string(gaussian_blur_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; gpu::Shader::makeProgram(*program, slotBindings); @@ -144,16 +144,16 @@ const gpu::PipelinePointer& AmbientOcclusion::getHBlurPipeline() { _hBlurTexture = gpu::TexturePointer(gpu::Texture::create2D(format, width, height, defaultSampler)); // Good to go add the brand new pipeline - _hBlurPipeline.reset(gpu::Pipeline::create(program, state)); + _hBlurPipeline = gpu::Pipeline::create(program, state); } return _hBlurPipeline; } const gpu::PipelinePointer& AmbientOcclusion::getBlendPipeline() { if (!_blendPipeline) { - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(ambient_occlusion_vert))); - auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(occlusion_blend_frag))); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); + auto vs = gpu::Shader::createVertex(std::string(ambient_occlusion_vert)); + auto ps = gpu::Shader::createPixel(std::string(occlusion_blend_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("blurredOcclusionTexture"), 0)); @@ -169,7 +169,7 @@ const gpu::PipelinePointer& AmbientOcclusion::getBlendPipeline() { gpu::State::INV_SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::SRC_ALPHA); // Good to go add the brand new pipeline - _blendPipeline.reset(gpu::Pipeline::create(program, state)); + _blendPipeline = gpu::Pipeline::create(program, state); } return _blendPipeline; } diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index d110576c0d..d17676fe90 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -95,10 +95,10 @@ AnimDebugDraw::AnimDebugDraw() : state->setBlendFunction(false, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - auto vertShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(animdebugdraw_vert))); - auto fragShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(animdebugdraw_frag))); - auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vertShader, fragShader)); - _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); + auto vertShader = gpu::Shader::createVertex(std::string(animdebugdraw_vert)); + auto fragShader = gpu::Shader::createPixel(std::string(animdebugdraw_frag)); + auto program = gpu::Shader::createProgram(vertShader, fragShader); + _pipeline = gpu::Pipeline::create(program, state); _animDebugDrawData = std::make_shared(); _animDebugDrawPayload = std::make_shared(_animDebugDrawData); diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index aaef67542f..d4707c172d 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -34,9 +34,9 @@ Antialiasing::Antialiasing() { const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { if (!_antialiasingPipeline) { - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(fxaa_vert))); - auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(fxaa_frag))); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); + auto vs = gpu::Shader::createVertex(std::string(fxaa_vert)); + auto ps = gpu::Shader::createPixel(std::string(fxaa_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("colorTexture"), 0)); @@ -59,7 +59,7 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { _antialiasingTexture = gpu::TexturePointer(gpu::Texture::create2D(format, width, height, defaultSampler)); // Good to go add the brand new pipeline - _antialiasingPipeline.reset(gpu::Pipeline::create(program, state)); + _antialiasingPipeline = gpu::Pipeline::create(program, state); } int w = DependencyManager::get()->getFrameBufferSize().width(); @@ -73,9 +73,9 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { const gpu::PipelinePointer& Antialiasing::getBlendPipeline() { if (!_blendPipeline) { - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(fxaa_vert))); - auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(fxaa_blend_frag))); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); + auto vs = gpu::Shader::createVertex(std::string(fxaa_vert)); + auto ps = gpu::Shader::createPixel(std::string(fxaa_blend_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("colorTexture"), 0)); @@ -87,7 +87,7 @@ const gpu::PipelinePointer& Antialiasing::getBlendPipeline() { state->setDepthTest(false, false, gpu::LESS_EQUAL); // Good to go add the brand new pipeline - _blendPipeline.reset(gpu::Pipeline::create(program, state)); + _blendPipeline = gpu::Pipeline::create(program, state); } return _blendPipeline; } diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 57e3129ef8..342471454d 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -87,18 +87,18 @@ gpu::PipelinePointer DeferredLightingEffect::getPipeline(SimpleProgramKey config gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); gpu::ShaderPointer program = (config.isEmissive()) ? _emissiveShader : _simpleShader; - gpu::PipelinePointer pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); + gpu::PipelinePointer pipeline = gpu::Pipeline::create(program, state); _simplePrograms.insert(config, pipeline); return pipeline; } void DeferredLightingEffect::init(AbstractViewStateInterface* viewState) { - auto VS = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(simple_vert))); - auto PS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(simple_textured_frag))); - auto PSEmissive = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(simple_textured_emisive_frag))); + auto VS = gpu::Shader::createVertex(std::string(simple_vert)); + auto PS = gpu::Shader::createPixel(std::string(simple_textured_frag)); + auto PSEmissive = gpu::Shader::createPixel(std::string(simple_textured_emisive_frag)); - _simpleShader = gpu::ShaderPointer(gpu::Shader::createProgram(VS, PS)); - _emissiveShader = gpu::ShaderPointer(gpu::Shader::createProgram(VS, PSEmissive)); + _simpleShader = gpu::Shader::createProgram(VS, PS); + _emissiveShader = gpu::Shader::createProgram(VS, PSEmissive); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), DeferredLightingEffect::NORMAL_FITTING_MAP_SLOT)); @@ -150,7 +150,7 @@ void DeferredLightingEffect::init(AbstractViewStateInterface* viewState) { gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); blitState->setColorWriteMask(true, true, true, false); - _blitLightBuffer = gpu::PipelinePointer(gpu::Pipeline::create(blitProgram, blitState)); + _blitLightBuffer = gpu::Pipeline::create(blitProgram, blitState); } // Allocate a global light representing the Global Directional light casting shadow (the sun) and the ambient light @@ -721,10 +721,10 @@ void DeferredLightingEffect::setupTransparent(RenderArgs* args, int lightBufferU } static void loadLightProgram(const char* vertSource, const char* fragSource, bool lightVolume, gpu::PipelinePointer& pipeline, LightLocationsPtr& locations) { - auto VS = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(vertSource))); - auto PS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(fragSource))); + auto VS = gpu::Shader::createVertex(std::string(vertSource)); + auto PS = gpu::Shader::createPixel(std::string(fragSource)); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(VS, PS)); + gpu::ShaderPointer program = gpu::Shader::createProgram(VS, PS); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("diffuseMap"), 0)); @@ -769,7 +769,7 @@ static void loadLightProgram(const char* vertSource, const char* fragSource, boo } else { state->setCullMode(gpu::State::CULL_BACK); } - pipeline.reset(gpu::Pipeline::create(program, state)); + pipeline = gpu::Pipeline::create(program, state); } diff --git a/libraries/render-utils/src/Environment.cpp b/libraries/render-utils/src/Environment.cpp index b26d402fa3..f7327a2b72 100644 --- a/libraries/render-utils/src/Environment.cpp +++ b/libraries/render-utils/src/Environment.cpp @@ -51,10 +51,10 @@ void Environment::init() { void Environment::setupAtmosphereProgram(const char* vertSource, const char* fragSource, gpu::PipelinePointer& pipeline, int* locations) { - auto VS = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(vertSource))); - auto PS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(fragSource))); + auto VS = gpu::Shader::createVertex(std::string(vertSource)); + auto PS = gpu::Shader::createPixel(std::string(fragSource)); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(VS, PS)); + gpu::ShaderPointer program = gpu::Shader::createProgram(VS, PS); gpu::Shader::BindingSet slotBindings; gpu::Shader::makeProgram(*program, slotBindings); @@ -67,7 +67,7 @@ void Environment::setupAtmosphereProgram(const char* vertSource, const char* fra gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); + pipeline = gpu::Pipeline::create(program, state); locations[CAMERA_POS_LOCATION] = program->getUniforms().findLocation("v3CameraPos"); locations[LIGHT_POS_LOCATION] = program->getUniforms().findLocation("v3LightPos"); diff --git a/libraries/render-utils/src/FboCache.h b/libraries/render-utils/src/FboCache.h index 78c3194eb5..cedc4bff82 100644 --- a/libraries/render-utils/src/FboCache.h +++ b/libraries/render-utils/src/FboCache.h @@ -12,7 +12,6 @@ #ifndef hifi_FboCache_h #define hifi_FboCache_h -#include #include #include #include diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index eed52fccbc..b9fdcd95a4 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -214,6 +214,19 @@ VertexVector tesselate(const VertexVector& startingTriangles, int count) { return triangles; } +size_t GeometryCache::getShapeTriangleCount(Shape shape) { + return _shapes[shape]._indexCount / VERTICES_PER_TRIANGLE; +} + +size_t GeometryCache::getSphereTriangleCount() { + return getShapeTriangleCount(Sphere); +} + +size_t GeometryCache::getCubeTriangleCount() { + return getShapeTriangleCount(Cube); +} + + // FIXME solids need per-face vertices, but smooth shaded // components do not. Find a way to support using draw elements // or draw arrays as appropriate @@ -1701,9 +1714,9 @@ void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { if (!_standardDrawPipeline) { - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(standardTransformPNTC_vert))); - auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(standardDrawTexture_frag))); - auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); + auto vs = gpu::Shader::createVertex(std::string(standardTransformPNTC_vert)); + auto ps = gpu::Shader::createPixel(std::string(standardDrawTexture_frag)); + auto program = gpu::Shader::createProgram(vs, ps); gpu::Shader::makeProgram((*program)); auto state = std::make_shared(); @@ -1712,14 +1725,14 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { // enable decal blend state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); - _standardDrawPipeline.reset(gpu::Pipeline::create(program, state)); + _standardDrawPipeline = gpu::Pipeline::create(program, state); auto stateNoBlend = std::make_shared(); auto noBlendPS = gpu::StandardShaderLib::getDrawTextureOpaquePS(); - auto programNoBlend = gpu::ShaderPointer(gpu::Shader::createProgram(vs, noBlendPS)); + auto programNoBlend = gpu::Shader::createProgram(vs, noBlendPS); gpu::Shader::makeProgram((*programNoBlend)); - _standardDrawPipelineNoBlend.reset(gpu::Pipeline::create(programNoBlend, stateNoBlend)); + _standardDrawPipelineNoBlend = gpu::Pipeline::create(programNoBlend, stateNoBlend); } if (noBlend) { batch.setPipeline(_standardDrawPipelineNoBlend); @@ -1727,3 +1740,4 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { batch.setPipeline(_standardDrawPipeline); } } + diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index aa1593db78..c408115011 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -151,16 +151,19 @@ public: void renderWireShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& transformBuffer, gpu::BufferPointer& colorBuffer); void renderShape(gpu::Batch& batch, Shape shape); void renderWireShape(gpu::Batch& batch, Shape shape); + size_t getShapeTriangleCount(Shape shape); void renderCubeInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer); void renderWireCubeInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer); void renderCube(gpu::Batch& batch); void renderWireCube(gpu::Batch& batch); + size_t getCubeTriangleCount(); void renderSphereInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer); void renderWireSphereInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer); void renderSphere(gpu::Batch& batch); void renderWireSphere(gpu::Batch& batch); + size_t getSphereTriangleCount(); void renderGrid(gpu::Batch& batch, int xDivisions, int yDivisions, const glm::vec4& color); void renderGrid(gpu::Batch& batch, int x, int y, int width, int height, int rows, int cols, const glm::vec4& color, int id = UNKNOWN_ID); diff --git a/libraries/render-utils/src/HitEffect.cpp b/libraries/render-utils/src/HitEffect.cpp index 06bd07b456..7e8b5f5fa0 100644 --- a/libraries/render-utils/src/HitEffect.cpp +++ b/libraries/render-utils/src/HitEffect.cpp @@ -36,9 +36,9 @@ HitEffect::HitEffect() { const gpu::PipelinePointer& HitEffect::getHitEffectPipeline() { if (!_hitEffectPipeline) { - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(hit_effect_vert))); - auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(hit_effect_frag))); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); + auto vs = gpu::Shader::createVertex(std::string(hit_effect_vert)); + auto ps = gpu::Shader::createPixel(std::string(hit_effect_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; @@ -54,7 +54,7 @@ const gpu::PipelinePointer& HitEffect::getHitEffectPipeline() { gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); // Good to go add the brand new pipeline - _hitEffectPipeline.reset(gpu::Pipeline::create(program, state)); + _hitEffectPipeline = gpu::Pipeline::create(program, state); } return _hitEffectPipeline; } diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 44acb61a52..bb37bb4932 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -39,9 +39,14 @@ namespace render { using namespace render; -MeshPartPayload::MeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex) : - model(model), meshIndex(meshIndex), partIndex(partIndex), _shapeID(shapeIndex) -{ +MeshPartPayload::MeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, + glm::vec3 position, glm::quat orientation) : + model(model), + meshIndex(meshIndex), + partIndex(partIndex), + _shapeID(shapeIndex), + _modelPosition(position), + _modelOrientation(orientation) { initCache(); } @@ -66,6 +71,11 @@ void MeshPartPayload::initCache() { } +void MeshPartPayload::updateModelLocation(glm::vec3 position, glm::quat orientation) { + _modelPosition = position; + _modelOrientation = orientation; +} + render::ItemKey MeshPartPayload::getKey() const { ItemKey::Builder builder; builder.withTypeShape(); @@ -91,7 +101,7 @@ render::ItemKey MeshPartPayload::getKey() const { render::Item::Bound MeshPartPayload::getBound() const { // NOTE: we can't cache this bounds because we need to handle the case of a moving // entity or mesh part. - return model->getPartBounds(meshIndex, partIndex); + return model->getPartBounds(meshIndex, partIndex, _modelPosition, _modelOrientation); } void MeshPartPayload::drawCall(gpu::Batch& batch) const { @@ -222,7 +232,7 @@ void MeshPartPayload::bindTransform(gpu::Batch& batch, const ModelRender::Locati transform = Transform(state.clusterMatrices[0]); } } - transform.preTranslate(model->_translation); + transform.preTranslate(_modelPosition); batch.setModelTransform(transform); } @@ -247,7 +257,7 @@ void MeshPartPayload::render(RenderArgs* args) const { } // Back to model to update the cluster matrices right now - model->updateClusterMatrices(); + model->updateClusterMatrices(_modelPosition, _modelOrientation); const FBXMesh& mesh = geometry.meshes.at(meshIndex); diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 51e577e7c7..b29d9510d1 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -24,7 +24,7 @@ class Model; class MeshPartPayload { public: - MeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex); + MeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, glm::vec3 position, glm::quat orientation); typedef render::Payload Payload; typedef Payload::DataPointer Pointer; @@ -33,7 +33,11 @@ public: int meshIndex; int partIndex; int _shapeID; - + glm::vec3 _modelPosition; + glm::quat _modelOrientation; + + void updateModelLocation(glm::vec3 position, glm::quat orientation); + // Render Item interface render::ItemKey getKey() const; render::Item::Bound getBound() const; @@ -63,4 +67,4 @@ namespace render { template <> void payloadRender(const MeshPartPayload::Pointer& payload, RenderArgs* args); } -#endif // hifi_MeshPartPayload_h \ No newline at end of file +#endif // hifi_MeshPartPayload_h diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 0070b99591..b367d299e5 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -72,13 +72,16 @@ Model::~Model() { AbstractViewStateInterface* Model::_viewState = NULL; + void Model::setTranslation(const glm::vec3& translation) { _translation = translation; + enqueueLocationChange(); } - + void Model::setRotation(const glm::quat& rotation) { _rotation = rotation; -} + enqueueLocationChange(); +} void Model::setScale(const glm::vec3& scale) { setScaleInternal(scale); @@ -104,6 +107,20 @@ void Model::setOffset(const glm::vec3& offset) { _snappedToRegistrationPoint = false; } +void Model::enqueueLocationChange() { + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); + + render::PendingChanges pendingChanges; + foreach (auto itemID, _renderItems.keys()) { + pendingChanges.updateItem(itemID, [=](MeshPartPayload& data) { + data.updateModelLocation(_translation, _rotation); + data.model->_needsUpdateClusterMatrices = true; + }); + } + + scene->enqueuePendingChanges(pendingChanges); +} + void Model::initJointTransforms() { if (!_geometry || !_geometry->isLoaded()) { return; @@ -337,7 +354,7 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { _calculatedMeshPartBoxes.clear(); for (int i = 0; i < numberOfMeshes; i++) { const FBXMesh& mesh = geometry.meshes.at(i); - Extents scaledMeshExtents = calculateScaledOffsetExtents(mesh.meshExtents); + Extents scaledMeshExtents = calculateScaledOffsetExtents(mesh.meshExtents, _translation, _rotation); _calculatedMeshBoxes[i] = AABox(scaledMeshExtents); @@ -527,6 +544,7 @@ void Model::removeFromScene(std::shared_ptr scene, render::Pendin pendingChanges.removeItem(item); } _renderItems.clear(); + _renderItemsSet.clear(); _readyWhenAdded = false; } @@ -624,7 +642,8 @@ Extents Model::getUnscaledMeshExtents() const { return scaledExtents; } -Extents Model::calculateScaledOffsetExtents(const Extents& extents) const { +Extents Model::calculateScaledOffsetExtents(const Extents& extents, + glm::vec3 modelPosition, glm::quat modelOrientation) const { // we need to include any fst scaling, translation, and rotation, which is captured in the offset matrix glm::vec3 minimum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f)); glm::vec3 maximum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f)); @@ -632,17 +651,17 @@ Extents Model::calculateScaledOffsetExtents(const Extents& extents) const { Extents scaledOffsetExtents = { ((minimum + _offset) * _scale), ((maximum + _offset) * _scale) }; - Extents rotatedExtents = scaledOffsetExtents.getRotated(_rotation); + Extents rotatedExtents = scaledOffsetExtents.getRotated(modelOrientation); - Extents translatedExtents = { rotatedExtents.minimum + _translation, - rotatedExtents.maximum + _translation }; + Extents translatedExtents = { rotatedExtents.minimum + modelPosition, + rotatedExtents.maximum + modelPosition }; return translatedExtents; } /// Returns the world space equivalent of some box in model space. -AABox Model::calculateScaledOffsetAABox(const AABox& box) const { - return AABox(calculateScaledOffsetExtents(Extents(box))); +AABox Model::calculateScaledOffsetAABox(const AABox& box, glm::vec3 modelPosition, glm::quat modelOrientation) const { + return AABox(calculateScaledOffsetExtents(Extents(box), modelPosition, modelOrientation)); } glm::vec3 Model::calculateScaledOffsetPoint(const glm::vec3& point) const { @@ -747,6 +766,14 @@ bool Model::getJointTranslation(int jointIndex, glm::vec3& translation) const { return _rig->getJointTranslation(jointIndex, translation); } +bool Model::getAbsoluteJointRotationInRigFrame(int jointIndex, glm::quat& rotation) const { + return _rig->getAbsoluteJointRotationInRigFrame(jointIndex, rotation); +} + +bool Model::getAbsoluteJointTranslationInRigFrame(int jointIndex, glm::vec3& translation) const { + return _rig->getAbsoluteJointTranslationInRigFrame(jointIndex, translation); +} + bool Model::getJointCombinedRotation(int jointIndex, glm::quat& rotation) const { return _rig->getJointCombinedRotation(jointIndex, rotation, _rotation); } @@ -928,14 +955,14 @@ void Model::simulate(float deltaTime, bool fullUpdate) { //virtual void Model::updateRig(float deltaTime, glm::mat4 parentTransform) { _needsUpdateClusterMatrices = true; - _rig->updateAnimations(deltaTime, parentTransform); + _rig->updateAnimations(deltaTime, parentTransform); } void Model::simulateInternal(float deltaTime) { // update the world space transforms for all joints glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset); updateRig(deltaTime, parentTransform); } -void Model::updateClusterMatrices() { +void Model::updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation) { PerformanceTimer perfTimer("Model::updateClusterMatrices"); if (!_needsUpdateClusterMatrices) { @@ -949,7 +976,7 @@ void Model::updateClusterMatrices() { glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); auto cauterizeMatrix = _rig->getJointTransform(geometry.neckJointIndex) * zeroScale; - glm::mat4 modelToWorld = glm::mat4_cast(_rotation); + glm::mat4 modelToWorld = glm::mat4_cast(modelOrientation); for (int i = 0; i < _meshStates.size(); i++) { MeshState& state = _meshStates[i]; const FBXMesh& mesh = geometry.meshes.at(i); @@ -971,16 +998,21 @@ void Model::updateClusterMatrices() { // Once computed the cluster matrices, update the buffer(s) if (mesh.clusters.size() > 1) { if (!state.clusterBuffer) { - state.clusterBuffer = std::make_shared(state.clusterMatrices.size() * sizeof(glm::mat4), (const gpu::Byte*) state.clusterMatrices.constData()); + state.clusterBuffer = std::make_shared(state.clusterMatrices.size() * sizeof(glm::mat4), + (const gpu::Byte*) state.clusterMatrices.constData()); } else { - state.clusterBuffer->setSubData(0, state.clusterMatrices.size() * sizeof(glm::mat4), (const gpu::Byte*) state.clusterMatrices.constData()); + state.clusterBuffer->setSubData(0, state.clusterMatrices.size() * sizeof(glm::mat4), + (const gpu::Byte*) state.clusterMatrices.constData()); } if (!_cauterizeBoneSet.empty() && (state.cauterizedClusterMatrices.size() > 1)) { if (!state.cauterizedClusterBuffer) { - state.cauterizedClusterBuffer = std::make_shared(state.cauterizedClusterMatrices.size() * sizeof(glm::mat4), (const gpu::Byte*) state.cauterizedClusterMatrices.constData()); + state.cauterizedClusterBuffer = + std::make_shared(state.cauterizedClusterMatrices.size() * sizeof(glm::mat4), + (const gpu::Byte*) state.cauterizedClusterMatrices.constData()); } else { - state.cauterizedClusterBuffer->setSubData(0, state.cauterizedClusterMatrices.size() * sizeof(glm::mat4), (const gpu::Byte*) state.cauterizedClusterMatrices.constData()); + state.cauterizedClusterBuffer->setSubData(0, state.cauterizedClusterMatrices.size() * sizeof(glm::mat4), + (const gpu::Byte*) state.cauterizedClusterMatrices.constData()); } } } @@ -1059,7 +1091,7 @@ void Model::deleteGeometry() { _blendedBlendshapeCoefficients.clear(); } -AABox Model::getPartBounds(int meshIndex, int partIndex) { +AABox Model::getPartBounds(int meshIndex, int partIndex, glm::vec3 modelPosition, glm::quat modelOrientation) { if (!_geometry || !_geometry->isLoaded()) { return AABox(); @@ -1070,7 +1102,7 @@ AABox Model::getPartBounds(int meshIndex, int partIndex) { bool isSkinned = state.clusterMatrices.size() > 1; if (isSkinned) { // if we're skinned return the entire mesh extents because we can't know for sure our clusters don't move us - return calculateScaledOffsetAABox(_geometry->getFBXGeometry().meshExtents); + return calculateScaledOffsetAABox(_geometry->getFBXGeometry().meshExtents, modelPosition, modelOrientation); } } if (_geometry->getFBXGeometry().meshes.size() > meshIndex) { @@ -1088,7 +1120,7 @@ AABox Model::getPartBounds(int meshIndex, int partIndex) { // // If we not skinned use the bounds of the subMesh for all it's parts const FBXMesh& mesh = _geometry->getFBXGeometry().meshes.at(meshIndex); - return calculateScaledOffsetExtents(mesh.meshExtents); + return calculateScaledOffsetExtents(mesh.meshExtents, modelPosition, modelOrientation); } return AABox(); } @@ -1123,7 +1155,7 @@ void Model::segregateMeshGroups() { // Create the render payloads int totalParts = mesh.parts.size(); for (int partIndex = 0; partIndex < totalParts; partIndex++) { - _renderItemsSet << std::make_shared(this, i, partIndex, shapeID); + _renderItemsSet << std::make_shared(this, i, partIndex, shapeID, _translation, _rotation); shapeID++; } } @@ -1143,6 +1175,7 @@ bool Model::initWhenReady(render::ScenePointer scene) { _renderItems.insert(item, renderPayload); pendingChanges.resetItem(item, renderPayload); pendingChanges.updateItem(item, [&](MeshPartPayload& data) { + data.updateModelLocation(_translation, _rotation); data.model->_needsUpdateClusterMatrices = true; }); } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index bab4ae0aa8..d77cf830bd 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -88,7 +88,8 @@ public: bool isVisible() const { return _isVisible; } - AABox getPartBounds(int meshIndex, int partIndex); + void updateRenderItems(); + AABox getPartBounds(int meshIndex, int partIndex, glm::vec3 modelPosition, glm::quat modelOrientation); bool maybeStartBlender(); @@ -111,7 +112,7 @@ public: bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; } virtual void simulate(float deltaTime, bool fullUpdate = true); - void updateClusterMatrices(); + void updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation); /// Returns a reference to the shared geometry. const QSharedPointer& getGeometry() const { return _geometry; } @@ -165,6 +166,10 @@ public: bool getJointRotation(int jointIndex, glm::quat& rotation) const; bool getJointTranslation(int jointIndex, glm::vec3& translation) const; + // model frame + bool getAbsoluteJointRotationInRigFrame(int jointIndex, glm::quat& rotation) const; + bool getAbsoluteJointTranslationInRigFrame(int jointIndex, glm::vec3& translation) const; + /// Returns the index of the parent of the indexed joint, or -1 if not found. int getParentJointIndex(int jointIndex) const; @@ -185,6 +190,8 @@ public: void setScale(const glm::vec3& scale); const glm::vec3& getScale() const { return _scale; } + void enqueueLocationChange(); + /// enables/disables scale to fit behavior, the model will be automatically scaled to the specified largest dimension bool getIsScaledToFit() const { return _scaledToFit; } /// is model scaled to fit const glm::vec3& getScaleToFitDimensions() const { return _scaleToFitDimensions; } /// the dimensions model is scaled to @@ -216,10 +223,10 @@ protected: Extents getUnscaledMeshExtents() const; /// Returns the scaled equivalent of some extents in model space. - Extents calculateScaledOffsetExtents(const Extents& extents) const; + Extents calculateScaledOffsetExtents(const Extents& extents, glm::vec3 modelPosition, glm::quat modelOrientation) const; /// Returns the world space equivalent of some box in model space. - AABox calculateScaledOffsetAABox(const AABox& box) const; + AABox calculateScaledOffsetAABox(const AABox& box, glm::vec3 modelPosition, glm::quat modelOrientation) const; /// Returns the scaled equivalent of a point in model space. glm::vec3 calculateScaledOffsetPoint(const glm::vec3& point) const; diff --git a/libraries/render-utils/src/ModelRender.cpp b/libraries/render-utils/src/ModelRender.cpp index c614fae67a..73f3d715b6 100644 --- a/libraries/render-utils/src/ModelRender.cpp +++ b/libraries/render-utils/src/ModelRender.cpp @@ -42,26 +42,26 @@ ModelRender::RenderPipelineLib ModelRender::_renderPipelineLib; const ModelRender::RenderPipelineLib& ModelRender::getRenderPipelineLib() { if (_renderPipelineLib.empty()) { // Vertex shaders - auto modelVertex = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(model_vert))); - auto modelNormalMapVertex = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(model_normal_map_vert))); - auto modelLightmapVertex = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(model_lightmap_vert))); - auto modelLightmapNormalMapVertex = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(model_lightmap_normal_map_vert))); - auto modelShadowVertex = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(model_shadow_vert))); - auto skinModelVertex = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(skin_model_vert))); - auto skinModelNormalMapVertex = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(skin_model_normal_map_vert))); - auto skinModelShadowVertex = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(skin_model_shadow_vert))); + auto modelVertex = gpu::Shader::createVertex(std::string(model_vert)); + auto modelNormalMapVertex = gpu::Shader::createVertex(std::string(model_normal_map_vert)); + auto modelLightmapVertex = gpu::Shader::createVertex(std::string(model_lightmap_vert)); + auto modelLightmapNormalMapVertex = gpu::Shader::createVertex(std::string(model_lightmap_normal_map_vert)); + auto modelShadowVertex = gpu::Shader::createVertex(std::string(model_shadow_vert)); + auto skinModelVertex = gpu::Shader::createVertex(std::string(skin_model_vert)); + auto skinModelNormalMapVertex = gpu::Shader::createVertex(std::string(skin_model_normal_map_vert)); + auto skinModelShadowVertex = gpu::Shader::createVertex(std::string(skin_model_shadow_vert)); // Pixel shaders - auto modelPixel = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(model_frag))); - auto modelNormalMapPixel = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(model_normal_map_frag))); - auto modelSpecularMapPixel = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(model_specular_map_frag))); - auto modelNormalSpecularMapPixel = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(model_normal_specular_map_frag))); - auto modelTranslucentPixel = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(model_translucent_frag))); - auto modelShadowPixel = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(model_shadow_frag))); - auto modelLightmapPixel = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(model_lightmap_frag))); - auto modelLightmapNormalMapPixel = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(model_lightmap_normal_map_frag))); - auto modelLightmapSpecularMapPixel = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(model_lightmap_specular_map_frag))); - auto modelLightmapNormalSpecularMapPixel = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(model_lightmap_normal_specular_map_frag))); + auto modelPixel = gpu::Shader::createPixel(std::string(model_frag)); + auto modelNormalMapPixel = gpu::Shader::createPixel(std::string(model_normal_map_frag)); + auto modelSpecularMapPixel = gpu::Shader::createPixel(std::string(model_specular_map_frag)); + auto modelNormalSpecularMapPixel = gpu::Shader::createPixel(std::string(model_normal_specular_map_frag)); + auto modelTranslucentPixel = gpu::Shader::createPixel(std::string(model_translucent_frag)); + auto modelShadowPixel = gpu::Shader::createPixel(std::string(model_shadow_frag)); + auto modelLightmapPixel = gpu::Shader::createPixel(std::string(model_lightmap_frag)); + auto modelLightmapNormalMapPixel = gpu::Shader::createPixel(std::string(model_lightmap_normal_map_frag)); + auto modelLightmapSpecularMapPixel = gpu::Shader::createPixel(std::string(model_lightmap_specular_map_frag)); + auto modelLightmapNormalSpecularMapPixel = gpu::Shader::createPixel(std::string(model_lightmap_normal_specular_map_frag)); // Fill the renderPipelineLib @@ -181,7 +181,7 @@ void ModelRender::RenderPipelineLib::addRenderPipeline(ModelRender::RenderKey ke slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), ModelRender::LIGHT_BUFFER_SLOT)); slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), DeferredLightingEffect::NORMAL_FITTING_MAP_SLOT)); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vertexShader, pixelShader)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vertexShader, pixelShader); gpu::Shader::makeProgram(*program, slotBindings); @@ -209,7 +209,7 @@ void ModelRender::RenderPipelineLib::addRenderPipeline(ModelRender::RenderKey ke gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); // Good to go add the brand new pipeline - auto pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); + auto pipeline = gpu::Pipeline::create(program, state); insert(value_type(key.getRaw(), RenderPipeline(pipeline, locations))); @@ -221,7 +221,7 @@ void ModelRender::RenderPipelineLib::addRenderPipeline(ModelRender::RenderKey ke wireframeState->setFillMode(gpu::State::FILL_LINE); // create a new RenderPipeline with the same shader side and the wireframe state - auto wireframePipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, wireframeState)); + auto wireframePipeline = gpu::Pipeline::create(program, wireframeState); insert(value_type(wireframeKey.getRaw(), RenderPipeline(wireframePipeline, locations))); } } diff --git a/libraries/render-utils/src/QOpenGLContextWrapper.cpp b/libraries/render-utils/src/QOpenGLContextWrapper.cpp new file mode 100644 index 0000000000..3e879df7af --- /dev/null +++ b/libraries/render-utils/src/QOpenGLContextWrapper.cpp @@ -0,0 +1,44 @@ +// +// QOpenGLContextWrapper.cpp +// +// +// Created by Clement on 12/4/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "QOpenGLContextWrapper.h" + +#include + + +QOpenGLContextWrapper::QOpenGLContextWrapper() : + _context(new QOpenGLContext) +{ +} + +void QOpenGLContextWrapper::setFormat(const QSurfaceFormat& format) { + _context->setFormat(format); +} + +bool QOpenGLContextWrapper::create() { + return _context->create(); +} + +void QOpenGLContextWrapper::swapBuffers(QSurface* surface) { + _context->swapBuffers(surface); +} + +bool QOpenGLContextWrapper::makeCurrent(QSurface* surface) { + return _context->makeCurrent(surface); +} + +void QOpenGLContextWrapper::doneCurrent() { + _context->doneCurrent(); +} + +bool isCurrentContext(QOpenGLContext* context) { + return QOpenGLContext::currentContext() == context; +} \ No newline at end of file diff --git a/libraries/render-utils/src/QOpenGLContextWrapper.h b/libraries/render-utils/src/QOpenGLContextWrapper.h new file mode 100644 index 0000000000..832119162c --- /dev/null +++ b/libraries/render-utils/src/QOpenGLContextWrapper.h @@ -0,0 +1,35 @@ +// +// QOpenGLContextWrapper.h +// +// +// Created by Clement on 12/4/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_QOpenGLContextWrapper_h +#define hifi_QOpenGLContextWrapper_h + +class QOpenGLContext; +class QSurface; +class QSurfaceFormat; + +class QOpenGLContextWrapper { +public: + QOpenGLContextWrapper(); + + void setFormat(const QSurfaceFormat& format); + bool create(); + void swapBuffers(QSurface* surface); + bool makeCurrent(QSurface* surface); + void doneCurrent(); + +private: + QOpenGLContext* _context { nullptr }; +}; + +bool isCurrentContext(QOpenGLContext* context); + +#endif // hifi_QOpenGLContextWrapper_h \ No newline at end of file diff --git a/libraries/render-utils/src/QOpenGLDebugLoggerWrapper.cpp b/libraries/render-utils/src/QOpenGLDebugLoggerWrapper.cpp new file mode 100644 index 0000000000..bd185034f4 --- /dev/null +++ b/libraries/render-utils/src/QOpenGLDebugLoggerWrapper.cpp @@ -0,0 +1,24 @@ +// +// QOpenGLDebugLoggerWrapper.cpp +// +// +// Created by Clement on 12/4/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "QOpenGLDebugLoggerWrapper.h" + +#include +#include + +void setupDebugLogger(QObject* window) { + QOpenGLDebugLogger* logger = new QOpenGLDebugLogger(window); + logger->initialize(); // initializes in the current context, i.e. ctx + logger->enableMessages(); + QObject::connect(logger, &QOpenGLDebugLogger::messageLogged, window, [&](const QOpenGLDebugMessage & debugMessage) { + qDebug() << debugMessage; + }); +} \ No newline at end of file diff --git a/libraries/render-utils/src/QOpenGLDebugLoggerWrapper.h b/libraries/render-utils/src/QOpenGLDebugLoggerWrapper.h new file mode 100644 index 0000000000..e2b1c5d9d4 --- /dev/null +++ b/libraries/render-utils/src/QOpenGLDebugLoggerWrapper.h @@ -0,0 +1,19 @@ +// +// QOpenGLDebugLoggerWrapper.h +// +// +// Created by Clement on 12/4/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_QOpenGLDebugLoggerWrapper_h +#define hifi_QOpenGLDebugLoggerWrapper_h + +class QObject; + +void setupDebugLogger(QObject* window); + +#endif // hifi_QOpenGLDebugLoggerWrapper_h \ No newline at end of file diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index e65018ad3d..1430651c86 100755 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -231,16 +231,16 @@ void DrawTransparentDeferred::run(const SceneContextPointer& sceneContext, const gpu::PipelinePointer DrawOverlay3D::_opaquePipeline; const gpu::PipelinePointer& DrawOverlay3D::getOpaquePipeline() { if (!_opaquePipeline) { - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(overlay3D_vert))); - auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(overlay3D_frag))); - auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); + auto vs = gpu::Shader::createVertex(std::string(overlay3D_vert)); + auto ps = gpu::Shader::createPixel(std::string(overlay3D_frag)); + auto program = gpu::Shader::createProgram(vs, ps); auto state = std::make_shared(); state->setDepthTest(false); // additive blending state->setBlendFunction(true, gpu::State::ONE, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - _opaquePipeline.reset(gpu::Pipeline::create(program, state)); + _opaquePipeline = gpu::Pipeline::create(program, state); } return _opaquePipeline; } @@ -307,8 +307,8 @@ const gpu::PipelinePointer& DrawStencilDeferred::getOpaquePipeline() { if (!_opaquePipeline) { const gpu::int8 STENCIL_OPAQUE = 1; auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(drawOpaqueStencil_frag))); - auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); + auto ps = gpu::Shader::createPixel(std::string(drawOpaqueStencil_frag)); + auto program = gpu::Shader::createProgram(vs, ps); gpu::Shader::makeProgram((*program)); @@ -318,7 +318,7 @@ const gpu::PipelinePointer& DrawStencilDeferred::getOpaquePipeline() { state->setStencilTest(true, 0xFF, gpu::State::StencilTest(STENCIL_OPAQUE, 0xFF, gpu::ALWAYS, gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_REPLACE)); state->setColorWriteMask(0); - _opaquePipeline.reset(gpu::Pipeline::create(program, state)); + _opaquePipeline = gpu::Pipeline::create(program, state); } return _opaquePipeline; } diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index 0157d873d3..5587185ead 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -216,9 +216,9 @@ void Font::setupGPU() { // Setup render pipeline { - auto vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(sdf_text3D_vert))); - auto pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(sdf_text3D_frag))); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vertexShader, pixelShader)); + auto vertexShader = gpu::Shader::createVertex(std::string(sdf_text3D_vert)); + auto pixelShader = gpu::Shader::createPixel(std::string(sdf_text3D_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vertexShader, pixelShader); gpu::Shader::BindingSet slotBindings; gpu::Shader::makeProgram(*program, slotBindings); @@ -233,7 +233,7 @@ void Font::setupGPU() { state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); + _pipeline = gpu::Pipeline::create(program, state); } // Sanity checks diff --git a/libraries/render/src/render/DrawStatus.cpp b/libraries/render/src/render/DrawStatus.cpp index 5e8fd74e5f..3f37ce378b 100644 --- a/libraries/render/src/render/DrawStatus.cpp +++ b/libraries/render/src/render/DrawStatus.cpp @@ -31,9 +31,9 @@ using namespace render; const gpu::PipelinePointer DrawStatus::getDrawItemBoundsPipeline() { if (!_drawItemBoundsPipeline) { - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(drawItemBounds_vert))); - auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(drawItemBounds_frag))); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); + auto vs = gpu::Shader::createVertex(std::string(drawItemBounds_vert)); + auto ps = gpu::Shader::createPixel(std::string(drawItemBounds_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; gpu::Shader::makeProgram(*program, slotBindings); @@ -51,16 +51,16 @@ const gpu::PipelinePointer DrawStatus::getDrawItemBoundsPipeline() { gpu::State::DEST_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ZERO); // Good to go add the brand new pipeline - _drawItemBoundsPipeline.reset(gpu::Pipeline::create(program, state)); + _drawItemBoundsPipeline = gpu::Pipeline::create(program, state); } return _drawItemBoundsPipeline; } const gpu::PipelinePointer DrawStatus::getDrawItemStatusPipeline() { if (!_drawItemStatusPipeline) { - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(drawItemStatus_vert))); - auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(drawItemStatus_frag))); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); + auto vs = gpu::Shader::createVertex(std::string(drawItemStatus_vert)); + auto ps = gpu::Shader::createPixel(std::string(drawItemStatus_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("iconStatusMap"), 0)); @@ -81,7 +81,7 @@ const gpu::PipelinePointer DrawStatus::getDrawItemStatusPipeline() { gpu::State::DEST_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ZERO); // Good to go add the brand new pipeline - _drawItemStatusPipeline.reset(gpu::Pipeline::create(program, state)); + _drawItemStatusPipeline = gpu::Pipeline::create(program, state); } return _drawItemStatusPipeline; } diff --git a/libraries/script-engine/src/SceneScriptingInterface.h b/libraries/script-engine/src/SceneScriptingInterface.h index 6be0ce44a8..8cd48d9ddd 100644 --- a/libraries/script-engine/src/SceneScriptingInterface.h +++ b/libraries/script-engine/src/SceneScriptingInterface.h @@ -109,7 +109,7 @@ public: Q_INVOKABLE void setEngineDisplayItemStatus(int display) { _drawItemStatus = display; } Q_INVOKABLE int doEngineDisplayItemStatus() { return _drawItemStatus; } - + Q_INVOKABLE void setEngineDisplayHitEffect(bool display) { _drawHitEffect = display; } Q_INVOKABLE bool doEngineDisplayHitEffect() { return _drawHitEffect; } @@ -144,7 +144,7 @@ protected: int _maxDrawnOverlay3DItems = -1; int _drawItemStatus = 0; - + bool _drawHitEffect = false; }; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 49009a3ad2..3306659c80 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -111,7 +111,7 @@ static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName const auto exception = engine.uncaughtException().toString(); const auto line = QString::number(engine.uncaughtExceptionLineNumber()); engine.clearExceptions(); - + auto message = QString("[UncaughtException] %1 in %2:%3").arg(exception, fileName, line); if (!backtrace.empty()) { static const auto lineSeparator = "\n "; @@ -326,7 +326,7 @@ void ScriptEngine::init() { } _isInitialized = true; - + auto entityScriptingInterface = DependencyManager::get(); entityScriptingInterface->init(); @@ -392,7 +392,7 @@ void ScriptEngine::init() { registerGlobalObject("Recording", recordingInterface.data()); registerGlobalObject("Assets", &_assetScriptingInterface); - + } void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) { @@ -401,8 +401,8 @@ void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) { qDebug() << "*** WARNING *** ScriptEngine::registerValue() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; #endif QMetaObject::invokeMethod(this, "registerValue", - Q_ARG(const QString&, valueName), - Q_ARG(QScriptValue, value)); + Q_ARG(const QString&, valueName), + Q_ARG(QScriptValue, value)); return; } @@ -428,17 +428,17 @@ void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) { void ScriptEngine::registerGlobalObject(const QString& name, QObject* object) { if (QThread::currentThread() != thread()) { - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::registerGlobalObject() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name; - #endif +#endif QMetaObject::invokeMethod(this, "registerGlobalObject", - Q_ARG(const QString&, name), - Q_ARG(QObject*, object)); + Q_ARG(const QString&, name), + Q_ARG(QObject*, object)); return; } - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "ScriptEngine::registerGlobalObject() called on thread [" << QThread::currentThread() << "] name:" << name; - #endif +#endif if (!globalObject().property(name).isValid()) { if (object) { @@ -452,18 +452,18 @@ void ScriptEngine::registerGlobalObject(const QString& name, QObject* object) { void ScriptEngine::registerFunction(const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) { if (QThread::currentThread() != thread()) { - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name; - #endif +#endif QMetaObject::invokeMethod(this, "registerFunction", - Q_ARG(const QString&, name), - Q_ARG(QScriptEngine::FunctionSignature, functionSignature), - Q_ARG(int, numArguments)); + Q_ARG(const QString&, name), + Q_ARG(QScriptEngine::FunctionSignature, functionSignature), + Q_ARG(int, numArguments)); return; } - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "ScriptEngine::registerFunction() called on thread [" << QThread::currentThread() << "] name:" << name; - #endif +#endif QScriptValue scriptFun = newFunction(functionSignature, numArguments); globalObject().setProperty(name, scriptFun); @@ -471,18 +471,18 @@ void ScriptEngine::registerFunction(const QString& name, QScriptEngine::Function void ScriptEngine::registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) { if (QThread::currentThread() != thread()) { - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] parent:" << parent << "name:" << name; - #endif +#endif QMetaObject::invokeMethod(this, "registerFunction", - Q_ARG(const QString&, name), - Q_ARG(QScriptEngine::FunctionSignature, functionSignature), - Q_ARG(int, numArguments)); + Q_ARG(const QString&, name), + Q_ARG(QScriptEngine::FunctionSignature, functionSignature), + Q_ARG(int, numArguments)); return; } - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "ScriptEngine::registerFunction() called on thread [" << QThread::currentThread() << "] parent:" << parent << "name:" << name; - #endif +#endif QScriptValue object = globalObject().property(parent); if (object.isValid()) { @@ -492,22 +492,22 @@ void ScriptEngine::registerFunction(const QString& parent, const QString& name, } void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, - QScriptEngine::FunctionSignature setter, const QString& parent) { + QScriptEngine::FunctionSignature setter, const QString& parent) { if (QThread::currentThread() != thread()) { - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::registerGetterSetter() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " - " name:" << name << "parent:" << parent; - #endif + " name:" << name << "parent:" << parent; +#endif QMetaObject::invokeMethod(this, "registerGetterSetter", - Q_ARG(const QString&, name), - Q_ARG(QScriptEngine::FunctionSignature, getter), - Q_ARG(QScriptEngine::FunctionSignature, setter), - Q_ARG(const QString&, parent)); + Q_ARG(const QString&, name), + Q_ARG(QScriptEngine::FunctionSignature, getter), + Q_ARG(QScriptEngine::FunctionSignature, setter), + Q_ARG(const QString&, parent)); return; } - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "ScriptEngine::registerGetterSetter() called on thread [" << QThread::currentThread() << "] name:" << name << "parent:" << parent; - #endif +#endif QScriptValue setterFunction = newFunction(setter, 1); QScriptValue getterFunction = newFunction(getter); @@ -527,19 +527,19 @@ void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::Func // Unregister the handlers for this eventName and entityID. void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { if (QThread::currentThread() != thread()) { - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::removeEventHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " - "entityID:" << entityID << " eventName:" << eventName; - #endif + "entityID:" << entityID << " eventName:" << eventName; +#endif QMetaObject::invokeMethod(this, "removeEventHandler", - Q_ARG(const EntityItemID&, entityID), - Q_ARG(const QString&, eventName), - Q_ARG(QScriptValue, handler)); + Q_ARG(const EntityItemID&, entityID), + Q_ARG(const QString&, eventName), + Q_ARG(QScriptValue, handler)); return; } - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "ScriptEngine::removeEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName; - #endif +#endif if (!_registeredHandlers.contains(entityID)) { return; @@ -557,20 +557,20 @@ void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QStrin // Register the handler. void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { if (QThread::currentThread() != thread()) { - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::addEventHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " - "entityID:" << entityID << " eventName:" << eventName; - #endif + "entityID:" << entityID << " eventName:" << eventName; +#endif QMetaObject::invokeMethod(this, "addEventHandler", - Q_ARG(const EntityItemID&, entityID), - Q_ARG(const QString&, eventName), - Q_ARG(QScriptValue, handler)); + Q_ARG(const EntityItemID&, entityID), + Q_ARG(const QString&, eventName), + Q_ARG(QScriptValue, handler)); return; } - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "ScriptEngine::addEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName; - #endif +#endif if (_registeredHandlers.count() == 0) { // First time any per-entity handler has been added in this script... // Connect up ALL the handlers to the global entities object's signals. @@ -579,7 +579,7 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityID) { _registeredHandlers.remove(entityID); }); - + // Two common cases of event handler, differing only in argument signature. using SingleEntityHandler = std::function; auto makeSingleEntityHandler = [this](QString eventName) -> SingleEntityHandler { @@ -587,22 +587,22 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this) }); }; }; - + using MouseHandler = std::function; auto makeMouseHandler = [this](QString eventName) -> MouseHandler { return [this, eventName](const EntityItemID& entityItemID, const MouseEvent& event) { forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this), event.toScriptValue(this) }); }; }; - + using CollisionHandler = std::function; auto makeCollisionHandler = [this](QString eventName) -> CollisionHandler { return [this, eventName](const EntityItemID& idA, const EntityItemID& idB, const Collision& collision) { forwardHandlerCall(idA, eventName, { idA.toScriptValue(this), idB.toScriptValue(this), - collisionToScriptValue(this, collision) }); + collisionToScriptValue(this, collision) }); }; }; - + connect(entities.data(), &EntityScriptingInterface::enterEntity, this, makeSingleEntityHandler("enterEntity")); connect(entities.data(), &EntityScriptingInterface::leaveEntity, this, makeSingleEntityHandler("leaveEntity")); @@ -635,18 +635,18 @@ QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fi if (QThread::currentThread() != thread()) { QScriptValue result; - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " "sourceCode:" << sourceCode << " fileName:" << fileName << "lineNumber:" << lineNumber; - #endif +#endif QMetaObject::invokeMethod(this, "evaluate", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QScriptValue, result), - Q_ARG(const QString&, sourceCode), - Q_ARG(const QString&, fileName), - Q_ARG(int, lineNumber)); + Q_RETURN_ARG(QScriptValue, result), + Q_ARG(const QString&, sourceCode), + Q_ARG(const QString&, fileName), + Q_ARG(int, lineNumber)); return result; } - + // Check syntax const QScriptProgram program(sourceCode, fileName, lineNumber); if (!hasCorrectSyntax(program)) { @@ -656,7 +656,7 @@ QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fi ++_evaluatesPending; const auto result = QScriptEngine::evaluate(program); --_evaluatesPending; - + const auto hadUncaughtException = hadUncaughtExceptions(*this, program.fileName()); if (_wantSignals) { emit evaluationFinished(result, hadUncaughtException); @@ -668,13 +668,12 @@ void ScriptEngine::run() { if (_stoppingAllScripts) { return; // bail early - avoid setting state in init(), as evaluate() will bail too } - + if (!_isInitialized) { init(); } _isRunning = true; - _isFinished = false; if (_wantSignals) { emit runningStateChanged(); } @@ -900,7 +899,7 @@ void ScriptEngine::print(const QString& message) { void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callback) { if (_stoppingAllScripts) { qCDebug(scriptengine) << "Script.include() while shutting down is ignored..." - << "includeFiles:" << includeFiles << "parent script:" << getFilename(); + << "includeFiles:" << includeFiles << "parent script:" << getFilename(); return; // bail early } QList urls; @@ -956,7 +955,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac void ScriptEngine::include(const QString& includeFile, QScriptValue callback) { if (_stoppingAllScripts) { qCDebug(scriptengine) << "Script.include() while shutting down is ignored... " - << "includeFile:" << includeFile << "parent script:" << getFilename(); + << "includeFile:" << includeFile << "parent script:" << getFilename(); return; // bail early } @@ -971,7 +970,7 @@ void ScriptEngine::include(const QString& includeFile, QScriptValue callback) { void ScriptEngine::load(const QString& loadFile) { if (_stoppingAllScripts) { qCDebug(scriptengine) << "Script.load() while shutting down is ignored... " - << "loadFile:" << loadFile << "parent script:" << getFilename(); + << "loadFile:" << loadFile << "parent script:" << getFilename(); return; // bail early } @@ -1015,58 +1014,63 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin // for the download void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) { if (QThread::currentThread() != thread()) { - #ifdef THREAD_DEBUGGING - qDebug() << "*** WARNING *** ScriptEngine::loadEntityScript() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " +#ifdef THREAD_DEBUGGING + qDebug() << "*** WARNING *** ScriptEngine::loadEntityScript() called on wrong thread [" + << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " "entityID:" << entityID << "entityScript:" << entityScript <<"forceRedownload:" << forceRedownload; - #endif +#endif QMetaObject::invokeMethod(this, "loadEntityScript", - Q_ARG(const EntityItemID&, entityID), - Q_ARG(const QString&, entityScript), - Q_ARG(bool, forceRedownload)); + Q_ARG(const EntityItemID&, entityID), + Q_ARG(const QString&, entityScript), + Q_ARG(bool, forceRedownload)); return; } - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "ScriptEngine::loadEntityScript() called on correct thread [" << thread() << "] " "entityID:" << entityID << "entityScript:" << entityScript << "forceRedownload:" << forceRedownload; - #endif +#endif // If we've been called our known entityScripts should not know about us.. assert(!_entityScripts.contains(entityID)); - #ifdef THREAD_DEBUGGING - qDebug() << "ScriptEngine::loadEntityScript() calling scriptCache->getScriptContents() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; - #endif +#ifdef THREAD_DEBUGGING + qDebug() << "ScriptEngine::loadEntityScript() calling scriptCache->getScriptContents() on thread [" + << QThread::currentThread() << "] expected thread [" << thread() << "]"; +#endif DependencyManager::get()->getScriptContents(entityScript, [=](const QString& scriptOrURL, const QString& contents, bool isURL, bool success) { - #ifdef THREAD_DEBUGGING - qDebug() << "ScriptEngine::entityScriptContentAvailable() IN LAMBDA contentAvailable on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; - #endif +#ifdef THREAD_DEBUGGING + qDebug() << "ScriptEngine::entityScriptContentAvailable() IN LAMBDA contentAvailable on thread [" + << QThread::currentThread() << "] expected thread [" << thread() << "]"; +#endif - this->entityScriptContentAvailable(entityID, scriptOrURL, contents, isURL, success); - }, forceRedownload); + this->entityScriptContentAvailable(entityID, scriptOrURL, contents, isURL, success); + }, forceRedownload); } // since all of these operations can be asynch we will always do the actual work in the response handler // for the download void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success) { if (QThread::currentThread() != thread()) { - #ifdef THREAD_DEBUGGING - qDebug() << "*** WARNING *** ScriptEngine::entityScriptContentAvailable() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " - "entityID:" << entityID << "scriptOrURL:" << scriptOrURL << "contents:" << contents << "isURL:" << isURL << "success:" << success; - #endif +#ifdef THREAD_DEBUGGING + qDebug() << "*** WARNING *** ScriptEngine::entityScriptContentAvailable() called on wrong thread [" + << QThread::currentThread() << "], invoking on correct thread [" << thread() + << "] " "entityID:" << entityID << "scriptOrURL:" << scriptOrURL << "contents:" + << contents << "isURL:" << isURL << "success:" << success; +#endif QMetaObject::invokeMethod(this, "entityScriptContentAvailable", - Q_ARG(const EntityItemID&, entityID), - Q_ARG(const QString&, scriptOrURL), - Q_ARG(const QString&, contents), - Q_ARG(bool, isURL), - Q_ARG(bool, success)); + Q_ARG(const EntityItemID&, entityID), + Q_ARG(const QString&, scriptOrURL), + Q_ARG(const QString&, contents), + Q_ARG(bool, isURL), + Q_ARG(bool, success)); return; } - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "ScriptEngine::entityScriptContentAvailable() thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; - #endif +#endif auto scriptCache = DependencyManager::get(); bool isFileUrl = isURL && scriptOrURL.startsWith("file://"); @@ -1089,14 +1093,16 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co if (hadUncaughtExceptions(sandbox, program.fileName())) { return; } - + if (!testConstructor.isFunction()) { qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID << "\n" - " NOT CONSTRUCTOR\n" - " SCRIPT:" << scriptOrURL; + " NOT CONSTRUCTOR\n" + " SCRIPT:" << scriptOrURL; + if (!isFileUrl) { scriptCache->addScriptToBadScriptList(scriptOrURL); } + return; // done processing script } @@ -1120,19 +1126,19 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) { if (QThread::currentThread() != thread()) { - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::unloadEntityScript() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " "entityID:" << entityID; - #endif +#endif QMetaObject::invokeMethod(this, "unloadEntityScript", - Q_ARG(const EntityItemID&, entityID)); + Q_ARG(const EntityItemID&, entityID)); return; } - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "ScriptEngine::unloadEntityScript() called on correct thread [" << thread() << "] " "entityID:" << entityID; - #endif +#endif if (_entityScripts.contains(entityID)) { callEntityScriptMethod(entityID, "unload"); @@ -1194,21 +1200,21 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) { void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params) { if (QThread::currentThread() != thread()) { - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " "entityID:" << entityID << "methodName:" << methodName; - #endif +#endif QMetaObject::invokeMethod(this, "callEntityScriptMethod", - Q_ARG(const EntityItemID&, entityID), - Q_ARG(const QString&, methodName), - Q_ARG(const QStringList&, params)); + Q_ARG(const EntityItemID&, entityID), + Q_ARG(const QString&, methodName), + Q_ARG(const QStringList&, params)); return; } - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] " "entityID:" << entityID << "methodName:" << methodName; - #endif +#endif refreshFileScript(entityID); if (_entityScripts.contains(entityID)) { @@ -1226,21 +1232,21 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const MouseEvent& event) { if (QThread::currentThread() != thread()) { - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " "entityID:" << entityID << "methodName:" << methodName << "event: mouseEvent"; - #endif +#endif QMetaObject::invokeMethod(this, "callEntityScriptMethod", - Q_ARG(const EntityItemID&, entityID), - Q_ARG(const QString&, methodName), - Q_ARG(const MouseEvent&, event)); + Q_ARG(const EntityItemID&, entityID), + Q_ARG(const QString&, methodName), + Q_ARG(const MouseEvent&, event)); return; } - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] " "entityID:" << entityID << "methodName:" << methodName << "event: mouseEvent"; - #endif +#endif refreshFileScript(entityID); if (_entityScripts.contains(entityID)) { @@ -1258,23 +1264,23 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision) { if (QThread::currentThread() != thread()) { - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " "entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision"; - #endif +#endif QMetaObject::invokeMethod(this, "callEntityScriptMethod", - Q_ARG(const EntityItemID&, entityID), - Q_ARG(const QString&, methodName), - Q_ARG(const EntityItemID&, otherID), - Q_ARG(const Collision&, collision)); + Q_ARG(const EntityItemID&, entityID), + Q_ARG(const QString&, methodName), + Q_ARG(const EntityItemID&, otherID), + Q_ARG(const Collision&, collision)); return; } - #ifdef THREAD_DEBUGGING +#ifdef THREAD_DEBUGGING qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] " "entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision"; - #endif - +#endif + refreshFileScript(entityID); if (_entityScripts.contains(entityID)) { EntityScriptDetails details = _entityScripts[entityID]; @@ -1287,4 +1293,4 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS entityScript.property(methodName).call(entityScript, args); } } -} +} \ No newline at end of file diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index fed3384d0b..90b99d46fd 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -67,7 +67,7 @@ public: /// run the script in the callers thread, exit when stop() is called. void run(); - + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // NOTE - these are NOT intended to be public interfaces available to scripts, the are only Q_INVOKABLE so we can // properly ensure they are only called on the correct thread @@ -77,14 +77,14 @@ public: /// registers a global getter/setter Q_INVOKABLE void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, - QScriptEngine::FunctionSignature setter, const QString& parent = QString("")); - + QScriptEngine::FunctionSignature setter, const QString& parent = QString("")); + /// register a global function Q_INVOKABLE void registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments = -1); /// register a function as a method on a previously registered global object Q_INVOKABLE void registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature fun, - int numArguments = -1); + int numArguments = -1); /// registers a global object by name Q_INVOKABLE void registerValue(const QString& valueName, QScriptValue value); @@ -93,12 +93,12 @@ public: Q_INVOKABLE QScriptValue evaluate(const QString& program, const QString& fileName, int lineNumber = 1); // this is also used by the script tool widget /// if the script engine is not already running, this will download the URL and start the process of seting it up - /// to run... NOTE - this is used by Application currently to load the url. We don't really want it to be exposed + /// to run... NOTE - this is used by Application currently to load the url. We don't really want it to be exposed /// to scripts. we may not need this to be invokable void loadURL(const QUrl& scriptURL, bool reload); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // NOTE - these are intended to be public interfaces available to scripts + // NOTE - these are intended to be public interfaces available to scripts Q_INVOKABLE void addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); Q_INVOKABLE void removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); @@ -209,4 +209,4 @@ protected: static bool _stoppingAllScripts; }; -#endif // hifi_ScriptEngine_h +#endif // hifi_ScriptEngine_h \ No newline at end of file diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index 7c5ba6bb48..257ef2f00e 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -279,18 +279,20 @@ glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal) { glm::quat glmExtractRotation(const glm::mat4& matrix) { glm::vec3 scale = extractScale(matrix); - float maxScale = std::max(std::max(scale.x, scale.y), scale.z); - if (maxScale > 1.01f || maxScale <= 0.99f) { - // quat_cast doesn't work so well with scaled matrices, so cancel it out. - glm::mat4 tmp = glm::scale(matrix, 1.0f / scale); - return glm::normalize(glm::quat_cast(tmp)); - } else { - return glm::normalize(glm::quat_cast(matrix)); - } + // quat_cast doesn't work so well with scaled matrices, so cancel it out. + glm::mat4 tmp = glm::scale(matrix, 1.0f / scale); + return glm::normalize(glm::quat_cast(tmp)); } glm::vec3 extractScale(const glm::mat4& matrix) { - return glm::vec3(glm::length(matrix[0]), glm::length(matrix[1]), glm::length(matrix[2])); + glm::mat3 m(matrix); + float det = glm::determinant(m); + if (det < 0) { + // left handed matrix, flip sign to compensate. + return glm::vec3(-glm::length(m[0]), glm::length(m[1]), glm::length(m[2])); + } else { + return glm::vec3(glm::length(m[0]), glm::length(m[1]), glm::length(m[2])); + } } float extractUniformScale(const glm::mat4& matrix) { diff --git a/libraries/shared/src/OctalCode.cpp b/libraries/shared/src/OctalCode.cpp index 0b77683d4c..1fa18903cb 100644 --- a/libraries/shared/src/OctalCode.cpp +++ b/libraries/shared/src/OctalCode.cpp @@ -264,29 +264,6 @@ unsigned char* chopOctalCode(const unsigned char* originalOctalCode, int chopLev return newCode; } -unsigned char* rebaseOctalCode(const unsigned char* originalOctalCode, const unsigned char* newParentOctalCode, - bool includeColorSpace) { - - int oldCodeLength = numberOfThreeBitSectionsInCode(originalOctalCode); - int newParentCodeLength = numberOfThreeBitSectionsInCode(newParentOctalCode); - int newCodeLength = newParentCodeLength + oldCodeLength; - int bufferLength = newCodeLength + (includeColorSpace ? SIZE_OF_COLOR_DATA : 0); - unsigned char* newCode = new unsigned char[bufferLength]; - *newCode = newCodeLength; // set the length byte - - // copy parent code section first - for (int sectionFromParent = 0; sectionFromParent < newParentCodeLength; sectionFromParent++) { - char sectionValue = getOctalCodeSectionValue(newParentOctalCode, sectionFromParent); - setOctalCodeSectionValue(newCode, sectionFromParent, sectionValue); - } - // copy original code section next - for (int sectionFromOriginal = 0; sectionFromOriginal < oldCodeLength; sectionFromOriginal++) { - char sectionValue = getOctalCodeSectionValue(originalOctalCode, sectionFromOriginal); - setOctalCodeSectionValue(newCode, sectionFromOriginal + newParentCodeLength, sectionValue); - } - return newCode; -} - bool isAncestorOf(const unsigned char* possibleAncestor, const unsigned char* possibleDescendent, int descendentsChild) { if (!possibleAncestor || !possibleDescendent) { return false; diff --git a/libraries/shared/src/OctalCode.h b/libraries/shared/src/OctalCode.h index 9229157c3d..09766b685a 100644 --- a/libraries/shared/src/OctalCode.h +++ b/libraries/shared/src/OctalCode.h @@ -36,8 +36,6 @@ const int UNKNOWN_OCTCODE_LENGTH = -2; int numberOfThreeBitSectionsInCode(const unsigned char* octalCode, int maxBytes = UNKNOWN_OCTCODE_LENGTH); unsigned char* chopOctalCode(const unsigned char* originalOctalCode, int chopLevels); -unsigned char* rebaseOctalCode(const unsigned char* originalOctalCode, const unsigned char* newParentOctalCode, - bool includeColorSpace = false); const int CHECK_NODE_ONLY = -1; bool isAncestorOf(const unsigned char* possibleAncestor, const unsigned char* possibleDescendent, diff --git a/libraries/shared/src/PhysicsHelpers.h b/libraries/shared/src/PhysicsHelpers.h index 7ceafea915..3b6fccdc99 100644 --- a/libraries/shared/src/PhysicsHelpers.h +++ b/libraries/shared/src/PhysicsHelpers.h @@ -16,7 +16,7 @@ #include const int PHYSICS_ENGINE_MAX_NUM_SUBSTEPS = 6; // Bullet will start to "lose time" at 10 FPS. -const float PHYSICS_ENGINE_FIXED_SUBSTEP = 1.0f / 60.0f; +const float PHYSICS_ENGINE_FIXED_SUBSTEP = 1.0f / 90.0f; // return incremental rotation (Bullet-style) caused by angularVelocity over timeStep glm::quat computeBulletRotationStep(const glm::vec3& angularVelocity, float timeStep); diff --git a/libraries/shared/src/SpatialParentFinder.h b/libraries/shared/src/SpatialParentFinder.h new file mode 100644 index 0000000000..936d497eae --- /dev/null +++ b/libraries/shared/src/SpatialParentFinder.h @@ -0,0 +1,37 @@ +// +// SpatialParentFinder.h +// libraries/shared/src/ +// +// Created by Seth Alves on 2015-10-18 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_SpatialParentFinder_h +#define hifi_SpatialParentFinder_h + +#include + +#include "DependencyManager.h" + +class SpatiallyNestable; +using SpatiallyNestableWeakPointer = std::weak_ptr; +using SpatiallyNestablePointer = std::shared_ptr; +class SpatialParentFinder : public Dependency { + + + +// This interface is used to turn a QUuid into a pointer to a "parent" -- something that children can +// be spatially relative to. At this point, this means either an EntityItem or an Avatar. + + +public: + SpatialParentFinder() { } + virtual ~SpatialParentFinder() { } + + virtual SpatiallyNestableWeakPointer find(QUuid parentID) const = 0; +}; + +#endif // hifi_SpatialParentFinder_h diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp new file mode 100644 index 0000000000..bde788f6ff --- /dev/null +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -0,0 +1,382 @@ +// +// SpatiallyNestable.cpp +// libraries/shared/src/ +// +// Created by Seth Alves on 2015-10-18 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "DependencyManager.h" +#include "SpatiallyNestable.h" + + +SpatiallyNestable::SpatiallyNestable(NestableTypes::NestableType nestableType, QUuid id) : + _nestableType(nestableType), + _id(id), + _transform() { + // set flags in _transform + _transform.setTranslation(glm::vec3(0.0f)); + _transform.setRotation(glm::quat()); +} + +Transform SpatiallyNestable::getParentTransform() const { + Transform result; + SpatiallyNestablePointer parent = getParentPointer(); + if (parent) { + Transform parentTransform = parent->getTransform(_parentJointIndex); + result = parentTransform.setScale(1.0f); + } + return result; +} + +SpatiallyNestablePointer SpatiallyNestable::getParentPointer() const { + SpatiallyNestablePointer parent = _parent.lock(); + + if (!parent && _parentID.isNull()) { + // no parent + return nullptr; + } + + if (parent && parent->getID() == _parentID) { + // parent pointer is up-to-date + if (!_parentKnowsMe) { + parent->beParentOfChild(getThisPointer()); + _parentKnowsMe = true; + } + return parent; + } + + SpatiallyNestablePointer thisPointer = getThisPointer(); + + if (parent) { + // we have a parent pointer but our _parentID doesn't indicate this parent. + parent->forgetChild(thisPointer); + _parentKnowsMe = false; + _parent.reset(); + } + + // we have a _parentID but no parent pointer, or our parent pointer was to the wrong thing + QSharedPointer parentFinder = DependencyManager::get(); + if (!parentFinder) { + return nullptr; + } + _parent = parentFinder->find(_parentID); + parent = _parent.lock(); + if (parent) { + parent->beParentOfChild(thisPointer); + _parentKnowsMe = true; + } + + if (parent || _parentID.isNull()) { + thisPointer->parentChanged(); + } + + return parent; +} + +void SpatiallyNestable::beParentOfChild(SpatiallyNestablePointer newChild) const { + _childrenLock.withWriteLock([&] { + _children[newChild->getID()] = newChild; + }); +} + +void SpatiallyNestable::forgetChild(SpatiallyNestablePointer newChild) const { + _childrenLock.withWriteLock([&] { + _children.remove(newChild->getID()); + }); +} + +void SpatiallyNestable::setParentID(const QUuid& parentID) { + if (_parentID != parentID) { + _parentID = parentID; + _parentKnowsMe = false; + } + parentChanged(); +} + +glm::vec3 SpatiallyNestable::worldToLocal(const glm::vec3& position, const QUuid& parentID, int parentJointIndex) { + QSharedPointer parentFinder = DependencyManager::get(); + Transform parentTransform; + if (parentFinder) { + auto parentWP = parentFinder->find(parentID); + auto parent = parentWP.lock(); + if (parent) { + parentTransform = parent->getTransform(parentJointIndex); + parentTransform.setScale(1.0f); + } + } + + Transform positionTransform; + positionTransform.setTranslation(position); + Transform myWorldTransform; + Transform::mult(myWorldTransform, parentTransform, positionTransform); + + myWorldTransform.setTranslation(position); + Transform result; + Transform::inverseMult(result, parentTransform, myWorldTransform); + return result.getTranslation(); +} + +glm::quat SpatiallyNestable::worldToLocal(const glm::quat& orientation, const QUuid& parentID, int parentJointIndex) { + QSharedPointer parentFinder = DependencyManager::get(); + Transform parentTransform; + if (parentFinder) { + auto parentWP = parentFinder->find(parentID); + auto parent = parentWP.lock(); + if (parent) { + parentTransform = parent->getTransform(parentJointIndex); + parentTransform.setScale(1.0f); + } + } + + Transform orientationTransform; + orientationTransform.setRotation(orientation); + Transform myWorldTransform; + Transform::mult(myWorldTransform, parentTransform, orientationTransform); + myWorldTransform.setRotation(orientation); + Transform result; + Transform::inverseMult(result, parentTransform, myWorldTransform); + return result.getRotation(); +} + +glm::vec3 SpatiallyNestable::localToWorld(const glm::vec3& position, const QUuid& parentID, int parentJointIndex) { + QSharedPointer parentFinder = DependencyManager::get(); + Transform parentTransform; + if (parentFinder) { + auto parentWP = parentFinder->find(parentID); + auto parent = parentWP.lock(); + if (parent) { + parentTransform = parent->getTransform(parentJointIndex); + parentTransform.setScale(1.0f); + } + } + Transform positionTransform; + positionTransform.setTranslation(position); + Transform result; + Transform::mult(result, parentTransform, positionTransform); + return result.getTranslation(); +} + +glm::quat SpatiallyNestable::localToWorld(const glm::quat& orientation, const QUuid& parentID, int parentJointIndex) { + QSharedPointer parentFinder = DependencyManager::get(); + Transform parentTransform; + if (parentFinder) { + auto parentWP = parentFinder->find(parentID); + auto parent = parentWP.lock(); + if (parent) { + parentTransform = parent->getTransform(parentJointIndex); + parentTransform.setScale(1.0f); + } + } + Transform orientationTransform; + orientationTransform.setRotation(orientation); + Transform result; + Transform::mult(result, parentTransform, orientationTransform); + return result.getRotation(); +} + +glm::vec3 SpatiallyNestable::getPosition() const { + return getTransform().getTranslation(); +} + +glm::vec3 SpatiallyNestable::getPosition(int jointIndex) const { + return getTransform(jointIndex).getTranslation(); +} + +void SpatiallyNestable::setPosition(const glm::vec3& position) { + Transform parentTransform = getParentTransform(); + Transform myWorldTransform; + _transformLock.withWriteLock([&] { + Transform::mult(myWorldTransform, parentTransform, _transform); + myWorldTransform.setTranslation(position); + Transform::inverseMult(_transform, parentTransform, myWorldTransform); + }); + locationChanged(); +} + +glm::quat SpatiallyNestable::getOrientation() const { + return getTransform().getRotation(); +} + +glm::quat SpatiallyNestable::getOrientation(int jointIndex) const { + return getTransform(jointIndex).getRotation(); +} + +void SpatiallyNestable::setOrientation(const glm::quat& orientation) { + Transform parentTransform = getParentTransform(); + Transform myWorldTransform; + _transformLock.withWriteLock([&] { + Transform::mult(myWorldTransform, parentTransform, _transform); + myWorldTransform.setRotation(orientation); + Transform::inverseMult(_transform, parentTransform, myWorldTransform); + }); + locationChanged(); +} + +const Transform SpatiallyNestable::getTransform() const { + // return a world-space transform for this object's location + Transform parentTransform = getParentTransform(); + Transform result; + _transformLock.withReadLock([&] { + Transform::mult(result, parentTransform, _transform); + }); + return result; +} + +const Transform SpatiallyNestable::getTransform(int jointIndex) const { + // this returns the world-space transform for this object. It finds its parent's transform (which may + // cause this object's parent to query its parent, etc) and multiplies this object's local transform onto it. + Transform worldTransform = getTransform(); + Transform jointInObjectFrame = getAbsoluteJointTransformInObjectFrame(jointIndex); + Transform jointInWorldFrame; + Transform::mult(jointInWorldFrame, worldTransform, jointInObjectFrame); + return jointInWorldFrame; +} + +void SpatiallyNestable::setTransform(const Transform& transform) { + Transform parentTransform = getParentTransform(); + _transformLock.withWriteLock([&] { + Transform::inverseMult(_transform, parentTransform, transform); + }); + locationChanged(); +} + +glm::vec3 SpatiallyNestable::getScale() const { + glm::vec3 result; + _transformLock.withReadLock([&] { + result = _transform.getScale(); + }); + return result; +} + +glm::vec3 SpatiallyNestable::getScale(int jointIndex) const { + // XXX ... something with joints + return getScale(); +} + +void SpatiallyNestable::setScale(const glm::vec3& scale) { + _transformLock.withWriteLock([&] { + _transform.setScale(scale); + }); + dimensionsChanged(); +} + +const Transform SpatiallyNestable::getLocalTransform() const { + Transform result; + _transformLock.withReadLock([&] { + result =_transform; + }); + return result; +} + +void SpatiallyNestable::setLocalTransform(const Transform& transform) { + _transformLock.withWriteLock([&] { + _transform = transform; + }); + locationChanged(); +} + +glm::vec3 SpatiallyNestable::getLocalPosition() const { + glm::vec3 result; + _transformLock.withReadLock([&] { + result = _transform.getTranslation(); + }); + return result; +} + +void SpatiallyNestable::setLocalPosition(const glm::vec3& position) { + _transformLock.withWriteLock([&] { + _transform.setTranslation(position); + }); + locationChanged(); +} + +glm::quat SpatiallyNestable::getLocalOrientation() const { + glm::quat result; + _transformLock.withReadLock([&] { + result = _transform.getRotation(); + }); + return result; +} + +void SpatiallyNestable::setLocalOrientation(const glm::quat& orientation) { + _transformLock.withWriteLock([&] { + _transform.setRotation(orientation); + }); + locationChanged(); +} + +glm::vec3 SpatiallyNestable::getLocalScale() const { + glm::vec3 result; + _transformLock.withReadLock([&] { + result = _transform.getScale(); + }); + return result; +} + +void SpatiallyNestable::setLocalScale(const glm::vec3& scale) { + _transformLock.withWriteLock([&] { + _transform.setScale(scale); + }); + dimensionsChanged(); +} + +QList SpatiallyNestable::getChildren() const { + QList children; + _childrenLock.withReadLock([&] { + foreach(SpatiallyNestableWeakPointer childWP, _children.values()) { + SpatiallyNestablePointer child = childWP.lock(); + if (child) { + children << child; + } + } + }); + return children; +} + +const Transform SpatiallyNestable::getAbsoluteJointTransformInObjectFrame(int jointIndex) const { + Transform jointTransformInObjectFrame; + glm::vec3 position = getAbsoluteJointTranslationInObjectFrame(jointIndex); + glm::quat orientation = getAbsoluteJointRotationInObjectFrame(jointIndex); + jointTransformInObjectFrame.setRotation(orientation); + jointTransformInObjectFrame.setTranslation(position); + return jointTransformInObjectFrame; +} + +SpatiallyNestablePointer SpatiallyNestable::getThisPointer() const { + SpatiallyNestableConstPointer constThisPointer = shared_from_this(); + SpatiallyNestablePointer thisPointer = std::const_pointer_cast(constThisPointer); // ermahgerd !!! + return thisPointer; +} + +void SpatiallyNestable::forEachChild(std::function actor) { + foreach(SpatiallyNestablePointer child, getChildren()) { + actor(child); + } +} + +void SpatiallyNestable::forEachDescendant(std::function actor) { + QQueue toProcess; + foreach(SpatiallyNestablePointer child, getChildren()) { + toProcess.enqueue(child); + } + + while (!toProcess.empty()) { + SpatiallyNestablePointer object = toProcess.dequeue(); + actor(object); + foreach (SpatiallyNestablePointer child, object->getChildren()) { + toProcess.enqueue(child); + } + } +} + +void SpatiallyNestable::locationChanged() { + forEachChild([&](SpatiallyNestablePointer object) { + object->locationChanged(); + }); +} diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h new file mode 100644 index 0000000000..e656957912 --- /dev/null +++ b/libraries/shared/src/SpatiallyNestable.h @@ -0,0 +1,128 @@ +// +// SpatiallyNestable.h +// libraries/shared/src/ +// +// Created by Seth Alves on 2015-10-18 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_SpatiallyNestable_h +#define hifi_SpatiallyNestable_h + +#include + +#include "Transform.h" +#include "SpatialParentFinder.h" +#include "shared/ReadWriteLockable.h" + + +class SpatiallyNestable; +using SpatiallyNestableWeakPointer = std::weak_ptr; +using SpatiallyNestableWeakConstPointer = std::weak_ptr; +using SpatiallyNestablePointer = std::shared_ptr; +using SpatiallyNestableConstPointer = std::shared_ptr; + +class NestableTypes { +public: + using NestableType = enum NestableType_t { + Entity, + Avatar + }; +}; + +class SpatiallyNestable : public std::enable_shared_from_this { +public: + SpatiallyNestable(NestableTypes::NestableType nestableType, QUuid id); + virtual ~SpatiallyNestable() { } + + virtual const QUuid& getID() const { return _id; } + virtual void setID(const QUuid& id) { _id = id; } + + virtual const QUuid getParentID() const { return _parentID; } + virtual void setParentID(const QUuid& parentID); + + virtual quint16 getParentJointIndex() const { return _parentJointIndex; } + virtual void setParentJointIndex(quint16 parentJointIndex) { _parentJointIndex = parentJointIndex; } + + static glm::vec3 worldToLocal(const glm::vec3& position, const QUuid& parentID, int parentJointIndex); + static glm::quat worldToLocal(const glm::quat& orientation, const QUuid& parentID, int parentJointIndex); + + static glm::vec3 localToWorld(const glm::vec3& position, const QUuid& parentID, int parentJointIndex); + static glm::quat localToWorld(const glm::quat& orientation, const QUuid& parentID, int parentJointIndex); + + // world frame + virtual const Transform getTransform() const; + virtual void setTransform(const Transform& transform); + + virtual Transform getParentTransform() const; + + virtual glm::vec3 getPosition() const; + virtual void setPosition(const glm::vec3& position); + + virtual glm::quat getOrientation() const; + virtual glm::quat getOrientation(int jointIndex) const; + virtual void setOrientation(const glm::quat& orientation); + + virtual glm::vec3 getScale() const; + virtual void setScale(const glm::vec3& scale); + + // get world-frame values for a specific joint + virtual const Transform getTransform(int jointIndex) const; + virtual glm::vec3 getPosition(int jointIndex) const; + virtual glm::vec3 getScale(int jointIndex) const; + + // object's parent's frame + virtual const Transform getLocalTransform() const; + virtual void setLocalTransform(const Transform& transform); + + virtual glm::vec3 getLocalPosition() const; + virtual void setLocalPosition(const glm::vec3& position); + + virtual glm::quat getLocalOrientation() const; + virtual void setLocalOrientation(const glm::quat& orientation); + + virtual glm::vec3 getLocalScale() const; + virtual void setLocalScale(const glm::vec3& scale); + + QList getChildren() const; + NestableTypes::NestableType getNestableType() const { return _nestableType; } + + // this object's frame + virtual const Transform getAbsoluteJointTransformInObjectFrame(int jointIndex) const; + virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const { assert(false); return glm::quat(); } + virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const { assert(false); return glm::vec3(); } + + SpatiallyNestablePointer getThisPointer() const; + +protected: + NestableTypes::NestableType _nestableType; // EntityItem or an AvatarData + QUuid _id; + QUuid _parentID; // what is this thing's transform relative to? + quint16 _parentJointIndex { 0 }; // which joint of the parent is this relative to? + SpatiallyNestablePointer getParentPointer() const; + mutable SpatiallyNestableWeakPointer _parent; + + virtual void beParentOfChild(SpatiallyNestablePointer newChild) const; + virtual void forgetChild(SpatiallyNestablePointer newChild) const; + + mutable ReadWriteLockable _childrenLock; + mutable QHash _children; + + virtual void parentChanged() {} // called when parent pointer is updated + virtual void locationChanged(); // called when a this object's location has changed + virtual void dimensionsChanged() {} // called when a this object's dimensions have changed + + void forEachChild(std::function actor); + void forEachDescendant(std::function actor); + +private: + mutable ReadWriteLockable _transformLock; + Transform _transform; // this is to be combined with parent's world-transform to produce this' world-transform. + mutable bool _parentKnowsMe = false; +}; + + +#endif // hifi_SpatiallyNestable_h diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index 7fd956a08f..7c057c7152 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -15,13 +15,6 @@ uvec2 OculusBaseDisplayPlugin::getRecommendedRenderSize() const { return _desiredFramebufferSize; } -void OculusBaseDisplayPlugin::preRender() { -#if (OVR_MAJOR_VERSION >= 6) - ovrFrameTiming ftiming = ovr_GetFrameTiming(_hmd, _frameIndex); - _trackingState = ovr_GetTrackingState(_hmd, ftiming.DisplayMidpointSeconds); -#endif -} - glm::mat4 OculusBaseDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseProjection) const { return _eyeProjections[eye]; } @@ -29,7 +22,6 @@ glm::mat4 OculusBaseDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseP void OculusBaseDisplayPlugin::resetSensors() { #if (OVR_MAJOR_VERSION >= 6) ovr_RecenterPose(_hmd); - preRender(); #endif } @@ -37,15 +29,14 @@ glm::mat4 OculusBaseDisplayPlugin::getEyeToHeadTransform(Eye eye) const { return glm::translate(mat4(), toGlm(_eyeOffsets[eye])); } -glm::mat4 OculusBaseDisplayPlugin::getHeadPose() const { - return toGlm(_trackingState.HeadPose.ThePose); +glm::mat4 OculusBaseDisplayPlugin::getHeadPose(uint32_t frameIndex) const { +#if (OVR_MAJOR_VERSION >= 6) + auto frameTiming = ovr_GetFrameTiming(_hmd, frameIndex); + auto trackingState = ovr_GetTrackingState(_hmd, frameTiming.DisplayMidpointSeconds); + return toGlm(trackingState.HeadPose.ThePose); +#endif } -void OculusBaseDisplayPlugin::setEyeRenderPose(Eye eye, const glm::mat4& pose) { - _eyePoses[eye] = ovrPoseFromGlm(pose); -} - - bool OculusBaseDisplayPlugin::isSupported() const { #if (OVR_MAJOR_VERSION >= 6) if (!OVR_SUCCESS(ovr_Initialize(nullptr))) { @@ -77,6 +68,7 @@ void OculusBaseDisplayPlugin::deinit() { } void OculusBaseDisplayPlugin::activate() { + WindowOpenGLDisplayPlugin::activate(); #if (OVR_MAJOR_VERSION >= 6) if (!OVR_SUCCESS(ovr_Initialize(nullptr))) { qFatal("Could not init OVR"); @@ -123,8 +115,6 @@ void OculusBaseDisplayPlugin::activate() { eyeSizes[0].x + eyeSizes[1].x, std::max(eyeSizes[0].y, eyeSizes[1].y)); - _frameIndex = 0; - if (!OVR_SUCCESS(ovr_ConfigureTracking(_hmd, ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) { qFatal("Could not attach to sensor device"); @@ -145,8 +135,6 @@ void OculusBaseDisplayPlugin::activate() { qFatal("Could not attach to sensor device"); } #endif - - WindowOpenGLDisplayPlugin::activate(); } void OculusBaseDisplayPlugin::deactivate() { @@ -159,9 +147,6 @@ void OculusBaseDisplayPlugin::deactivate() { #endif } -void OculusBaseDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { - ++_frameIndex; -} float OculusBaseDisplayPlugin::getIPD() const { float result = OVR_DEFAULT_IPD; diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.h b/plugins/oculus/src/OculusBaseDisplayPlugin.h index ba1924bfff..711be6aa5e 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.h +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.h @@ -30,24 +30,18 @@ public: virtual glm::uvec2 getRecommendedUiSize() const override final { return uvec2(1920, 1080); } virtual void resetSensors() override final; virtual glm::mat4 getEyeToHeadTransform(Eye eye) const override final; - virtual glm::mat4 getHeadPose() const override final; - virtual void setEyeRenderPose(Eye eye, const glm::mat4& pose) override final; virtual float getIPD() const override final; + virtual glm::mat4 getHeadPose(uint32_t frameIndex) const override; protected: virtual void customizeContext() override; - virtual void preRender() override final; - virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; protected: - ovrPosef _eyePoses[2]; ovrVector3f _eyeOffsets[2]; mat4 _eyeProjections[3]; mat4 _compositeEyeProjections[2]; uvec2 _desiredFramebufferSize; - ovrTrackingState _trackingState; - unsigned int _frameIndex{ 0 }; #if (OVR_MAJOR_VERSION >= 6) ovrHmd _hmd; diff --git a/plugins/oculus/src/OculusDebugDisplayPlugin.cpp b/plugins/oculus/src/OculusDebugDisplayPlugin.cpp index 7a8b355ddd..26bb3cf9b2 100644 --- a/plugins/oculus/src/OculusDebugDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDebugDisplayPlugin.cpp @@ -28,13 +28,3 @@ void OculusDebugDisplayPlugin::customizeContext() { OculusBaseDisplayPlugin::customizeContext(); enableVsync(false); } - -void OculusDebugDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { - WindowOpenGLDisplayPlugin::display(finalTexture, sceneSize); - OculusBaseDisplayPlugin::display(finalTexture, sceneSize); -} - -void OculusDebugDisplayPlugin::finishFrame() { - swapBuffers(); - doneCurrent(); -}; diff --git a/plugins/oculus/src/OculusDebugDisplayPlugin.h b/plugins/oculus/src/OculusDebugDisplayPlugin.h index d23c6ba567..04b68704cc 100644 --- a/plugins/oculus/src/OculusDebugDisplayPlugin.h +++ b/plugins/oculus/src/OculusDebugDisplayPlugin.h @@ -15,10 +15,7 @@ public: virtual bool isSupported() const override; protected: - virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; virtual void customizeContext() override; - // Do not perform swap in finish - virtual void finishFrame() override; private: static const QString NAME; diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index 923b8bde6e..8846b8a6a6 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -144,8 +144,7 @@ static const QString MONO_PREVIEW = "Mono Preview"; static const QString FRAMERATE = DisplayPlugin::MENU_PATH() + ">Framerate"; void OculusDisplayPlugin::activate() { - - _container->addMenuItem(MENU_PATH(), MONO_PREVIEW, + _container->addMenuItem(PluginType::DISPLAY_PLUGIN, MENU_PATH(), MONO_PREVIEW, [this](bool clicked) { _monoPreview = clicked; }, true, true); @@ -170,18 +169,19 @@ void OculusDisplayPlugin::customizeContext() { _enablePreview = !isVsyncEnabled(); } -void OculusDisplayPlugin::deactivate() { +void OculusDisplayPlugin::uncustomizeContext() { #if (OVR_MAJOR_VERSION >= 6) - makeCurrent(); _sceneFbo.reset(); - doneCurrent(); #endif - - OculusBaseDisplayPlugin::deactivate(); + OculusBaseDisplayPlugin::uncustomizeContext(); } -void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { +void OculusDisplayPlugin::internalPresent() { #if (OVR_MAJOR_VERSION >= 6) + if (!_currentSceneTexture) { + return; + } + using namespace oglplus; // Need to make sure only the display plugin is responsible for // controlling vsync @@ -196,7 +196,7 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi } else { Context::Viewport(windowSize.x, windowSize.y); } - glBindTexture(GL_TEXTURE_2D, finalTexture); + glBindTexture(GL_TEXTURE_2D, _currentSceneTexture); GLenum err = glGetError(); Q_ASSERT(0 == err); drawUnitQuad(); @@ -205,16 +205,24 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi _sceneFbo->Bound([&] { auto size = _sceneFbo->size; Context::Viewport(size.x, size.y); - glBindTexture(GL_TEXTURE_2D, finalTexture); + glBindTexture(GL_TEXTURE_2D, _currentSceneTexture); GLenum err = glGetError(); drawUnitQuad(); }); - ovr_for_each_eye([&](ovrEyeType eye) { - _sceneLayer.RenderPose[eye] = _eyePoses[eye]; - }); + uint32_t frameIndex { 0 }; + EyePoses eyePoses; + { + Lock lock(_mutex); + Q_ASSERT(_sceneTextureToFrameIndexMap.contains(_currentSceneTexture)); + frameIndex = _sceneTextureToFrameIndexMap[_currentSceneTexture]; + Q_ASSERT(_frameEyePoses.contains(frameIndex)); + eyePoses = _frameEyePoses[frameIndex]; + } + + _sceneLayer.RenderPose[ovrEyeType::ovrEye_Left] = eyePoses.first; + _sceneLayer.RenderPose[ovrEyeType::ovrEye_Right] = eyePoses.second; - auto windowSize = toGlm(_window->size()); { ovrViewScaleDesc viewScaleDesc; viewScaleDesc.HmdSpaceToWorldScaleInMeters = 1.0f; @@ -228,19 +236,26 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi } } _sceneFbo->Increment(); - - ++_frameIndex; #endif -} -/* + /* The swapbuffer call here is only required if we want to mirror the content to the screen. - However, it should only be done if we can reliably disable v-sync on the mirror surface, + However, it should only be done if we can reliably disable v-sync on the mirror surface, otherwise the swapbuffer delay will interefere with the framerate of the headset -*/ -void OculusDisplayPlugin::finishFrame() { + */ if (_enablePreview) { swapBuffers(); } - doneCurrent(); -}; +} + +void OculusDisplayPlugin::setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) { + auto ovrPose = ovrPoseFromGlm(pose); + { + Lock lock(_mutex); + if (eye == Eye::Left) { + _frameEyePoses[frameIndex].first = ovrPose; + } else { + _frameEyePoses[frameIndex].second = ovrPose; + } + } +} diff --git a/plugins/oculus/src/OculusDisplayPlugin.h b/plugins/oculus/src/OculusDisplayPlugin.h index c1224ecf3a..5509715b9f 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.h +++ b/plugins/oculus/src/OculusDisplayPlugin.h @@ -15,22 +15,21 @@ using SwapFboPtr = QSharedPointer; class OculusDisplayPlugin : public OculusBaseDisplayPlugin { public: virtual void activate() override; - virtual void deactivate() override; virtual const QString & getName() const override; + virtual void setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) override final; protected: - virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; + virtual void internalPresent() override; virtual void customizeContext() override; - // Do not perform swap in finish - virtual void finishFrame() override; + virtual void uncustomizeContext() override; private: + using EyePoses = std::pair; static const QString NAME; bool _enablePreview { false }; bool _monoPreview { true }; + QMap _frameEyePoses; -#if (OVR_MAJOR_VERSION >= 6) SwapFboPtr _sceneFbo; -#endif }; diff --git a/plugins/oculus/src/OculusHelpers.cpp b/plugins/oculus/src/OculusHelpers.cpp index f93580e5a3..37c560e9ad 100644 --- a/plugins/oculus/src/OculusHelpers.cpp +++ b/plugins/oculus/src/OculusHelpers.cpp @@ -11,7 +11,6 @@ namespace Oculus { ovrHmd _hmd; - unsigned int _frameIndex{ 0 }; ovrEyeRenderDesc _eyeRenderDescs[2]; ovrPosef _eyePoses[2]; ovrVector3f _eyeOffsets[2]; diff --git a/plugins/oculusLegacy/CMakeLists.txt b/plugins/oculusLegacy/CMakeLists.txt index bf9d22410d..44cee83a7d 100644 --- a/plugins/oculusLegacy/CMakeLists.txt +++ b/plugins/oculusLegacy/CMakeLists.txt @@ -6,7 +6,8 @@ # See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html # -if (NOT WIN32) +#if (NOT WIN32) +if (FALSE) set(TARGET_NAME oculusLegacy) setup_hifi_plugin() diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index ddf251778f..078b6586c1 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -12,10 +12,8 @@ #include #include #include -#include #include #include -#include #include #include @@ -167,8 +165,11 @@ void OculusLegacyDisplayPlugin::activate() { } }); - ovrBool result = ovrHmd_ConfigureRendering(_hmd, &config.Config, distortionCaps, _eyeFovs, _eyeRenderDescs); - Q_ASSERT(result); + #ifndef NDEBUG + ovrBool result = + #endif + ovrHmd_ConfigureRendering(_hmd, &config.Config, distortionCaps, _eyeFovs, _eyeRenderDescs); + assert(result); } void OculusLegacyDisplayPlugin::deactivate() { diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h index 6e3f864aee..ccf1ffdb57 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h @@ -46,7 +46,6 @@ private: static const QString NAME; ovrHmd _hmd; - unsigned int _frameIndex; ovrTrackingState _trackingState; ovrEyeRenderDesc _eyeRenderDescs[2]; ovrPosef _eyePoses[2]; diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index 1b5bb4739a..64db3f6154 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -194,10 +194,7 @@ void AnimTests::testVariant() { auto floatVarNegative = AnimVariant(-1.0f); auto vec3Var = AnimVariant(glm::vec3(1.0f, -2.0f, 3.0f)); auto quatVar = AnimVariant(glm::quat(1.0f, 2.0f, -3.0f, 4.0f)); - auto mat4Var = AnimVariant(glm::mat4(glm::vec4(1.0f, 2.0f, 3.0f, 4.0f), - glm::vec4(5.0f, 6.0f, -7.0f, 8.0f), - glm::vec4(9.0f, 10.0f, 11.0f, 12.0f), - glm::vec4(13.0f, 14.0f, 15.0f, 16.0f))); + QVERIFY(defaultVar.isBool()); QVERIFY(defaultVar.getBool() == false); @@ -232,12 +229,6 @@ void AnimTests::testVariant() { QVERIFY(q.x == 2.0f); QVERIFY(q.y == -3.0f); QVERIFY(q.z == 4.0f); - - QVERIFY(mat4Var.isMat4()); - auto m = mat4Var.getMat4(); - QVERIFY(m[0].x == 1.0f); - QVERIFY(m[1].z == -7.0f); - QVERIFY(m[3].w == 16.0f); } void AnimTests::testAccumulateTime() { @@ -323,3 +314,83 @@ void AnimTests::testAccumulateTimeWithParameters(float startFrame, float endFram QVERIFY(!triggers.empty() && triggers[0] == "testNodeOnLoop"); triggers.clear(); } + + +void AnimTests::testAnimPose() { + const float PI = (float)M_PI; + const glm::quat ROT_X_90 = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f)); + const glm::quat ROT_Y_180 = glm::angleAxis(PI, glm::vec3(0.0f, 1.0, 0.0f)); + const glm::quat ROT_Z_30 = glm::angleAxis(PI / 6.0f, glm::vec3(1.0f, 0.0f, 0.0f)); + + std::vector scaleVec = { + glm::vec3(1), + glm::vec3(2.0f, 1.0f, 1.0f), + glm::vec3(1.0f, 0.5f, 1.0f), + glm::vec3(1.0f, 1.0f, 1.5f), + glm::vec3(2.0f, 0.5f, 1.5f), + glm::vec3(-2.0f, 0.5f, 1.5f), + glm::vec3(2.0f, -0.5f, 1.5f), + glm::vec3(2.0f, 0.5f, -1.5f), + glm::vec3(-2.0f, -0.5f, -1.5f), + }; + + std::vector rotVec = { + glm::quat(), + ROT_X_90, + ROT_Y_180, + ROT_Z_30, + ROT_X_90 * ROT_Y_180 * ROT_Z_30, + -ROT_Y_180 + }; + + std::vector transVec = { + glm::vec3(), + glm::vec3(10.0f, 0.0f, 0.0f), + glm::vec3(0.0f, 5.0f, 0.0f), + glm::vec3(0.0f, 0.0f, 7.5f), + glm::vec3(10.0f, 5.0f, 7.5f), + glm::vec3(-10.0f, 5.0f, 7.5f), + glm::vec3(10.0f, -5.0f, 7.5f), + glm::vec3(10.0f, 5.0f, -7.5f) + }; + + const float EPSILON = 0.001f; + + for (auto& scale : scaleVec) { + for (auto& rot : rotVec) { + for (auto& trans : transVec) { + + // build a matrix the old fashioned way. + glm::mat4 scaleMat = glm::scale(glm::mat4(), scale); + glm::mat4 rotTransMat = createMatFromQuatAndPos(rot, trans); + glm::mat4 rawMat = rotTransMat * scaleMat; + + // use an anim pose to build a matrix by parts. + AnimPose pose(scale, rot, trans); + glm::mat4 poseMat = pose; + + QCOMPARE_WITH_ABS_ERROR(rawMat, poseMat, EPSILON); + } + } + } + + for (auto& scale : scaleVec) { + for (auto& rot : rotVec) { + for (auto& trans : transVec) { + + // build a matrix the old fashioned way. + glm::mat4 scaleMat = glm::scale(glm::mat4(), scale); + glm::mat4 rotTransMat = createMatFromQuatAndPos(rot, trans); + glm::mat4 rawMat = rotTransMat * scaleMat; + + // use an anim pose to decompse a matrix into parts + AnimPose pose(rawMat); + + // now build a new matrix from those parts. + glm::mat4 poseMat = pose; + + QCOMPARE_WITH_ABS_ERROR(rawMat, poseMat, EPSILON); + } + } + } +} diff --git a/tests/animation/src/AnimTests.h b/tests/animation/src/AnimTests.h index 7bd05369c7..a07217b91a 100644 --- a/tests/animation/src/AnimTests.h +++ b/tests/animation/src/AnimTests.h @@ -26,6 +26,7 @@ private slots: void testLoader(); void testVariant(); void testAccumulateTime(); + void testAnimPose(); }; #endif // hifi_AnimTests_h diff --git a/tests/controllers/src/main.cpp b/tests/controllers/src/main.cpp index 664a894e44..fe49c2b385 100644 --- a/tests/controllers/src/main.cpp +++ b/tests/controllers/src/main.cpp @@ -83,7 +83,7 @@ public: virtual ~PluginContainerProxy() {} virtual void addMenu(const QString& menuName) override {} virtual void removeMenu(const QString& menuName) override {} - virtual QAction* addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") override { return nullptr; } + virtual QAction* addMenuItem(PluginType type, const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") override { return nullptr; } virtual void removeMenuItem(const QString& menuName, const QString& menuItem) override {} virtual bool isOptionChecked(const QString& name) override { return false; } virtual void setIsOptionChecked(const QString& path, bool checked) override {} @@ -91,7 +91,12 @@ public: virtual void unsetFullscreen(const QScreen* avoidScreen = nullptr) override {} virtual void showDisplayPluginsTools() override {} virtual void requestReset() override {} - virtual QGLWidget* getPrimarySurface() override { return nullptr; } + virtual bool makeRenderingContextCurrent() override { return true; } + virtual void releaseSceneTexture(uint32_t texture) override {} + virtual void releaseOverlayTexture(uint32_t texture) override {} + virtual GLWidget* getPrimaryWidget() override { return nullptr; } + virtual QWindow* getPrimaryWindow() override { return nullptr; } + virtual QOpenGLContext* getPrimaryContext() override { return nullptr; } virtual bool isForeground() override { return true; } virtual const DisplayPlugin* getActiveDisplayPlugin() const override { return nullptr; } }; diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index 80c2dbf8e9..aa763196d1 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -33,9 +33,8 @@ #include #include -// Must come after GL headers -#include -#include +#include +#include #include #include @@ -85,9 +84,9 @@ public: uint32_t toCompactColor(const glm::vec4& color); gpu::ShaderPointer makeShader(const std::string & vertexShaderSrc, const std::string & fragmentShaderSrc, const gpu::Shader::BindingSet & bindings) { - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(vertexShaderSrc)); - auto fs = gpu::ShaderPointer(gpu::Shader::createPixel(fragmentShaderSrc)); - auto shader = gpu::ShaderPointer(gpu::Shader::createProgram(vs, fs)); + auto vs = gpu::Shader::createVertex(vertexShaderSrc); + auto fs = gpu::Shader::createPixel(fragmentShaderSrc); + auto shader = gpu::Shader::createProgram(vs, fs); if (!gpu::Shader::makeProgram(*shader, bindings)) { printf("Could not compile shader\n"); exit(-1); @@ -118,7 +117,7 @@ gpu::Stream::FormatPointer& getInstancedSolidStreamFormat(); class QTestWindow : public QWindow { Q_OBJECT - QOpenGLContext* _qGlContext{ nullptr }; + QOpenGLContextWrapper _qGlContext; QSize _size; gpu::ContextPointer _context; @@ -151,19 +150,12 @@ public: setFormat(format); - _qGlContext = new QOpenGLContext; - _qGlContext->setFormat(format); - _qGlContext->create(); + _qGlContext.setFormat(format); + _qGlContext.create(); show(); makeCurrent(); - QOpenGLDebugLogger* logger = new QOpenGLDebugLogger(this); - logger->initialize(); // initializes in the current context, i.e. ctx - connect(logger, &QOpenGLDebugLogger::messageLogged, [](const QOpenGLDebugMessage& message){ - qDebug() << message; - }); - logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); - + setupDebugLogger(this); gpu::Context::init(); _context = std::make_shared(); @@ -172,7 +164,7 @@ public: auto state = std::make_shared(); state->setMultisampleEnable(true); state->setDepthTest(gpu::State::DepthTest { true }); - _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(shader, state)); + _pipeline = gpu::Pipeline::create(shader, state); _instanceLocation = _pipeline->getProgram()->getUniforms().findLocation("Instanced"); // Clear screen @@ -371,7 +363,7 @@ public: geometryCache->renderWireCube(batch); _context->render(batch); - _qGlContext->swapBuffers(this); + _qGlContext.swapBuffers(this); fps.increment(); if (fps.elapsed() >= 0.5f) { @@ -381,7 +373,7 @@ public: } void makeCurrent() { - _qGlContext->makeCurrent(this); + _qGlContext.makeCurrent(this); } protected: diff --git a/tests/render-utils/src/main.cpp b/tests/render-utils/src/main.cpp index 0fa261db8d..4afb2d4807 100644 --- a/tests/render-utils/src/main.cpp +++ b/tests/render-utils/src/main.cpp @@ -14,8 +14,8 @@ #include -#include -#include +#include +#include #include #include @@ -77,7 +77,7 @@ const QString& getQmlDir() { class QTestWindow : public QWindow { Q_OBJECT - QOpenGLContext* _context{ nullptr }; + QOpenGLContextWrapper _context; QSize _size; //TextRenderer* _textRenderer[4]; RateCounter fps; @@ -104,9 +104,8 @@ public: setFormat(format); - _context = new QOpenGLContext; - _context->setFormat(format); - _context->create(); + _context.setFormat(format); + _context.create(); show(); makeCurrent(); @@ -114,15 +113,7 @@ public: gpu::Context::init(); - { - QOpenGLDebugLogger* logger = new QOpenGLDebugLogger(this); - logger->initialize(); // initializes in the current context, i.e. ctx - logger->enableMessages(); - connect(logger, &QOpenGLDebugLogger::messageLogged, this, [&](const QOpenGLDebugMessage & debugMessage) { - qDebug() << debugMessage; - }); - // logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); - } + setupDebugLogger(this); qDebug() << (const char*)glGetString(GL_VERSION); //_textRenderer[0] = TextRenderer::getInstance(SANS_FONT_FAMILY, 12, false); @@ -147,7 +138,7 @@ public: void draw(); void makeCurrent() { - _context->makeCurrent(this); + _context.makeCurrent(this); } protected: @@ -170,9 +161,9 @@ static const glm::vec3 COLORS[4] = { { 1.0, 1.0, 1.0 }, { 0.5, 1.0, 0.5 }, { void testShaderBuild(const char* vs_src, const char * fs_src) { - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(vs_src))); - auto fs = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(fs_src))); - auto pr = gpu::ShaderPointer(gpu::Shader::createProgram(vs, fs)); + auto vs = gpu::Shader::createVertex(std::string(vs_src)); + auto fs = gpu::Shader::createPixel(std::string(fs_src)); + auto pr = gpu::Shader::createProgram(vs, fs); gpu::Shader::makeProgram(*pr); } @@ -185,7 +176,7 @@ void QTestWindow::draw() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glViewport(0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio()); - _context->swapBuffers(this); + _context.swapBuffers(this); glFinish(); fps.increment(); diff --git a/tests/shaders/src/main.cpp b/tests/shaders/src/main.cpp index 3edff67d66..52e43e2faa 100644 --- a/tests/shaders/src/main.cpp +++ b/tests/shaders/src/main.cpp @@ -10,8 +10,6 @@ #include -#include - #include #include #include @@ -22,6 +20,9 @@ #include +#include +#include + #include "../model/Skybox_vert.h" #include "../model/Skybox_frag.h" @@ -120,7 +121,7 @@ // Create a simple OpenGL window that renders text in various ways class QTestWindow : public QWindow { Q_OBJECT - QOpenGLContext* _context{ nullptr }; + QOpenGLContextWrapper _context; protected: void renderText(); @@ -128,24 +129,16 @@ protected: public: QTestWindow() { setSurfaceType(QSurface::OpenGLSurface); - QSurfaceFormat format = getDefaultOpenGlSurfaceFormat(); + QSurfaceFormat format = getDefaultOpenGLSurfaceFormat(); setFormat(format); - _context = new QOpenGLContext; - _context->setFormat(format); - _context->create(); + _context.setFormat(format); + _context.create(); show(); makeCurrent(); gpu::Context::init(); - { - QOpenGLDebugLogger* logger = new QOpenGLDebugLogger(this); - logger->initialize(); // initializes in the current context, i.e. ctx - logger->enableMessages(); - connect(logger, &QOpenGLDebugLogger::messageLogged, this, [&](const QOpenGLDebugMessage & debugMessage) { - qDebug() << debugMessage; - }); - } + setupDebugLogger(this); makeCurrent(); resize(QSize(800, 600)); } @@ -155,14 +148,14 @@ public: void draw(); void makeCurrent() { - _context->makeCurrent(this); + _context.makeCurrent(this); } }; void testShaderBuild(const char* vs_src, const char * fs_src) { - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(vs_src))); - auto fs = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(fs_src))); - auto pr = gpu::ShaderPointer(gpu::Shader::createProgram(vs, fs)); + auto vs = gpu::Shader::createVertex(std::string(vs_src)); + auto fs = gpu::Shader::createPixel(std::string(fs_src)); + auto pr = gpu::Shader::createProgram(vs, fs); if (!gpu::Shader::makeProgram(*pr)) { throw std::runtime_error("Failed to compile shader"); } @@ -248,7 +241,7 @@ void QTestWindow::draw() { testShaderBuild(polyvox_vert, polyvox_frag); }); - _context->swapBuffers(this); + _context.swapBuffers(this); } void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { diff --git a/tools/udt-test/src/UDTTest.cpp b/tools/udt-test/src/UDTTest.cpp index 32ab1780e0..d4054527f9 100644 --- a/tools/udt-test/src/UDTTest.cpp +++ b/tools/udt-test/src/UDTTest.cpp @@ -176,9 +176,36 @@ UDTTest::UDTTest(int& argc, char** argv) : } else { // this is a receiver - in case there are ordered packets (messages) being sent to us make sure that we handle them // so that they can be verified - _socket.setPacketListHandler( - [this](std::unique_ptr packetList) { handlePacketList(std::move(packetList)); }); + _socket.setMessageHandler( + [this](std::unique_ptr packet) { + auto messageNumber = packet->getMessageNumber(); + auto it = _pendingMessages.find(messageNumber); + + if (it == _pendingMessages.end()) { + auto message = std::unique_ptr(new Message { messageNumber, packet->readAll() }); + message->data.reserve(_messageSize); + if (packet->getPacketPosition() == udt::Packet::ONLY) { + handleMessage(std::move(message)); + } else { + _pendingMessages[messageNumber] = std::move(message); + } + } else { + auto& message = it->second; + message->data.append(packet->readAll()); + + if (packet->getPacketPosition() == udt::Packet::LAST) { + handleMessage(std::move(message)); + _pendingMessages.erase(it); + } + } + + }); } + _socket.setMessageFailureHandler( + [this](HifiSockAddr from, udt::Packet::MessageNumber messageNumber) { + _pendingMessages.erase(messageNumber); + } + ); // the sender reports stats every 100 milliseconds, unless passed a custom value @@ -308,11 +335,11 @@ void UDTTest::sendPacket() { } -void UDTTest::handlePacketList(std::unique_ptr packetList) { +void UDTTest::handleMessage(std::unique_ptr message) { // generate the byte array that should match this message - using the same seed the sender did int packetSize = udt::Packet::maxPayloadSize(true); - int messageSize = packetList->getMessageSize(); + int messageSize = message->data.size(); QByteArray messageData(messageSize, 0); @@ -323,13 +350,13 @@ void UDTTest::handlePacketList(std::unique_ptr packetList) { messageData.replace(i, sizeof(randomInt), reinterpret_cast(&randomInt), sizeof(randomInt)); } - bool dataMatch = messageData == packetList->getMessage(); + bool dataMatch = messageData == message->data; - Q_ASSERT_X(dataMatch, "UDTTest::handlePacketList", + Q_ASSERT_X(dataMatch, "UDTTest::handleMessage", "received message did not match expected message (from seeded random number generation)."); if (!dataMatch) { - qCritical() << "UDTTest::handlePacketList" << "received message did not match expected message" + qCritical() << "UDTTest::handleMessage" << "received message did not match expected message" << "(from seeded random number generation)."; } } diff --git a/tools/udt-test/src/UDTTest.h b/tools/udt-test/src/UDTTest.h index fda57cc183..3a4b1b897d 100644 --- a/tools/udt-test/src/UDTTest.h +++ b/tools/udt-test/src/UDTTest.h @@ -23,6 +23,13 @@ #include #include +#include + +struct Message { + udt::MessageNumber messageNumber; + QByteArray data; +}; + class UDTTest : public QCoreApplication { Q_OBJECT public: @@ -34,7 +41,7 @@ public slots: private: void parseArguments(); - void handlePacketList(std::unique_ptr packetList); + void handleMessage(std::unique_ptr message); void sendInitialPackets(); // fills the queue with packets to start void sendPacket(); // constructs and sends a packet according to the test parameters @@ -53,6 +60,8 @@ private: bool _sendOrdered { false }; // whether to send ordered packets int _messageSize { 10000000 }; // number of bytes per message while sending ordered + + std::unordered_map> _pendingMessages; std::random_device _randomDevice; std::mt19937 _generator { _randomDevice() }; // random number generator for ordered data testing diff --git a/unpublishedScripts/hiddenEntityReset.js b/unpublishedScripts/hiddenEntityReset.js index 982c65fc08..ee53e36f9e 100644 --- a/unpublishedScripts/hiddenEntityReset.js +++ b/unpublishedScripts/hiddenEntityReset.js @@ -241,7 +241,6 @@ gravity: BOW_GRAVITY, shapeType: 'compound', compoundShapeURL: COLLISION_HULL_URL, - collisionSoundURL: "http://hifi-public.s3.amazonaws.com/sounds/bow_fall.L.wav", script: bowScriptURL, userData: JSON.stringify({ resetMe: { @@ -656,6 +655,7 @@ y: -3.5, z: 0 }, + restitution: 0, velocity: { x: 0, y: -0.01, @@ -1027,6 +1027,7 @@ y: -9.8, z: 0 }, + restitution: 0, dimensions: { x: 0.08, y: 0.21, @@ -1189,6 +1190,7 @@ collisionsWillMove: true, collisionSoundURL: "http://hifi-public.s3.amazonaws.com/sounds/SpryPntCnDrp1.L.wav", shapeType: 'box', + restitution: 0, gravity: { x: 0, y: -3.0, @@ -1365,7 +1367,11 @@ userData: JSON.stringify({ resetMe: { resetMe: true + }, + grabbableKey: { + invertSolidWhileHeld: true } + }) }); diff --git a/unpublishedScripts/masterReset.js b/unpublishedScripts/masterReset.js index 6e89e12d41..1837f4d656 100644 --- a/unpublishedScripts/masterReset.js +++ b/unpublishedScripts/masterReset.js @@ -220,7 +220,6 @@ MasterReset = function() { gravity: BOW_GRAVITY, shapeType: 'compound', compoundShapeURL: COLLISION_HULL_URL, - collisionSoundURL: "http://hifi-public.s3.amazonaws.com/sounds/bow_fall.L.wav", script: bowScriptURL, userData: JSON.stringify({ resetMe: { @@ -636,6 +635,7 @@ MasterReset = function() { y: -3.5, z: 0 }, + restitution: 0, velocity: { x: 0, y: -0.01, @@ -1007,6 +1007,7 @@ MasterReset = function() { y: -9.8, z: 0 }, + restitution: 0, dimensions: { x: 0.08, y: 0.21, @@ -1174,6 +1175,7 @@ MasterReset = function() { y: -3.0, z: 0 }, + restitution: 0, velocity: { x: 0, y: -1, @@ -1345,6 +1347,9 @@ MasterReset = function() { userData: JSON.stringify({ resetMe: { resetMe: true + }, + grabbableKey: { + invertSolidWhileHeld: true } }) });